1. 開始

Post date: 2012/3/7 上午 06:51:55

本書介紹了在Microsoft Windows 98、Microsoft Windows NT 4.0和Windows NT 5.0下程式寫作的方法。這些程式用C語言編寫並使用原始的Windows Application Programming Interface(API)。如在本章稍後所討論的,這不是寫作Windows程式的唯一方法。然而,無論最終您使用什麼方式寫作程式,瞭解Windows API都是非常重要的。

正如您可能知道的,Windows 98已成為使用Intel 32位元微處理器(例如486和Pentium)的IBM相容型個人電腦環境上最新的圖形作業系統之代表。Windows NT是IBM PC相容機種以及一些RISC(精簡指令集電腦)工作站上使用的Windows工業增強型版本。

使用本書有三個先決條件。首先,您應該從使用者的角度熟悉Windows 98。不要期望可以在不瞭解Windows使用者介面的情形下開發其應用程式。因此,我建議您在開發程式(或在進行其他工作)時使用執行Windows的機器來跑Windows應用程式。

第二,您應瞭解C語言。如果要寫Windows程式,一開始卻不想瞭解C語言,那不是一個好主意。我建議您在文字控制台環境中,例如在Windows 98 MS-DOS命令提示視窗下提供的環境中學習C語言。Windows程式設計有時包括一些非文字模式程式設計的C語言部分;在這些情況下,我將針對這些問題提供討論。但大多數情況下,您應非常熟悉該語言,特別是C語言的結構和指標。瞭解標準C語言執行期程式庫的一些相關知識是有幫助的,但不是必要的。

第三,您應該在機器上安裝一個適於進行Windows程式設計的32位元C語言編譯器和開發環境。在本書中,假定您正在使用Microsoft Visual C++ 6.0,該套裝軟體可獨立購買,也可作為Visual Studio 6.0套裝軟體的一部分購買。

到此為止,我將不再假設您具有任何圖形使用者介面(如Windows)的程式寫作經驗。

WINDOWS環境

Windows幾乎不需要介紹。然而人們很容易忘記Windows給辦公室和家庭桌上型電腦所帶來的重大改變。Windows在其早期曾經走過一段坎坷的道路,征服桌上型電腦市場的前途一度相當渺茫。

Windows簡史

在1981年秋天IBM PC推出之後不久,MS-DOS就已經很明顯成為PC上的主流作業系統。MS-DOS代表Microsoft Disk Operating System(磁碟作業系統)。MS-DOS是一個小型的作業系統。MS-DOS提供給用戶一種命令列介面,提供如DIR和TYPE的命令,也可以將應用程式載入記憶體執行。對於應用程式寫作者,它提供了一組函式呼叫,進行檔案的輸入輸出(I/O )。對於其他的週邊處理-尤其是將文字或圖形寫到顯示器上-應用程式可以直接存取PC的硬體。

由於記憶體和硬體的限制,成熟的圖形環境緩慢地才到來。當蘋果電腦公司不幸的Lisa電腦在1983年1月發表時,它提供了不同於文字模式環境的另一種選擇,並在1984年1月成為Macintosh上圖形環境的一種標準。儘管Macintosh的市場佔有率在下降,但是它仍然被認為是衡量所有其他圖形環境的標準。包括Macintosh和Windows的所有圖形環境,其實都要歸功於Xerox Palo Alto Research Center(PARC)在70年代中期所作的開拓性研究工作。

Windows是由微軟在1983年11月(在Lisa之後,Macintosh之前)宣佈,並在兩年後(1985年11月)發行。在此後的兩年中,緊隨著Microsoft Windows早期版本1.0之後,又推出了幾種改進版本,以支援國際商業市場,並提供新型視訊顯示器和印表機的驅動程式。

Windows版本2.0是在1987年11月正式在市場上推出的。該版本對使用者介面做了一些改進。這些改進中最有效的是使用了可重疊式視窗,而Windows 1.0中使用的是並排式視窗。Windows 2.0還增強了鍵盤和滑鼠介面,特別是加入了功能表和對話方塊。

至此,Windows還只要求Intel 8086或者8088等級的微處理器,以「實際模式」執行,只能存取位址在1MB以下的記憶體。Windows/386(在Windows 2.0之後不久發行的)使用Intel 386微處理器的「虛擬8086」模式,實現將直接存取硬體的多個MS-DOS程式視窗化和多工化。為了統一起見,Windows版本2.1被更名為Windows/286。

Windows 3.0是在1990年5月22日發表的。它將Windows/286和Windows/386結合到同一種產品中。Windows 3.0有了一個很大的改變,這就是對Intel的286、386和486微處理器保護模式的支援。這能使Windows和Windows應用程式能存取高達16MB的記憶體。Windows用於執行程式和維護檔案的「外殼」程式得到了全面的改進。Windows 3.0是第一個在家用和辦公室市場上取得立足點的版本。

