使用WINAPI创建一个窗口程序
发表于更新于
本文档是将《Windows环境下32位汇编语言程序设计(典藏版)》第4章汇编代码部分转换成对应的C语言代码,并对C语言代码如何使用WINAPI进行部分补充。
第94页 FirstWindow.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #include <Windows.h>
HINSTANCE hInstance; HWND hWinMain; LPCSTR szClassName = "MyClass"; LPCSTR szCaptionMain = "My first Window !"; LPCSTR szText = "Win32 Assembly, Simple and powerful !";
LRESULT CALLBACK _ProcWinMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); int WINAPI _WinMain();
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd) { return _WinMain(); }
LRESULT CALLBACK _ProcWinMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_PAINT: { PAINTSTRUCT stPs; RECT stRect; HDC hDc; hDc = BeginPaint(hWnd, &stPs); GetClientRect(hWnd,&stRect); DrawTextA(hDc,szText,-1,&stRect,DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hWnd, &stPs); } break; case WM_CLOSE: DestroyWindow(hWinMain); PostQuitMessage(NULL); break; default: return DefWindowProcA(hWnd,uMsg,wParam,lParam); } return 0; } int WINAPI _WinMain() { WNDCLASSEXA stWndClass; MSG stMsg; hInstance = GetModuleHandleA(NULL); RtlZeroMemory(&stWndClass,sizeof(stWndClass));
stWndClass.hCursor = LoadCursorA(0,IDC_ARROW); stWndClass.hInstance = hInstance; stWndClass.cbSize = sizeof(WNDCLASSEX); stWndClass.style = CS_HREDRAW | CS_VREDRAW; stWndClass.lpfnWndProc = _ProcWinMain; stWndClass.hbrBackground = COLOR_WINDOW + 1; stWndClass.lpszClassName = szClassName; RegisterClassExA(&stWndClass);
hWinMain = CreateWindowExA(WS_EX_CLIENTEDGE,szClassName,szCaptionMain,WS_OVERLAPPEDWINDOW,100,100,600,400,NULL,NULL,hInstance,NULL); ShowWindow(hWinMain,SW_SHOWNORMAL); UpdateWindow(hWinMain);
while(1) { if(GetMessageA(&stMsg,NULL,0,0) == 0) break; TranslateMessage(&stMsg); DispatchMessageA(&stMsg); } return 0; }
|
C语言建立窗口程序需要指定子系统为windows,以Visual Studio为例,打开项目属性,进行如下配置:

这样,main不再是入口函数,程序的入口函数定义为:
1
| int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd);
|
直接复制用就行,四个形参几乎很少使用。如果有需要的话可以查看WINAPI文档。涉及到处理字符串的WINAPI,都会有UNICODE版本和ANSI版本之分,使用时请注意整个项目的字符集与函数一致。
1 2 3 4
| LPCSTR strANSI = "Hello World!";
LPCWSTR strUNICODE = L"Hello World!"
|
Windows.h头文件提供了大量数据类型的定义,部分是基本数据类型的重命名,比如DWORD的实现:
1
| typedef unsigned long DWORD;
|
再比如LPCSTR相当于char*变量,所以这些变量类型并不难理解。但是调用函数时要按照形参要求传递对应类型的变量,必要时要用强制转换。
调用涉及字符串的WINAPI,要注意调用对应的函数。一般情况下,需要ANSI字符串的API以A结尾,需要UNICODE字符串的API以W结尾。例如MessageBox就有两个版本:
1 2
| MessageBoxA(NULL,"Hello World!","VLSMB",MB_OK); MessageBoxW(NULL,L"Hello World!",L"VLSMB",MB_OK);
|
当然,也可以直接使用MessageBox,但一定要注意编译器默认的字符集是什么。

我这里的环境下是Unicode字符集,MessageBox和MessageBoxW等价。手动修改字符集定义,结果为:

