第一章 不能免俗的“Hello, World!”
在這一章里,就像所有的入門級教程一樣,我也將不能免俗地以一個“Hello, World!”程序開始我的教程。然后,我將逐步深入,向你介紹這個ATL版本程序中所有必要的信息。此外,我還將介紹一些Win32中你可能不知道的東西,包括WinMain的_t兼容以及如何在MessageBox中加入自己的圖標等等。
接近,接近,再接近……
可以說,所有“Hello, World!”程序的內(nèi)容不外乎都是以十分有限的幾行代碼向當前的目標屏幕環(huán)境上輸出一個字符串“Hello, World!”。這個程序通常具有以下幾個特點:
- 排除印刷錯誤的可能性,幾乎所有的初學者都可以照葫蘆畫瓢地獨立書寫、編譯并運行這個程序。
- 這個程序可以體現(xiàn)出當前語言環(huán)境的典型配置方式。
- 這個程序中具有當前語言特定的程序入口點。
- 這個程序中含有一條當前環(huán)境典型的輸出語句(通常也是最簡單、最常用的),由這條語句來輸出“Hello, World!”字符串。
- 從這個程序可以很清楚的了解當前語言環(huán)境下程序運行的典型流程。
- 這個程序可能還會表現(xiàn)當前語言的一些其它特點。
那么,首先讓我以最簡單的C語言版“Hello, World!”開始吧:
#include <stdio.h>
int main() { printf( "Hello, World!n" ); return 0; } |
雖然是不到10行的代碼,但它仍然五臟俱全。現(xiàn)在,就由我將它和上述的特點對號入座吧。也就是說,這個程序能體現(xiàn)出C程序設(shè)計的以下特點:
- C語言的程序以main函數(shù)作為程序入口點。
- printf是C中用來輸出字符串的代碼。
- 函數(shù)是C語言程序的基本單位,它通常由返回值、函數(shù)名、參數(shù)列表、函數(shù)體、return組成。
- 調(diào)用函數(shù)的時候要include相應的頭文件。
- n是C語言中的轉(zhuǎn)義字符,代表換行符。
接下來,我們來看一看Win32版的“Hello, World!”:
#include <windows.h>
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) { MessageBox( NULL, TEXT("Hello, World!"), TEXT("Hello"), 0 ); return 0; } |
這個程序告訴你了以下幾件事:
- 所有Win32下的C程序都需要包含windows.h頭文件。
- Win32下的程序是以WinMain作為程序入口點的,而不是main。
- Win32下最常用輸出信息的方法是MessageBox。
- WINAPI是Win32 API函數(shù)的調(diào)用約定,也就是__stdcall。
- HINSTANCE、LPSTR都是Win32自定義的數(shù)據(jù)類型,分別表示應用程序?qū)嵗浔鸵钥兆址Y(jié)尾的ANSI字符串指針。
- TEXT宏用于在源代碼一級保證ANSI/Unicode字符串的兼容。
如果你對以上的幾個知識點仍然有些許迷茫,請參考Charles Petzold的《Programming Windows》(中譯《Windows程序設(shè)計》)的第一章。這段代碼就是幾乎原封不動地搬過來的。不過,我在編寫這段代碼的時候,通常會這么寫:
#include <windows.h> #include <tchar.h>
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd ) { MessageBox( NULL, _T("Hello, World!"), _T("Hello"), 0 ); return 0; } |
是的,有幾個地方有些不一樣,我對它們的解釋是:
- tchar.h中包含了對C runtime library中ANSI/Unicode字符串的源代碼級兼容。
- _tWinMain提供了對命令行參數(shù)lpCmdLine的ANSI/Unicode源碼級兼容。
- _T宏亦包含在tchar.h之中,它的作用和TEXT宏一樣,但它比TEXT宏更加短小,因此可以節(jié)省編碼的時間。
現(xiàn)在我可以告訴你,隨著我們的步步接近,接下來ATL版的“Hello, World!”程序就要出現(xiàn)在我們的眼前了。那么,就讓我們來看看這個猶抱琵琶半遮面的家伙吧。(請注意,雖然這是一個ATL版本的程序,但是你仍然需要建立一個Win32 Application的工程,而不是用ATL/COM Wizard。)
////////////////////////////////////////////////////////////////////////// // ATL的GUI程序設(shè)計配套源代碼 // 第一章 不能免俗的“Hello, World!” // 工程名稱:HelloWorld // 作者:李馬 // http://www.titilima.cn //////////////////////////////////////////////////////////////////////////
#include <atlbase.h> CComModule _Module;
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd ) { _Module.Init( NULL, hInstance ); MessageBox( NULL, _T("Hello, World!"), _T("Hello"), 0 ); _Module.Term(); return 0; } |
也許有些陌生了,不過所幸它并無太多的變化——畢竟整個代碼段就沒有多長。好了,這一節(jié)的內(nèi)容就到這里,希望李馬的這種漸近的方法沒讓大家覺得一切來得太突然。大家可以喝口水先,然后做個深呼吸再,因為接下來我們就要開始接觸真正的ATL程序了。
“不過如此”
說句題外話先。許是我太狂妄,又許是我太幼稚,總之我在上大學以來,越來越喜歡說“不過如此”這句話。譬如上了大學以后,沒過倆月我就覺得大學“不過如此”;學會喝酒之后,就又會覺得喝酒“不過如此”;到了北京以后,又覺得北京“不過如此”;參觀了某著名軟件公司之后,又覺得它“不過如此”……書歸正傳話歸正題,不知道你第一眼看過ATL版本的“Hello, World!”之后會不會同樣有這樣一種感覺?——自然,我希望是這樣的。
那么,在了解ATL之前,就讓我們先來目測一下這個“Hello, World!”吧。也許,你會從上面的代碼猜到以下內(nèi)容:
- atlbase.h大概其應該是ATL程序需要包含的頭文件。
- CComModule,從類名稱看應該是一個模塊類。_Module是這個模塊類的實例。
- WinMain沒變。
- CComModule::Init應該是對模塊進行初始化,這個方法應該是在程序初始化的時候調(diào)用。
- CComModule::Term應該是對模塊進行結(jié)束處理,這個方法應該是在程序結(jié)束之前調(diào)用。
- WinMain的最后仍然是以return結(jié)尾。
好,是不是“不過如此”呢?沒錯!
大抵如此
到此為止,希望你的猜想能夠讓你對ATL的恐懼感(如果有的話)一掃而光。那么,現(xiàn)在李馬來為你補充上幾點:
atlbase.h在用ATL進行GUI程序設(shè)計的時候,就如同SDK中的windows.h一樣重要。對于GUI程序設(shè)計的部分,這個文件中主要有這么幾個值得關(guān)注的地方:
- Win32程序設(shè)計必備的頭文件,諸如windows.h、tchar.h等。
- CComModule的定義。對于GUI程序設(shè)計,我們可以將它簡單地看作對HINSTANCE的一個封裝。
- 一些簡單的工具類。(請原諒我不能在這里提供給你它們具體的名字,因為ATL 3.0和ATL 7.0是不一樣的。VC 6.0附帶的是ATL 3.0,它的atlbase.h中主要提供了一些COM的智能指針和字符串,如CComPtr、CComBSTR等;而VS2003中的ATL 7.0中則附帶了一些更有趣的類,比如CRegKey、CHandle等。)
下面接著說CComModule。相信你可以從它的類名稱中看出來,這個類主要用來管理COM的各種信息。如果你深入到ATL的源代碼之中,你可能會為它的眾多成員與方法感覺到迷惑。其實在進行GUI程序設(shè)計的時候,你只需要關(guān)心以下這些內(nèi)容:
- HRESULT CComModule::Init( _ATL_OBJMAP_ENTRY* p, HINSTANCE h, const GUID* plibid = NULL );
進行模塊的初始化,第一個參數(shù)取NULL,第二個參數(shù)取應用程序的實例句柄,也就是WinMain中傳入的hInstance。
- void CComModule::Term();
進行模塊的卸載,在程序結(jié)束時調(diào)用。
- HINSTANCE CComModule::GetModuleInstance();
獲取應用程序?qū)嵗浔鶦ComModule::m_hInst。
- HINSTANCE CComModule::GetResourceInstance();
獲取資源模塊句柄CComModule::m_hInstResource,這個值在默認情況下是和CComModule::m_hInst一致的。如果你程序的所有資源位于一個DLL之中,那么你可以在初始化應用程序中將CComModule::m_hInstResource成員賦值為這個DLL的模塊句柄。
接著說CComModule的實例_Module。可以說,這個全局變量貫穿于ATL整個框架的始終,無論你是使用它編寫COM組件還是GUI程序。譬如,你可能不止一次地需要使用模塊的實例句柄(LoadIcon、LoadCursor),那么你只需要這樣調(diào)用:
extern CComModule _Module; HICON hIcon = ::LoadIcon( _Module.GetResourceInstance(), MAKEINTRESOURCE( IDI_YOURICON ) ); |
好了,那么現(xiàn)在我們可以充分展示一下這個模塊類的具體使用了。在此,我僅僅將我先前的“Hello, World!”作了一番擴展,如下:
////////////////////////////////////////////////////////////////////////// // ATL的GUI程序設(shè)計配套源代碼 // 第一章 不能免俗的“Hello, World!” // 工程名稱:HelloWorldEx // 作者:李馬 // http://www.titilima.cn //////////////////////////////////////////////////////////////////////////
#include <atlbase.h> CComModule _Module;
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd ) { _Module.Init( NULL, hInstance ); _Module.m_hInstResource = LoadLibrary( _T("shell32.dll") );
MSGBOXPARAMS mbp; ZeroMemory( &mbp, sizeof( mbp ) ); mbp.cbSize = sizeof( mbp ); mbp.dwLanguageId = GetSystemDefaultLangID(); mbp.dwStyle = MB_USERICON; mbp.hInstance = _Module.GetResourceInstance(); mbp.lpszCaption = _T("Hello"); mbp.lpszIcon = MAKEINTRESOURCE( 44 ); mbp.lpszText = _T("Hello, World!"); MessageBoxIndirect( &mbp );
FreeLibrary( _Module.m_hInstResource ); _Module.m_hInstResource = NULL; _Module.Term(); return 0; } |
這個程序運行起來是這個樣子:

