前言
在上一节中我们了解了win32的入口函数、ESP与回调函数,以及如何在od里定位这些结构,这一节我们来对子窗口和在od中如何定位消息处理函数进行探究。
子窗口
在窗口中创建按钮
我们知道创建窗口使用到CreateWindow
这个api,在前面我们已经提到过这个api,这里我们再来看一下它的结构
void CreateWindow(
[in, optional] lpClassName, // 窗口类名称
[in, optional] lpWindowName, // 窗口标题
[in] dwStyle, // 窗口风格,或称窗口格式
[in] x, // 初始 x 坐标
[in] y, // 初始 y 坐标
[in] nWidth, // 初始 x 方向尺寸
[in] nHeight, // 初始 y 方向尺寸
[in, optional] hWndParent, // 父窗口句柄
[in, optional] hMenu, // 窗口菜单句柄
[in, optional] hInstance, // 程序实例句柄
[in, optional] lpParam // 创建参数
);
那么我们创建一个窗口并在窗口中创建一个普通按钮
void CreateButton(HWND hwnd)
{
HWND hwndPushButton;
hwndPushButton = CreateWindow (
TEXT("button"),
TEXT("普通按钮"),
//WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON,
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON,
10, 10,
80, 20,
hwnd, //父窗口句柄
(HMENU)1001, //子窗口ID
hAppInstance,
NULL);
}
这里的hAppInstance
本来为当前应用程序的句柄,这里把hAppInstance
定义为全局变量,即HINSTANCE hAppInstance;
还有一个要注意的点是button
不能够独立存在,所以这里需要用父窗口的句柄,而且这个函数需要在窗口创建出来之后才能使用,所以这里要放在创建窗口的后面
这里编译一下,在我们之前创建的第一个窗口里面就出现了一个普通按钮
这里探究一下WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON
这几个参数,在https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles里有完整的对于`dwStyle`这个参数可设置的值
WS_CHILD Creates a child window. Cannot be used with the WS_POPUP style.
WS_VISIBLE Creates a window that is initially visible.
BS_PUSHBUTTON Creates a pushbutton that posts a WM_COMMAND message to the owner window when the user selects the button.
BS_DEFPUSHBUTTON Creates a button that has a heavy black border. The user can select this button by pressing the ENTER key. This style enables the user to quickly select the most likely option (the default option).
那么这里我们也可以生成其他的按钮,如复选框、单选按钮等等,这里都是通过改变dwStyle
这个参数来达到实现不同按钮样式的效果,实现代码如下
void CreateButton(HWND hwnd)
{
HWND hwndPushButton;
HWND hwndCheckBox;
HWND hwndRadio;
hwndPushButton = CreateWindow (
TEXT("button"),
TEXT("普通按钮"),
//WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON,
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON,
10, 10,
80, 20,
hwnd,
(HMENU)1001, //子窗口ID
hAppInstance,
NULL);
hwndCheckBox = CreateWindow (
TEXT("button"),
TEXT("复选框"),
//WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BS_AUTOCHECKBOX,
WS_CHILD | WS_VISIBLE | BS_CHECKBOX |BS_AUTOCHECKBOX ,
10, 40,
80, 20,
hwnd,
(HMENU)1002, //子窗口ID
hAppInstance,
NULL);
hwndRadio = CreateWindow (
TEXT("button"),
TEXT("单选按钮"),
//WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON | BS_AUTORADIOBUTTON,
WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON ,
10, 70,
80, 20,
hwnd,
(HMENU)1003, //子窗口ID
hAppInstance,
NULL);
}
编译过后我们就能得到拥有三个按钮的窗口了
按钮事件的处理
在自己创建窗口的时候第一行应该定义一个类,但是在创建button的时候这里直接就可以使用。是因为系统已经定义好了常用的一些窗口函数,即按钮的WNDCLASS
不是我们定义的,是系统预定义好的。
这里我们如果我们想看一下button究竟是怎样定义的WNDCLASS
,就可以调用GetClassName
函数找到类名存到szBuffer
分配的缓冲区里
TCHAR szBuffer[0x20];
GetClassName(hwndPushButton,szBuffer,0x20);
这里看一下GetClassName()
这个函数
int GetClassName(
HWND hWnd, // handle to window
LPTSTR lpClassName, // class name
int nMaxCount // size of class name buffer
);
- hWnd
[in] Handle to the window and, indirectly, the class to which the window belongs.
- lpClassName
[out] Pointer to the buffer that is to receive the class name string.
- nMaxCount
[in] Specifies the length, in TCHARs, of the buffer pointed to by the lpClassName parameter. The class name string is truncated if it is longer than the buffer.
lpClassName
为out参数,指向缓冲区地址(即存放类函数),nMaxCount
是给缓冲区分配的空间大小
然后查看具体信息
WNDCLASS wc;
GetClassInfo(hAppInstance,szBuffer,&wc);
OutputDebugStringF("-->%s\n",wc.lpszClassName);
OutputDebugStringF("-->%x\n",wc.lpfnWndProc);
这里又用到一个api函数GetClassInfo()
BOOL GetClassInfo(
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpClassName, // class name
LPWNDCLASS lpWndClass // class data
);
- hInstance
[in] Handle to the instance of the application that created the class. To retrieve information about classes defined by the system (such as buttons or list boxes), set this parameter to NULL.
- lpClassName
[in] Pointer to a null-terminated string containing the class name. The name must be that of a preregistered class or a class registered by a previous call to the RegisterClass or RegisterClassEx function. Alternatively, this parameter can be an atom. If so, it must be a class atom created by a previous call to RegisterClass or RegisterClassEx. The atom must be in the low-order word of lpClassName; the high-order word must be zero.
- lpWndClass
[out] Pointer to a WNDCLASS structure that receives the information about the class.
hInstance
是函数的ImageBase
,lpClassName
是前面得到的缓冲区,lpWndClass
为out参数,将结构的信息写到lpWndClass
所在的地址
然后再使用wc
这个结构体打印信息,这里使用lpszClassName
和lpfnWndProc
打印地址
继续探究,这里当我不点击这几个按钮所在的区域的时候,会产生消息,但是当我点击这几个按钮的时候是不会显示消息的,这里不会显示不代表没有产生消息,只是这里因为这几个按钮是子窗口,所以这里的消息不会在父窗口里的消息函数中直接输出显示,按钮是一种特殊的窗体,并不需要提供单独的窗口回调函数。
当按钮有事件产生时,会给父窗口消息处理程序发送一个WM_COMMAND
消息,我们就需要增加一个WM_COMMAND
函数
也就是说在子窗口调用WinProc
的时候需要用WM_COMMAND
去调用父窗口的WinProc
这里添加代码对我们的猜想进行验证
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case 1001:
MessageBox(hwnd,"Hello Button 1","Demo",MB_OK);
return 0;
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
这里出现了弹框证明了我们的猜想成功。
那么到这个地方我们就已经实现了创建窗口、创建按钮的操作,这里我们再去od里面看一下消息堆栈以及事件处理怎样定位。
消息堆栈
F7生成release版本进行调试,拖入od定位回调函数的地址为00401260
在401260
处下断点,这里返回的为消息类型
我们看一下WM_COMMAND
的WindowProc
的结构
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
WM_COMMAND, // the message to send
WPARAM wParam, // notification code and identifier
LPARAM lParam // handle to control (HWND)
);
- wParam
The high-order word specifies the notification code if the message is from a control. If the message is from an accelerator, this value is 1. If the message is from a menu, this value is zero. The low-order word specifies the identifier of the menu item, control, or accelerator.
- lParam
Handle to the control sending the message if the message is from a control. Otherwise, this parameter is NULL.
回调函数的堆栈如图所示,我们是通过地址跳转到401260
这个地址,那么esp还是在WindowProC
结构处不变,所以esp+0x8
就是指向的uMsg
的值,即消息类型
按钮事件定位
这里再去找WM_COMMAND
这个消息类型,我们知道在回调函数里面我们给按钮定义的消息就是WM_COMMAND
,代码如下
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case 1001:
MessageBox(hwnd,"Hello Button 1","Demo",MB_OK);
return 0;
case 1002:
MessageBox(hwnd,"Hello Button 2","Demo",MB_OK);
return 0;
case 1003:
MessageBox(hwnd,"Hello Button 3","Demo",MB_OK);
return 0;
}
那么我们通过esp+8
来定位到WM_COMMAND
这里我点击父窗口(即三个按钮之外的地方)的时候是没有反应的
当我点击子窗口的时候发现已经暂停
再运行一下,有了弹窗
这里就有一个问题,当我们点击每一个按钮的时候都会触发断点,所以我们需要想一个方法来断指定的按钮,这里就需要用到wParam
例如我只想断复选框,这里找到十六进制情况下的ID为000003EA
再添加条件,使用&&符号必须同时满足才会断点
当我点普通按钮的时候是不断的
当我点击复选框之后就暂停了,就可以找到指定按钮的消息在od里面的定位