抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

本文档是将《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 文件定义
#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();

// 主函数,子系统选择windows
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为例,打开项目属性,进行如下配置: alt text 这样,main不再是入口函数,程序的入口函数定义为:
1
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd);
直接复制用就行,四个形参几乎很少使用。如果有需要的话可以查看WINAPI文档。涉及到处理字符串的WINAPI,都会有UNICODE版本和ANSI版本之分,使用时请注意整个项目的字符集与函数一致。
1
2
3
4
// 定义ANSI字符串
LPCSTR strANSI = "Hello World!";
// 定义UNICODE字符串,注意字符串前要加上L
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,但一定要注意编译器默认的字符集是什么。 alt text 我这里的环境下是Unicode字符集,MessageBox和MessageBoxW等价。手动修改字符集定义,结果为: alt text 因此建议把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);

// 获取User32.dll的句柄
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; // 结构的字节数
/* Win 3.x */
UINT style; // 类风格
WNDPROC lpfnWndProc; // 窗口过程的地址
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance; // 所属的实例句柄
HICON hIcon; // 窗口图标
HCURSOR hCursor; // 窗口光标
HBRUSH hbrBackground; // 背景色
LPCSTR lpszMenuName; // 窗口菜单
LPCSTR lpszClassName; // 类名字符串
/* Win 4.0 */
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);
// OK前面加的&不是取地址的意思,不加这个符号也可以。这个过程需要父窗口提供的窗口句柄hWnd和进程实例hInstance
第108页 消息循环

消息循环的一般形式:

1
2
3
4
5
6
7
8
9
// 这个TRUE是windows.h提供的,C语言中没有C++的true和false关键字。你也可以用1和0替代。
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
1
typedef MSG* LPMSG;
类似的,LPCSTR相当于CSTR
,相当于const char*

其他形式的消息循环:

1
2
3
4
5
6
7
int dwQuitFlag=0;
/* CreateWindowA(...);
ShowWindow(...);
UpdateWindow(...);
*/
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:
// 处理WM_XXX消息
break;
case WM_YYY:
// 处理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];
// WINAPI里需要用\r\n同时在一起才能换行
LPCSTR szReceive = "Receive WM_SETTEXT message\r\nparam: %08x\r\ntext: \"%s\"\r\n";
LPCSTR szCaptionMain = "Receive Message";

// wsprintfA和sprintf效果和用法相似,UNICODE字符串需要用wsprintfW
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;
/*
stCopyData.dwData = ?
stCopyData.cbData = ?
stCopyData.lpData = ?
*/
SendMessage(hDestWnd,WM_COPYDATA,hWnd,&stCopyData);