1. 程式人生 > >Windows SDK程式設計 API入門系列

Windows SDK程式設計 API入門系列

之一 -那‘煩人’的Windows資料型別

原創文章,轉載請註明作者及出處。

首發

Hello Everybody This is beyondcode

大家好 再次自我介紹一下 我是beyondcode, 這次心血來潮, 計劃著做一系列關於Windows API 程式設計的教程,用於幫助一些在Windows API程式設計上有疑惑的,納悶的,迷惑的新手朋友們。

先解釋一些術語或名詞吧   SDK是Software Development Kit的簡寫,也就是軟體開發包的意思,其中就包含了我們寫程式要用到的一些標頭檔案,庫,工具,幫助文件之類的。

Windows API程式設計是指呼叫Windows的介面函式來進行程式的編寫,例如MessageBox就是一個API函式或者說介面函式。怎麼說都可以,自己理解就行。如果你連這個都不太懂,我想也不會搜到這篇文章了吧~·

為什麼做這個系列教程呢,請聽我一一道來先,最近遇到一些事一些人,讓我真的感覺在這方面的引匯入門文章真的很是匱乏,加上Windows SDK標頭檔案中那些複雜,龐大,'煩人'的巨集定義與資料型別定義,對於一個新手來說(我所說的新手不單隻剛接觸程式設計的,還特指那些在其他語言領域有比較高造詣的朋友) 一個純SDK寫的helloworld程式都算是一個有些困難和挑戰的任務了吧。 本著幫助別人,高興自己的原則,我有了這個打算,當然對自己以前所學,所經歷做一次回憶,也是這次計劃的一部分。

宣告一下,本系列教程是面向廣大初次接觸WIN32 SDK程式編寫的新手朋友們的,如果你是高手,一笑而過吧~當然,除了一笑而過,也多謝你們提出指正文章中的錯誤,以免我誤人子弟啊~~謝謝

 ok, 廢話不多說,進入正題,今天第一篇,講什麼?   對於一個新人來說,第一次接觸SDK程式設計或者說API程式設計,什麼最迷惑你們的,我們講它,我覺得Windows SDK中那'煩人'的資料型別定義和巨集定義應該算這個很角色吧。。

其實微軟的本意也是善良的,為了減輕程式設計師的負擔,和為了程式設計的方便,才花了那麼多心思與精力定義出了這麼一大套資料型別與巨集定義,這也是我為什麼在之前說它煩人都是加上引號的原因,因為他不是真的煩人,熟練了,你不但不覺得它煩,反而離不開它了,呵呵,日久深情也就是這麼來的。

呵呵 先看幾個資料型別定義吧

typedef float FLOAT;

typedef long LONG;

typedef short SHORT

typedef int INT;

typedef char CHAR;

float, long, short, int, char 這幾個資料型別都是大家熟悉的C/C++的資料型別吧,微軟將他們重新定義了一下,很簡單,就是改變名字為大寫了,這樣做的目的大概是微軟為了編碼的方便吧,輸入法大小寫都不用切換了,多人性化呀 呵呵。。

再看幾個資料型別定義的例子

typedef unsigned int UINT;

typedef unsigned int UINT32;

typedef signed int INT32;

typedef unsigned long DWORD;

typedef unsigned short WORD;

這些資料型別的定義就稍微有實質性作用一些了,注意觀察,他們都比較短了,不用寫那麼長了,而且也還比較直觀,如果我要定義一個無符號整形, 我就不用寫 unsigned int a;

這麼長了,只需UINT a; 多簡單, 多明瞭,所以我說其實不煩人吧。

其中DWORD 算是SDK程式中可以經常看見的一個數據型別了,經常被使用,很多新手也就不明白,這是什麼資料型別啊,現在看到了吧,其實就是無符號長整形unsigned long,給他取了個外號而已··沒什麼技術含量,所以不用怕,程式中究竟是寫unsigned long 還是DWORD都看你自己心情,因為他們都代表同一種資料型別。

下面再介紹2個很重要的,經常被使用到的,無處不在的資料型別WPARAM,LPARAM

先看看他們定義吧

typedef LONG_PTR LPARAM;

typedef UINT_PTR WPARAM;

