Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Класс-обертка для диалогового окна

87
28 сентября 2008 года
Kogrom
2.7K / / 02.02.2008
Хочу написать класс для главного окна программы, построенного на основе диалога из ресурсов. Пока выходит кривовато:

// Для краткости все файлы тут вместе
Код:
#include <windows.h>
#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. В классе-обертке для простого окна ничего подобного не требовалось. Можно ли как-то тут упростить.
3
28 сентября 2008 года
Green
4.8K / / 20.01.2000
Изобретая велосипед, посмотри, как устроены др. велосипеды.
Например, в WTL.
87
28 сентября 2008 года
Kogrom
2.7K / / 02.02.2008
Цитата: Green
Изобретая велосипед, посмотри, как устроены др. велосипеды.
Например, в WTL.


Безобразие. Они использовали глобальную переменную:
CAppModule _Module;
Хуже того, затем в заголовочном файле использовали:
extern CAppModule _Module;

Так вот в этой переменной они и перенесли this объекта. Конечно, с использованием глобальных переменных всё проще, но я как раз от них хотел избавиться. Поэтому я засунул this в ячейку DWLP_USER самого диалогового окна. Но это можно сделать только после создания окна. То есть после выдачи сообщения WM_INITDIALOG.

Я понимаю, что у них мега-система, не велосипед, а целый экскаватор. Но мне нужен именно велосипед.

3
29 сентября 2008 года
Green
4.8K / / 20.01.2000
Цитата: Kogrom
Безобразие. Они использовали глобальную переменную:
CAppModule _Module;
Хуже того, затем в заголовочном файле использовали:
extern CAppModule _Module;

Так вот в этой переменной они и перенесли this объекта. Конечно, с использованием глобальных переменных всё проще, но я как раз от них хотел избавиться. Поэтому я засунул this в ячейку DWLP_USER самого диалогового окна. Но это можно сделать только после создания окна. То есть после выдачи сообщения WM_INITDIALOG.

Я понимаю, что у них мега-система, не велосипед, а целый экскаватор. Но мне нужен именно велосипед.


Я думаю, ты зря горячишься.
Сейчас в WTL навернуто довольно много всего в виду её повсеместного использования, но когда эта библиотека была совсем солода, там все легко просматривалось, а принцип состоит в следующем.
Для каждого вновь создаваемого окна формируется динамически переходник _stdcallthunk, в который жестко прописывается код:

 
Код:
mov ecx, this
jmp nonstaticWinProc


Сейчас все немного изменилось и вместо установки this, подменяется hWnd:
 
Код:
mov dword ptr [esp+0x4], pThis      // esp+0x4 is hWnd
jmp WndProc

Но суть остается прежней - динамически формируемый переходник.
Подробнее можно прочитать здесь:
WTL. Переходники и процесс создания окна.

А вообще, как передать this в статическую оконную процедуру я уже неоднократно описывал. Вот к примеру:
http://forum.codenet.ru/showthread.php?p=57283
87
29 сентября 2008 года
Kogrom
2.7K / / 02.02.2008
Цитата: Green
Подробнее можно прочитать здесь:
WTL. Переходники и процесс создания окна.

А вообще, как передать this в статическую оконную процедуру я уже неоднократно описывал. Вот к примеру:
http://forum.codenet.ru/showthread.php?p=57283


Так на этих данных я и строю свой класс. И все хорошо получается с обычным окном (frame based вроде бы оно называется).

Совсем другое дело, когда окно создается на основе немодального диалога. Там операция CreateDialog одновременно и HWND возвращает и создает окно и посылает в это окно сообщение WM_INITDIALOG. То есть я еще не успеваю передать this, а статическая оконная процедура уже обрабатывает сообщение.

Вот как сделали в WTL
[QUOTE=RSDN]Но откуда StartWindowProc узнаёт адрес объекта, связанного с окном? Эта информация записывается в объект _Module непосредственно перед вызовом CreateWindowEx[/QUOTE]
а объект этот глобальный...

87
29 сентября 2008 года
Kogrom
2.7K / / 02.02.2008
В принципе, можно передать this, если воспользоваться CreateDialogParam в переменной типа LPARAM. А в статической функции переправить его в ячейку DWLP_USER, если сообщение WM_INITDIALOG. Но в таком случае придется каждый раз проверять в статической функции какое сообщение пришло.