任何Windows的歷史介紹都必須包括一些OS/2的說明,OS/2是對DOS和Windows的另一種選擇,最初是由Microsoft和IBM合作開發的。OS/2版本1.0(只有文字模式)在Intel 286(或者後來的)微處理器上運行,在1987年末發佈。在1988年10月的OS/2版本1.1中出現了管理圖形使用者介面的PM(Presentation Manager)。PM最初的設計構想是成為Windows的一種保護模式版本,但是圖形API改變程度太大,致使軟體生產廠商很難提供對這兩種平臺的支援。

到1990年9月,IBM和Microsoft之間的衝突達到了高峰,導致這兩個公司最後分道揚鑣。IBM接管了OS/2,而Microsoft明確表示Windows將是他們作業系統策略的中心。雖然OS/2仍然擁有一些狂熱的崇拜者,但是它遠不及Windows這樣的普及程度。

Microsoft Windows版本3.1是1992年4月發佈的,其中包括的幾個重要特性是TrueType字體技術(給Windows帶來可縮放的輪廓字體 )、多媒體(聲音和音樂 )、物件連結和嵌入(OLE:Object Linking and Embedding)和通用對話方塊。跟OS/2一樣,Windows 3.1只能在保護模式下運作,並且要求至少配置了1MB記憶體的286或386處理器。

在1993年7月發表的Windows NT是第一個支援Intel 386、486和Pentium微處理器32位元保護模式的Windows版本。Windows NT提供32位元平坦定址,並使用32位元的指令集。(本章後面我會談到一些定址空間的問題 )。Windows NT還可以移植到非Intel處理器上,並在幾種使用RISC晶片的工作站上執行。

Windows 95是在1995年8月發佈的。和Windows NT一樣,Windows 95也支援Intel 386或更高等級處理器的32位元保護模式。雖然它缺少Windows NT中的某些功能,諸如高安全性和對RISC機器的可攜性等,但是Windows 95具有需要較少硬體資源的優點。

Windows 98在1998年6月發佈,具有許多加強功能,包括執行效能的提高、更好的硬體支援以及與網際網路和全球資訊網(WWW)更緊密的結合。

Windows方面

Windows 98和Windows NT都是支援32位元優先權式多工(preemptive multitasking)及多執行緒的圖形作業系統。Windows擁有圖形使用者介面(GUI ),這種使用者介面也稱作「視覺化介面」或「圖形視窗環境」。有關GUI的概念可追溯至70年代中期,在Alto和Star等機器上以及SmallTalk等環境中由Xerox PARC所作的研究工作。該項研究的成果後來被Apple Computer和Microsoft引入主流並流行起來。雖然有一些爭議,但現在已非常清楚,GUI是(Microsoft的Charles Simonyi的說法)一個在個人電腦工業史上集各方面技術大成於一體的最重要產物。

所有GUI都在點矩陣對應的視訊顯示器上處理圖形。圖形提供了使用螢幕的最佳方式、傳遞資訊的視覺化豐富多彩環境,以及能夠WYSIWYG(what you see is what you get:所見即所得)的圖形視訊顯示和為書面文件準備好格式化文字輸出內容。

在早期,視訊顯示器僅用於回應使用者通過鍵盤輸入的文字。在圖形使用者介面中,視訊顯示器自身成為使用者輸入的一個來源。視訊顯示器以圖示和輸入設備(例如按鈕和捲軸)的形式顯示多種圖形物件。使用者可以使用鍵盤(或者更直接地使用滑鼠等指向裝置)直接在螢幕上操縱這些物件,拖動圖形物件、按下滑鼠按鈕以及滾動捲軸。

因此,使用者與程式的交流變得更為親密。這不再是一種從鍵盤到程式,再到視訊顯示器的單向資訊流動,使用者已經能夠與顯示器上的物件直接交互作用了。

使用者不再需要花費長時間學習如何使用電腦或掌握新程式了。Windows讓這一切成真,因為所有應用程式都有相同的基本外觀和感覺。程式佔據一個視窗-螢幕上的一塊矩形區域。每個視窗由一個標題列標識。大多數程式功能由程式的功能表開始。用戶可使用捲軸觀察那些無法在一個螢幕中裝下的資訊。某些功能表項目觸發對話方塊,用戶可在其中輸入額外的資訊。幾乎在每個大的Windows程式中都有一個用於開啟檔案的特殊對話方塊。該對話方塊在所有這些Windows程式中看起來都一樣(或接近相同),而且幾乎總是從同一功能表選項中啟動。

一旦您瞭解使用一個Windows程式的方法,您就非常容易學習其他的Windows程式。功能表和對話方塊允許用戶試驗一個新程式並探究它的功能。大多數Windows程式同時具有鍵盤介面和滑鼠介面。雖然Windows程式的大多數功能可通過鍵盤控制,但使用滑鼠要容易得多。

