【Sciter】线程中操作UI

以前用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

你可能感兴趣的:(Sciter)