如你所見,在這里我使用了來自應用程序之外的資源,也就是對CComModule::GetModuleInstance進行了特殊處理。WTL就是對CComModule這個類進行了繼承處理而派生出了CAppModule類,使之成為了更適合應用程序使用的模塊類。有興趣的朋友可以參看WTL附帶的atlapp.h文件,我這里就不多說了。
“貌合神離”
字典上對這個詞的解釋是:“表面上很親密而實際上懷有二心”。在此,我將它用在ATL 3.0與7.0上,用來表示它們倆“用法兼容而實現(xiàn)迥異”的既有事實。不過,對于GUI程序設(shè)計而言,你并不需要深入了解這方面的內(nèi)容。因此我這里列舉的,也只是與GUI有關(guān)的部分。
- ATL 3.0之中,CComModule直接繼承自_ATL_MODULE;而ATL 7.0之中,CComModule則經(jīng)歷了一串的繼承鏈。
- 相比之下,ATL 7.0中的CComModule更有COM的味道,譬如它的ModuleInstance、ResourceInstance都可以作為COM組件的property,使用get、put來處理。
當然,ATL畢竟是一個為開發(fā)COM組件而構(gòu)建的Framework,所以ATL 7.0中的atlbase.h之中還包含了更多有關(guān)COM開發(fā)的工具類。這些內(nèi)容與本書無關(guān),而且李馬也自認現(xiàn)在尚無能力來解說這些內(nèi)容,所以一并從略了就。