将指向成员函数的指针传递给单独的线程

Passing pointer to a member function to a seperate thread

我正在使用 OpenGL 显示图形数据和自定义 UI 元素。我有一个选项卡式 SDI,其中文档 windows 使用无边界 windows 作为视口,每个视口包含一个单独的线程 运行 仅 GL 绘图操作,而所有其他操作(鼠标,键盘等) are/should 在视口回调中处理。图形线程是使用 _beginthreadex 创建的;

void ViewportController::create(){
    hThreadHandle = reinterpret_cast<HANDLE>(
        _beginthreadex(NULL,
            0,
            &threadProc,
            (void*)lpThreadController,
            0,
            &uiThreadID)
        );
    if (hThreadHandle == 0)
    {
        throw std::exception("Failed to create thread");
    }
}

使用 start_address 函数包含一个循环,使用 PeekMessage 允许视口将消息传递给图形线程,ThreadController class 作为arglist 参数;

unsigned __stdcall ViewportController::threadProc(void* pArguments)
{
    ThreadController* ctrl = static_cast<ThreadController*>(pArguments);
    MSG mMsg = { 0 };
    bool bStop = false;

    if (wglMakeCurrent(ctrl->getDC(), ctrl->getRC()))
    {
        ctrl->InitGL();

        if (!bStop)
            bStop = (bool)!SendMessage(ctrl->getHandle(), UWM_SHOW, 0, 0);
    }

    while (!bStop)
    {
        while ((bool)PeekMessage(&mMsg, (HWND)(-1), 0, 0, PM_REMOVE))
        {
            switch (mMsg.message)
            {
            case UWM_DRAW:
                ctrl->draw(mMsg.lParam, mMsg.lParam);
                break;
            case WM_CLOSE:
                bStop = true;
                break;
            case WM_KEYDOWN:
                ctrl->keyDown(mMsg.wParam, mMsg.lParam);
                break;
            case WM_LBUTTONDOWN:
                ctrl->lButtonDown(mMsg.lParam, mMsg.lParam);
                break;
            case WM_MOUSEMOVE:
                ctrl->mouseMove(mMsg.lParam, mMsg.lParam);
                break;
            case WM_MOUSEWHEEL:
                ctrl->mouseWheel(mMsg.lParam, mMsg.lParam);
                break;
            case WM_PAINT:
                ctrl->paint();
                break;
            case WM_SIZE:
                ctrl->size(mMsg.lParam, mMsg.lParam);
                break;
            default:
                return 0;
            }

            SwapBuffers(ctrl->getDC());
        }
    }
    _endthreadex(0);
    return 0;
}

目前,我已将所有自定义 UI 元素(文本、编辑框等)包含在 ThreadController class 中。这使得 UI 的动态创建变得非常困难,更重要的是破坏了拥有纯图形管道的目标,所以我想做的是这个;

  1. 在绘制数据和 UI 元素的视口线程中声明函数;
void ViewportController::drawSomeStuff()
{
    glMatrixMode(GL_PROJECTION);
    /*get the appropriate cameras projection matrix*/

    glMatrixMode(GL_MODELVIEW);
    /*get the same cameras modelview matrix*/

    /*Lots of stuff beginning with "gl" goes here*/
}

void ViewportController::drawSomeOtherStuff()
{
    /*See above*/
}

void ViewportController::drawSomeDifferentOtherStuffMaybeAnEditBox()
{
    /*See above*/
}

和 ThreadController 中具有相同签名的“虚拟”函数class;

void drawSomeStuff(){}
  1. 使用 PostThreadMessage() 将指向所需函数的指针传递给图形线程;
typedef void (ViewportController::*RenderThreadFn)(); // declare pointer typedef in the ViewportController class

void ViewportController::size(){
RenderThreadFn pRenderFrame = &ViewportController::drawSomeStuff/*drawSomeOtherStuff*//*drawSomeDifferentOtherStuffMaybeAnEditBox*/;
PostThreadMessage(uiThreadID, UWM_DRAW, 0,reinterpret_cast<LPARAM(&pRenderFrame));
}
  1. 在图形线程中捕获 PeekMessage() 并执行函数;
typedef void (ThreadController::*RenderThreadFn)(); // declare pointer typedef in the ThreadController class

ThreadController::draw(WPARAM wParam, LPARAM lParam){
    RenderThreadFn* pRenderFrame = reinterpret_cast<RenderThreadFn*>(lParam); // get the pointer - everything up to here works 
    (*this.*pRenderFrame)(); // Executing the function here does not work
}
  1. 给自己加薪吧,因为他什么都擅长。

这不起作用。显然每个 class 中的指针都有不同的定义,但我不知道如何解决这个问题,或者我如何才能获得相同的结果。

我将非常感谢任何关于这方面的指导,我已经尝试了一些事情,比如为视口和线程控制器 classes 继承一个共同的 class 并定义 typedef对于其中的指针,但我仍然是一个初学者,我解决问题的方法并不比随机敲击键盘更先进或更有成效。

关于如何做到这一点有什么想法吗?完全可以做到吗?这是一个好主意还是我应该使用完全不同的方法来获得我想要的东西?如果可以请帮忙,我会在我有钱有名的时候记住你

PostMessage 不是在线程之间编组函数指针和工作工作项的好方法。您最好只是发明自己的 thread-safe 数据结构并将工作项排队到渲染线程。

但如果您真的想使用 PostMessage 传递函数...那么 std::function 与 lambda 的组合可能会起作用。

在通用头文件中声明:

struct MyCustomMessage
{
    std::function<void()> fn;
};

从 ViewPortController 线程执行此操作以将函数调用“发送”到另一个线程。

MyCustomMessage *pMsg = new MyCustomMessage();
pMsg->fn = [this](){
    this->DrawSomeStuff();
};
PostThreadMessage(uiThreadID, UWM_DRAW, 0, reinterpret_cast<LPARAM>(pMsg);

在另一个线程中:

ThreadController::draw(WPARAM wParam, LPARAM lParam) {

     MyCustomMessage* pMsg = reinterpret_cast<MyCustomMessage*>(lParam);

     pMsg->fn();

     delete pMsg;
}

您可以使用 Active-Object-Pattern 通过 Concurrent-Queue 将数据发送到线程中。活动对象(检查 Wikipedia 条目)封装 thread-function 和 concurrent-queue 以将命令排队到该线程。要发送命令,您可以这样:

struct Command {
    int id;
    std::function<void()> fn;
    Command(int id_, std::function<void()> &fn, size_t size){ ... }
};
MyActiveObject object;
Command *drawCmd = new Command(DrawUiEvent, func); 
object.enqueue(drawCmd);

这将有助于解决由于很多 user-interaction 或其他原因而必须由您的 thread-function 进行大量更新的问题。