來(lái)自:http://www.relisoft.com/win32/winnie.html
譯:聶元朗
1. The Simplest Windows Program
1.最簡(jiǎn)單的Windows程序
Before you can even begin thinking about programming in Windows, you have to be able to understand how this simple program works.
在你開始想如何在windows下編程之前,你必須要弄清楚下面這個(gè)簡(jiǎn)單windows程序的工作原理。
Windows API calls are highlighted in blue and Windows specific data types are shown in green. I will also usually put a double colon in front of API calls. In C++, that simply means that I'm calling a global function, in case there is some ambiguity.
我用藍(lán)色來(lái)標(biāo)識(shí)Windows的API調(diào)用,用綠色來(lái)標(biāo)識(shí)Windows中的特殊的數(shù)據(jù)類型。同時(shí)我在API函數(shù)的前面放置了一對(duì)冒號(hào)::。在C++中,這對(duì)冒號(hào)就是告訴編譯器我在調(diào)用一個(gè)全局函數(shù)。由于有時(shí)候有同名的函數(shù),為了不引起混淆,在前面放置冒號(hào)是一種很好的做法
Sources (zipped file 4k) are right here. Remember to compile them as a Windows application. For instance, in Visual C++ select File.New.Projects.Win32 Application. Otherwise you'll get the error: unresolved external _main. (I provided project file Winnie.dsp for those of you who use MS VC++ 6.0 and Winnie.sln for the users of VC++ 7.0)
我提供了源代碼(一個(gè)4k大小的壓縮文件)。請(qǐng)記住要把它們編譯成一個(gè)windows應(yīng)用程序而不是windows控制臺(tái)程序。否則,你就會(huì)得到找不到main函數(shù)的錯(cuò)誤。在VC++6.0中,我們可以通過(guò)選擇File——New Projects——Win32 Application來(lái)創(chuàng)建一個(gè)windows應(yīng)用程序的工程。當(dāng)然,你用不著這么擔(dān)心,我已經(jīng)提供了兩個(gè)做好的工程文件給你。一個(gè)是winnie.dsp,你可以在VC++6.0下使用。一個(gè)是Winnie.sln,你可以在VC++7.0下使用。
First, in a Windows program, you have to define a Window Class, the "class" of window(s) that will be displayed by your application (not a C++ class). In our case we will display only one window, but still, we need to give Windows some minimal information about its Class. The most important part of the WinClass (now, that's a C++ class that describes the Window Class) is the address of the callback procedure, or the Window Procedure. Windows is supposed to call us--Windows sends messages to our program by calling this procedure.
好了,讓我們來(lái)看看如何寫這個(gè)最簡(jiǎn)單的程序吧。首先,在一個(gè)windows程序里面,你需要定義一個(gè)窗口類,注意這個(gè)類的概念不同于C++中的類,這個(gè)窗口類相當(dāng)于C語(yǔ)言中的結(jié)構(gòu)體,你把這個(gè)結(jié)構(gòu)體填好了,然后你的應(yīng)用程序就會(huì)根據(jù)你填的這些信息來(lái)顯示窗口。在我們的例子里面,我們僅僅顯示一個(gè)窗口。但是就算是這樣,我們也必須要給我們的窗口最少的窗口類信息。而其中最重要的部分就是回調(diào)函數(shù)了,有時(shí)候我們也稱它為窗口過(guò)程。其實(shí)這是一個(gè)函數(shù)指針,每次程序需要處理消息的時(shí)候,windows就通過(guò)這個(gè)函數(shù)指針來(lái)調(diào)用我們寫好的回調(diào)函數(shù)。是的,你看到,是windows調(diào)用我們編寫的函數(shù)。你還記得函數(shù)指針嗎?如果不是很清楚,可以google一下,o(∩_∩)o…
Notice the declaration of WindowProcedure. Windows calls it with a handle to the window in question, the message, and two data items associated with the message, the paramters, WPARAM and LPARAM.
讓我們看看WindowProcedure(窗口過(guò)程、回調(diào)函數(shù))的定義。仔細(xì)看一下,第一個(gè)函數(shù)是一個(gè)句柄,也就是個(gè)整數(shù)。Windows用它來(lái)區(qū)別是哪個(gè)窗口。然后是消息,然后是與消息關(guān)系密切的兩個(gè)參數(shù)WPARAM和LPARAM。這個(gè)我本來(lái)想講一下,留到以后吧,畢竟原文中在這里沒(méi)有講。
In WinClass we also have to specify things like the program instance handle HINSTANCE, the mouse cursor (we just load the standard arrow cursor), the brush to paint the window's background (we chose the default window color brush), and the symbolic name of our class (you don't have to understand the meaning of all those yet).
在下面的WinClass類中我們必須給一些窗口類的字段賦值,如窗口的實(shí)例句柄HINSTACE,鼠標(biāo)光標(biāo)(我們僅僅加載了標(biāo)準(zhǔn)的箭頭鼠標(biāo)),畫窗口背景的畫刷(我們用的是窗口缺省的背景顏色畫刷),最后還有窗口類的名字(窗口類的名字最好和別人的不同).(當(dāng)然,現(xiàn)在你不需要弄明白這些參數(shù)的意義)
Once all the fields of WNDCLASS are filled, we register the class with the Windows system.
一旦我們把WNDCLASS結(jié)構(gòu)體填完了,我們就通過(guò)調(diào)用RegisterClass函數(shù)在windows系統(tǒng)中注冊(cè)我們的窗口類。好了,先看看我是怎樣封裝WNDCLASS到一個(gè)WinClass類中去的。
#include <windows.h>
LRESULT CALLBACK WindowProcedure
(HWND hwnd, unsigned int message, WPARAM wParam, LPARAM lParam);
class WinClass
{
public:
WinClass (WNDPROC winProc, char const * className, HINSTANCE hInst);
void Register ()
{
::RegisterClass (&_class);//now we don’t deal with the error
}//we will process the error in the next version
private:
WNDCLASS _class;//define a private WNDCLASS object member
};
WinClass::WinClass
(WNDPROC winProc, char const * className, HINSTANCE hInst)
{
_class.style = 0;
_class.lpfnWndProc = winProc; // window procedure: mandatory
_class.cbClsExtra = 0;
_class.cbWndExtra = 0;
_class.hInstance = hInst; // owner of the class: mandatory
_class.hIcon = 0;
_class.hCursor = ::LoadCursor (0, IDC_ARROW); // optional
_class.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // optional
_class.lpszMenuName = 0;
_class.lpszClassName = className; // mandatory
}
Once the Window Class is registered, we can proceed with the creation of a window. This is done by calling the CreateWindow API. It takes a lot of arguments: the name of the window class that we have just registered, the caption that will appear in the title bar, window style, position, size, and the application instance. The rest of the arguments, for the time being, will be left equal to zero.
一旦窗口類注冊(cè)成功,我們就可以用它來(lái)創(chuàng)建一個(gè)窗口。我們通過(guò)調(diào)用CreateWindow這個(gè)API函數(shù)來(lái)完成它。它有很多參數(shù),這可能讓你有點(diǎn)煩,不過(guò)不要進(jìn),你只要寫一次就好了。這些參數(shù)包括:我們剛剛注冊(cè)好的窗口類的名字(這個(gè)要是搞錯(cuò)了,你的窗口可能就出不來(lái)了),我們窗口的標(biāo)題欄上的標(biāo)題,窗口的樣式,位置,大小還有應(yīng)用程序的實(shí)例句柄。剩下的參數(shù)暫時(shí)我們還不需要用到,我們把它們初始化為0。
This part of the program can also be encapsulated into a C++ class, WinMaker.
程序的這部分也被我封裝到了一個(gè)C++的類WinMaker當(dāng)中。
The window will not appear on the screen until you tell Windows to show it.
就算你創(chuàng)建好了窗口,但你還是看不到,你必須調(diào)用ShowWindow函數(shù)讓它顯示。
class WinMaker
{
public:
WinMaker (): _hwnd (0) {}
WinMaker (char const * caption,
char const * className,
HINSTANCE hInstance);
void Show (int cmdShow)
{
::ShowWindow (_hwnd, cmdShow);
::UpdateWindow (_hwnd);
}
protected:
HWND _hwnd;
};
WinMaker::WinMaker (char const * caption,
char const * className,
HINSTANCE hInstance)
{
_hwnd = ::CreateWindow (
className, // name of a registered window class
caption, // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // x position
CW_USEDEFAULT, // y position
CW_USEDEFAULT, // witdh
CW_USEDEFAULT, // height
0, // handle to parent window
0, // handle to menu
hInstance, // application instance
0); // window creation data
}
A Windows program is event-driven. It means that you, as a programmer, are supposed to be on the defensive. The user will bombard Windows with various input actions, and Windows will bombard your program with messages corresponding to these actions. All you have to do is to respond to these messages. The picture below shows schematically how it works.
Windows程序是事件驅(qū)動(dòng)的。這就是說(shuō)你作為一個(gè)windows程序員,所做的應(yīng)該是防御式編程。其實(shí)也沒(méi)有這么嚴(yán)重。具體來(lái)說(shuō)就是,用戶會(huì)發(fā)給windows多種輸入行為,而windows把這些輸入用消息的方式發(fā)給你的程序。而你,所做的就是針對(duì)每一個(gè)消息在回調(diào)函數(shù)中做出處理。下面的這張圖示意性的說(shuō)明了這個(gè)過(guò)程。圖在下一頁(yè)的下一頁(yè),o(∩_∩)o…
Windows gets various events from the keyboard, the mouse, the ports, etc. Each event is quickly converted into a message. Windows (the operating system) dispatches messages to appropriate windows. For instance, all keyboard messages go to the window that currently has the input focus (the active window). Mouse messages are dispatched according to the position of the mouse cursor. They usually go to the window that is directly under the cursor (unless some program captured the mouse).
首先,windows系統(tǒng)從鍵盤、鼠標(biāo)、端口等等各種設(shè)備中獲得各種事件。每個(gè)事件被快速的轉(zhuǎn)換為消息。Windows分派這些消息給合適的窗口。舉個(gè)例子,所有的鍵盤消息都會(huì)傳給當(dāng)前具有輸入焦點(diǎn)的窗口(也就是所謂的活動(dòng)窗口)。鼠標(biāo)消息則會(huì)根據(jù)鼠標(biāo)光標(biāo)所在的位置分派給相應(yīng)的窗口。這些窗口通常是直接位于光標(biāo)下的第一個(gè)窗口(除非有某些程序捕獲鼠標(biāo)消息)。
All these messages end up in message queues. Windows keeps a message queue for every running application (actually, for every thread). It is your duty to retrieve these messages one-by-one in what is called a message loop. Your program has to call GetMessage to retrieve a message. Then you call DispatchMessage to give it back to Windows. Couldn't Windows just go ahead and dispatch all these messages itself? In principle it could, but a message loop gives your program a chance to have a peek at them and maybe perform some additional actions before dispatching them. Or not...
所有的消息都放在消息隊(duì)列里面。Windows為每一個(gè)運(yùn)行的應(yīng)用程序維護(hù)一個(gè)消息隊(duì)列(實(shí)際上,是為每一個(gè)線程)。把這些消息一個(gè)個(gè)的取出來(lái),這是你的責(zé)任。通常我們把這段代碼叫做消息循環(huán)。在你的程序里,首先調(diào)用GetMessage函數(shù)取出一條消息,然后調(diào)用DispatchMessage把它返回給Windows。Windows還會(huì)重新分派這些消息嗎?原則上不會(huì),但是你可以在消息循環(huán)中使用PeekMessage函數(shù)來(lái)查看消息而不分派它,因?yàn)槟承┣闆r下,你需要在分派這些消息之前做一些額外的工作。
Each message is addressed to a particular window. When you tell Windows to dispatch such a message, it will figure out the class of this window, find the associated Window Procedure, and call it. Every single message sent to our window will end up in our window procedure. It is now up to us to respond to it. So, do we have to respond appropriately to every possible type of Windows message? There a hundreds of them! Fortunately, no! We only need to intercept those messages that we are interested in. Everything else we pass back to Windows for default processing using DefWindowProc.
每個(gè)消息被傳遞到一個(gè)特殊的窗口。當(dāng)你告訴windows分派一條信息的時(shí)候,windows會(huì)先找出這個(gè)窗口的類,然后找到與之關(guān)聯(lián)的窗口過(guò)程,最后調(diào)用這個(gè)窗口過(guò)程。每個(gè)發(fā)送到我們窗口的消息都會(huì)在我們的窗口過(guò)程中終止。現(xiàn)在輪到我們來(lái)處理這些消息了。因此,我們必須要處理所有的消息類型?不,這里有幾百種消息,我們只需要處理對(duì)我們有用的消息就可以了。但其它消息怎么辦呢?不用擔(dān)心,其它消息我們通過(guò)調(diào)用DefWindowProc這個(gè)函數(shù)來(lái)自動(dòng)處理。
Let's have a look at WinMain. The execution of a Windows program doesn't start in main--it starts in WinMain. In our WinMain, we create a WinClass and register it. Then we create an actual window (of the class we've just registered) and show it. Actually, WinMain is called with the appropriate show directive--the user might want to start the application minimized or maximized. So we just follow this directive. Next, we enter the message loop and keep retrieving and dispatching messages until GetMessage returns 0. At that point the message's wParam will contain the return code of the whole program.
讓我們看看WinMain這個(gè)函數(shù)。一個(gè)windows函數(shù)的開始不是main函數(shù),而是WinMain。在我們的winMain函數(shù)里,我們創(chuàng)建了一個(gè)WinClass的對(duì)象winClass,然后我們?cè)趙indows中注冊(cè)了它。接下來(lái)我們創(chuàng)建了一個(gè)窗口(我們剛剛注冊(cè)的窗口類所對(duì)應(yīng)的窗口)并且顯示它。實(shí)際上,WinMain由一個(gè)參數(shù)是用來(lái)指明窗口顯示效果的(這樣,用戶可以指定窗口顯示的方式)——用戶可能希望打開這個(gè)程序用最小化或者最大化的方式打開,因此,我們?cè)谶@里只需要直接使用這個(gè)參數(shù)就可以了。然后我們進(jìn)入消息循環(huán)并不斷取出消息和進(jìn)行分派,直到GetMessage函數(shù)返回0。這個(gè)時(shí)候消息的wParam參數(shù)將會(huì)包含整個(gè)程序的返回值。
int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInst,
char * cmdParam, int cmdShow)
{
char className [] = "Winnie";
WinClass winClass (WindowProcedure, className, hInst);
winClass.Register ();
WinMaker win ("Hello Windows!", className, hInst);
win.Show (cmdShow);
MSG msg;
int status;
while ((status = ::GetMessage (& msg, 0, 0, 0)) != 0)
{
if (status == -1)
return -1;
::DispatchMessage (& msg);
}
return msg.wParam;
}
The GetMessage API is an interesting example of the bizarre Microsoft Troolean (as opposed to traditional, Boolean) logic. GetMessage is defined to return a BOOL, but the documentation specifies three types of returns, non-zero, zero and -1. I am not making it up! Here's an excerpt from the help file:
GetMessage函數(shù)是一個(gè)很好笑的微軟的三值布爾類型的例子。GetMessage函數(shù)定義的返回值是BOOL類型,但是文檔卻明確指出它含有三種返回值,非0、0和-1。我不想在這里多費(fèi)口舌。下面是從MSDN中的摘錄。
· If the function retrieves a message other than WM_QUIT, the return value is nonzero.
· If the function retrieves the WM_QUIT message, the return value is zero.
· If there is an error, the return value is -1.
The other important part of every Windows program is the Windows Procedure. Remember, Windows will call it with all kinds of messages. All these messages can be ignored by sending them to DefWindowProc. There is only one message that we must intercept. That's the WM_DESTROY message that is sent by Windows when the user decides to close the window (by pressing the close button in the title bar). The standard response to WM_DESTROY is to post the quit message and return zero. That's all there is to it.
讓我們看看所有windows程序都很重要的回調(diào)函數(shù)吧。你需要注意的是,windows將為所有的消息調(diào)用這個(gè)函數(shù)。所以剩余的消息一定要送給DefWindowProc去處理。在這里我們只處理了一個(gè)消息。那就是WM_DESTROY。當(dāng)用戶要關(guān)閉一個(gè)窗口的時(shí)候,windows會(huì)發(fā)送這個(gè)消息。標(biāo)準(zhǔn)的處理WM_DESTROY消息是發(fā)送一條退出消息,并返回0。好了,這就是我們要講的全部了。*_*
// Window Procedure called by Windows
LRESULT CALLBACK WindowProcedure
(HWND hwnd, unsigned int message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
::PostQuitMessage (0);
return 0;
}
return ::DefWindowProc (hwnd, message, wParam, lParam );
}