C/WINAPI编程:使用资源
VLSMB本文档为《Windows环境下32位汇编语言程序设计》第五章使用资源的部分代码的C语言版,并提供一些必要的补充说明。
5.1 菜单资源
122页:Menu.rc
资源代码(*.rc)文件分既不是汇编语言也不完全是C语言。资源代码可以直接放在C语言工程文件中编译。但考虑到读者复制方便,本文档会重新抄写一遍原书中的资源代码文件。
1 | #include <resource.h> |
第128页 Menu.c代码
1 | // include 文件定义 |
稍微补充几点书上没有提到的问题(因为汇编语言不会遇到而C语言会遇到):
首先是新的几个需要区分A和W版本的函数:
1 | AppendMenuA(); |
之后是资源编号的问题。
C语言中读取资源的函数,以LoadMenuA为例,它的第二个参数只支持传入字符串地址即LPCSTR。但是根据书上所说,这个参数小于0x10000时API会认为是数字,大于0x10000时会认为是字符串地址。因此我们调用这列函数的时候,只需要在编号之前做强制类型转换:
1 | LoadMenuA(hInstance, (LPCSTR)IDM_MAIN); |
把这个数字传给API就行了,底层API仍然会按照参数小于0x10000是数字,大于0x10000时是字符串地址来解读。当然你也可以直接用字符串进行编号。
此外,如何在C语言项目中添加并编辑资源代码(*.rc),以Visual Studio为例:
1、右键资源文件,添加新建项。
2、创建以rc结尾的文件。
3、右键选择打开方式。
4、选择C++源码编辑器
5、打开编辑rc文件,编辑完之后保存并关闭。
完成之后,你再在visual studio上双击打开这个资源文件,你会发现vs自动解析了这个资源代码。
在资源视图里可以直接右键添加资源。
事实上,读者完全可以只写一个基本的rc框架,然后用资源视图进行添加资源。
补充说明一:Windows程序的换行
无论你是用MessageBoxA还是其他显示字符串的winapi,请用”\r\n”代表换行。单纯的”\n”无法起到换行的作用。这也能解释为什么一些OnlineJudge上的测试样例需要连着两次getchar()才能过滤掉回车,就是测试样例用的是”\r\n”做回车。Linux等其他操作系统一般用”\n”代表换行。
补充说明二:数据类型 BYTE WORD DWORD
这三种数据类型可以代表Winapi中几乎所有的变量类型(除了结构体类型)。BYTE为字节,大小为1字节,对应C语言的char变量类型的大小。WORD为字,大小为2字节,对应C语言的unsigned short变量类型的大小。DWORD为双字,大小为4字节,对应C语言的unsigned int/unsigned long变量类型的大小。
Winapi中的一些变量类型比如一大堆句柄(HMENU、HWND、HINSTANCE等)归根结底都是这三种基本类型的重命名。必要时可以用强制类型转换。
补充说明三:两个重要的参数wParam和lParam
在winapi中,wParam和lParam是传递消息的重要变量,类型均为DWORD。在不同的消息中,wParam和lParam数值的含义各不相同。普遍情况下,wParam的低2字节和高2字节分别具有不同的含义。
1 | wParam & 0xFFFF; // 取低2字节的数据 |
这里简要说明一下最常见的WM_COMMAD消息中,wParam和lParam的代表意义。下图为MSDN简单介绍:
wParam高2字节为通知码,低2字节为命令ID,lParam为发送命令消息的子窗体句柄。
对于菜单和加速键来说:
lParam始终为0,wParam高2字节菜单为0、加速键为1,wParam低2字节为资源代码文件中对菜单定义的ID值。
对于按钮来说:
lParam为按钮句柄,wParam低2字节为资源代码中对按钮定义的ID值。若是通过CreateWindowEx创建的按钮,则ID为创建按钮传给CreateWindowEx时HMENU hMenu参数的值。
wParam高2字节通知码为以下宏定义:
1 | /* |
对于编辑框来说:
lParam为编辑框句柄,wParam低2字节为资源代码中对编辑框定义的ID值。若是通过CreateWindowEx创建的编辑框,则ID为创建编辑框传给CreateWindowEx时HMENU hMenu参数的值。
wParam高2字节通知码为以下宏定义:
1 | /* |
其余控件的详细信息请查询MSDN文档。
这里提供几个相关资料:
1 | MSDN文档——Windows控件: |
补充说明四:系统菜单与非系统菜单
Windows程序的菜单分两类:系统菜单和非系统菜单。对应的加速键(快捷键)也分系统与非系统。
简单来说,非系统菜单就是程序员在资源文件中定义的菜单,点击非系统菜单的menuitem,发送的消息是WM_COMMAND;系统菜单是Windows维护的菜单,包含最大化最小化关闭等基本任务,发送的消息是WM_SYSCOMMAND。非系统菜单的句柄需要使用LoadMenu获取,而系统菜单的句柄需要使用GetSysMenu函数获取。
对于Menu.c缩写的程序,非系统菜单是这些。标题下面的菜单项及其子项,右键弹出的菜单项及其子项都是非系统菜单:
单击图标或者按Alt+Space弹出的菜单是系统菜单:
我们稍微的对Menu.c的代码改动一下,方便读者更好地区分哪部分是系统菜单哪部分是非系统菜单:
1 | //代码部分,增加一个int i参数 |
非必要的情况下,请最好不要改动系统菜单。(本书的例子其实就是非必要,但可能是为了介绍系统菜单才这么用的吧)如若需要改动系统菜单,一定要执行DefWindowProcA(),否则,窗口将不能收到最小化、关闭、移动等基本消息,出现假卡死现象。
1 | case WM_SYSCOMMAND: |
WM_COMMAND可以不执行DefWindowProcA(),因为都是程序员自定义的菜单所传的消息。
P132页 1、加载菜单:
用CreateWindowEx指定菜单
1 | // 建立并显示窗口,这里传了个hMenu |
在参数中指出的hMenu必须要有LoadMenu从资源二进制文件(.res,由.rc编译而来)获取到菜单句柄。
1 | hMenu = LoadMenuA(hInstance, (LPCSTR)IDM_MAIN); |
如果是字符串定义的菜单,则直接传字符串。
加速键(快捷键)与菜单一样,需要获取句柄。
1 | hAccelerator = LoadAcceleratorsA(hInstance, (LPCSTR)IDA_MAIN); |
要实现加速键,好需要在消息循环中处理用户按键的消息,因此要用TranslateAccelerator,如果消息循环中检测到用户按下了某一快捷键,则会执行目标窗口的WM_COMMAD消息并返回TRUE。而其他不是加速键的消息就要用之前的方式处理这些消息。
1 | // 在win32汇编中,eax是32位通用寄存器,其基本功能之一就是存储函数的返回值;invoke指令是调用函数,因此书上的代码意思就是TranslateAccelerator函数返回值位0时执行TranslateMessage和DispatchMessage |
134页 WM_COMMAND分支的一般结构
1 | switch(uMsg) |
书上说的ax扩展eax等关于寄存器的操作可以忽略掉,也不要把eax当作wParam的替身。eax指的是什么,完全由汇编程序员的意愿决定。
135页 菜单项的修改
程序运行中可以动态修改菜单项,由以下几个API完成:
1 | BOOL WINAPI AppendMenuA(HMENU hMenu, UINT uFlags,UINT_PTR uIDNewItem,LPCSTR lpNewItem); //添加菜单项 |
这些函数的详细介绍看书上的就可以了。
136页 使用系统菜单
(本人并不推荐这种做法,一是容易把程序写崩,二是很少有人知道怎样打开程序的系统菜单。)
1 | // 窗口过程 消息循环中 |
137页 右键弹出菜单
实现函数:
1 | BOOL WINAPI TrackPopupMenu(HMENU hMenu,UINT uFlags,int x,int y,int nReserved,HWND hWnd,const RECT *prcRect); |
获取鼠标位置:
1 | POINT stPos; |
Menu.c中的相关代码:
1 | POINT stPos; |
主菜单不能作为popup菜单,只能使用二级菜单(子菜单),因此需要获取子菜单句柄:
1 | HMENU WINAPI GetSubMenu(HMENU hMenu,int nPos); |
菜单状态的检测与设置:
1 | UINT WINAPI GetMenuState(HMENU hMenu,UINT uId,UINT uFlags); |
例子:
1 | unsigned st = GetMenuState(...); |
设置菜单项的状态:
1 | BOOL WINAPI EnableMenuItem(HMENU hMenu,UINT uIDEnableItem,UINT uEnable); |
Menu.c中的应用:
1 | else if (wParam >= IDM_TOOLBAR && wParam <= IDM_STATUSBAR) |
5.2 图标和光标
图标的话就是在资源文件里定义图标资源,在CreateWindowEx之前调用LoadIcon。
定义图标资源:
1 | // resource.rc |
1 | hIcon = LoadIconA(hInstance,lpIconName); |
如果不定义光标的话,将使用Windows默认光标。
5.4 对话框
(本人推荐主窗口用CreateWindow,做子任务的子窗口用对话框)
1 | //Dialog.rc |
使用对话框代码Dialog.c:
1 | #include <windows.h> |
创建模态对话框
1 | INT_PTR WINAPI DialogBoxParamA(HINSTANCE hInstance,LPCSTR lpTemplateName,HWND hWndParent,DLGPROC lpDialogFunc,LPARAM dwInitParam); |
其中lpTemplateName为对话框资源ID,可以是低于0x10000的数字也可以是字符串地址。lpDialogFunc为过程函数地址,用法为
1 | (DLGPROC)函数名称 |
要结束模态对话框,必须在对话框过程的WM_CLOSE使用EndDialog
1 | BOOL WINAPI EndDialog(HWND hDlg,INT_PTR nResult); |
创建非模态对话框
1 | HWND WINAPI CreateDialogParamA(HINSTANCE hInstance,LPCSTR lpTemplateName,HWND hWndParent,DLGPROC lpDialogFunc,LPARAM dwInitParam); |
关闭非模态对话框要使用DestroyWindow。
对话框过程,与普通窗口的过程一致:
1 | LRESULT CALLBACK _ProcDlgMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) |
注意对话框过程和普通窗口过程在使用时有以下区别(见书上描述)
在对话框中使用子窗口控件
Control.rc
1 | #include <resource.h> |
注意在编译时,你的项目工程里要有Main.ico、Picture1.bmp和Picture2.bmp文件。
Control.c
1 | #include <windows.h> |
162页,子窗口控件的通用方法
通用函数:
1 | HWND WINAPI GetDlgItem(HWND hDlg,int nIDDlgItem); //通过控件ID获取其句柄 |
使用单选钮和复选框:
1 | UINT WINAPI IsDlgButtonChecked(HWND hDlg,int nIDButton); |
静态控件(文本标签、图片)。
文本编辑框:
1 | UINT WINAPI GetDlgItemTextA(HWND hDlg,int nIDDlgItem,LPCSTR lpString,int cchMax); |
使用滚动条:
1 | // 消息处理分支,uMsg为WM_HSCROLL时 |
位置矫正:
1 | if (dwPos < 0)dwPos = 0; |
使用列表框:
ListBox.rc
1 | #include <resource.h> |
ListBox.c
1 | #define _CRT_SECURE_NO_WARNINGS |