從程式寫作者的角度看,一致的使用者介面來自於Windows建構功能表和對話方塊的內置程式。所有功能表都有同樣的鍵盤和滑鼠介面,因為這項工作是由Windows處理,而不是由應用程式處理。

為便於多個程式的使用,以及這些程式間資訊的交換,Windows支援多工。在同一時刻能有多個Windows程式顯示並運行。每個程式在螢幕上佔據一個視窗。用戶可在螢幕上移動視窗,改變它們的大小,在不同程式間切換,並從一個程式向另一個程式傳送資料。因為這些視窗看起來有些像桌面上的紙(當然,這是電腦還未佔據辦公桌之前的年代),Windows有時被稱作:一個顯示多個程式的「具象化桌面」。

Windows的早期版本使用一種「非優先權式(non-preemptive)」的多工系統。這意味著Windows不使用系統計時器將處理時間分配給系統中運行的多個應用程式,程式必須自願放棄控制以便其他程式運行。在Windows NT和Windows 98中,多工是優先權式的,而且程式自身可分割成近乎同時執行的多個執行緒。

作業系統不對記憶體進行管理便無法實現多工。當新程式啟動、舊程式終止時,記憶體會出現碎裂空間。系統必須能夠將閒置的記憶體空間組織在一起,因此系統必須能夠移動記憶體中的程式碼和資料塊。

即使是在8088微處理器上跑的Windows 1.0也能進行這類記憶體管理。在實際模式限制下,這種能力被認為是軟體工程一個令人驚訝的成就。在Windows 1.0中,PC硬體結構的640KB記憶體限制,在不要求任何額外記憶體的情況下被有效地擴展了。但Microsoft並未就此停步:Windows 2.0允許Windows應用程式存取延伸記憶體(EMS);Windows 3.0在保護模式下,允許Windows應用程式存取高達16MB的擴展記憶體。Windows NT和Windows 98通過成熟的32位元作業系統及平坦定址空間,擺脫了這些舊的限制。

Windows上執行的程式可共用在稱為「動態連結程式庫」的檔案中的常式。Windows包括一個機制,能夠在執行時連結使用動態連結程式庫中常式的程式。Windows自身基本上就是一個動態連結程式庫的集合。

Windows是一個圖形介面,Windows程式能夠在視訊顯示器和印表機上充分利用圖形和格式化文字。圖形介面不僅在外觀上更有吸引力,而且還能夠讓使用者傳遞高層次的資訊。

Windows應用程式不能直接存取螢幕和印表機等圖形顯示設備硬體。相反,Windows提供一種圖形程式語言(稱作圖形裝置介面,或者GDI),使顯示圖形和格式化文字更容易。Windows虛擬化了顯示硬體,使為Windows編寫的程式可使用任何具有Windows裝置驅動程式的視頻卡或印表機,而程式無需確定系統相連的裝置類型。

對Windows開發者來說,將與裝置無關的圖形介面輸出到IBM PC上不是件輕鬆的事。PC的設計是基於開放式架構的原則,鼓勵第三方硬體製造商為PC開發周邊設備,而且開發了大量這樣的設備。雖然出現了多種標準,PC上的傳統MS-DOS程式仍不得不各自支援許多不同的硬體設備。這對MS-DOS文字處理軟體來說非常普遍,它們連同1到2張有許多小檔案的磁片一同銷售,每個檔案支援一種特定的印表機。Windows程式不要求每個應用程式都自行開發這些驅動程式,因為這種支援是Windows的一部分。

動態連結

Windows運作機制的核心是一個稱作「動態連結」的概念。Windows提供了應用程式豐富的可呼叫函式,大多數用於實作其使用者介面和在視訊顯示器上顯示文字和圖形。這些函式採用動態連結程式庫(Dynamic Linking Library,DLL)的方式撰寫。這些動態連結程式庫是些具有.DLL或者有時是.EXE副檔名的檔案,在Windows 98中通常位於\WINDOWS\SYSTEM子目錄中,在Windows NT中通常位於 \WINNT\SYSTEM和\WINNT\SYSTEM32子目錄中。

在早期,Windows的主要部分僅通過三個動態連結程式庫實作。這代表了Windows的三個主要子系統,它們被稱作Kernel、User和GDI。當子系統的數目在Windows最近版本中增多時,大多數典型的Windows程式產生的函式呼叫仍對應到這三個模組之一。Kernel(日前由16位元的KRNL386.EXE和32位元的KERNEL32.DLL實現)處理所有在傳統上由作業系統核心處理的事務-記憶體管理、檔案I/O和多工管理。User(由16位的USER.EXE和32位的USER32.DLL實作)指使用者介面,實作所有視窗運作機制。GDI(由16位的GDI.EXE和32位的GDI32.DLL實作)是一個圖形裝置介面,允許程式在螢幕和印表機上顯示文字和圖形。

