Передача указателя на функцию-член в отдельный поток

#c #multithreading #opengl #member-function-pointers

#c #многопоточность #opengl #указатель на член

Вопрос:

Я использую OpenGL для отображения графических данных и пользовательских элементов пользовательского интерфейса. У меня есть SDI с вкладками, в котором окна документов используют окна без полей в качестве видовых экранов, каждый видовой экран содержит отдельный поток для выполнения только операций рисования GL, В то время как все остальные операции (мышь, клавиатура и т. Д.) обрабатываются / должны обрабатываться в обратном вызове видового экрана. Графический поток создается с помощью _beginthreadex;

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

с помощью функции start_address, содержащей цикл с использованием PeekMessage, позволяющий области просмотра передавать сообщения в графический поток, с классом ThreadController, переданным в качестве аргумента 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(amp;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;
}
  

На данный момент у меня есть все пользовательские элементы пользовательского интерфейса (текст, поля редактирования и т.д.), Содержащиеся в классе ThreadController. Это очень затрудняет динамическое создание пользовательского интерфейса и, что более важно, сводит на нет цель создания конвейера только для графики, поэтому я хотел бы сделать следующее;

  1. Объявляйте функции в потоке окна просмотра, которые отображают данные и элементы пользовательского интерфейса;
 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;

 void drawSomeStuff(){}
  
  1. Передайте указатель на требуемую функцию графическому потоку с помощью PostThreadMessage();
 typedef void (ViewportController::*RenderThreadFn)(); // declare pointer typedef in the ViewportController class

void ViewportController::size(){
RenderThreadFn pRenderFrame = amp;ViewportController::drawSomeStuff/*drawSomeOtherStuff*//*drawSomeDifferentOtherStuffMaybeAnEditBox*/;
PostThreadMessage(uiThreadID, UWM_DRAW, 0,reinterpret_cast<LPARAM(amp;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. Повышаю себе зарплату за то, что я так чертовски хорош во всем.

Это не работает. Очевидно, что указатели в каждом классе имеют разные определения, но я не знаю, как это обойти или как еще я мог бы добиться того же результата.

Я был бы чрезвычайно благодарен за любые рекомендации по этому вопросу, я пробовал несколько вещей, например, наследование общего класса как для классов viewport, так и для контроллеров потоков и определение typedef для указателя в этом, но я все еще очень новичок, и мое решение проблем не намного более продвинуто илипродуктивнее, чем просто случайное нажатие на клавиатуру.

Есть идеи о том, как это можно сделать? Можно ли это вообще сделать? Это хорошая идея или я должен использовать совершенно другой метод, чтобы получить то, что я хочу? Пожалуйста, помогите, если сможете, я буду помнить вас, когда стану богатым и знаменитым.

Ответ №1:

postMessage — не лучший способ упорядочивать указатели на функции и рабочие элементы между потоками. Вам лучше просто изобрести свою собственную потокобезопасную структуру данных и поставить рабочие элементы в очередь в поток рендеринга.

Но если вы действительно хотите передать функцию с помощью postMessage … тогда std::function комбинация с лямбдой может сработать.

Объявите это в общем заголовочном файле:

 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;
}
  

Комментарии:

1. При условии, что вы обеспечиваете адекватное время жизни, нет проблем с последующей передачей ссылок на целые объекты в другие потоки. Вы можете создавать / удалять или, что еще лучше, создавать их пул, чтобы их можно было перерабатывать.

Ответ №2:

Вы можете использовать шаблон Active-Object для отправки данных в ваш поток через параллельную очередь. Активный объект (проверьте запись в Википедии ) инкапсулирует потоковую функцию и параллельную очередь для постановки команд в очередь для этого потока. Для отправки команд вы могли бы сделать что-то вроде:

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

Это поможет решить проблемы, когда ваша потоковая функция должна выполнять множество обновлений из-за большого взаимодействия с пользователем или чего-то другого.