前言
做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);
三、完整代码
1、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);
}
1、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();
}
四、使用示例
1、C
void action(void* arg)
{
printf("invoked %d\n",(int)arg);
}
invoke(hwnd,action,123)
2、C++
int a=123;
invoke(hwnd, [&]() {
printf("invoked %d\n", a);
});
总结
以上就是今天要讲的内容,本文仅仅简单的实现了通用的线程invoke,且只支持同步,通用的异步invoke实现稍微复杂些(基本实现的方式则比较简单),以后有空再做。总的来说,有了本文的代码很大程度的方便了使用,尤其是一个新的项目突然需要invoke功能,按照基本实现的方式在窗口中写一遍是很麻烦的,而优化的实现则可以直接复用,调用invoke即可。