先告訴你,這2個數據型別很重要,不是危言聳聽,以後你寫SDK程式就知道了,看他們的定義如上,有些迷糊? 別,我們一步一步分析,我們分析LPARAM。首先定義LPARAM 為LONG_PTR也就是用LPARAM的地方也就可以寫成LONG_PTR,LONG_PTR又是被定義成什麼的呢?  

typedef long LONG_PTR;

看到了嗎?   也就是long 所以歸根結底,LPARAM 就是long型,所有LPARAM型的變數,你都可以直接使用long資料型別代替。不過不推薦這樣,至於為什麼,各位思考思考呢~~

以上這些資料型別是參考MSDN中的說明,或者可以檢視WinDef.h這個標頭檔案檢視這些Windows資料型別的定義,那麼也請各位自己推推看LARAM和WPARAM的真面目吧~

各位朋友在推導的過程中可能發現LONG_PTR的定義是這樣寫的

#if defined(_WIN64)

typedef __int64 LONG_PTR;

#else

typedef long LONG_PTR;

#endif

這是什麼意思呢,能看懂英文都能知道這在定義些什麼,如果定義了 _WIN64這個巨集 那麼就定義 LONG_PTR 為 __int64,否則定義LONG_PTR為long。 很簡單吧 也就是說如果_WIN64這個巨集在前面被定義了,那麼這裡的LONG_PTR就被定義為__int64這個在64位程式設計下的資料型別,否則就定義為long型,這樣說應該比較好理解了吧。在這裡,各位就不必深究__int64了, 在目前的主流32位程式設計下很少使用它啦。理解就ok了。這樣定義是微軟為了程式設計師編寫的程式能在32位與64位下都能編譯而採用的伎倆。

有關這些Windows的資料型別,想檢視他們的真面目,其實很簡單,在VC6.0,VS2008 這些整合開發環境裡面,你只需要在一個數據型別上面點選右鍵,在彈出選單中選擇‘Goto Defination’ 或者是 ‘檢視定義’就可以看到了,如果看到的還不是最終面目,在繼續上面步驟。直到看到它的本質資料型別為止。通過這樣,新手對於Windows的這些複雜的資料型別定義也就有了根本的認識,不再是迷迷糊糊,在以後的程式設計中也就不會出現不知道用哪種資料型別或者哪些資料型別之間可以相互轉換的情況了。不過還需要多多觀察與練習才是啊~~

下面再來看一看windows中定義的一些巨集

#define   VOID void

#define CONST   const

2個最簡單的巨集,也是隻變成大寫而已,難道又是為了方便程式設計師不切換輸入法?還真的人性化呀。

Windows SDK中的巨集定義是最龐大的,最複雜的,但也是最靈活的,為什麼這樣說,先不告訴你,我會在以後的系列文章中一點一點的講解,累積,因為太多了,也比較複雜,我們就採取在需要用到的時候才講解它,目前看來還沒這個必要了解那麼多,就瞭解上面2個很簡單的好了,像其他如:WINAPI    CALLBACK    GetWindowText 這些巨集現在講了不但記不住還會增加你們的負擔。,我們就在以後要用到的時候再做講解。

到這裡第一篇系列文章的內容也就差不多了。新手朋友們哪些地方迷惑的,提出來,我可以考慮是否加在後續的文章中進行解說。本SDK系列入門教程需要你們的支援。謝謝。 

之二 -Unicode還是ASCII

今天,開始第二篇文章,這章我準備介紹一下Windows平臺下程式設計中Unicode編碼和ASCII編碼的相關問題。

不知道各位新手朋友們遇到這樣的問題沒有呢,新建一個Windows應用程式,呼叫MessageBox這個函式,準備讓它彈出一段提示文字,可是編譯器在編譯的時候卻報錯說,不能將 const char* 或者 const char[] 轉換為 const wchar_t* 之類的提示呢,很多剛接觸Windows API程式設計的朋友們在這裡可能就卡住了,不知如何下手解決了,其實,這就是Unicode編碼和ASCII編碼的問題了。我下面就會一一道來