Windows 98支援應用程式可使用的上千種函式呼叫。每個函數都有一個描述名稱,例如CreateWindow。該函數(如您所猜想的)為程式建立新視窗。所有應用程式可以使用的Windows函式都在表頭檔案裏預先宣告過。

在Windows程式中,使用Windows函式的方式通常與使用如strlen等C語言程式庫函式的方式相同。主要的區別在於C語言程式庫函式的機械碼連結到您的程式碼中,而Windows函式的程式碼在您程式執行檔外的DLL中。

當您執行Windows程式時,它通過一個稱作「動態連結」的過程與Windows相接。一個Windows的 .EXE檔案中有使用到的不同動態連結程式庫的參考資料,所使用的函式即在那些動態連結程式庫中。當Windows程式被載入到記憶體中時,程式中的呼叫被指向DLL函式的入口。如果該DLL不在記憶體中,就把它載入到記憶體中。

當您連結Windows程式以產生一個可執行檔案時,您必須連結程式開發環境提供的特定「引用程式庫(import library)」。這些引用程式庫包含了動態連結程式庫名稱和所有Windows函式呼叫的引用資訊。連結程式使用該資訊在.EXE檔案中建立一個表格,在載入程式時,Windows使用它將呼叫轉換為Windows函式。

WINDOWS程式設計選項

為說明Windows程式設計的多種技術,本書提供了許多範例程式。這些程式使用C語言撰寫並原原本本的使用Windows API來開發程式。我將這種方法稱作「古典」Windows程式設計。這是我們在1985年為Windows 1.0寫程式的方法,它今天仍是寫作Windows程式的有效方法。

API和記憶體模式

對於程式寫作者來說,作業系統是由本身的API定義的。API包含了所有應用程式能夠使用的作業系統函式呼叫,同時包含了相關的資料型態和結構。在Windows中,API還意味著一個特殊的程式架構,我們將在每章的開頭進行研究。

一般而言,Windows API自Windows 1.0以來一直保持一致,沒什麼重大改變。具有Windows 98程式寫作經驗的Windows程式寫作者會對Windows 1.0程式的原始碼感覺非常熟悉。API改變的一種方式是進行增強。Windows 1.0支援不到450個函式呼叫,現在已有了上千種函式呼叫。

Windows API和它的語法的最大變化來自於從16位元架構向32位元架構轉化的過程中。Windows從版本1.0到版本3.1使用16位元Intel 8086、8088、和286微處理器上所謂的分段記憶體模式,由於相容性的原因,從386開始的32位元Intel微處理器也支援該模式。在這種模式下,微處理器暫存器的大小為16位元,因此C的int資料型態也是16位元寬。在分段記憶體模式下,記憶體位址由兩個部分組成-一個16位元段(segment)指標和一個16位偏移量(offset)指標。從程式寫作者的角度看,這非常凌亂並帶來了long或far指標(包括段位址和偏移量位址)和short或near指標(包括帶有假定段位址的偏移量位址)的區別。

從Windows NT和Windows 95開始,Windows支援使用Intel 386、486和Pentium處理器32位元模式下的32位元平坦定址記憶體模式。C語言的int資料型態也擴展為32位元的值。為32位元版本Windows編寫的程式使用簡單的平坦線性空間定址的32位元指標值。

用於16位元版本Windows的API(Windows 1.0到Windows 3.1)現在稱作Win16。用於32位元版本Windows的API(Windows 95、Windows 98和所有版本的Windows NT)現在稱作Win32。許多函式呼叫在從Win16到Win32的轉變中保持相同,但有些需要增強。例如,圖像座標點由Win16中的16位元值變為Win32中的32位元值。此外,某些Win16函式呼叫返回一個包含在32位元整數值中的二維座標點。這在Win32中不可能,因此增加的新函式呼叫以不同方式運作。

所有32位元版本的Windows都支援Win16 API(以確保和舊有應用程式相容)和Win32 API(以運行新應用程式)。非常有趣的是,Windows NT與Windows 95及Windows 98的工作方式不同。在Windows NT中,Win16函式呼叫通過一個轉換層被轉化為Win32函式呼叫,然後被作業系統處理。在Windows 95和Windows 98中,該操作正相反:Win32函式呼叫通過轉換層轉換為Win16函式呼叫,再由作業系統處理。

在同一時刻有兩個不同的Windows API集(至少名稱不同)。Win32s (「s」代表「subset(子集)」)是一個API,允許程式寫作者編寫在Windows 3.1上執行的32位元應用程式。該API僅支援已被Win16支援的32位元函式版本。此外,Windows 95 API一度被稱作Win32c(「c」代表「compatibility(相容性)」),但該術語已被拋棄了。