因此建议把A和W具体的写出来,而不是使用笼统的函数。否则相同的代码放在其他人的电脑上运行可能就会出现乱码。(很显然,第一个图片运行正常而第二个运行就会乱码)
另外,C语言代码中有些函数前面的WINAPI、CALLBACK必须要有,这个是函数的调用约定。函数的调用约定是指函数的参数传递方式。C语言的默认调用约定是cdecall而WINAPI几乎都是stdcall,因此你定义的窗口的回调函数必须是stdcall。WINAPI和CALLBACK都是__stcall的宏定义,这两个之间替换无所谓,以下回调函数的写法都正确:
1 2 3
| LRESULT CALLBACK _ProcWinMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT WINAPI _ProcWinMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT __stdcall _ProcWinMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
99页 GetModuleHandle的用法:(这本书中所有函数都是用A版本的函数,因此作者并没有显示写出来)
1 2 3 4 5 6
| HMODULE WINAPI GetModuleHandleA(LPCSTR lpModuleName);
LPCSTR szUserDll = "User32.dll"; HINSTANCE hUserDllHandle = GetModuleHandleA(szUserDll);
|
第101页WNDCLASSEXA的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| typedef struct WNDCLASSEXA { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; HICON hIconSm; }
|
在FirstWindow程序中,注册窗口类的代码是:
1 2 3 4 5 6 7 8 9 10
| WNDCLASSEXA stWndClass; RtlZeroMemory(&stWndClass,sizeof(stWndClass)); stWndClass.hCursor = LoadCursorA(0,IDC_ARROW); stWndClass.hInstance = hInstance; stWndClass.cbSize = sizeof(WNDCLASSEX); stWndClass.style = CS_HREDRAW | CS_VREDRAW; stWndClass.lpfnWndProc = _ProcWinMain; stWndClass.hbrBackground = COLOR_WINDOW + 1; stWndClass.lpszClassName = szClassName; RegisterClassExA(&stWndClass);
|
hbrBackground成员:
1 2 3 4
| stWndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
stWndClass.hbrBackground = COLOR_WINDOW + 1;
|
104页 CreateWindowExA的使用:
1
| HWND WINAPI CreateWindowExA(DWORD dwExStyle,LPCSTR lpClassName,LPCSTR lpWindowName,DWORD dwStyle,int X,int Y,int nWidth,int nHeight,HWND hWndParent,HMENU hMenu,HINSTANCE hInstance,LPVOID lpParam);
|
FirstWindow程序中建立窗口的相关代码:
1 2 3
| hWinMain = CreateWindowExA(WS_EX_CLIENTEDGE,szClassName,szCaptionMain,WS_OVERLAPPEDWINDOW,100,100,600,400,NULL,NULL,hInstance,NULL); ShowWindow(hWinMain,SW_SHOWNORMAL); UpdateWindow(hWinMain);
|
CreateWindowEx也可以创建按钮和文本框,以按钮为例:
1 2
| CreateWindowExA(NULL,"button","&OK",WS_CHILD | WS_VISIBLE,10,10,65,22,hWnd,1,hInstance,NULL);
|
第108页 消息循环
消息循环的一般形式:
1 2 3 4 5 6 7 8 9
| MSG stMsg; while(TRUE) { if(GetMessageA(&stMsg,NULL,0,0) == 0) break; TranslateMessage(&stMsg); DispatchMessageA(&stMsg); }
|
MSG结构体:
1 2 3 4 5 6 7 8
| typedef struct MSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; }
|
GetMessageA的声明:
1
| BOOL WINAPI GetMessageA(LPMSG lpMsg,HWND hWnd,wMsgFilterMin,UINT wMsgFilterMax);
|
WINAPI变量中,带lp前缀的表示是一个指针类型的变量。因此LPMSG等效于MSG*
类似的,LPCSTR相当于CSTR*,相当于const char*
其他形式的消息循环:
1 2 3 4 5 6 7
| int dwQuitFlag=0;
while(!dwQuitFlag); ExitProcess(NULL);
|
或者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| MSG stMsg;
while(TRUE) { if(PeekMessageA(&stMsg,NULL,0,0,PM_REMOVE)) { if(stMsg.message==WM_QUIT) break; TranslateMessage(&stMsg); DispatchMessageA(&stMsg); } else { } }
|
110页 窗口过程
窗口过程(回调函数)必须遵循规定的格式,回调函数的地址是必须的,因此必须为stdcall调用约定。
1 2
| LRESULT CALLBACK _ProcWinMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
窗口过程的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| LRESULT CALLBACK _ProcWinMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_XXX: break; case WM_YYY: break; case WM_CLOSE: DestroyWindow(hWinMain); PostQuitMessage(NULL); break; default: return DefWindowProcA(hWnd, uMsg, wParam, lParam); } return 0; }
|
115页 窗口间的通信
PostMessageA和SendMessageA的声明:
1 2
| BOOL WINAPI PostMessageA(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam); LRESULT WINAPI SendMessageA(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam);
|
窗口通信实验,接受程序用FirstWindow.c,在窗口过程的分支中加上如下代码:
1 2 3 4 5 6 7 8 9 10 11 12
| case WM_SETTEXT: { char szBuffer[256]; LPCSTR szReceive = "Receive WM_SETTEXT message\r\nparam: %08x\r\ntext: \"%s\"\r\n"; LPCSTR szCaptionMain = "Receive Message";
wsprintfA(szBuffer,szReceive,lParam,lParam); MessageBoxA(hWnd,szBuffer,szCaptionMain,MB_OK); } break;
|
117页发送程序的代码 Sender.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <windows.h> char szBuffer[256];
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd) { HWND hWnd = FindWindowA("MyClass",NULL); LPCSTR szText = "Text send to other windows"; if(hWnd) { wsprintfA(szBuffer,"Press OK to start SendMessage, param: %08x!",szText); MessageBoxA(NULL,szBuffer,"SendMessage",MB_OK); SendMessageA(hWnd,WM_SETTEXT,0,szText); MessageBoxA(NULL,"SendMessage returned!","SendMessage",MB_OK); } else { MessageBoxA(NULL,"Receive Message Window not found!","SendMessage",MB_OK); } return 0; }
|
FindWindowA的使用方法:
1
| HWND WINAPI FindWindowA(LPCSTR lpClassName, LPCSTR lpWindowName);
|
119页 WM_COPYDATA
COPYDATASTRUCT的定义:
1 2 3 4 5
| typedef struct COPYDATASTRUCT { ULONG_PTR dwData; DWORD cbData; PVOID lpData; };
|
使用SendMessageA函数:
1 2 3 4 5 6 7
| COPYDATASTRUCT stCopyData;
SendMessage(hDestWnd,WM_COPYDATA,hWnd,&stCopyData);
|