關於Unicode和ASCII具體的編碼是怎麼的,我這裡就不詳細介紹了,也介紹不了,如果需要深入瞭解,網上有很多這方面的專門文章,我這裡就只對Unicode編碼和ASCII編碼在Windows平臺下的程式設計相關的內容進行介紹。

我們都知道Unicode和ASCII最大的區別就是Unicode採用2個位元組來儲存一個字元,不管是英文,漢字,還是其他國家的文字,都有能用2個位元組來進行編碼,而ASCII採用一個位元組儲存一個字元,所以對於英文的編碼,那是足夠的了,可是對於漢字的編碼,則必須採用一些特殊的方法,用2個ASCII字元來表示一個漢字。

我們在寫程式的過程中,勢必要和字元打交道,要輸入,獲取,顯示字元,到底是選用Unicode字元呢還是ASCII字元呢,這都是各位自己的權利。但為了程式的通用性和符合目前作業系統的主流趨勢,Unicode編碼是被推薦的。由於Unicode字元要比ASCII字元佔用的空間大一倍,編譯出來的程式在體積上和佔用的記憶體上必定要大一些,不過這並不是什麼很大的問題。所以微軟目前的SDK中保留了2套API,一套用於採用Unicode編碼處理字元的程式的編寫,一套用於採用ASCII編碼處理字元的程式的編寫。 例如,我們上面提到的MessageBox,它其實不是一個函式名,而是一個巨集定義,我們先來看看它是怎麼被定義的,再來討論它。

#ifdef UNICODE

#define MessageBox  MessageBoxW

#else

#define MessageBox  MessageBoxA

#endif 

看到了嗎?  很簡單是不是, 如果定義了UNICODE 這個巨集 那麼就定義MessageBox為MessageBoxW,如果沒有定義UNICODE這個巨集, 那麼就定義MessageBox 為MessageBoxA,MessageBox後面的W和A 就是代表寬位元組(Unicode)和ASCII,這樣,其實存在於SDK中的函式是MessageBoxW和MessageBoxA這兩個函式.

MessageBox只是一個巨集而已。所以在程式中,這3個名字你都可以使用,只不過需要注意的是,使用MessageBoxA的話,那麼你要注意傳給它的引數,字元都必須是單位元組,也就是ASCII, 在程式中就是char,如果使用MessageBoxW的話,那麼,字元都必須使用Unicode,程式中就是 wchar_t。 但是這樣有個非常不方便的地方那就是,如果你使用W字尾系列的函式的話,那麼你的程式使用的字元就是Unicode字元編碼的,但是如果你需要用這個程式的原始碼編譯出字符采用ASCII編碼的程式,那麼需要改動的地方就太大了。凡是涉及到字元操作的地方都需要改變。那麼 ,有沒有比較好的辦法不做更改就可以用同樣的程式碼編譯出ASCII版本的程式呢。  

當然有,就是我們在程式設計的時候儘量使用不帶字尾的巨集定義,如上例,就使用MessageBox,其中的引數也不明確使用char 還是wchar_t 而是使用微軟給我們定義的TCHAR字元資料型別,它的定義和上面MessageBox函式的定義差不多,都是根據是否定義了UNICODE這個巨集來判斷是將TCHAR定義為char還是wchar_t,所以這樣一來,這個TCHAR的資料型別就是可變的了,它根據工程的設定而定義為相應的最終字元型別,這樣我們的程式就可以不做任何更改就可以輕鬆的編譯出另外一個版本的了。是不是非常方便。

前面2篇文章純文字的介紹比較多,因為很多是概念性的,需要理解,後面的文章我準備配合一些小示例程式,使用一些簡單的API函式,遇到的相關的概念在一併介紹的方法進行。所以,前2篇文章如果各位朋友不是很能理解,不用擔心,影響不是很大,經過後面的學習,你就會慢慢的理解前面所說的內容了。 

之三 -那迷惑人的Windows字元和字元指標型別

大家好,通過前面兩篇打頭文章,我也看了留言,感謝那些給我提意見的人和指出錯誤之處的人。再次謝謝你們的支援。 另外,Windows SDK程式設計交流群已經建立了,歡迎各位志同道合者加入進行交流( 群號:81543028 )