現在,Windows NT和Windows 98都被認為能夠支援Win32 API。然而,每個作業系統依然都支援某些不被別的作業系統支援的某些功能特性。因為它們的相同之處是相當可觀的,所以有可能編寫在兩個作業系統下都可執行的程式。而且,人們普遍認為這兩個產品最終會合而為一。

語言選項

使用C語言和原始的API不是編寫Windows 98程式的唯一方法。然而,這種方法卻提供給您最佳的性能、最強大的功能和在發掘Windows特性方面最大的靈活性。可執行檔案相對較小且運行時不要求外部程式庫(自然,Windows DLL自身除外)。最重要的是,不管您最終以什麼方式開發Windows應用程式,熟悉API會使您對Windows內部有更深入的瞭解。

雖然我認為學習古典的Windows程式設計對任何Windows程式寫作者都是重要的,我沒有必要建議使用C和API編寫每個Windows應用程式。許多程式寫作者,特別是那些為公司內部開發程式或在家編寫娛樂程式的程式寫作者喜歡輕鬆的開發環境,例如Microsoft Visual Basic或者Borland Delphi(它結合了物件導向的Pascal版本)。這些環境使程式寫作者將精力集中於應用程式的使用者介面和相關使用者介面物件的程式碼上。要學習Visual Basic,您也許需要參考Microsoft Press的一些其他圖書,例如Michael Halvorson1996年著的《Learn Visual Basic Now》。

在專業程式寫作者中-特別是那些開發商業應用程式的程式寫作者-Microsoft Visual C++和Microsoft Foundation Class Library(MFC)是近年來流行的選擇。MFC在一組C++物件類別中封裝了許多Windows程式設計中的瑣碎細節。Jeff Prosise的《Programming Windows with MFC,第二版》(Microsoft Press,1999年)提供了MFC程式的寫作指南。

最近,Internet和World Wide Web的流行大力推廣著Sun Microsystems的Java,這是一個受C++啟發卻與微處理器無關的程式設計語言,而且結合了可在幾個作業系統平臺上執行的圖形應用程式開發工具組。Microsoft Press有一本關於Microsoft J++(Microsoft的Java)開發工具的好書,《Programming Visual J++ 6.0》(1998年),由Stephen R. Davis著。

顯然,很難說哪種方法更有利於開發Windows應用程式。更主要的是,也許是應用程式自身的特性決定了所使用的工具。不管您最後實際上使用什麼工具寫作程式,學習Windows API將使您更深入地瞭解Windows工作的方式。Windows是一個複雜的系統,在API上增加一個程式寫作層並未減少它的複雜性,僅僅是掩蓋了它,早晚您會碰到它。瞭解API會給您更好的補救機會。

在原始的Windows API之上的任何軟體層都必定將您限制在全部功能的一個子集內。您也許發現,例如,使用Visual Basic編寫應用程式非常理想,然而它不允許您做一個或兩個很簡單的基本工作。在這種情況下,您將不得不使用原始的API呼叫。API定義了作為Windows程式寫作者所需的一切。沒有什麼方法比直接使用API更萬能的了。

MFC尤其問題百出。雖然它大幅簡化了某些工作(例如OLE),我卻經常發現要讓它們按我所想的去工作時,會在其他特性(例如Document/View架構)上碰壁。MFC還不是Windows程式設計者所追求的靈丹妙藥,很少有人認為它是一個好的物件導向設計的模型。MFC程式寫作者從他們使用的物件類別定義如何工作中受益頗深,並會發現他們經常參考MFC原始碼,搞懂這些原始碼是學習Windows API的好處之一。

程式開發環境

在本書中,假定您正使用Microsoft Visual C++ 6.0,標準版、專業版和企業版都可以。經濟的標準版足以應付本書中的程式設計需求。Visual C++ 還是Visual Studio 6.0中的一部分。

Microsoft Visual C++ 套裝軟體中包括C編譯器和其他編譯及連結Windows程式所需的檔案和工具等。它還包括Visual C++ Developer Studio,一個可編輯原始碼、以交談方式建立資源(如圖示和對話方塊)以及編輯、編譯、執行和測試程式的環境。

如果您正使用Visual C++ 5.0,則需要為Windows 98和Windows NT 5.0更新表頭檔案和引用程式庫,這些東西可從Microsoft的網站上得到。在 http://www.microsoft.com/msdn/ ,選擇「 Downloads 」,然後選擇「 Platform SDK 」(軟體開發套件),您就能在選擇的目錄中下載和安裝更新檔案。要讓Microsoft Developer Studio瀏覽這些目錄,可以從「 Tool 」功能表項選擇「 Options 」然後按下「 Directories 」標籤。

Microsoft網站上的msdn部分代表「Microsoft Developer Network(Microsoft軟體開發者網路)」。這是一個向程式寫作者提供了經常更新的CD-ROM的計劃,這些CD-ROM中包含了程式寫作者在Windows開發中所需的最新東西。您也可以訂閱MSDN,這樣就避免經常得從Microsoft的網站下載檔案。