Такой подход лучше подходит для модальных диалогов, так как там сложнее получить HWND окна.
14
29 сентября 2008 года
Phodopus
3.3K / / 19.06.2008
Сделай две статические функции если тебя это так смущает. В первой проверяй на WM_INITDIALOG и когда оно придет подмени DWLP_DLGPROC на вторую, где эта проверка отсутствует
87
29 сентября 2008 года
Kogrom
2.7K / / 02.02.2008
Цитата: Phodopus
Сделай две статические функции если тебя это так смущает. В первой проверяй на WM_INITDIALOG и когда оно придет подмени DWLP_DLGPROC на вторую, где эта проверка отсутствует


Это опять-таки финты. Тем более, что опыты с передачей this через LPARAM показывают небезопасность такого подхода.

В общем, получается, что первоначальный вариант был самый простой. Осталось немного причесать и пользоваться.

Хуже с модальными диалогами - фунция их создающая не возвращает HWND окна. Но так как модальный диалог всегда один в определенный момент, то наверно тут можно воспользоваться статическим указателем для this.

3
29 сентября 2008 года
Green
4.8K / / 20.01.2000
Я чего-то не пойму, чем тебя не устраивает вариант с переходником?
Создаешь нестатический обработчик:
 
Код:
INT_PTR MyDialog::dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
)
{

}


Динамически создаешь переходник:
 
Код:
INT_PTR CALLBACK thunkDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
)
{
    pThis = 0xADDRESS;
    return pThis->dialogProc(hwndDlg, uMsg, wParam, lParam);
}


Задаешь переходник в качестве оконной процедуры при создании диалога:
 
Код:
CreateDialog(...., thunkDialogProc);


А использовать DWLP_USER - это не очень хорошее решение, т.к. сокращает возможности пользователя.
87
29 сентября 2008 года
Kogrom
2.7K / / 02.02.2008
Цитата: Green
Динамически создаешь переходник:
 
Код:
INT_PTR CALLBACK thunkDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
)
{
    pThis = 0xADDRESS;
    return pThis->dialogProc(hwndDlg, uMsg, wParam, lParam);
}


Вот этот момент совсем не понятен. Что значит динамически создаешь?

Что есть 0xADDRESS? Откуда его взять?

Из статьи RSDN я понял следующее:
есть глобальное хранилище указателей _Module. Перед созданием окна в это хранилище сохраниется this упакованный в переходник:
_Module.AddCreateWndData(&m_thunk.cd, this);
Далее при использовании CreateDialog в качестве функции обратного вызова указывается StartWindowProc, её задача - создать для окна переходник к функции WindowProc и задать его адрес в качестве новой оконной процедуры. Т.е. тут происходит подмена хендла окна на указатель объекта. А уже в WindowProc используется преобразование из хэндла окна в указатель на объект.

Все хитро и наворочено, но не отменяет существования глобального хранилища указателей, пусть даже оно используется только один раз при запуске окна.

7.9K
29 сентября 2008 года
***Zebr@XXL***
47 / / 18.08.2005
Если я правильно понял, то Green имел в виду такой код:
Код:
template <class T>
__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 );
}


Создание и вызов:
 
Код:
CreateDialog(...., CreateDelegate(GetMethodPointer(&MyDialog::dialogProc), this) );


Проблема лишь в том что этот код необходимо использовать с огромной осторожностью, так как можно нарваться на огромные не приятности
3
29 сентября 2008 года
Green
4.8K / / 20.01.2000
Цитата: Kogrom

Вот этот момент совсем не понятен. Что значит динамически создаешь?

Что есть 0xADDRESS? Откуда его взять?


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

0xADDRESS - это адрес твоего созданного объекта, т.е. this.

Т.е. в обем что-то типа:

Код:
#pragma pack(push,1)
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).

Цитата: Kogrom

Все хитро и наворочено, но не отменяет существования глобального хранилища указателей, пусть даже оно используется только один раз при запуске окна.


Windows - не объектно-ориентированная ОС, поэтому внедрение в неё ООП это в большинстве случаев хитрость.
На счет навороченности, это субъективно.
На счет глобального хранилища я уже высказался, - конструируй переходник в момент создания объекта и ужё его передавай в качестве оконной процедуры. Не будет никакого глобального хранилища.

87
29 сентября 2008 года
Kogrom
2.7K / / 02.02.2008
После того, как ***Zebr@XXL*** все подробно объяснил идея мне стала ясна. В общем, по смыслу в функцию обратного вызова добавляется еще одна переменная. Точнее, константа для данного окна. Ну, тоже вариант.

Цитата: Green
А использовать DWLP_USER - это не очень хорошее решение, т.к. сокращает возможности пользователя.


У пользователя не должно быть потребности в DWLP_USER - у него есть класс, в который он может натолкать сколько угодно переменных.

Ну, если что, альтернатива понятна. Всем спасибо.