本打算通過前面兩篇文章的講解,後來的系列就可以通過使用一些簡單的,常用的API寫一些示例程式的講解進行,但是發現還有一個不得不先講一講的要點,Windows下和字串操作有關的資料型別。我看留言中也有幾位朋友提到了,那我就在這篇中講它吧。不會很枯燥的,各位慢慢看下去就是了。

下面我羅列一些我們在Windows平臺下程式設計經常使用到的和字元或字串有關的資料型別。

char  和  wchar_t 

這兩個型別大家絕對不會陌生吧,一個是單位元組的字元型別,一個是寬位元組的字元型別(也就是Unicode字元)。

char   c = 'b';

wcha_t  wc = L'b';

上面我就分別定義了2個變數c和wc ,相信第一個定義大家都看的懂,就是定一個字元變數c,其中儲存了'b'這個字元。 那麼第二個呢?  我相信還是很多人都看的懂,要是你看不懂也沒關係,現在就告訴你,也是定義一個字元變數wc, 只不過這個字元變數是Unicode字元變數,用2個位元組來儲存一個字元,而上面的c這個字元變數只有一個位元組來儲存,那麼在'b'前面的L又是什麼意思呢,它就表示這裡的'b'這個字元是一個Unicode字元,所以第二個定義的意思就是將L'b'這個Unicode字元儲存到wc這個Unicode字元變數中。

如果我要定義一個字元陣列怎麼定義呢? 用分別用單位元組的char和寬位元組的wchar_t來定義就應該是:

char  c[10];

wchar_t wc[10];

如果是要帶初始化的字元陣列的宣告,我們來看看怎麼寫

char c[] = "beyondcode";

wchar_t wc[] = L"beyondcode";

看到了嗎,寬位元組的操作其實和單位元組的字元操作一樣吧,只是在前面加上L表示是寬位元組的字元或者字串。

上面都是屬於C/C++中的知識,並沒有涉及太多Windows中的資料型別,那麼各位朋友們在Windows程式設計中看到的滿到處都是的 TCHAR,LPSTR, LPCSTR, LPWSTR, LPCWSTR, LPTSTR, LPCTSTR 這些資料型別又是怎麼回事呢? 別急,我們一步一步的來,最後我會聯絡到那上面去的。

上面的你都知道或者是理解了的話,那我們繼續,除了可以宣告一個字元陣列,我還可以定義一個字元指標變數來指向一個字元陣列,當然這個字元陣列可以是Unicode的寬位元組字元陣列,也可以是單位元組字元陣列,如下:

char  c[] = "hello beyondcode"; //定義一個字元陣列

wchar_t  wc[] = L"hello beyondcode"; //定義一個寬位元組字元陣列

char   *p = c; //定義一個字元指標,指向剛才的字元陣列

wchar_t *wp = wc; //定義一個寬位元組字元指標,指向剛才的寬位元組字元陣列

這樣之後,我就可以通過指標來改變剛才我們定義的2個數組,例如:

p[0] = 'H';

wp[0] = L'H';

把上面2個數組的第一個字元通過指標改變成大寫。這裡是可以通過指標來修改的,因為我沒有定義指標為常量指標,也就是沒有加const 修飾符。如果我像下面這樣定義的話,那麼就不能通過這些指標來改變他們所指向的資料了,而是隻有讀取他們。

const  char  *p = c;

const  wchar_t  *wp = wc;

上面將的都是C/C++的基礎知識,有點囉嗦,為了照顧新手朋友們嘛,下面我們就來看看Windows是怎麼定義它的資料型別的

首先,定義了CHAR, WCHAR的這2個字元資料型別,就是我們上面討論的兩個字元資料型別改了一下名字而已。現在你還不昏吧··

typedef char  CHAR;

typedef wchar_t  WCHAR;

然後,用剛才定義的 CHAR, WCHAR這2個字元資料型別去定義了一系列其他字元指標型別。

typedef  CHAR  *LPSTR;

typedef  WCHAR  *LPWSTR;

