Передача вызова функции в GUI тред
От: Andrew S Россия http://alchemy-lab.com
Дата: 23.05.03 10:53
Оценка: 15 (3) +1
Все привет.
Собственно, этот постинг — это мой вариант решения к вопросу, обсуждаемому здесь
Автор: Andrew S
Дата: 22.05.03
.
Вкратце постановка задачи — есть 2 или более потоков. Один из потоков GUI (т.е. имеет цикл выборки сообщений). Необходимо выполнить код (метод или функцю) в GUI потоке, при этом инициируя выполнение оной в worker thread — в потоке, где нет выборки сообщений. Это нужно, например, если worker thread работает с классом, методы которого вызывают функции гуи (создание окон, и т.п. — т.е. те функции, которые _обязаны_ выполняться в GUI потоке).
При этом синтаксис вызова должен быть как можно проще и скрывать всю функциональность. С другой стороны, на принимающей вызов стороне (в GUI треде) обработчик вызова тоже должен быть предельно прост.
Решение получилось досточно простым, однако, возможно, компилерозависимым. На VC6 это у меня работает без проблем.
Вот сам код:


=================================================================
//
// worker thread

struct MethodProxy
{
    struct X{};
    typedef void (X::*type)();
    union
    {
        void *m_pData;
        type m_fun;
    };
    MethodProxy(type fun):m_fun(fun)
    {
    }
    operator void *()
    {
        return m_pData;
    }
};

struct CGuiCallProxy
{
    int m_iEsp;
    BOOL GuiCall(HWND hWnd, void *pMethod,...)
    {
        return SendMessage(hWnd, WM_USER + 1, m_iEsp, (LPARAM)&pMethod);
    }
};


#define GUI_CALL(arg) \
{\
    CGuiCallProxy guiCall;\
    guiCall.m_iEsp = GetESP();\
    guiCall.GuiCall arg;\
}

#ifdef _DEBUG
__declspec(naked) int GetESP()
{
    __asm
    {
        lea eax, [esp + 4]
        ret
    }
}
#else
#pragma warning(disable : 4035)
__inline int GetESP()
{
    __asm mov eax, esp;
}

#endif


//=============================================================
//
// gui thread

afx_msg void CMainFrame::OnUserCall(WPARAM wParam, LPARAM lParam)
{
    __asm
    {
        mov ebx, [lParam]
        mov ecx, [wParam]
        mov eax, [ebx]
        sub ecx, ebx
        sub ecx, 4    
        sub esp, ecx
        shr ecx, 2

        lea esi, [ebx + 4]
        mov edi, esp
        rep movsd
        call eax
    }
}


Где:

MethodProxy — прокси для вызова "простых" методов класса,
CGuiCallProxy — собственно прокси вызова
GUI_CALL — макрос для работы с прокси вызова


Все остальное используется внутри механизма вызова и к интерфейсу отношения не имеет.
Вкратце алгоритм работы:

В качестве прокси GUI вызова используется функция с переменным количеством аргументов.
Передача параметров GUI треду осуществляется посылкой сообщения + копированием стека аргументов. Размер стека аргументов вычисляется как разница указателя стека до вызова функции прокси и указателя на первый параметр из аргументов "переменного количества". Это разница получается больше на размер указателя pMethod. Коррекция размера стека аргументов произовдится в функции-приемнике. После чего осуществляется копирование параметров в стек GUI треда. Единственное ограничение — функции/методы должны быть WINAPI. Собственно, это ограничение легко обойти и написать варианты и для других соглашений вызова.

И пример использования:


void WINAPI testfn(LPCTSTR s)
{
    MessageBox(NULL, s, s, MB_OK);
}

class Test
{
public:
    void WINAPI ShowMessase(LPCTSTR sCaption, LPCTSTR sText)
    {
        MessageBox(NULL, sCaption, sText, MB_OK);
    }
};

DWORD WINAPI ClientThreadFunc(LPVOID lparam)
{
    GUI_CALL((((CMainFrame *)lparam)->m_hWnd, &testfn, "12345"));
    Test test;
    GUI_CALL((
                ((CMainFrame *)lparam)->m_hWnd,
                MethodProxy((MethodProxy::type)&Test::ShowMessase),
                &test,
                "Caption",
                "Text"
             ));
    return 0;
}

void CMainFrame::OnViewGuicall() 
{
    DWORD ThreadId;
    CreateThread(
                NULL,
                NULL,
                (LPTHREAD_START_ROUTINE)ClientThreadFunc,
                this,
                NULL,
                &ThreadId
                );    
}


Буду рад, если это кому-нибудь пригодится, кто-то найдет в этом баги, улучшит или подскажет более интересные и красивые решения.
Успехов!
http://www.rusyaz.ru/pr — стараемся писАть по-русски
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.