API文件

本書不是Windows API權威的正式文件的替代品。那組文件不再以印刷形式出版,它僅能從CD-ROM或Internet上取得。

當您安裝Visual C++ 6.0時,您將得到一個包括API文件的線上求助系統。您可通過訂閱MSDN或使用Microsoft網站上的線上求助系統更新該文件。連接到 http://www.microsoft.com/msdn/ ,並選擇「 MSDN Library Online 」。

在Visual C++ 6.0中,從「 Help 」功能表項選擇「 Contents 」項目開啟MSDN視窗。API文件按樹形結構組織,尋找標有「 Platform SDK 」的部分,所有在本書中引用的文件都來自於該部分。我將向您介紹如何從「 Platform SDK 」開始尋找以斜線分層分門別類的文件的位置。(我知道「Platform SDK」是整個MSDN知識庫中較為晦澀的部分,但我敢保證那是Windows程式設計的基本核心。)例如,對於如何在Windows程式中使用滑鼠的文件,您可參考/ Platform SDK / User Interface Services / User Input / Mouse Input。

我在前面提到Windows大致分為Kernel、User和GDI子系統。kernel介面在/ Platform SDK / Windows Base Services中,User介面函式在 / Platform SDK / User Interface Services中,GDI位於 / Platform SDK / Graphics and Multimedia Services / GDI中。

編寫第一個WINDOWS程式

現在是開始寫些程式的時候了。為了便於對比,讓我們以一個非常短的Windows程式和一個簡短的文字模式程式開始。這會幫助我們找到使用開發環境並感受建立和編譯程式機制的正確方向。

文字模式(Character-Mode)模型

程式寫作者們喜愛的一本書是《The C Programming Language》(Prentice Hall,1978年和1988年),由Brian W. Kernighan和Dennis M. Ritchie(親切地稱為K&R)編著。該書的第一章以一個顯示「hello, world」的C語言程式開始。

這裏是在《The C Programming Language》第一版第6頁中出現的程式:

main () { printf ("hello, world\n") ; }

以前C程式寫作者在使用printf等C執行期程式庫函式時,無需先宣告它們。但這是90年代,我們願意給編譯器一個在我們的程式中標出錯誤的機會。這裏是在K&R第二版中修正的程式:

#include <stdio.h> main () { printf ("hello, world\n") ; }

該程式仍然是那麼短。但它可通過編譯並執行得很好,但當今許多程式寫作者更願意清楚地說明main函式的返回值,在這種情況下ANSI C規定該函式必須返回一個值:

#include <stdio.h> int main () { printf ("hello, world\n") ; return 0 ; }

我們還可以包括main的參數,把程式弄得更長一些,但讓我們暫且這樣就好了-包括一個include宣告、程式的進入點、一個對執行期程式庫函式的呼叫和一個return語句。

同樣效果的Windows程式

Windows關於「hello, world」程式的等價程式有和文字模式版本完全相同的元件。它有一個include宣告、一個程式進入點、一個函式呼叫和一個return語句。下面便是該程式:

/*------------------------------------------------------------------ HelloMsg.c -- Displays "Hello, Windows 98!" in a message box (c) Charles Petzold, 1998 --------------------------------------------------------------------*/ #include <windows.h> int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { MessageBox (NULL, TEXT ("Hello, Windows 98!"), TEXT ("HelloMsg"), 0); return 0 ; }

在剖析該程式之前,讓我們看一下在Visual C++ Developer Studio中建立新程式的方式。

首先,從 File 功能表中選 New 。在 New 對話方塊中,單擊 Projects 頁面標籤,選擇 Win32 Application 。在 Location 欄中,選擇一個子目錄,在 Project Name 欄中,輸入該專案的名稱,此時該名稱是 HelloMsg ,這便是在 Location 欄中顯示的目錄的子目錄。 Create New Workspace 核取方塊應該勾起來, Platforms 部分應該顯示 Win32 ,選擇 OK

將會出現一個標題為 Win32 Application - Step 1 Of 1 的對話方塊,指出要建立一個 Empty Project ,並按下 Finish 按鈕。

File 功能表中再次選擇 New 。在 New 對話方塊中,選擇 Files 頁面標籤,選擇 C++ Source File Add To Project 核取方塊應被選中,並應顯示 HelloMsg 。在 File Name 欄中輸入 HelloMsg.c ,選中 OK

現在您可輸入上面所示的HELLOMSG.C檔案,您也可以選擇 Insert 功能表和 File As Text 選項從本書附帶的CD-ROM上複製HELLOMSG.C的內容。