這樣一定義之後,LPSTR的就是 CHAR*, 而CHAR 又是char, 所以LPSTR的本質就是 char*,也就是我們上面熟悉的不能再熟悉的字元指標,  那LPWSTR不用我推導,相信你也推匯出來了吧。不過我還是推導一下,LPWSTR是 WCHAR * , WCHAR是wchar_t,這樣LPWSTR就是 wchar_t* ,也就是我們上面討論的寬位元組字元指標。上面這些定義都是在WinNT.h這個標頭檔案中定義的,讀者朋友們有興趣在這個標頭檔案裡面去挖掘挖掘吧,上面2個定義我只是提取了重要的部分,其實在裡面他還定義了其他很多別名.

看了LPSTR, LPWSTR是怎麼一回事之後,我們再接再厲,看看LPCSTR,LPCWSTR這2個數據型別又是怎麼一回事呢, 老規矩,先看windows的定義。

typedef  CONST  CHAR  *LPCSTR;

typedef  CONST  WCHAR *LPCWSTR;

和上面的比較,名字中就多了一個大寫的C,這個C的含義就代表是const修飾符,也就是我們上面所說的常量指標,指向的內容不能通過這個指標被改變,但可以讀取。定義中的大寫的CONST也是一個巨集,我在第一篇文章中就講過了,代換出來也就是const, 所以請讀者自己推導一下這兩個資料型別的本質是什麼。

所以,在windows平臺下的程式設計過程中,凡是可以使用char* 的地方,你都可以使用LPSTR來代替,凡是可以使用wchar_t*的地方,你都可以使用LPWSTR來代替,至於怎麼用,還是那句老話,看你個人心情,只不過Windows的API函式中關於字串的都是使用LP這種資料型別。但是你還是可以給他傳遞char* 或者 wchar_t* ,只要他們的本質是一樣的,那怎麼不可以呢~~

下面,我們來看一看一些示例。

char  c = 'c';  和 CHAR c = 'c';    是一樣的

wchar_t wc = L'w'; 和 WCHAR wc = L'w';    是一樣的

char* p  和 LPSTR p 是一樣的

wchar_t* wp  和 LPWSTR wp    是一樣的

再來看看動態記憶體分配怎麼寫的呢

char* p = new char[10]; //動態分配了十個字元

也可以寫成

CHAR* p = new CHAR[10];

LPSTR p = new CHAR[10];

LPSTR p = new char[10];

寬位元組的再來一次

wchar_t* wp = new wchar_t[10];

也可以寫成下面這些形式

WCHAR*  wp = new WCHAR[10];

LPWSTR  wp = new WCHAR[10];

LPWSTR  wp = new wchar_t[10];

上面定義的這些字元指標 p , wp都沒有用const修飾符,所以可以通過他們來修改他們所指向的內容。這裡留給讀者一個問題,怎麼定義有const修飾符的字元指標呢,都可以用什麼形式來寫呢,寫得越多越好喲。。

通過上面這些,我想你大概已經瞭解了LPSTR, LPCSTR, LPWSTR, LPCWSTR這四個資料型別了,他們無非就是:

LPSTR  -------    char*

LPCSTR -------  const char*

LPWSTR -------  wchar_t*

LPCWSTR --------   const wchar_t* 

下面我提一個問題,如果你在你的程式中使用的字串都是通過LPWSTR,LPCWSTR這種寬位元組(Unicode)字元指標來進行操作的,那麼在Unicode環境下編譯,完全沒有問題,如果這時你需要編譯一套ASCII版本的程式,那你會怎麼辦呢?   你說將用LPWSTR 和LPCWSTR的地方全部換成LPSTR和LPCSTR,再將字串前面的L去掉就可以了,對,這是一種方法,但是!!所有人在這裡都應該知道我要說但是,這也太麻煩了吧。難道沒有通用點的方法嗎?   有!!  所有人在這裡也都知道我會說有,呵呵。  那就是使用微軟的通用資料型別,說通用資料型別有點太專業了,其實也就那樣,請聽我慢慢分析來。我在上一篇文章中說過,凡是涉及字串操作的API函式有2套,一個A系列的,一套W系列的,還有一套巨集,能根據不同的工程環境定義成不同的API函式名。那麼在字元型別上微軟也使用幾乎同樣的技術,定義了一套巨集能根據不同的工程環境定義成不同的字元資料型別。我上面就提到過的TCHAR,LPTSTR, LPCTSTR就是這樣的型別。

首先說說TCHAR,它是被這樣定義的:

