C++任意线程通过hwnd实现将操作发送到UI线程执行
作者:CodeOfCC
做Windows界面开发时,经常需要在多线程环境中将操作抛到主线程执行,下面我们就来学习一下如何在不需要重新定义消息以及接收消息的情况下实现这一要求,感兴趣的可以了解下
前言
做Windows界面开发时,经常需要在多线程环境中将操作抛到主线程执行,通常做法是定义一个WM_USER消息,将函数指针通过SendMessage发送给窗口,窗口过程中接收消息后执行函数。本文提供的方法可以在任意地方使用,不需要重新定义消息以及接收消息。
一、基本实现
只是基本的实现方法,也包含了基本原理。
1、自定义WM消息
#define WM_INVOKE WM_USER+3328
2、发送消息
需要UI线程执行的函数
 void action(void* arg){
 //ui线程执行的操作
 }
发送到ui线程
SendMessage(hwnd, WM_INVOKE, action, arg);
3、窗口过程中执行函数
static LRESULT  wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparma)
{
    switch (msg) {
    case WM_INVOKE:
    {    
        void(*action)(void* s);
        action = wparam;
        action(lparma);
    }
    break;
    }
    return 0;
}
二、优化实现
上述实现,需要在每个窗口或每个项目的窗口过程写代码,移植起来很麻烦。我们通过钩子的方式做成,实现一次到处使用。
定义消息略,与基本实现一致。
1、钩子过程中执行函数
定义一个钩子过程,并在钩子过程中执行函数。
LRESULT CALLBACK hook_proc(int code, WPARAM wParam, LPARAM lParam) {
    CWPSTRUCT* msg = lParam;
    if (msg->message == WM_INVOKE) { 
        ((void(*)(void* s)) msg->wParam)(msg->lParam);
    }
    return 0;
}
2、设置钩子
HHOOK hook = SetWindowsHookEx(WH_CALLWNDPROC, hook_proc, GetModuleHandle(NULL), GetWindowThreadProcessId(hwnd, NULL));
3、发送消息
将函数通过消息发送出去
SendMessage(hwnd, WM_INVOKE, action, arg);
4、卸载钩子
UnhookWindowsHookEx(hook);
三、完整代码
C
#include<Windows.h>
#define  WM_INVOKE WM_USER+3328
static LRESULT CALLBACK hook_proc(int code, WPARAM wParam, LPARAM lParam) {
    CWPSTRUCT* msg = lParam;
    if (msg->message == WM_INVOKE) { 
        ((void(*)(void* s)) msg->wParam)(msg->lParam);
    }
    return 0;
}
/// <summary>
/// 将操作切换到窗口线程执行,同步。
/// </summary>
/// <param name="hwnd">窗口句柄</param>
/// <param name="action">执行的操作</param>
/// <param name="arg">透传参数</param>
void invoke(HWND hwnd, void(*action)(void* arg), void* arg) {
    if (GetCurrentThreadId() != GetWindowThreadProcessId(hwnd, NULL)) {
        HHOOK hook = SetWindowsHookEx(WH_CALLWNDPROC, hook_proc, GetModuleHandle(NULL), GetWindowThreadProcessId(hwnd, NULL));
        SendMessage(hwnd, WM_INVOKE, action, arg);
        UnhookWindowsHookEx(hook);
    }
    else action(arg);
}
C++
与c的区别是,能使用std::function,可捕获变量。
#include<Windows.h>
#include<functional>
#define  WM_INVOKE WM_USER+3328
static LRESULT CALLBACK hook_proc(int code, WPARAM wParam, LPARAM lParam) {
    CWPSTRUCT* msg = (CWPSTRUCT*)lParam;
    if (msg->message == WM_INVOKE) {
        (*((std::function<void()>*) msg->wParam))();
    }
    return 0;
}
/// <summary>
/// 将操作切换到窗口线程执行,同步。
/// </summary>
/// <param name="hwnd">窗口句柄</param>
/// <param name="func">执行的操作</param>
void invoke(HWND hwnd, const std::function<void()>& func) {
    if (GetCurrentThreadId() != GetWindowThreadProcessId(hwnd, NULL)) {
        HHOOK hook = SetWindowsHookEx(WH_CALLWNDPROC, hook_proc, GetModuleHandle(NULL), GetWindowThreadProcessId(hwnd, NULL));
        SendMessage(hwnd, WM_INVOKE, (WPARAM)&func, 0);
        UnhookWindowsHookEx(hook);
    }
    else func();
}
四、使用示例
C
void action(void* arg)
{
    printf("invoked %d\n",(int)arg);
}
invoke(hwnd,action,123)
C++
int a=123;
invoke(hwnd, [&]() {
    printf("invoked %d\n", a);
    });
总结
以上就是今天要讲的内容,本文仅仅简单的实现了通用的线程invoke,且只支持同步,通用的异步invoke实现稍微复杂些(基本实现的方式则比较简单),以后有空再做。总的来说,有了本文的代码很大程度的方便了使用,尤其是一个新的项目突然需要invoke功能,按照基本实现的方式在窗口中写一遍是很麻烦的,而优化的实现则可以直接复用,调用invoke即可。
到此这篇关于C++任意线程通过hwnd实现将操作发送到UI线程执行的文章就介绍到这了,更多相关C++操作发送至主线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
