文章目录
windows编程与控制台编程的区别在于
属性 - 链接器 - 系统 - 子系统
,选错会报错,
无法解析的外部符号
。
0x00. 数据类型
windows没有提供新的类型,而是大量重定义。
句柄类型是windows面向对象的实现,用来标识一个对象,大小4或8字节,其实就是一个数值, 有时候等于地址,但不是指针,。
把句柄理解为对象就阔以了。
0x01. 调试
OutputDebugString()
OutputDebugStringA(LPCWSTR)
,会在调试界面的输出窗口输出信息。
BOOL print(const TCHAR* format, ...) //变参函数
{
TCHAR buffer[1000];
va_list argptr;
va_start(argptr, format);
//将格式化信息写入指定的缓冲区
wvsprintf(buffer, format, argptr);
va_end(argptr);
//将缓冲区信息输出
OutputDebugString(buffer);
return TRUE;
}
err,hr
在监视窗口输入err,hr
。
FormatMessage()
由错误码得到错误信息字符串。
void MyGetErrorInfo(
UINT unErrCode,
UINT unLine)// unLine=__LINE__
{
LPTSTR lpMsgBuf = NULL;
WCHAR szMessage[128] = { 0 };
// 通过错误代码获取到信息
FormatMessageW(0x1300, NULL, unErrCode,
0x400, (LPTSTR)& lpMsgBuf, 64, NULL);
// 拼接错误信息和错误代码
swprintf_s(szMessage, 128,
L"%d - [0x%08X]: %s", unLine, unErrCode, lpMsgBuf);
// 输出调试信息
OutputDebugString(szMessage);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
CreateFile(0,0,0,0,0,0,0);
DWORD ErrorCode = GetLastError();
MyGetErrorInfo(ErrorCode,__LINE__);
return 0;
}
工具
spy++,获取窗口消息,注意在工具栏 - 消息 - 记录选项
中勾选消息类别。
错误查找,输入错误码返回错误信息。
0x02. 消息
windows消息分类
windows有3种消息类型:通用窗口消息,控件消息,自定义消息。
通用窗口消息以WM
开头,分为:
- 窗口消息,由os和控制其它窗口的窗口使用;
CreateWindow(), DestroyWindow(), MoveWindow(), 点击鼠标
等都会产生; - 命令消息,特指
WM_COMMAND
消息,子控件通知父窗口,例如按下按钮等标准控件; - 通知消息:特指
WM_NOTIFY
消息,命令消息无法满足复杂控件给父窗口传递消息的要求而衍生出来的消息,只适用于Windows、 - 公共控件,如树、列表。
控件消息:一些控件有自己单独处理的消息。
自定义的消息必须是WM_USER
之后的值,因为前面的值都已经被使用了
消息标识符:
- 系统保留消息:
0x0000 - 0x03ff
- 应用程序消息:
0x0400(WM_USER) - 0x7fff
由自己定义;0xc000 - 0xffff
用来和其它程序通信。
#define UM_USER (WM_USER + 1)
使用 SendMessage()
和 PostMessage()
可以发送自定义消息,发送的消息可以是给自己的窗口,也可以是给其它的窗口,由参数一指定
队列消息
队列、非队列消息是按照发送途径来分类的。
消息队列分为:
- 系统消息队列,由windows维护;
- 线程消息队列,gui线程自己维护。
一个消息会经过这两个队列。
事件消息发送给消息队列,由线程取出并发给窗口过程处理;
不同的消息对应了不同的附加参数,具体的意义需要查文档。
WM_PAINT是个例外,客户区部分或全部无效,或需要更新的时候会发送,多个该消息会合并为一个,目的是减少刷新次数。
通过PostMessage
发送队列消息,消息会被放置到当前应用程序的消息队列中,不等待处理结果,所以是一个非阻塞的函/数。
消息通过GetMessage + DispatchMessage
进行处理。
不想处理的消息要发送给DefWIndowProc()
进行处理
非队列消息
绕过队列,直接发送给窗口过程。
通过SendMessage
发送非队列消息,消息会被直接传递给消息回调函数,等待执行完毕后才会返回(同步函数),所以是阻塞的函数。
0x03. 主函数
int APIENTRY WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd){}
APIENTRY
,调用约定,必须;
_In_
,批注,传入/传出/可选;
hinstance
,表示当前应用程序;
hPrevInstance
,废弃;
lpCmdLine
,命令行参数,对应argv;
nShowCmd
,显示方式。
流程
- 定义窗口类结构(WNDCLASS)
- 注册窗口类(RegisterClass)
- 创建窗口(CreateWindow)
- 显示窗口(ShowWindow)
- 更新窗口(UpdateWindow)
- 消息泵(GetMessage -> TranslateMessage ->DispatchMessage)
WNDCLASS
窗口类结构的两个成员非常重要,
wndcls.lpszClassName = L"MyWindow";
wndcls.lpfnWndProc = WndProc;
CreateWindow
一个窗口类只能被注册一次,即统一名称的模板只有一个。
CreateWindow()
是一个宏(紫色),不是函数。
3种重要的风格:
- 重叠窗口,
WS_OVERLAPPEDWINDOW
,可拉伸,顶层,默认有标题栏和边框; - 弹出窗口,
WS_POPUPWINDOW
,默认无标题栏,不可拉伸; - 子窗口,
WS_CHILDWINDOW
,它不能超出父窗口客户区,如所有控件。
窗口其它设置一般在窗口过程的case WM_CREATE
处理。
其它窗口函数汇总:
- MoveWindow() 移动窗口的位置
- GetClientRect()获取窗口相对于用户界面左上角的位置
- GetDlgItem() 获取控件的句柄值用于操作
- GetWindowText() 获取一个窗口的文字信息
- SetWindowText() 设置一个窗口的文字信息
- FindWindow(窗口类名,窗口名) 查找窗口
- EnumChildWindow() 枚举目标窗口下的所有子窗口
- EnumWindow() 枚举桌面的所有子窗口
- GetDesktopWindow(): 获取桌面的句柄
- SetParent() 重置窗口的父窗口
- Get\SetClassLong() 设置或者获取窗口类的属性 GCL
- Get\SetDlgItemText 获取控件的文字信息
- Get\SetDlgItemInt 将获取到的信息转换成整数类型
消息泵
TranslateMessage()
转换函数,举例说明,可以将一组 WM_KEYDOWN 和 WM_KEYUP 组合成一个 WM_CHAR保留原有的两个消息,将新生成的消息放置到消息队列中
0x04. 控件
控件一般在窗口过程的case WM_CREATE
创建,CreateWindow()
坐标是相对于父窗口的坐标,当创建的是一个控件,那么第一个参数lpClassName
是由系统定义的,HMENU
保存的是一个 id,用于标识当前的消息是有哪一个控件产生的。
所有控件都有WS_CHILD | WS_VISIBLE
风格。
控件名由系统确定。
获得句柄
获得控件/子窗口句柄:GetDlgItem(hParentWnd, ID);
获得父窗口句柄:GetParent()
获得主窗口句柄:AfxGetMainWnd()
控件消息
标准控件响应WM_COMMAND
:
- wParam低位是控件id,高位是通知码;
- lParam是控件句柄。
Message Source | wParam (high word) | wParam (low word) | lParam |
---|---|---|---|
Menu | 0 | Menu identifier (IDM_*) | 0 |
Accelerator | 1 | Accelerator identifier (IDM_*) | 0 |
Control | Control-defined notification code | Control identifier | Handle to the control window |
通用控件响应WM_NOTIFY
:
- wParam: The identifier of the common control sending the message.
- lParam: A pointer to an NMHDR structure that contains the notification code and additional information.
示例
注意要有背景色,在窗口需要重新绘制的时候,填充使用的颜色,否则MoveWindow()
后旧窗口仍然显示。
#include <windows.h>
/*
hWnd: 表示的是遍历到的所有子窗口的句柄
lParam: 调用函数时传入的 lParam
*/
BOOL CALLBACK EnumProc(HWND hWnd, LPARAM lParam)
{
CHAR Buffer[100] = { 0 };
GetWindowTextA(hWnd, Buffer, 100);
if (Buffer[0])
{
MessageBoxA(NULL, Buffer, Buffer, MB_OK);
return TRUE;
}
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInstance;
WCHAR *pStr[5] = { L"move btn" ,L"text" ,L"set father",L"each child", L"I'm a editing box"};
int i = 0;
hInstance = (HINSTANCE)GetModuleHandle(NULL);
switch (uMsg)
{
case WM_CREATE:
for (i = 0; i < 4; ++i)
{
CreateWindow(L"button",pStr[i], WS_CHILD | WS_VISIBLE,
10, 10 + 60 * i, 200, 50,
hWnd, (HMENU)(0x1000 + i), hInstance, NULL);
}
CreateWindow(L"edit", pStr[i], WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE,
10, 10 + 60 * 4, 200, 50,
hWnd, (HMENU)0x1004, hInstance, NULL);
break;
case WM_CLOSE:
DestroyWindow(hWnd);
PostQuitMessage(0);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case 0x1000:
{
// 1. 获取客户区的矩形区域
RECT ClientRect = { 0 };
GetClientRect(hWnd, &ClientRect);
// 2. 计算出一个有效范围内的新的位置
LONG x = 10 + rand() % 200;
LONG y = ClientRect.top + rand() % 50;
// 3. 移动当前按钮的位置
MoveWindow((HWND)lParam, x, y, 200, 50, TRUE);
break;
}
// 每点击一次按钮,就获取编辑框的文字并设置新的文字
case 0x1001:
{
// 1. 通过传入控件 id,获取指定窗口中控件的句柄(GetDlgItemTextA())
HWND hEditWnd = GetDlgItem(hWnd, 0x1004);
// 2. 获取编辑框的信息
CHAR TextBuffer[100] = { 0 };
GetWindowTextA(hEditWnd, TextBuffer, 100);
MessageBoxA(hWnd, TextBuffer, TextBuffer, MB_OK);
// 3. 设置新的编辑框的信息(SetDlgItemText())
SetWindowTextA(hEditWnd, "这是新的信息");
break;
}
// 将父窗口设置成桌面
case 0x1002:
{
// 1. 获取桌面的句柄
// HWND hDesktopWnd = GetDesktopWindow();
HWND hFindWnd = FindWindow(NULL, "Win32Test - Microsoft Visual Studio");
// 2. 设置新的父窗口(设置后这里就无法响应消息)
SetParent((HWND)lParam, hFindWnd);
break;
}
// 遍历指定窗口的所有子窗口
case 0x1003:
{
HWND hFindWnd = FindWindowA(NULL, "Win32Test - Microsoft Visual Studio");
// 遍历桌面的所有子窗口
EnumChildWindows(hFindWnd, EnumProc, NULL);
break;
}
}
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASS wndcls = { 0 };
MSG msg = { 0 };
HWND hWnd;
wndcls.lpszClassName = L"MyWindow";
wndcls.lpfnWndProc = WndProc;
wndcls.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
RegisterClass(&wndcls);
hWnd = CreateWindow(wndcls.lpszClassName, L"caption",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
NULL, NULL,
hInstance, 0);
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0))
{
DispatchMessage(&msg);
}
return 0;
}
0x05. 资源
这一部分,应注意编译版本为32位,工具集和vs版本匹配,否则出现未定义符号。
光标(.cur),位图(bmp),图标(ico),加速键,菜单,字符串,对话框等。
获取资源要用LoadXXX()
api。
- 添加了资源之后,会产生一个头文件 resource.h,保存的是资源对应的ID
- 大多数的资源都是使用 LoadXXX(hInstance,MAKEINTRESOURCE(ID)) 的形式被加载
- 双击项目下的资源目录中的 xxxxx.rc 进入资源管理器
- 同一时刻不能同时打开【对话框编辑器】和【资源头文件】
- 操作对话框时,在工具箱中拖拽需要的控件
以cursor、icon、menu为例。
WM_COMMAND
代表菜单消息时:
- wParam低位是菜单id,高位是0;
- lParam是0。
菜单有3种创建方法
- 窗口类中
lpszMenuName
成员指定; - 创建窗口时加载,即
CreateWindow()
时指定,优先级高于第一种方法; - 动态加载,
SetMenu(HWND,HMENU);
示例
#include <windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCur;
static BOOL b = TRUE;
hInstance = (HINSTANCE)GetModuleHandle(NULL);
switch (uMsg)
{
case WM_CREATE:
{
// 所有的控件必须拥有的风格是 WS_CHILD 和 WS_VISIBLE(默认显示的)
CreateWindow(L"button", L"修改图标", WS_CHILD | WS_VISIBLE,
// 坐标是相对于父窗口的坐标,当创建的是一个控件,那么 HMENU 保
// 存的是一个 id ,用于标识当前的消息是有哪一个控件产生的
10, 10, 200, 50, hWnd, (HMENU)0x1000, hInstance, NULL);
CreateWindow(L"button", L"修改光标", WS_CHILD | WS_VISIBLE,
10, 70, 200, 50, hWnd, (HMENU)0x1001, hInstance, NULL);
CreateWindow(L"button", L"设置菜单", WS_CHILD | WS_VISIBLE,
10, 130, 200, 50, hWnd, (HMENU)0x1002, hInstance, NULL);
break;
}
case WM_CLOSE:
DestroyWindow(hWnd);
PostQuitMessage(0);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case 0x1000:
hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
SetClassLong(hWnd, GCL_HICON, (LONG)hIcon);
break;
case 0x1001:
hCur = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1));
SetClassLong(hWnd, GCL_HCURSOR, (LONG)hCur);
break;
case 0x1002:
{
if (b == TRUE)
SetMenu(hWnd, LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1)));
else
SetMenu(hWnd, LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU2)));
b = !b;
break;
}
case ID_MENU1_SUB1:
{
MessageBoxA(hWnd, "子菜单1", "子菜单1", MB_OK);
break;
}
case ID_MENU2_SUBMENU1:
{
MessageBoxA(hWnd, "子菜单2", "子菜单2", MB_OK);
break;
}
}
case WM_RBUTTONDOWN:
{
// 1. 获取到鼠标的位置
POINT point = { LOWORD(lParam), HIWORD(lParam) };
// 1.1 因为获取到的坐标是客户区的坐标,但是弹出的位置是桌面的位置
ClientToScreen(hWnd, &point);
// 2. 获取到具体的菜单
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1));
// 3. 获取到菜单的子菜单
HMENU hSubMenu = GetSubMenu(hMenu, 0);
// 4. 弹出菜单
TrackPopupMenu(hSubMenu, TPM_LEFTALIGN, point.x, point.y, 0, hWnd, NULL);
}
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASS wndcls = { 0 };
MSG msg = { 0 };
HWND hWnd;
HMENU hMenu;
wndcls.lpszClassName = L"MyWindow";
wndcls.lpfnWndProc = WndProc;
wndcls.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
wndcls.hIcon = LoadIcon(NULL, IDI_QUESTION);
wndcls.hCursor = LoadCursor(NULL, IDC_WAIT);
wndcls.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
hMenu = LoadMenu(hInstance, wndcls.lpszMenuName);
RegisterClass(&wndcls);
hWnd = CreateWindow(wndcls.lpszClassName, L"caption",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
NULL, hMenu,
hInstance, 0);
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
0x06. 对话框
与窗口区别:
窗口 | 对话框 | |
---|---|---|
原型 | DefWindowProc() | INT_PTR |
返回值 | LRESULT | BOOL |
初始化 | WM_CREATE, | WM_INITDIALOG,依赖于资源(拖拽) |
消息 | 不处理WM_INITDIALOG | 不处理WM_CREATE,WM_DESTROY, WM_PAINT,但处理WM_CLOSE |
对话框主要处理的消息:
- WM_INITDIALOG
- WM_COMMAND
- WM_NOTIFY
模态对话框
由DialogBox()
创建,自带消息循环,可以自动显示,所以关闭后父窗口才能响应消息。
退出需要用EndDialog()
。
#include <windows.h>
#include "resource.h"
INT_PTR CALLBACK DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// 对话框的回调函数中,如果消息已经被处理就返回 TRUE, 否则返回FALSE
switch (uMsg)
{
// 创建,对话框在创建时响应 WM_INITDIALOG 消息
case WM_INITDIALOG:
{
MessageBoxA(hWnd, "创建对话框", "提示", MB_OK);
break;
}
// 关闭
case WM_CLOSE:
{
// 模态对话框的关闭必须使用 EndDialog
EndDialog(hWnd, 0);
break;
}
default:
// 没有处理的所有消息都返回 FALSE
return FALSE;
}
return TRUE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// 实例句柄; 对话框id; 父窗口; 回调函数
DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAINDLG), NULL, DlgProc);
return 0;
}
非模态对话框
由CreateDialog()
创建,
两个api实际都调用了CreateWindow()
#include <windows.h>
#include "resource.h"
// 对话框的回调函数
INT_PTR CALLBACK DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// 对话框的回调函数中,如果消息已经被处理就返回 TRUE, 否则返回FALSE
switch (uMsg)
{
// 创建,对话框在创建时响应 WM_INITDIALOG 消息
case WM_INITDIALOG:
{
MessageBoxA(hWnd, "创建对话框", "提示", MB_OK);
break;
}
// 关闭
case WM_CLOSE:
{
// 非模态对话框的关闭应该使用 DestroyWindow
// 因为提供了一个消息循环,所以需要发送退出消息
DestroyWindow(hWnd);
PostQuitMessage(0);
break;
}
default:
// 没有处理的所有消息都返回 FALSE
return FALSE;
}
return TRUE;
}
// 一个 GUI 程序的入口函数
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// 实例句柄; 对话框id; 父窗口; 回调函数
HWND hWnd = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);
ShowWindow(hWnd, SW_SHOWNORMAL);
// 对于一个非模态对话框,一定需要提供消息循环,且主动调用函数显示窗口,CreateDialog 是
// 一个非阻塞函数
MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}