#ifdef  UNICODE

typedef  WCHAR  TCHAR;

#else

typedef  char TCHAR

看到了嗎? 它也是根據UNICODE這個巨集被定義沒有,如果被定義了,那麼TCHAR代表的資料型別就是WCHAR, 也就是wchar_t, 如果沒被定義,那麼TCHAR 就代表的是char

同樣LPTSTR,LPCTSTR也是這樣的,考慮到篇幅,我就只列出LPTSTR來給大家看看了

#ifdef   UNICODE

typedef  LPWSTR   LPTSTR;

#else

typedef  LPSTR LPTSTR;

這個是我簡化了的定義,真實面目有些複雜,不過意思也是如此,有興趣可以自己看看,在WinNT.h這個標頭檔案中。下面再次解釋一下上面這個LPTSTR的定義, 還是老樣子,根據UNICODE這個巨集被定義與否來決定怎麼定義LPTSTR ,如果是定義了UNICODE這個巨集,表示當前工程環境是Unicode環境,那麼LPTSTR就被定義為了LPWSTR, LPWSTR就是我們前面所講的wchar_t* ,所以此時LPTSTR代表的資料型別就是wchar_t* ,  如果這時的工程沒有定義UNICODE這個巨集,那麼就定義LPTSTR為LPSTR,而LPSTR就是我們前面所說的char* ,所以這是的LPTSTR就代表char*。懂了嗎?各位,我都覺得自己有些囉嗦了··不好意思···

然後還有一個巨集需要講一下,由於我們使用通用資料型別,那麼我事先就不知道我的原始碼需要在Unicode下編譯還是在ASCII環境下編譯,所以如下這種情況

TCHAR tc = 'a';  或者是 TCHAR tc = L'a';  是否合適呢? 前面我已經說過了字元或字串常量前面加L代表這是寬位元組的字元或字串,將一個寬位元組字元賦值給一個TCHAR資料型別的變數tc,什麼情況下是正確的呢?  各位思考一下呢?  

如果當前工程是Unicode環境,那麼TCHAR資料型別就是wchar_t的寬位元組型別,所以tc就是寬位元組字元變數,那麼上面第二個賦值語句就是正確的,而第一個就是錯誤的。

如果反過來,當前的工程是ASCII環境,那麼TCHAR代表的是char這種資料型別,那麼第一個賦值語句就是正確的,而第二個就是錯誤的了。

分析了這麼多,我就是要講一個巨集 _T(), 只要將字元或者字串常量放在_T()這個巨集裡面,那麼這個巨集就能根據當前的環境決定是否在字元或字串前面加L,如下面:

TCHAR tc = _T('A');

如果這麼寫,在不需要改寫原始碼的情況下,就可以編譯出Unicode和ASCII兩套程式

而只需要改變工程的環境而已。

這篇文章的內容大概就這麼多了,關於後續文章的內容安排,我會適當採納各位朋友的留言來進行安排。

在這裡我介紹的是Windows平臺下的和字串操作有關的資料型別,至於MFC中的CString類,c++標準庫中的string,我就不做講解了。

還有,前兩篇的文章中我說微軟定義這些資料型別為大寫是為了編碼的方便,不需要切換輸入法,很多朋友都留言給我指出了,其實我是開了一個小玩笑,畢竟面對的是初學者,我覺得在目前這種環境下,沒有必要給他們講什麼程式碼移植性啊,相容性這些概念,那些在達到一定程度之後就會慢慢理解的,所以我自己想了一個小小的優點來說明那個問題,不過還是感謝你們的補充說明,謝謝··

之四 -一個相當簡單的SDK程式

 大家好,還是我beyondcode,再次見面,前面介紹的那麼多'理論知識',你們都懂了嗎? 就算還沒有徹底領悟,但至少還是有那麼一點意識了吧,知道有那麼一回事了吧。這一篇我打算通過一個小小小例子,來回憶一下我們以前介紹的相關知識,如Windows的資料型別,特別是和字元和字串操作相關的資料型別,還有就是Unicode和ASCII在API函式上的具體體現。

另外,SDK程式設計交流群已經建立,很多朋友踴躍參加,系列文章和群的發展離不開你們。群號:81543028。

Ok,我們正式開始,我打算從一個簡單的SDK程式開始,別怕,就幾行程式碼而已··

/* BY beyondcode */

#include <windows.h>

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

{

MessageBoxA( NULL, "Hello beyondcode", "Title", MB_OK );

return 0;

}

程式你已經看到了,這恐怕就是一個最簡單的帶視窗的SDK程式了吧,如果你能寫出程式碼行數比這個還少,又帶視窗顯示字串的SDK程式,歡迎交流,呵呵,開個玩笑。

程式倒是簡單,可是我還是要問一問,這個程式,你通過觀察我在字串的處理,還是在API函式的呼叫,還是主函式的引數寫法,你能看出什麼問題呢?.....................................對,就是我全部明確指出是單位元組版本的,WinMain的第三個引數是LPSTR型別,呼叫的MessageBox是帶A字尾的單位元組版本,字串常量"Hello beyondcode"和"Title"都沒有使用L字首。那麼第二個問題來了, 如果我告訴你我現在的工程環境是 使用Unicode字符集 (工程使用的字符集可以在 【專案】->工程屬性 彈出的屬性頁中的 【配置屬性】中的【常規】左邊的【字符集】中設定),那麼我上面的程式能正常通過編譯嗎? 當然能,因為我已經試過了,不信你也可以試試,可是為什麼呢? 這是因為我指定的引數和函式需要的引數都是單位元組版本的,也就是說他們相互匹配。要是我這裡將MessageBoxA改成MessageBoxW呢? 就會出錯吧,因為MessageBoxW的第二個,和第三個引數是需要LPCWSTR,通過上一篇學習,我們知道也就是const wchar_t*, 而我給出的兩個字串常量卻沒有用L字首.也就是說他們是單位元組的,傳給寬位元組版本的MessageBoxW當然就型別不匹配了啊,所以就通不過編譯了吧。

通過上面的學習,我再出一個問題,如果我此時的工程環境是使用Unicode字符集,而這裡我不用MessageBoxA,也不用MessageBoxW,而是用MessageBox,其他的都不變,結果會怎麼樣呢?   不能理解的可以加群討論喲~~~

好了,單位元組版本的程式,我們已經看到了,我們再來看看我們怎麼才能把它改成寬位元組版本的呢?

其實需要改的地方不多,也就5處WinMain改成wWinMain, WinMain的第三個引數改成LPWSTR,MessageBoxA改成W,兩個字串常量加L就ok了。

/* BY beyondcode */

#include <windows.h>

int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd )

{

MessageBoxW( NULL, L"Hello Beyondcode", L"Title", MB_OK );

return 0;

}

如果我想寫一個程式碼比較通用的版本,也就是可以不用改動程式碼,就能編譯出Unicode和ASCII的兩個版本的程式,我應該怎麼寫呢?  其實就是我上一篇重點討論的,凡是涉及到字串的都不明確指出是Unicode還是ASCII版本的,呼叫的API函式凡是涉及到字串引數的都不明確指出呼叫是A字尾的還是W字尾的函式,而是呼叫沒有後綴的函式,如上面的MessageBox,這樣就能寫出程式碼比較通用的程式了。那麼我們現在來把我們上面的程式改一改,讓它通用

/* BY beyondcode */

#include <windows.h>

#include <tchar.h>

int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd )

{

MessageBox( NULL, _T("Hello Beyondcode"), _T("Title"), MB_OK );

return 0;

}

WinMain被改成了_tWinMain ,_tWinMain也是一個巨集,根據UNICODE這個巨集被設定與否而被定義成WinMain或wWinMain,和LPTSTR是一樣的,這裡還需要注意的是要包含tchar.h這個標頭檔案,因為_tWinMain和_T()這些巨集是被定義在裡面的。經過上面我們就寫出了第一個SDK的可以編譯出兩個版本的比較通用的程式程式碼了。是不是有點成就感了呢。。