從結構上說,HELLOMSG.C與K&R的「hello,world」程式是相同的。表頭檔案STDIO.H已被WINDOWS.H所代替,進入點main被WinMain所代替,而且C語言執行時期程式庫函式printf被Windows API函式MessageBox所代替。然而,在程式中有許多新東西,包括幾個陌生的大寫識別字。

讓我們從頭開始。

表頭檔案

HELLOMSG.C以一個前置處理器指示命令開始,實際上在每個用C編寫的Windows程式的開頭都可看到:

WINDOWS.H是主要的含入檔案,它包含了其他Windows表頭檔案,這些表頭檔案的某些也包含了其他表頭檔案。這些表頭檔案中最重要的和最基本的是:

#include <windows.h>

  • WINDEF.H 基本型態定義。
  • WINNT.H 支援Unicode的型態定義。
  • WINBASE.H Kernel函式。
  • WINUSER.H 使用者介面函式。
  • WINGDI.H 圖形裝置介面函式。

這些表頭檔案定義了Windows的所有資料型態、函式呼叫、資料結構和常數識別字,它們是Windows文件中的一個重要部分。使用Visual C++ Developer Studio的 Edit 功能表中的 Find in Files 搜索這些表頭檔案非常方便。您還可以在Developer Studio中打開這些表頭檔案並直接閱讀它們。

程式進入點

正如在C程式中的進入點是函數main一樣,Windows程式的進入點是WinMain,總是像這樣出現:

該進入點在 / Platform SDK / User Interface Services / Windowing / Windows / Window Reference / Window Functions中有說明。它在WINBASE.H中宣告如下:

int WINAPI WinMain ( HINSTANCE hInstance,HINSTANCE hPrevInstance, PSTR szCmdLine,int iCmdShow)

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd );

您會注意到我在HELLOMSG.C中做了許多小改動。第三個參數在WINBASE.H中定義為LPSTR,我將它改為PSTR。這兩種資料型態都定義在WINNT.H中,作為指向字串的指標。LP字首代表「長指標」,這是16位元Windows下的產物。

我還在WinMain宣告中改變了兩個參數的名稱。許多Windows程式中的變數名使用一種稱作「匈牙利表示法」的命名系統,該系統在變數名稱前面增加了表示變數資料型態的短字首,我將在第三章更詳細地討論這個概念。現在僅需記住字首i表示int、sz表示「以零結束的字串」。

WinMain函式宣告為返回一個int值。WINAPI識別字在WINDEF.H定義,語句如下:

該語句指定了一個呼叫約定,包括如何生產機械碼以在堆疊中放置函式呼叫的參數。許多Windows函式呼叫宣告為WINAPI。

WinMain的第一個參數被稱作「執行實體代號」。在Windows程式設計中,代號僅是一個應用程式用來識別某些東西的數字。在這種情況下,該代號唯一地標識該程式,還需要它在其他Windows函式呼叫中作為參數。在Windows的早期版本中,當同時運行同一程式多次時,您便創建了該程式的「多個執行實體(multiple instances)」。同一應用程式的所有執行實體共用程式和唯讀的記憶體(通常是例如功能表和對話方塊模板的資源)。程式通過檢查hPrevInstance參數就能夠確定自身的其他執行實體是否正在運行。然後它可以略過一些繁雜的工作並從前面的執行實體將某些資料移到自己的資料區域。

在32位元Windows版本中,該概念已被拋棄。傳給WinMain的第二個參數總是NULL(定義為0)。

WinMain的第三個參數是用於執行程式的命令列。某些Windows應用程式利用它在程式啟動時將檔案載入記憶體。WinMain的第四個參數指出程式最初顯示的方式,可以是正常的或者是最大化地充滿整個畫面,或者是最小化顯示在工作列中。我們將在 第三章 中介紹使用該參數的方法。

MessageBox函式

MessageBox函式用於顯示短資訊。雖然,MessageBox顯示的小視窗不具有什麼功能,實際上它被認為是一個對話方塊。

MessageBox的第一個參數通常是視窗代號,我們將在第三章介紹其含義。第二個參數是在訊息方塊主體中顯示的字串,第三個參數是出現在訊息方塊標題列上的字串。在HELLMSG.C中,這些文字字串的每一個都被封裝在一個TEXT巨集中。通常您不必將所有字串都封裝在TEXT巨集中,但如果想將您的程式轉換為Unicode字元集,這確是一個好主意。我將在第二章詳細討論該問題。

MessageBox的第四個參數可以是在WINUSER.H中定義的一組以字首MB_開始的常數的組合。您可從第一組中選擇一個常數指出希望在對話方塊中顯示的按鈕:

#define WINAPI __stdcall

#define MB_OK 0x00000000L #define MB_OKCANCEL 0x00000001L #define MB_ABORTRETRYIGNORE 0x00000002L #define MB_YESNOCANCEL 0x00000003L #define MB_YESNO 0x00000004L #define MB_RETRYCANCEL 0x00000005L

