在运行时创建事件处理程序而不使用 WndProc win32 c++

Posted

技术标签:

【中文标题】在运行时创建事件处理程序而不使用 WndProc win32 c++【英文标题】:Create event handler at runtime without using WndProc win32 c++ 【发布时间】:2021-01-05 05:10:22 【问题描述】:

在使用 C# 时,之前很容易在运行时创建事件处理程序,例如:

Button button1 = new button1();

button1.click += Button_Click(); //Create handler 
button1.click -= Button_Click(); //Remove handler 

public void Button_Click()

    //Button clicked

但在 win32 中,我遇到了必须处理所有事件的 WndProc 回调。我想为特定消息创建一个处理程序并将其附加到特定的 void

目前,我正在使用 WndProc 来捕获 WM_CREATE 消息并绘制控件:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

    switch (message)
    
        case WM_CREATE:
            Draw(hWnd); 
            break; 
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    
    return 0;


void Draw(HWND hWnd)

    HWND button4 = CreateWindow(L"button", L"button4", WS_CHILD | WS_VISIBLE , 329, 118, 112, 67, hWnd, (HMENU)1001, hInst, NULL);
    HWND button3 = CreateWindow(L"button", L"button3", WS_CHILD | WS_VISIBLE , 212, 118, 112, 67,         
    ...

但我想创建或删除事件处理程序,而不是在运行时使用 WndProc,例如:

AddHandler WM_CREATE , Draw(hWnd);
DelHandler WM_CREATE , Draw(hWnd);

我尝试了什么?

SetWindowsHookEx 的问题在于它像 WndProc 一样处理整个消息。我不想要一个处理整个窗口消息并跳过其中一些消息的处理程序。这可能会导致性能或内存泄漏问题。

编辑:来自answer的实现示例:

#include <unordered_map>
using msgHandler = LRESULT(*)(HWND, UINT, WPARAM, LPARAM);
std::unordered_map<UINT, msgHandler> messageHandlers;

LRESULT handleCreate(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

    //Draw some buttons to see whether event WM_CREATE called or not
    HWND button4 = CreateWindow(L"button", L"button4", WS_CHILD | WS_VISIBLE, 329, 118, 112, 67, hWnd, (HMENU)1001, hInst, NULL);
    HWND button3 = CreateWindow(L"button", L"button3", WS_CHILD | WS_VISIBLE, 212, 118, 112, 67, hWnd, (HMENU)1002, hInst, NULL);
    HWND button2 = CreateWindow(L"button", L"button2", WS_CHILD | WS_VISIBLE, 329, 46,  112, 67, hWnd, (HMENU)1003, hInst, NULL);
    HWND button1 = CreateWindow(L"button", L"button1", WS_CHILD | WS_VISIBLE, 212, 46,  112, 67, hWnd, (HMENU)1004, hInst, NULL);
    return 0;


LRESULT handleClose(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

    //Quit form
    PostQuitMessage(0);           
    return 0;

void AddHandler() 

    messageHandlers[WM_CREATE] = handleCreate;
    messageHandlers[WM_DESTROY] = handleClose;


void DelHandler()

   messageHandlers.erase(WM_CREATE);


LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

    auto handler = messageHandlers.find(msg);
    if (handler != messageHandlers.end()) return handler->second(hWnd, msg, wParam, lParam);
    return DefWindowProc(hWnd, msg, wParam, lParam);


BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
    AddHandler();
    //DelHandler();
    ...

【问题讨论】:

一种选择是编写自己的 WndProc,它保存一个处理程序表以及它们注册的消息。另一种方法是编写一个 WndProc,为每条消息引发一个事件,并让每个人都注册他们想要处理的事件。 我需要尝试注册特定消息的第一个选项。你能指导我更多吗? 在此示例中,c# 事件处理程序是在编译时创建的。 //Create handler//Remove handler 和错误。 也许“原始”win32 不是您所需要的。 C/C++/Native Windows 编程还有其他选项。例如,您可以尝试 MFC docs.microsoft.com/en-us/cpp/mfc/mfc-desktop-applications 它更接近 .NET 事件,或者至少研究它以获取信息。 This answer 填写选项1的详细信息。 【参考方案1】:

消息 ID 只是一个无符号整数,因此并没有什么特别之处。尽管巨大的 switch 语句是处理消息的一种常用方法,但如果您愿意,您可以做完全不同的事情。为了支持处理程序的动态插入/删除,一种可能性是使用std::unordered_map

// a message handler receives the normal parameters:
using msgHandler = LRESULT(*)(HWND, UINT, WPARAM, LPARAM);

// a map from message numbers to the handler functions:
std::unordered_map<UINT, msgHandler> messageHandlers;

// A couple of message handler functions:
LRESULT handleCreate(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 
    // ...


LRESULT handleDraw(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 
   // ...


// register them to handle the appropriate messages:
messageHandlers[WM_CREATE] = handleCreate;
messageHandlers[WM_PAINT] = handleDraw;

// and then our (now really tiny) window proc that uses those:
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

    auto handler = messageHandlers.find(msg);
    if (handler != messageHandlers.end())
        return handler->second(hWnd, msg, wParam, lParam);
    return DefWindowProc(hWnd, msg, wParam, lParam);

由于我们只是在 std::unordered_map 中存储指向函数的指针,因此添加、查找或删除处理程序都只需使用常规操作在 std::unodered_map 中添加、查找或删除某些内容(例如,@987654326 @ 从地图中删除 WM_CREATE 处理程序)。

如果您想对此进行更详细的说明,您可以创建特定类型来处理不同的消息,因此(例如)在其lParam 中没有收到任何有意义的消息的人根本不会收到lParam根本上,而另一个接收两个“平滑”在一起的东西,一个在lParam 的低位字中,另一个在lParam 的高位字中,可以将它们分成两个单独的参数。但这还有很多工作要做。

您可能还想查找 WindowsX.h,它是 Microsoft 在 SDK 中提供(或至少曾经提供)的头文件,它处理映射有点像我上面概述的(后一个版本,每个处理程序接收参数表示它接收的逻辑数据,而不是用于编码该数据的WPARAMLPARAM

【讨论】:

return handler-&gt;second 是干什么用的?以及如何删除使用 messageHandlers[WM_CREATE] 创建的处理程序? @GrayProgrammerz:find 查找特定消息的处理程序。返回值是映射中的迭代器,因此handler-&gt;first 为您提供刚刚查找的消息 ID,handler-&gt;second 为您提供(指向)您为该消息注册的函数。 return handler-&gt;second(...); 调用该函数,检索它返回的任何值(如果有)并将其传递回调用者。 至于“你如何删除句柄”:重点是messageHandlers记录了哪些处理程序负责哪些消息。如果要修改处理程序,请修改messageHandlers。要添加处理程序,请将其添加到 messageHandlers。要删除一个,请将其从messageHandlers 中删除。要搜索处理程序,请在 messageHandlers 中搜索。以此类推。 :) 谢谢大家。我试过这段代码,我解决了 1 个错误,第二个是很难。请参阅:i.ibb.co/tYhbQvX/Capture.png @GrayProgrammerz:这些是赋值语句,所以它们需要在函数内部。【参考方案2】:

您可以像这样动态处理消息:

typedef void (*FHANDLE)();
std::vector<FHANDLE> handles;

void AddHandler(FHANDLE handle)

    handles.push_back(handle);

void DelHandler(FHANDLE handle)

    auto it = std::find(handles.begin(), handles.end(), handle);
    handles.erase(it);


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

    switch (message)
    
    case WM_CREATE:
        for (int i = 0; i < handles.size(); i++)
        
            handles[i]();
        
        break;
    ...
    

并添加/删除句柄:

void myclick1()

    MessageBox(0, L"test1", L"message", 0);

void myclick2()

    MessageBox(0, L"test2", L"message", 0);


BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)

   hInst = hInstance; // Store instance handle in our global variable
   AddHandler(myclick1);
   AddHandler(myclick2);
   //DelHandler(myclick2);
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
   ...

【讨论】:

@raymond-chen 你能看看这个选项是什么吗?这是 ?顺便说一句,它看起来很干净。

以上是关于在运行时创建事件处理程序而不使用 WndProc win32 c++的主要内容,如果未能解决你的问题,请参考以下文章

DefWndProc/WndProc/IMessageFilter的区别

python中的异常处理:厌而不舍

初步剖析QT事件处理全过程(Windows)

WinForm中DefWndProcWndProc与IMessageFilter的区别

SpringIntegration 从队列中删除消息而不进行处理

如何在 WPF 中处理 WndProc 消息?