Класс-обертка для диалогового окна
// Для краткости все файлы тут вместе
#include "resource.h"
class ksWnd
{
HINSTANCE hInst;
HWND gHwnd;
static LRESULT CALLBACK WndProcStub(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
LRESULT WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
public:
inline ksWnd::ksWnd(){gHwnd = 0; hInst = 0;}
inline HWND GetHwnd()const{return gHwnd;}
inline HINSTANCE GetHinst()const{return hInst;}
inline void SetHwnd(HWND h){gHwnd = h;}
inline void SetHinst(HINSTANCE h){hInst = h;}
void Init(int nCmdShow);
void CreateStubData();
void OnCommand(HWND hwnd, WPARAM wParam, LPARAM lParam);
};
void ksWnd::CreateStubData()
{
SetWindowLongPtr(gHwnd, DWLP_DLGPROC, (LONG_PTR)WndProcStub);
SetWindowLongPtr(gHwnd, DWLP_USER, (LONG_PTR)this);
}
void ksWnd::Init(int nCmdShow)
{
ShowWindow(gHwnd, nCmdShow);
UpdateWindow(gHwnd);
}
// Функция-переходник, чтобы в WndProc могли использоваться
// не только статические переменные класса
// Можно обойтись без этого переходника, если в WndProc не
// использовать переменные класса.
LRESULT CALLBACK ksWnd::WndProcStub(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
ksWnd *pThis = (ksWnd*)GetWindowLongPtr(hwnd, DWLP_USER);
return pThis->WndProc(hwnd, message, wParam, lParam);
}
LRESULT ksWnd::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
OnCommand(hwnd, wParam, lParam);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return 0;
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
static ksWnd myWnd;
myWnd.SetHinst(hInstance);
MSG Msg;
// Создаем диалог без функции обратного вызова.
// это делается для того чтобы не использовать статические переменные в классе окна
// Особенность: WM_INITDIALOG в данном случае не работает
myWnd.SetHwnd(CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG), NULL, (DLGPROC)NULL));
if (!myWnd.GetHwnd())
{
MessageBox( NULL, TEXT("ERROR"), TEXT("ERROR"), MB_OK|MB_ICONERROR);
return FALSE;
}
// ...а вот тут и укажем функцию обратного вызова
myWnd.CreateStubData();
// замена WM_INITDIALOG
myWnd.Init(nCmdShow);
while (GetMessage(&Msg,NULL,0,0))
{
if ((!IsWindow(myWnd.GetHwnd()))||(!IsDialogMessage(myWnd.GetHwnd(),&Msg)))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
return Msg.wParam;
}
Меня смущает необходимость использования функции void ksWnd::CreateStubData() и навороты в WinMain. В классе-обертке для простого окна ничего подобного не требовалось. Можно ли как-то тут упростить.
Например, в WTL.
Например, в WTL.
Безобразие. Они использовали глобальную переменную:
CAppModule _Module;
Хуже того, затем в заголовочном файле использовали:
extern CAppModule _Module;
Так вот в этой переменной они и перенесли this объекта. Конечно, с использованием глобальных переменных всё проще, но я как раз от них хотел избавиться. Поэтому я засунул this в ячейку DWLP_USER самого диалогового окна. Но это можно сделать только после создания окна. То есть после выдачи сообщения WM_INITDIALOG.
Я понимаю, что у них мега-система, не велосипед, а целый экскаватор. Но мне нужен именно велосипед.
CAppModule _Module;
Хуже того, затем в заголовочном файле использовали:
extern CAppModule _Module;
Так вот в этой переменной они и перенесли this объекта. Конечно, с использованием глобальных переменных всё проще, но я как раз от них хотел избавиться. Поэтому я засунул this в ячейку DWLP_USER самого диалогового окна. Но это можно сделать только после создания окна. То есть после выдачи сообщения WM_INITDIALOG.
Я понимаю, что у них мега-система, не велосипед, а целый экскаватор. Но мне нужен именно велосипед.
Я думаю, ты зря горячишься.
Сейчас в WTL навернуто довольно много всего в виду её повсеместного использования, но когда эта библиотека была совсем солода, там все легко просматривалось, а принцип состоит в следующем.
Для каждого вновь создаваемого окна формируется динамически переходник _stdcallthunk, в который жестко прописывается код:
jmp nonstaticWinProc
Сейчас все немного изменилось и вместо установки this, подменяется hWnd:
jmp WndProc
Но суть остается прежней - динамически формируемый переходник.
Подробнее можно прочитать здесь:
WTL. Переходники и процесс создания окна.
А вообще, как передать this в статическую оконную процедуру я уже неоднократно описывал. Вот к примеру:
http://forum.codenet.ru/showthread.php?p=57283
WTL. Переходники и процесс создания окна.
А вообще, как передать this в статическую оконную процедуру я уже неоднократно описывал. Вот к примеру:
http://forum.codenet.ru/showthread.php?p=57283
Так на этих данных я и строю свой класс. И все хорошо получается с обычным окном (frame based вроде бы оно называется).
Совсем другое дело, когда окно создается на основе немодального диалога. Там операция CreateDialog одновременно и HWND возвращает и создает окно и посылает в это окно сообщение WM_INITDIALOG. То есть я еще не успеваю передать this, а статическая оконная процедура уже обрабатывает сообщение.
Вот как сделали в WTL
[QUOTE=RSDN]Но откуда StartWindowProc узнаёт адрес объекта, связанного с окном? Эта информация записывается в объект _Module непосредственно перед вызовом CreateWindowEx[/QUOTE]
а объект этот глобальный...
Такой подход лучше подходит для модальных диалогов, так как там сложнее получить HWND окна.
Это опять-таки финты. Тем более, что опыты с передачей this через LPARAM показывают небезопасность такого подхода.
В общем, получается, что первоначальный вариант был самый простой. Осталось немного причесать и пользоваться.
Хуже с модальными диалогами - фунция их создающая не возвращает HWND окна. Но так как модальный диалог всегда один в определенный момент, то наверно тут можно воспользоваться статическим указателем для this.
Создаешь нестатический обработчик:
)
{
}
Динамически создаешь переходник:
)
{
pThis = 0xADDRESS;
return pThis->dialogProc(hwndDlg, uMsg, wParam, lParam);
}
Задаешь переходник в качестве оконной процедуры при создании диалога:
А использовать DWLP_USER - это не очень хорошее решение, т.к. сокращает возможности пользователя.
)
{
pThis = 0xADDRESS;
return pThis->dialogProc(hwndDlg, uMsg, wParam, lParam);
}
Вот этот момент совсем не понятен. Что значит динамически создаешь?
Что есть 0xADDRESS? Откуда его взять?
Из статьи RSDN я понял следующее:
есть глобальное хранилище указателей _Module. Перед созданием окна в это хранилище сохраниется this упакованный в переходник:
_Module.AddCreateWndData(&m_thunk.cd, this);
Далее при использовании CreateDialog в качестве функции обратного вызова указывается StartWindowProc, её задача - создать для окна переходник к функции WindowProc и задать его адрес в качестве новой оконной процедуры. Т.е. тут происходит подмена хендла окна на указатель объекта. А уже в WindowProc используется преобразование из хэндла окна в указатель на объект.
Все хитро и наворочено, но не отменяет существования глобального хранилища указателей, пусть даже оно используется только один раз при запуске окна.
__inline LPVOID GetMethodPointer( T method )
{
union
{
T InObject;
LPVOID InMemory;
} Pointer;
Pointer.InObject = method;
return Pointer.InMemory;
}
unsigned char DelegateInMemoryCode[] = {
0xb9, 0xff, 0xff, 0xff, 0xff, //mov ecx, 0ffffffffh
0xb8, 0xff, 0xff, 0xff, 0xff, //mov eax, 0ffffffffh
0xff, 0xe0 //jmp eax
};
#define OBJECT_OFFSET 1
#define METHOD_OFFSET 6
LPVOID CreateDelegate(LPVOID Method,LPVOID Object)
{
char *ret = (char*)maloc( sizeof(DelegateInMemoryCode) );
memcpy( ret, DelegateInMemoryCode, sizeof(DelegateInMemoryCode) );
*(LPVOID*)(&ret[OBJECT_OFFSET]) = Object;
*(LPVOID*)(&ret[METHOD_OFFSET]) = Method;
return (LPVOID)ret;
}
void DestroyDelegate(LPVOID Delegate)
{
free( Delegate );
}
Создание и вызов:
Проблема лишь в том что этот код необходимо использовать с огромной осторожностью, так как можно нарваться на огромные не приятности
Вот этот момент совсем не понятен. Что значит динамически создаешь?
Что есть 0xADDRESS? Откуда его взять?
Это значит, что код этой функции ты генерируешь не во время компиляции, а уже при выполнении.
Как это делается можно посмотреть в вышеобозначенной статье, а можно и на сам код взглянуть, приводил уже название - _stdcallthunk.
0xADDRESS - это адрес твоего созданного объекта, т.е. this.
Т.е. в обем что-то типа:
struct _WndProcThunk
{
BYTE m_mov; // mov ecx, pThis
DWORD m_this;
BYTE m_jmp; // jmp WndProc
DWORD m_relpro // relative jmp
};
#pragma pack(pop)
void init(WNDPROC proc, void* pThis)
{
thunk.m_mov = 0xB9;
thunk.m_this = (DWORD)pThis;
thunk.m_jmp = 0xE9;
thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));
}
Конечно, этот код только для x86 платформ, для других платформ он будет выглядеть иначе (cм. всю ту же реализацию _stdcallthunk).
Все хитро и наворочено, но не отменяет существования глобального хранилища указателей, пусть даже оно используется только один раз при запуске окна.
Windows - не объектно-ориентированная ОС, поэтому внедрение в неё ООП это в большинстве случаев хитрость.
На счет навороченности, это субъективно.
На счет глобального хранилища я уже высказался, - конструируй переходник в момент создания объекта и ужё его передавай в качестве оконной процедуры. Не будет никакого глобального хранилища.
У пользователя не должно быть потребности в DWLP_USER - у него есть класс, в который он может натолкать сколько угодно переменных.
Ну, если что, альтернатива понятна. Всем спасибо.
Ну, наверно, в качестве альтернативы можно добавить указатель this как свойство окна:
SetProp(cHwnd, "this", this);
А затем в функции обратного вызова получить this:
ModelessDlg *pThis = (ModelessDlg*)GetProp(hwnd, "this");
От идеи использовать переходники я пока отказался, так как не знаю, как их использовать в MDI-документах, у которых на несколько окон может быть одна функция обратного вызова.
При использовании пользовательской переменной или функций SetProp, GetProp сделать классовую обертку для MDI-документов проще. Но я мало знаком с этими функциями, не знаю подводных камней.
Является ли использование свойств (property) лучшим решением, чем использование пользовательских переменных? Или я неправильно понимаю смысл свойств и так делать опасно?
То есть, теоретически дополнительное время потратится на поиск указателя по строке? Значит, чем меньше таких строк и чем они короче тем бысрее будет найден указатель?
Ох уж этот пользователь, везде залезет, все поломает...
Если я правильно понимаю, отказ от использования DWLP_USER дает мне возможность использовать сторонние библиотеки, которые также используют эту переменную. То есть, если мне попадется такая библиотека, то я не буду знать выход.
В случае, если в сторонней библиотеке также используется свойство "this", то я легко смогу переименовать свою строку.
Тут дилемма, либо использовать длинные строки, тем самым еще больше затормозив работу программы и увеличив ее размер, либо использовать короткие строки, рискуя напороться на несовместимую библиотеку. Однако, "this" действительно не очень удачное название...
Вот этот пункт я не понял. Я ж указатель void преобразую в указатель на объект моего класса окна и размер поэтому будет соответственный. За чем тут следить?
Тут дилемма, либо использовать длинные строки, тем самым еще больше затормозив работу программы и увеличив ее размер, либо использовать короткие строки, рискуя напороться на несовместимую библиотеку. Однако, "this" действительно не очень удачное название...
Не заморачивайчя на счет длины строки. Это будет не самое узкое место в производительности.
Ну вроде это логично? Или мало строк "работают" быстрее а много - медленнее, или как мало так и много строк "работают" одинаково медленно. Уверен что у MS первый метод :)
Если я правильно понимаю, отказ от использования DWLP_USER дает мне возможность использовать сторонние библиотеки, которые также используют эту переменную. То есть, если мне попадется такая библиотека, то я не буду знать выход.
Конечно, только выход у тебя будет - просто используй GUID
Просто хочу предостеречь что когда указатель this вдруг станет 64-битным(!) а параметр HANDLE hData (также вдруг) останется 32-х битным.. Ну, как-бы тут ничего нигде не обрезалось!.. Хотя, надеюсь,компилятор об этом позаботится
Этого вдруг не случится. HANDLE сам объявлен как void* (из WinNT.h):
Согласен! Но я все же ASSERT при инициализации поставил. Потому что мне было лень лезть в хедеры, и потому что это успокоило мою параною :)