如果在HELLOMSG中將第四個參數設置為0,則僅顯示「 OK 」按鈕。可以使用C語言的OR(|)操作符號將上面顯示的一個常數與代表內定按鈕的常數組合:

#define MB_DEFBUTTON1 0x00000000L #define MB_DEFBUTTON2 0x00000100L #define MB_DEFBUTTON3 0x00000200L #define MB_DEFBUTTON4 0x00000300L

還可以使用一個常數指出訊息方塊中圖示的外觀:

#define MB_ICONHAND 0x00000010L #define MB_ICONQUESTION 0x00000020L #define MB_ICONEXCLAMATION 0x00000030L #define MB_ICONASTERISK 0x00000040L

這些圖示中的某些有替代名稱:

#define MB_ICONWARNING MB_ICONEXCLAMATION #define MB_ICONERROR MB_ICONHAND #define MB_ICONINFORMATION MB_ICONASTERISK #define MB_ICONSTOP MB_ICONHAND

雖然只有少數其他MB_常數,但您可以自己參考表頭檔案或 / Platform SDK / User Interface Services / Windowing / Dialog Boxes / Dialog Box Reference / Dialog Box Functions裏的檔案。

在本程式中,MessageBox返回數值1,但更嚴格地說它返回IDOK,IDOK在WINUSER.H中定義,等於1。根據在訊息方塊中顯示的其他按鈕,MessageBox函式還可返回IDYES、IDNO、IDCANCEL、IDABORT、 IDRETRY或IDIGNORE。

這個小的Windows程式真的與K&R的「hello, world」程式有著同等效果嗎?您也許認為不是,因為MessageBox函式並沒有「hello, world」中printf函數所具有的潛在格式化文字能力。但我們將在下一章中看到編寫類似printf的MessageBox版本的方法。

編譯、連結和執行

當您準備編譯HELLOMSG時,您可從「 Build 」功能表中選擇「 Build Hellomsg.exe 」,或者按 F7 ,或者在「 Build 」工具列中選擇「 Build 」圖示。(該圖示的外觀顯示在「 Build 」功能表中。如果當前沒有顯示「 Build 」工具列,您可從「 Tools 」功能表中選擇「 Customize 」並選擇「 Toolbars 」頁面標籤,選中「 Build 」或者「 Build MiniBar 」。)

另一種方法,您可從「 Build 」功能表中選擇「 Execute Hellomsg.exe 」,或者按「 Ctrl+F5 」,或者在「 Build 」工具列單擊「 Execute Program 」圖示(該圖示看上去像一個紅的感歎號),就會彈出一個訊息方塊詢問是否編譯該程式。

正常情況下,在編譯階段,編譯器從C原始碼檔案產生一個.OBJ(目標)檔案。在連結階段,連結程式結合.OBJ檔案和.LIB(庫)檔案以建立.EXE(可執行)檔案。通過在「 Project 」頁面標籤上選擇「 Settings 」並單擊「 Link 」頁面標籤可以查看這些庫檔案的列表。特別地,您會注意到KERNEL32.LIB、USER32.LIB和GDI32.LIB。這些是三個主要Windows子系統的「引用程式庫」。它們包含了動態連結程式庫的名稱以及放進.EXE檔案的引用資訊。Windows使用該資訊處理程式對KERNEL32.DLL、USER32.DLL、GDI32.DLL動態連結程式庫中函數的呼叫。

在Visual C++ Developer Studio中,您可用不同的設定編譯和連結程式。內定情況下,它們是「Debug」和「Release」。可執行檔案被存放在以這些名稱命名的子目錄下。在Debug設定下,資訊被附加到 .EXE檔案中,這些資訊有助於測試程式和追蹤原始碼。

如果您喜歡在命令列下工作,附上的CD-ROM包含所有範例程式的.MAK(make)檔案。(可通過「 Tools 」功能表選擇「 Options 」,再選擇「 Build 」頁面標籤,來告訴Developer Studio生成make檔案。這裏有一個核取方塊需要勾選)。您需要執行在Developer Studio的BIN子目錄下的VCVARS32.BAT來設置環境變數。要從命令列執行make檔案,可以轉到HELLOMSG目錄並執行:

或者

然後您可通過輸入:

或者

從命令列執行.EXE檔案。

我已經在本書附上的CD-ROM中對專案檔案中的內定Debug設定做了一個改動。在「 Project Settings 」對話方塊中,選擇「 C/C++ 」頁面標籤後,在「 Preprocessor Definitions 」欄中,我已定義了識別字UNICODE。我將在 下章 中對此有更多的解釋。

NMAKE /f HelloMsg.mak CFG="HelloMsg - Win32 Debug"

NMAKE /f HelloMsg.mak CFG="HelloMsg - Win32 Release"

DEBUG\HELLOMSG

RELEASE\HELLOMSG