87
29 октября 2008 года
Kogrom
2.7K / / 02.02.2008
Цитата: Green
А использовать DWLP_USER - это не очень хорошее решение, т.к. сокращает возможности пользователя.


Ну, наверно, в качестве альтернативы можно добавить указатель this как свойство окна:

SetProp(cHwnd, "this", this);

А затем в функции обратного вызова получить this:

ModelessDlg *pThis = (ModelessDlg*)GetProp(hwnd, "this");

От идеи использовать переходники я пока отказался, так как не знаю, как их использовать в MDI-документах, у которых на несколько окон может быть одна функция обратного вызова.

При использовании пользовательской переменной или функций SetProp, GetProp сделать классовую обертку для MDI-документов проще. Но я мало знаком с этими функциями, не знаю подводных камней.

Является ли использование свойств (property) лучшим решением, чем использование пользовательских переменных? Или я неправильно понимаю смысл свойств и так делать опасно?

14
29 октября 2008 года
Phodopus
3.3K / / 19.06.2008
Фактически DWLP_USER - выделенный Prop (возможно слегка побыстрее работающий). А вот имя для Prop я использовал GUID-овское. Ибо ничто не мешает пользователю тоже создать Prop "this". Надо только следить за размером указателя this и размером того что вмещает Prop.
87
30 октября 2008 года
Kogrom
2.7K / / 02.02.2008
Цитата: Phodopus
Фактически DWLP_USER - выделенный Prop (возможно слегка побыстрее работающий).


То есть, теоретически дополнительное время потратится на поиск указателя по строке? Значит, чем меньше таких строк и чем они короче тем бысрее будет найден указатель?

Цитата: Phodopus
А вот имя для Prop я использовал GUID-овское. Ибо ничто не мешает пользователю тоже создать Prop "this".


Ох уж этот пользователь, везде залезет, все поломает...
Если я правильно понимаю, отказ от использования DWLP_USER дает мне возможность использовать сторонние библиотеки, которые также используют эту переменную. То есть, если мне попадется такая библиотека, то я не буду знать выход.

В случае, если в сторонней библиотеке также используется свойство "this", то я легко смогу переименовать свою строку.

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

Цитата: Phodopus
Надо только следить за размером указателя this и размером того что вмещает Prop.


Вот этот пункт я не понял. Я ж указатель void преобразую в указатель на объект моего класса окна и размер поэтому будет соответственный. За чем тут следить?

3
30 октября 2008 года
Green
4.8K / / 20.01.2000
Цитата: Kogrom

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


Не заморачивайчя на счет длины строки. Это будет не самое узкое место в производительности.

14
30 октября 2008 года
Phodopus
3.3K / / 19.06.2008
Цитата: Kogrom
То есть, теоретически дополнительное время потратится на поиск указателя по строке? Значит, чем меньше таких строк и чем они короче тем бысрее будет найден указатель?


Ну вроде это логично? Или мало строк "работают" быстрее а много - медленнее, или как мало так и много строк "работают" одинаково медленно. Уверен что у MS первый метод :)

Цитата: Kogrom
Ох уж этот пользователь, везде залезет, все поломает...
Если я правильно понимаю, отказ от использования DWLP_USER дает мне возможность использовать сторонние библиотеки, которые также используют эту переменную. То есть, если мне попадется такая библиотека, то я не буду знать выход.


Конечно, только выход у тебя будет - просто используй GUID

Цитата: Kogrom
Вот этот пункт я не понял. Я ж указатель void преобразую в указатель на объект моего класса окна и размер поэтому будет соответственный. За чем тут следить?


Просто хочу предостеречь что когда указатель this вдруг станет 64-битным(!) а параметр HANDLE hData (также вдруг) останется 32-х битным.. Ну, как-бы тут ничего нигде не обрезалось!.. Хотя, надеюсь,компилятор об этом позаботится

7.9K
30 октября 2008 года
***Zebr@XXL***
47 / / 18.08.2005
Цитата: Phodopus
Просто хочу предостеречь что когда указатель this вдруг станет 64-битным(!) а параметр HANDLE hData (также вдруг) останется 32-х битным.. Ну, как-бы тут ничего нигде не обрезалось!.. Хотя, надеюсь,компилятор об этом позаботится



Этого вдруг не случится. HANDLE сам объявлен как void* (из WinNT.h):

 
Код:
typedef void *HANDLE;
14
31 октября 2008 года
Phodopus
3.3K / / 19.06.2008
Цитата: ***Zebr@XXL***
Этого вдруг не случится. HANDLE сам объявлен как void* (из WinNT.h):


Согласен! Но я все же ASSERT при инициализации поставил. Потому что мне было лень лезть в хедеры, и потому что это успокоило мою параною :)

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог