热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Windows–使用类的成员函数作为Win32窗口消息回调处理函数WindowProc

1 Win32窗口的创建过程学过Win32界面编程的都知道,创建一个Win32窗口一般经过以下几个步骤

1 Win32窗口的创建过程

学过Win32界面编程的都知道,创建一个Win32窗口一般经过以下几个步骤:



  • (1)定义窗口对象

  • (2)注册窗口

  • (3)创建窗口

  • (4)显示窗口

  • (5)消息处理函数

  • (6)消息循环


一个简单的Win32窗口创建代码与以下代码类似,







#include
//声明函数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
//主函数WinMain()
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
//窗口类名
TCHAR cls_name[] = TEXT("MyWinClass");
//设计窗口类
WNDCLASSEX wce = { 0 };
wce.cbSize = sizeof(WNDCLASSEX); //结构体大小
wce.style = CS_HREDRAW | CS_VREDRAW; //大小改变,水平和垂直重绘
wce.lpfnWndProc = WindowProc; //窗口回调函数地址
wce.cbClsExtra = 0; //类的附加数据
wce.cbWndExtra = 0; //窗口的附加数据
wce.hInstance = hInstance; //应用程序实例句柄
wce.hIcon = LoadIcon(NULL, IDI_APPLICATION); //大图标
wce.hIcOnSm= wce.hIcon; //小图标
wce.hCursor = LoadCursor(NULL, IDC_ARROW); //光标
wce.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //背景色
wce.lpszMenuName = NULL; //菜单
wce.lpszClassName = cls_name; //窗口类的名称
//注册窗口类
//ATOM nres = RegisterClassEx(&wce);
if (FALSE == RegisterClassEx(&wce))
return 1;
//创建窗口
HWND hWnd = CreateWindowEx(
WS_EX_APPWINDOW, //窗口的扩展样式
cls_name, //窗口类名
TEXT("我的窗口"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格样式
200, 120, 600, 400, //窗口x,y,宽度,高度
NULL, //父窗口句柄
NULL, //菜单句柄
hInstance, //应用程序实例句柄
NULL); //附加数据
//回调函数要写上默认的处理DefWindowProc,不然创建失败
if (!hWnd)
return 1;
//显示,更新窗口
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
//添加加速键表
HACCEL hAccel=NULL;
/*::LoadAccelerators(hInstance,MAKEINTRESOURCE(IDR_ACCELERATOR1);*/
//消息循环
MSG msg;
while (GetMessage(&msg,NULL,0,0))
{
if(!TranslateAccelerator(hWnd,hAccel,&msg))//有加速键表时用这,没有就不用
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
//窗口消息处理函数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default: break;
}
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

C++


Copy


一般情况下我们都会使用一个静态函数作为窗口的消息处理函数,就如同上述代码所示。但是在某些特殊情况下我们需要将某个自定义窗口的消息处理函数不采用静态函数而采用某个类的成员函数,方便封装,这种情况下有没有什么骇客的方式可以实现呢?


2 使用类的成员函数作为Win32窗口消息处理函数WindowProc

2.1 实现方式


在本文,将介绍一种特殊的方式,该方式主要是使用了Delphi的MakeObjectInstance的思想。


工具WndProcRemapUtil.h的代码如下:


#ifndef WNDPROC_REMAP_UTIL_H_
#define WNDPROC_REMAP_UTIL_H_
#include
const SIZE_T PageSize = 4096;
//产生一个代理函数
template
static WNDPROC MakeObjectInstance(LPVOID AObject, T AMethod)
{
union
{
T MethodAddr;//成员函数指针
LPVOID NomralAddr;//正常指针
}ut;//因为VC不允许成员函数指针转换到普通指针。只能变通的通过union来实现
const unsigned char BlockCode[] = {
#ifdef _WIN64
0x55,//{ push rbp }
0x48, 0x83, 0xEC, 0x40,//{ sub rsp,0x40 }
0x48, 0x8B, 0xEC,//{ mov rbp,rsp }
0x48, 0x89, 0x4D, 0x50,//{ mov[rbp + 0x50],rcx }
0x89, 0x55, 0x58,//{ mov[rbp + 0x58],edx }
0x4C, 0x89, 0x45, 0x60,//{ mov[rbp + 0x60],r8 }
0x4C, 0x89, 0x4D, 0x68,//{ mov[rbp + 0x68],r9 }
0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,//{ mov rcx,AObject }
0x48, 0x8B, 0x55, 0x50,//{ mov rdx,[rbp + 0x50] }
0x44, 0x8B, 0x45, 0x58,//{ mov r8,[rbp + 0x58] }
0x4C, 0x8B, 0x4D, 0x60,//{ mov r9,[rbp + 0x60] }
0x48, 0x8B, 0x45, 0x68,//{ mov rax,[rbp + 0x68] }
0x48, 0x89, 0x44, 0x24, 0x20,//{ mov[rsp + 0x20],rax }
0x49, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,//{ mov r11, AMethod }
0x49, 0xFF, 0xD3,//{ call r11 }
0x48, 0x8D, 0x65, 0x40,//{ lea rsp,[rbp + 0x40] }
0x5D,//{ pop rbp }
0xC3//{ ret }
#else
0x58,//{ pop eax }
0x68, 0x00, 0x00, 0x00, 0x00,//{ push AObject }
0x50,//{ push eax }
0xB8, 0x00, 0x00, 0x00, 0x00,//{ mov eax, AMethod }
0xFF, 0xE0//{ jmp eax }
#endif // endif
};
size_t CodeBytes = sizeof(BlockCode);
LPVOID Block = VirtualAlloc(nullptr, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(Block, BlockCode, CodeBytes);
unsigned char* bBlock = (unsigned char*)Block;
ut.MethodAddr = AMethod;
#ifdef _WIN64
*PLONG64(&bBlock[25]) = LONG64(AObject);
*PLONG64(&bBlock[0x38]) = LONG64(ut.NomralAddr);
#else
*PLONG32(&bBlock[2]) = LONG32(AObject);
*PLONG32(&bBlock[8]) = LONG32(ut.NomralAddr);
#endif
return (WNDPROC)Block;
}
// 释放代理函数
static void FreeObjectInstance(WNDPROC wndProc)
{
VirtualFree(wndProc, PageSize, MEM_RELEASE);
}
#endif // !WNDPROC_REMAP_UTIL_H_

C++


Copy


2.2 使用方式


先在类A中声明一个成员函数作为窗口消息处理函数


class A
{
public:
A()
{
}
~A()
{
}
LRESULT CALLBACK OpenGLWndDisplayProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
switch (message)
{
case WM_CREATE:
{
break;
}
case WM_SIZE:
{
break;
}
case WM_PAINT:
{
HDC hDC = ::GetDC(hWnd);
RECT rc = { 0 };
GetClientRect(hWnd, &rc);
HBRUSH hbr = ::CreateSolidBrush(RGB(0x00, 0xFF, 0x00)); // 绿色画刷
::FillRect(hDC, &rc, hbr);
::DeleteObject(hbr);
::ReleaseDC(hWnd, hDC);
break;
}
case WM_DESTROY:
{
break;
}
case WM_LBUTTONDOWN:
{
break;
}
case WM_LBUTTONUP:
{
break;
}
case WM_MOUSEMOVE:
{
break;
}
case WM_RBUTTONDOWN:
{
break;
}
case WM_RBUTTONUP:
{
break;
}
case WM_MBUTTONDOWN:
{
break;
}
case WM_MBUTTONUP:
{
break;
}
case WM_MOUSEWHEEL:
{
break;
}
case WM_KEYDOWN:
{
break;
}
default:
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
};

C++


Copy


然后在创建Win32窗口,注册窗口类时使用以下代码将A类的成员函数OpenGLWndDisplayProc作为窗口消息回调函数,


A a;
WNDPROC wndProc = MakeObjectInstance(&a, &A::OpenGLWndDisplayProc);
//设计窗口类
WNDCLASSEX wce = { 0 };
wce.cbSize = sizeof(WNDCLASSEX); //结构体大小
wce.style = CS_HREDRAW | CS_VREDRAW; //大小改变,水平和垂直重绘
wce.lpfnWndProc = wndProc; //窗口回调函数地址
wce.cbClsExtra = 0; //类的附加数据
wce.cbWndExtra = 0; //窗口的附加数据
wce.hInstance = hInstance; //应用程序实例句柄
wce.hIcon = LoadIcon(NULL, IDI_APPLICATION); //大图标
wce.hIcOnSm= wce.hIcon; //小图标
wce.hCursor = LoadCursor(NULL, IDC_ARROW); //光标
wce.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //背景色
wce.lpszMenuName = NULL; //菜单
wce.lpszClassName = cls_name; //窗口类的名称
//注册窗口类
//ATOM nres = RegisterClassEx(&wce);
if (FALSE == RegisterClassEx(&wce))
return 1;

C++


Copy


最后在程序退出时释放


FreeObjectInstance(wndProc);


推荐阅读
  • 本文详细解析了使用C++实现的键盘输入记录程序的源代码,该程序在Windows应用程序开发中具有很高的实用价值。键盘记录功能不仅在远程控制软件中广泛应用,还为开发者提供了强大的调试和监控工具。通过具体实例,本文深入探讨了C++键盘记录程序的设计与实现,适合需要相关技术的开发者参考。 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • WinMain 函数详解及示例
    本文详细介绍了 WinMain 函数的参数及其用途,并提供了一个具体的示例代码来解析 WinMain 函数的实现。 ... [详细]
  • 多线程基础概览
    本文探讨了多线程的起源及其在现代编程中的重要性。线程的引入是为了增强进程的稳定性,确保一个进程的崩溃不会影响其他进程。而进程的存在则是为了保障操作系统的稳定运行,防止单一应用程序的错误导致整个系统的崩溃。线程作为进程的逻辑单元,多个线程共享同一CPU,需要合理调度以避免资源竞争。 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 利用 PHP APICommonUrl 中转实现 jQuery JSONP 请求优化 ... [详细]
  • 在前文探讨了Spring如何为特定的bean选择合适的通知器后,本文将进一步深入分析Spring AOP框架中代理对象的生成机制。具体而言,我们将详细解析如何通过代理技术将通知器(Advisor)中包含的通知(Advice)应用到目标bean上,以实现切面编程的核心功能。 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • 在Delphi7下要制作系统托盘,只能制作一个比较简单的系统托盘,因为ShellAPI文件定义的TNotifyIconData结构体是比较早的版本。定义如下:1234 ... [详细]
  • 在Java Web服务开发中,Apache CXF 和 Axis2 是两个广泛使用的框架。CXF 由于其与 Spring 框架的无缝集成能力,以及更简便的部署方式,成为了许多开发者的首选。本文将详细介绍如何使用 CXF 框架进行 Web 服务的开发,包括环境搭建、服务发布和客户端调用等关键步骤,为开发者提供一个全面的实践指南。 ... [详细]
  • 在C#开发中,实现UserControls之间高效传递CheckBox值是一个常见的需求。本文详细介绍了如何通过事件和委托机制,将UserControl3中的CheckBox值传递到UserControl1中,确保数据传递的准确性和实时性。此外,还提供了代码示例和最佳实践,帮助开发者更好地理解和应用这一技术。 ... [详细]
author-avatar
笨雄不笨
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有