本節課將簡單介紹下使用C++開發Windows桌面應用的一些基礎知識
目錄:
准備你的開發環境
Windows 代碼規范
操作字符串
什么是一個Window?
WinMain:程序的入口點
1. 准備你的開發環境
安裝 Windows SDK
要用C或者C++開發Windows 程序,你必須安裝 Microsoft Windows Software Development Kit (SDK) 或者一個包括Windows SDK的開發環境。
這個Windows SDK 包含了頭文件和編譯鏈接你的程序所需的類庫,這個Windows SDK 也包括Visual C++ 編譯和鏈接命令行工具。
盡管你用這個命令行工具可以編譯和鏈接運行你的程序,但是還是建議你安裝一個功能比較齊全的Microsoft Visual Studio.
Visual C++ Express 是一個免費的可下載的Visual C++ 軟件,Visual Studio Community是 Visual Studio Express 的更新替代產品。
Windows SDK 集成IDE 下載(官方推薦):https://www.visualstudio.com/zh-hans/vs/visual-studio-express/
Tips:
這個Windows SDK 不支持硬件驅動的開發,在這個也不作討論。
如果想學習這方面的開發,請移步https://docs.microsoft.com/zh-cn/windows-hardware/drivers/gettingstarted/index
Microsoft Visual C++ 2010 Express(已過時)是微軟推出的一款免費軟件,它比Microsoft Visual C++ 6.0 Enterprise Edition的兼容性和標准支持更好,比Microsoft Visual Studio 2010 Ultimate的體積更小。
如果你對使用Visual C++ 2010 Express感興趣,可以移步查看詳細教程:https://msdn.microsoft.com/en-us/library/ff742833.aspx
Windows代碼規范
如果你是一個開發Windows程序的新手,當你第一次看到一個WIndows程序的時候你可能會感到困惑。
因為這個代碼往往會出現像DWORD_PTR 和 LPRECT 一樣充滿了奇怪的類型定義和一些像hWnd 和 pwsz 變量(這稱為匈牙利命名法)。學習這些代碼規范是值得的。
絕大多數Windows API由函數或 Component Object Model(COM)組件對象模型接口組成。
很少有Windows API提供的C++類。(一個顯著的例外是GDI+,一種二維圖形API)
Windows的頭文件包含很多類型,這些很多都定義在頭文件WinDef.h 中,下面這些是你經常會遇到的。
你可以看到,在這些有一定數量的冗余類型。其中一些重疊僅僅是由於Windows API的歷史。這里列出的類型有固定的大小,在32位和64個應用程序中的大小相同。例如,DWORD類型總是32位的
Boolean Type
BOOL是一個整數類型的值,用於布爾上下文定義。頭文件 WinDef.h 也使用bool值定義了兩個。
BOOL is a typedef for an integer value that is used in a Boolean context. The header file WinDef.h also defines two values for use with BOOL
#define FALSE 0 #define TRUE 1
盡管布爾類型TRUE可以定義為任何一個非0的數字,但是你最好遵守規范總是TRUE設置為1
正確的方式
// Right way. BOOL result = SomeFunctionThatReturnsBoolean(); if (result) { ... }
錯誤的方式
// Wrong! if (result == TRUE) { ... }
Tips:注意BOOL是一個整數型且不可與C++ bool型互換。
指針類型:
Windows定義了很多表格 pointer-to-X 類型. 他們通常在名字的前面有前綴 P- or LP- .例如, LPRECT 是一個指向矩形的指針類型 , 這個矩形是一個描述了一個矩形的結構體.
下面這樣是等價的
RECT* rect; // Pointer to a RECT structure. LPRECT rect; // The same PRECT rect; // Also the same.
歷史上,P代表“指針”,LP代表“長指針”。長指針(也叫遠指針)從16位Windows的延期,當他們需要解決內存范圍以外的當前段。保留了LP前綴,使它更容易將32位Windows的16位代碼移植到32位窗口中。
但是今天沒有區別——指針就是指針。
指針精度類型
以下數據類型始終是指針的大小,即32位應用程序中的32位寬,64位應用程序中的位寬為64位。大小是在編譯時確定的。
當32位應用程序在64位Windows上運行時,這些數據類型仍為4字節寬。(64位應用程序不能在32位Windows上運行,因此不會出現相反的情況。)
- DWORD_PTR
- INT_PTR
- LONG_PTR
- ULONG_PTR
- UINT_PTR
These types are used in situations where an integer might be cast to a pointer. They are also used to define variables for pointer arithmetic and to define loop counters that iterate over the full range of bytes in memory buffers. More generally, they appear in places where an existing 32-bit value was expanded to 64 bits on 64-bit Windows.
在將整數轉換為指針的情況下使用這些類型。它們還用於定義指針算術的變量,並定義循環計數器,迭代內存緩沖區中的所有字節。更一般地,它們出現在64位窗口上現有32位值被擴展到64位的地方。
Hungarian Notation 匈牙利命名法
匈牙利符號是將前綴添加到變量名中的實踐,以便提供有關變量的附加信息。(符號的發明者查爾斯·西蒙尼是匈牙利人,因此得名)。
操作字符串
Windows本身支持UI元素,Unicode字符串的文件名,等等。Unicode是首選字符編碼,因為它支持所有字符集和語言。Windows是Unicode字符使用UTF-16編碼,其中每個字符被編碼為一個16位的值。UTF-16字符稱為寬字符,以區別於8位ANSI字符。Visual C++編譯器支持寬字符的內置數據類型
WinNT.h 頭文件中也定義了以下類型
typedef wchar_t WCHAR;
你會看到在MSDN的例子代碼的兩個版本。若要聲明寬字符文字或寬字符串文字,請將 L 置於文字之前。
wchar_t a = L'a'; wchar_t *str = L"hello";
這里有一些其他的字符串相關的定義,你會看到:
Unicode和ANSI函數
當微軟向Windows引入Unicode支持時,它通過提供兩組並行API,一個用於ANSI字符串,另一組用於Unicode字符串,從而緩解了過渡。
例如,設置窗口標題欄的文本有兩個函數:
SetWindowTextA //takes an ANSI string. SetWindowTextW //takes a Unicode string.
實際上真正使用的時候會加上判斷這樣定義
#ifdef UNICODE #define SetWindowText SetWindowTextW #else #define SetWindowText SetWindowTextA #endif
在MSDN中,函數的名字記錄SetWindowText函數下,即使那是真的宏的名字,不是實際的函數名。
新的應用程序應該總是調用Unicode版本。許多世界語言都需要Unicode。如果使用ANSI字符串,將不可能本地化應用程序。
ANSI版本的效率也較低,因為操作系統必須在運行時將ANSI字符串轉換為Unicode。
根據你的喜好,你可以調用Unicode功能明確,如setwindowtextw,或者使用宏。MSDN上的示例代碼通常會調用宏,但兩種形式是完全等價的。Windows中大多數更新的API只有Unicode版本,沒有相應的ANSI版本。
當應用程序需要同時支持Windows NT以及Windows 95、Windows 98和Windows Me 時,根據目標平台編譯ANSI或unicode字符串相同的代碼是非常有用的。
為此,Windows SDK提供了將字符串映射到Unicode或ANSI的宏,這取決於平台。
下面是個例子:
SetWindowText(TEXT("My Application"));
解析如下:
SetWindowTextW(L"My Application"); // Unicode function with wide-character string. SetWindowTextA("My Application"); // ANSI function.
TEXT 和 TCHAR 宏今天已經很少用了,因為所有的應用程序都應該使用Unicode ,然而你可能在MSDN看到一些這樣的代碼:
然而,你可能會在舊的代碼和一些MSDN代碼例子看到他們。
微軟C運行時庫的標題定義了一組類似的宏
#ifdef _UNICODE #define _tcslen wcslen #else #define _tcslen strlen #endif
注意:有些標題使用預處理器符號Unicode,別人用_unicode使用下划線前綴。總是定義這兩個符號。當你創建一個新項目,Visual C++將它們通過默認的方式設置。
什么是Window?
顯然,Windows是Windows的中心。它們是如此重要,以至於他們在操作系統之后命名了操作系統。
但是什么是窗口?當你想到一個窗口,你可能會想到這樣的東西:
這種類型的窗口稱為應用程序窗口或主窗口。它通常有一個標題欄、最小化和最大化按鈕以及其他標准UI元素的框架。
該框架稱為窗口的非客戶端區域,之所以稱為“窗口”,是因為操作系統負責管理窗口的那部分。框架內的區域是客戶區。這是您的程序管理的窗口的一部分。
這里是另一種類型的窗口:
如果您是Windows編程新手,您可能會驚訝於UI控件,如按鈕和編輯框本身就是Windows。UI控件和應用程序窗口之間的主要區別在於控件本身並不存在。
相反,控件相對於應用程序窗口定位。當拖動應用程序窗口時,控件將隨它移動,如您所期望的那樣。另外,控件和應用程序窗口可以相互通信。
(例如,應用程序窗口從按鈕接收單擊通知。)
因此, 當你考慮window時, 不要把它簡單地認為是個應用窗口,取而代之的是一種編程結構:
占據屏幕的某一部分。
在某一時刻可能不可見。
知道如何畫自己。
響應來自用戶或操作系統的事件。
Parent Windows and Owner Windows 父窗口和所有者窗口
在UI控件的情況下,控件窗口被稱為應用程序窗口的子窗口。應用程序窗口是控件窗口的父窗口。父窗口提供用於定位子窗口的坐標系統。有父窗口會影響窗口外觀的各個方面;例如,子窗口被裁剪,這樣子窗口的任何部分都不能出現在其父窗口的邊框外面。
另一種關系是應用程序窗口和模式對話框窗口之間的關系。當應用程序顯示一個模態對話框時,應用程序窗口是所有者窗口,而對話框是一個擁有窗口。擁有的窗口總是出現在它的所有者窗口前面。當所有者被最小化時,它被隱藏,並在所有者被銷毀的同時被銷毀。
下圖顯示了一個應用程序,其中顯示一個帶有兩個按鈕的對話框:
這個應用窗口有自己的對話框窗口,這個對話框窗口是父窗口中的兩個按鈕
下面是這兩者之間的關系:
Window Handles
窗口對象既有代碼和數據,但他們都不是C++類。相反,程序使用一個稱為Handle(句柄)的值引用一個窗口。
一個Handle 是一個不透明類型。本質上,它只是操作系統用來標識對象的一個數字。您可以將窗口視為創建所有窗口的大表。它使用此表通過Hanlder查找窗口。(無論是它是如何工作的內部是不重要的。)
窗口的數據類型Hanlder是一個HWND,這通常是明顯的“aitch-wind“。窗口句柄是由函數返回:CreateWindow和CreateWindowEx創建窗口。
在一個窗口進行操作,你通常會調用一些函數有一個窗口的句柄值作為參數。例如,復位窗口在屏幕上,調用MoveWindow函數:
BOOL MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint);
第一個參數是要移動的窗口的句柄。其他參數指定窗口的新位置和窗口是否應該重繪.
記住句柄不是指針。如果窗口是一個變量,包含一個hanlder,試圖引用句柄HWND *hwnd 這樣寫是錯誤的。
屏幕和窗口坐標
坐標是以設備無關的像素來度量的。在討論圖形時,我們將更多地討論與設備無關的像素的與設備無關的部分。
根據您的任務,您可以測量相對於屏幕的坐標,相對於窗口(包括幀),或相對於窗口的客戶端區域。
例如,您將使用屏幕坐標在屏幕上定位窗口,但您將使用客戶機坐標在窗口中繪圖。在每種情況下,原點(0, 0)總是區域的左上角。
WinMain: 程序入口點
每一個Windows程序,都有一個入口函數,叫做WinMain或wWinMain。
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);
這四個參數的意義:
hInstance : 是一種叫做“實例的句柄”或“處理模塊”的操作系統使用這個值來確定可執行文件(EXE)時,在內存中加載。實例句柄是某些Windows功能-例如,加載圖標或位圖。
hprevinstance: 沒有意義。它在16位窗口中使用,但現在總是0。
pcmdline: 包含命令行參數作為一個Unicode字符串。
ncmdshow: 是一個標志,是應用程序的主窗口將被最小化,最大化,或顯示正常。
函數返回一個int類型的值。操作系統不使用該返回值,但你可以使用返回值將狀態代碼發送給您編寫的其他程序。
WINAPI 的調用約定。調用約定定義函數如何從調用者接收參數。例如,它定義了參數出現在堆棧上的順序。只要確保你wWinMain函數如下聲明。
WinMain 函數和 wWinMain是相同的,唯一不同的是命令行參數作為一個ANSI字符串,Unicode 版本是首選。
你可以使用ANSI WinMain 函數,盡管你編譯的程序是以Unicode編碼。要得到一個Unicode字符編碼的命令行參數,可以調用 GetCommandLine 函數。
這個函數將返回一個單一的字符串,如果你想作為一個參數式陣列的參數,通過這個字符串CommandLineToArgvW。
CRT內部主要做一些額外的工作。例如,靜態初始化器是之前調用wWinMain。雖然可以告訴鏈接器使用不同的入口點函數,但如果鏈接到CRT,則使用默認值。
否則,將跳過CRT初始化代碼,結果不可預測。(例如,全局對象初始化不正確)。
這是一個空的函數
INT WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, INT nCmdShow) { return 0; }
現在你已經擁有了入口點並理解了一些基本術語和編碼約定,下篇博文我們就可以創建一個完整的窗口程序了。