Все привет.
Собственно, этот постинг — это мой вариант решения к вопросу, обсуждаемому
здесьАвтор: 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
);
}
Буду рад, если это кому-нибудь пригодится, кто-то найдет в этом баги, улучшит или подскажет более интересные и красивые решения.
Успехов!