下面,我們繼續在上面的程式中加一些功能,讓它計算1到10的和,然後把結果顯示給我們看,這個地方,很多SDK初學者就不知所措了,因為一個和是一個整數,怎麼顯示這個整數給我們呢,通過對話方塊? MessageBox,可是MessageBox顯示的是字串。而我們這裡又不是控制檯程式可以使用printf之類的格式化輸出函式來輸出數字,也不能使用cout之類的C++物件來輸出,那我們怎麼辦呢? 通過對話方塊來顯示結果是不錯的選擇,但是對話方塊需要的是字串,那我們就把我們的結果格式化到一個字串裡面,然後傳送給MessageBox讓它顯示出來。那麼就需要用到格式化字串函式,下面我們就介紹wsprintf這個函式

#ifdef UNICODE

#define wsprintf  wsprintfW

#else

#define wsprintf  wsprintfA

#endif // !UNICODE

說它是函式,是不確切的。因為它實際是一個巨集,根據環境被定義成不同的函式名wsprintfW或者wsprintfA, 而我們為了程式的通用性,直接使用wsprintf,傳遞的引數凡是涉及到字串常量的我們都是用_T()巨集,字串指標的我們都使用LPTSTR和LPCTSTR。

下面我就先貼出添加了功能的程式程式碼,然後在做分析,你可以先不看分析,自己看一看程式碼,不懂的猜一猜它的意思。

/* BY beyondcode */

#include <windows.h>

#include <tchar.h>

int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd )

{

int sum = 0;

for( int i = 1; i<=10; i++ )

sum += i;

TCHAR strSum[256] = { 0 };

wsprintf( strSum, _T("%d"), sum );

MessageBox( NULL, strSum, _T("Title"), MB_OK );

return 0;

}

怎麼樣,也還不算複雜吧,計算1到10的那部分不用我講了吧,最後的結果存放在sum這個變數裡,我們現在的目的就是要讓它顯示在MessageBox彈出的對話方塊上面。

首先我們定義一個字元陣列,我使用的是通用型別TCHAR,然後把它全部初始化為0。接著呼叫格式化字元函式wsprintf,它的第一個引數是LPTSTR型別的,指定經過格式化的字串存放的地方,第二個引數是指定以什麼格式來格式化後面的資料,這裡我們要格式化一個整數,所以指定%d,這個和printf這些函式是一樣的, 後面的引數就是我們要格式化的資料了。

這裡還有一點可能有些新手朋友們不太懂,那就是,wsprintf要求的第一個引數是LPTSTR,而我傳遞的是一個TCHAR的陣列名,這裡我就在囉嗦一次咯,我們假設我們的環境是UNICODE的,那麼LPTSTR相當於什麼型別呢? 上一篇就講過的啊,就是wchar_t* 就是寬字元指標,而我們知道陣列名就是代表這個陣列元素型別的指標,那麼這裡TCHAR陣列的元素型別就是TCHAR,在Unicode環境下,TCHAR就是wchar_t也就是說strSum代表的是wchar_t型別的指標,也就是wchar_t*,所以看到了嗎,他們是一樣的型別。

通過上面的wsprintf函式的呼叫strSum這個字元陣列中就包含了計算結果的字串表示,然後我們通過MessageBox講這個字元陣列中的內容顯示出來。在這裡MessageBox的第二個引數型別是LPCTSTR,也就是const wchar_t*, 而我們上面說過我們的strSum代表的是 wchar_t*,這裡同樣可以傳遞給它又是為什麼呢?這是因為阿,這裡strSum在傳遞給MessageBox的時候就隱式轉換成了const wchar_t*了。

有關格式化字串的函式還有如下,詳細用法各位可以檢視MSDN,和上面所介紹的都差不多

sprintf 單位元組版本的C/C++庫函式

swprintf 寬位元組版本的C/C++庫函式

而我們上面的wsprintf和上面兩個函式看起來很相似,大家不要搞混淆了啊,wsprintf最前面的w不是代表Wide,寬位元組的意思了,而是Windows的W,代表是windows的API函數了,其實它是一個巨集這在上面已經說過了,真正的API函式其實是wsprintfA和wsprintfW這兩個,在不嚴格的情況下通常我們也說wsprintf是函式,只要大家懂就行了~

OK, 這一篇文章就到這裡了,原始碼就這麼點,我也不上傳了,各位有興趣自己敲一下。下一篇,我講會就怎麼自己建立視窗進行介紹,謝謝大家的支援