以前用HTMLayout的时候,是可以直接在线程中操作UI的
element.set_attribute("src", "hello");
最近把工程移植到Sciter下,却发现上面的代码无法工作,会阻塞主线程
根据查看demos\ui-framework得知,要在线程中操作DOM,需要加上GUI_CODE_START和GUI_CODE_END
GUI_CODE_START
element.set_attribute("src", "hello");
GUI_CODE_END
那么GUI_CODE_START和GUI_CODE_END是什么鬼呢?在头文件sciter-x-threads.h中
#define GUI_CODE_START sciter::sync::gui_thread_ctx::exec([&]() {
#define GUI_CODE_END });
原来只是个宏,把用户代码包含在一个lambda中,并立即执行它。展开后是
sciter::sync::gui_thread_ctx::exec([&]{element.set_attribute("src", "hello");});
调用了gui_thread_ctx::exec静态方法,用户代码当作参数传递过去
static void exec( gui_block code )
{
event evt;
PostThreadMessage(thread_id(), message(), WPARAM(&evt),LPARAM(&code));
evt.wait(); // suspend worker thread until GUI will execute the block.
}
可以看到,我们的代码被包装为一个gui_block类型并传递给exec方法。(gui_block就是std::function
exec内部呢,向线程消息循环发送一个特定的消息,并将gui_block地址传递给消息循环处理。
但是消息循环如何处理特定的消息呢?别急,继续看
class gui_thread_ctx
{
HHOOK _hook;
typedef std::function gui_block;
static DWORD thread_id()
{
static DWORD _thread_id = ::GetCurrentThreadId();
return _thread_id;
}
static UINT message()
{
static UINT _message = ::RegisterWindowMessage( TEXT("GUI-THREAD-EXEC_RQ"));
return _message;
}
void install_hook()
{
message(); // force message to be registered
// setup the WH_GETMESSAGE hook.
// Using hooks here allows this mechanism to work even under modal dialogs.
_hook = ::SetWindowsHookEx(WH_GETMESSAGE,&exec_hook,THIS_HINSTANCE, thread_id());
}
void release_hook()
{
if(_hook) ::UnhookWindowsHookEx(_hook);
}
// message hook to handle WM_EXEC in GUI thread
static LRESULT CALLBACK exec_hook(int code, WPARAM wParam, LPARAM lParam )
{
MSG* pmsg = reinterpret_cast(lParam);
if(wParam == PM_REMOVE && pmsg->message == message())
{
event* pe = reinterpret_cast(pmsg->wParam);
gui_block* pf = reinterpret_cast(pmsg->lParam);
(*pf)(); // execute the block in this GUI thread
pe->signal(); // signal that we've done with it
// this will resume execution of worker thread.
}
return CallNextHookEx(0,code, wParam,lParam);
}
public:
gui_thread_ctx() { install_hook(); }
~gui_thread_ctx() { release_hook(); }
// this function is called from worker threads to
// execute the gui_block in GUI thread
static void exec( gui_block code )
{
event evt;
PostThreadMessage(thread_id(), message(), WPARAM(&evt),LPARAM(&code));
evt.wait(); // suspend worker thread until GUI will execute the block.
}
};
gui_thread_ctx是核心所在,它在构造函数中调用install_hook方法在当前线程中安装一个消息钩子,在钩子方法中执行用户的gui_block(也就是需要操作UI的代码)
通常安装钩子的线程应该是主线程,所以一般在main函数中声明一个gui_thread_ctx的实例来达到安装钩子的目的。
sciter::sync::gui_thread_ctx _;
至此,我们也就明白了,sciter同步代码的方式就是使用了Windows消息的思想,将用户代码丢到消息循环中去执行。
参考资料:
http://www.terrainformatica.com/2011/01/c0x-running-code-in-gui-thread-from-worker-threads
http://sciter.com/calling-code-behind-ui-from-worker-threads