Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Системное программирование и WinAPI > Сделать калбэк функцию окна объектной


Автор: Alexeis 4.4.2013, 11:19
  Собственно одно решение нашел в исходниках билдера/делфи. Для каждого объекта выделяется блок памяти под функцию и указатель на объект . В область функции зафигачивают машинные коды для перехода по адресу объектного обработчика ну и дополнительно инициализируют this . 
  Может кто-то встречал более переносимое решение чем вставка машинных кодов и правка адресов для перехода? 

  Исходников класса CWnd, так можно было еще там подсмотреть решение.

Автор: Dem_max 4.4.2013, 11:45
Смотря что за велосипед

Автор: GremlinProg 4.4.2013, 11:56
Цитата(Alexeis @  4.4.2013,  13:19 Найти цитируемый пост)
Для каждого объекта выделяется блок памяти под функцию и указатель на объект . В область функции зафигачивают машинные коды для перехода по адресу объектного обработчика ну и дополнительно инициализируют this

Зачем это? Обработкой всех сообщений очереди занимается поток. Один конкретный поток!

Есть ситуации, когда несколько потоков имеют очередь сообщений, но любое окно может принадлежать только одному потоку, следовательно защищать передачу объекта в процедуру окна бессмысленно, ему ничего не грозит даже в глобальном scope.

Автор: borisbn 4.4.2013, 12:52
а чем std:: или boost:: или на худой конец my_bicycle::bind  не угодил ?

Автор: Alexeis 4.4.2013, 14:50
Может не совсем точно объяснил. Задача сделать функцию типа 
Код

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
или
INT_PTR CALLBACK DialogProc(
  _In_  HWND hwndDlg,
  _In_  UINT uMsg,
  _In_  WPARAM wParam,
  _In_  LPARAM lParam
);

Не статическим членом класса.

К примеру взываем функцию 
Код

HWND WINAPI CreateDialog(
  _In_opt_  HINSTANCE hInstance,
  _In_      LPCTSTR lpTemplate,
  _In_opt_  HWND hWndParent,
  _In_opt_  DLGPROC lpDialogFunc
);

или 

::SetWindowLongPtr (hwndWindow, GWLP_WNDPROC, (LONG_PTR) fnNewProcedure);


Для создания диалога, а диалог у нас обернут в класс С++ . 

Очевидно boost::function никак не станет вместо WndProc или DialogProc .

Подсмотрел также решение MFC
Они решили вопрос так. Создали глобальрный ассоциативный массив СHandleMap, который с каждым хэндлом ассоциирует указатель на класс CWnd* . Функция окна одна на всех, при приходе очередного сообщения функция лезет в СHandleMap, находит правильный экземпляр наследник CWnd* и вызывает его виртуальную функцию обработки оконных сообщений. Это решение не такое быстрое, но универсальное. 

Мне хотелось как раз найти вот такое решение как привел borisbn. Cразу вспомнилось boost::function, но видимо не провернуть такой трюк никак. Пока что есть только решение от Microsoft. 

Автор: borisbn 4.4.2013, 14:59
Alexeis, я это делал следующим (да... знаю, что извращённым) способом:

Код
class MyClass {
private:
    static LRESULT CALLBACK s_WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
        MyClass * self = ( MyClass * )GetWindowLongPtr( hWnd, GWL_USERDATA );
        return self->WndProc( hWnd, message, wParam, lParam );
    }
    LRESULT WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
        ...
    }


    void installWindowProc( HWND hWnd ) {
        SetWindowLongPtr( hWnd, GWL_USERDATA, (LONG_PTR)this );
        SetWindowLongPtr( m_wnd, GWL_WNDPROC, (LONG_PTR)s_WndProc );
    }
};

Автор: GremlinProg 4.4.2013, 15:01
Цитата(Alexeis @  4.4.2013,  13:19 Найти цитируемый пост)
Для каждого объекта выделяется блок памяти под функцию и указатель на объект . В область функции зафигачивают машинные коды для перехода по адресу объектного обработчика ну и дополнительно инициализируют this

Скорее всего, это реализация событий у борланда, с процедурой окна она не имеет ничего общего, просто борланд унифицирует таким образом делегат (этот подход у него как минимум с Delphi 3-4)

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

Насколько я помню, у Borland'а существует глобальный указатель, типа pCreateObject, в который записывается указатель на объект, перед вызовом CreateWindow. Первый вызов процедуры окна просто берет этот указатель и пишет в недра свойств окна или WindowLong (не помню точно), при этом, меняет процедуру окна на штатную, которая извлекает указатель из недр и вызывает для него метод WndProc.

Автор: Alexeis 4.4.2013, 16:05
Цитата(borisbn @  4.4.2013,  15:59 Найти цитируемый пост)
Alexeis, я это делал следующим (да... знаю, что извращённым) способом:

  А чо, четенькое решенице. Быстрое и довольно честное.

 
Цитата(GremlinProg @  4.4.2013,  16:01 Найти цитируемый пост)
Скорее всего, это реализация событий у борланда, с процедурой окна она не имеет ничего общего, просто борланд унифицирует таким образом делегат (этот подход у него как минимум с Delphi 3-4)

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

  Делегаты у борлонда существуют ввиде структуры из 2х полей. Делегат имеет размер 8 байт и не может быть записан вместо адреса функции и тем более винда не будет вызывать его. 

Вот собственно привожу весь код создания объектного калбека. Тут правда паскаль, но код такой сиподобный ^. тоже что и -> , знак $ заменяет 0x , Move тоже что и memcpy только аргументы поменяны местами. @ тоже что & 
Код

    SetWindowLongPtr(Result, GWL_WNDPROC, IntPtr(MakeObjectInstance(AMethod)));


Код


function CalcJmpOffset(Src, Dest: Pointer): Longint;
begin
  Result := IntPtr(Dest) - (IntPtr(Src) + 5);
end;

function MakeObjectInstance(AMethod: TWndMethod): Pointer;
const
  BlockCode: array[1..CodeBytes] of Byte = (
{$IF Defined(CPUX86)}
    $59,                       { POP ECX }
    $E9);                      { JMP StdWndProc }
{$ELSEIF Defined(CPUX64)}
    $41,$5b,                   { POP R11 }
    $FF,$25,$00,$00,$00,$00);  { JMP [RIP+0] }
{$IFEND}
  PageSize = 4096;
var
  Block: PInstanceBlock;
  Instance: PObjectInstance;
begin
  if InstFreeList = nil then
  begin
    Block := VirtualAlloc(nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    Block^.Next := InstBlockList;
    Move(BlockCode, Block^.Code, SizeOf(BlockCode));
{$IF Defined(CPUX86)}
    Block^.WndProcPtr := Pointer(CalcJmpOffset(@Block^.Code[2], @StdWndProc));
{$ELSEIF Defined(CPUX64)}
    Block^.WndProcPtr := @StdWndProc;
{$IFEND}
    Instance := @Block^.Instances;
    repeat
      Instance^.Code := $E8;  { CALL NEAR PTR Offset }
      Instance^.Offset := CalcJmpOffset(Instance, @Block^.Code);
      Instance^.Next := InstFreeList;
      InstFreeList := Instance;
      Inc(PByte(Instance), SizeOf(TObjectInstance));
    until IntPtr(Instance) - IntPtr(Block) >= SizeOf(TInstanceBlock);
    InstBlockList := Block;
  end;
  Result := InstFreeList;
  Instance := InstFreeList;
  InstFreeList := Instance^.Next;
  Instance^.FMethod := AMethod;
end;

Автор: GremlinProg 5.4.2013, 08:17
Цитата(Alexeis @ 4.4.2013,  18:05)
Цитата(GremlinProg @  4.4.2013,  16:01 Найти цитируемый пост)
Скорее всего, это реализация событий у борланда, с процедурой окна она не имеет ничего общего, просто борланд унифицирует таким образом делегат (этот подход у него как минимум с Delphi 3-4)

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

  Делегаты у борлонда существуют ввиде структуры из 2х полей. Делегат имеет размер 8 байт и не может быть записан вместо адреса функции и тем более винда не будет вызывать его.

Alexeis, пытается троллить, забавно. В цитате я об этом и написал smile 
только размер делегата равен не 8 байтам, а равен размеру двух указателей (в x64 это 16 байт)
Цитата(Alexeis @  4.4.2013,  18:05 Найти цитируемый пост)
Тут правда паскаль, но код такой сиподобный ^. тоже что и -> , знак $ заменяет 0x , Move тоже что и memcpy только аргументы поменяны местами. @ тоже что & 

Тут большинство программисты и в общем случае понимают разницу между Object Pascal и Си smile
Цитата(Alexeis @  4.4.2013,  18:05 Найти цитируемый пост)
Вот собственно привожу весь код создания объектного калбека

Не советую этим пользоваться! Это бессмысленная "кучамала", первым комментарием уже сказал почему.

Автор: xvr 5.4.2013, 09:27
Цитата(GremlinProg @  5.4.2013,  08:17 Найти цитируемый пост)
В цитате я об этом и написал

Не корысти ради, а токмо точности для -
У Борланда существуют оба варианта, и делегаты из 2х указателей (которые в С++ у них называются __closure), и эти самые thunk'и, которые позволяют склеить this и метод в один указатель и приделать еще что нибудь. Второе, в частности, когда то использовалось для реализации таблиц виртуальных методов в множественном наследовании. У них thunk'и двигали this

Но все это генерил компилятор в compile time, а то, что показал Alexis делает библиотека VCL в run time

Цитата(GremlinProg @  5.4.2013,  08:17 Найти цитируемый пост)
Не советую этим пользоваться!

Пользоваться можно, но только если других методов уже не осталось  smile 

Автор: Dem_max 5.4.2013, 10:07
Я собственно использую __closure в С++ Builder, для того чтобы не делать функцию статической.
Для мелкософтовского компилятора это будет __thiscall 
Но это чисто компиляторозависимый велосипед.

Автор: GremlinProg 5.4.2013, 10:11
Цитата(xvr @  5.4.2013,  11:27 Найти цитируемый пост)
Пользоваться можно, но только если других методов уже не осталось

а я об этом как раз и писал, для передачи объекта в процедуру окна городить такой огород бессмысленно и неразумно

Автор: xvr 5.4.2013, 10:33
Цитата(Dem_max @  5.4.2013,  10:07 Найти цитируемый пост)
Я собственно использую __closure в С++ Builder, для того чтобы не делать функцию статической.
Для мелкософтовского компилятора это будет __thiscall 

Неа. __closure в Builder'е - это модификатор типа указателя, а __thiscall - это calling conversion для функции. На уровне сорцов это сразу видно - __closure указатели вызываются как обычные функции (я имею в виду синтаксис вызова), а __thiscall как члены класса, т.е. для них явно нужно указать this (или вызывать в контексте метода класса, тогда this компилятор подставит сам)

Цитата(GremlinProg @  5.4.2013,  10:11 Найти цитируемый пост)
а я об этом как раз и писал, для передачи объекта в процедуру окна городить такой огород бессмысленно и неразумно

С этим согласен. Если только GWL_USERDATA не занят уже под что то другое. В VCL Borland не мог воспользоваться GWL_USERDATA для передачи this, т.к. GWL_USERDATA находится в распоряжении пользователя библиотеки VCL, а не её самой.

Автор: GremlinProg 5.4.2013, 10:43
Цитата(xvr @  5.4.2013,  12:33 Найти цитируемый пост)
Если только GWL_USERDATA не занят уже под что то другое.

Для таких случаев существует как минимум 2 пути:
1. использовать http://msdn.microsoft.com/ru-RU/library/windows/desktop/ms632594(v=vs.85).aspx
2. расширить набор WindowLong (WNDCLASS::cbWndExtra) при регистрации класса (обычный подход)
Цитата(xvr @  5.4.2013,  12:33 Найти цитируемый пост)
В VCL Borland не мог воспользоваться GWL_USERDATA для передачи this, т.к. GWL_USERDATA находится в распоряжении пользователя библиотеки VCL, а не её самой.

Для расширения класса (например, для второго варианта) у Borland'а, насколько помню, всегда имелся метод CreateParams, который можно было переписать на свой вкус.

Автор: borisbn 5.4.2013, 13:05
Цитата(xvr @  5.4.2013,  10:33 Найти цитируемый пост)
В VCL Borland не мог воспользоваться GWL_USERDATA для передачи this, т.к. GWL_USERDATA находится в распоряжении пользователя библиотеки VCL, а не её самой

дык для этого ж они свойство Tag предоставляют

Автор: Alexeis 5.4.2013, 23:20
Цитата(GremlinProg @  5.4.2013,  09:17 Найти цитируемый пост)
Alexeis, пытается троллить, забавно. В цитате я об этом и написал  
только размер делегата равен не 8 байтам, а равен размеру двух указателей (в x64 это 16 байт)


Неа, ты писал 

Цитата(GremlinProg @  4.4.2013,  16:01 Найти цитируемый пост)
Скорее всего, это реализация событий у борланда, с процедурой окна она не имеет ничего общего, просто борланд унифицирует таким образом делегат (этот подход у него как минимум с Delphi 3-4)

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

  Что есть описание __closure . Поэтому я и уточнил, что оконная функция реализуется через генерацию машинного кода перехода. В свое время делая сабклассинг я был удивлен, что у разных экземпляров одного класса отличаются адреса функций окна. То, что указатель на функцию член сохраняется внутри через __closure фактор не существенный, основной замес именно в подмене функции окна. 

Автор: GremlinProg 6.4.2013, 08:56
Alexeis, ты можешь, конечно называть апельсин помидором, но суть от этого не поменяется,
цитируя ты повторил то же самое, о чем я писал.

О том, как делал процедуру окна борланд в Delphi я тоже http://forum.vingrad.ru/index.php?showtopic=365615&view=findpost&p=2557430 (последний абзац). Поясняю: он это делал аналогично тому, как предложил borisbn, только борланд вместо installWindowProc просто регистрировал начальную процедуру окна(InitWindowProc), которая делала инициализацию окна и подставляла на свое место штатную StdWindowProc. Я просто это все пишу по памяти, т.к. уже много лет не работаю с продуктами от борланд.

Автор: mes 6.4.2013, 16:24
Цитата(Alexeis @  5.4.2013,  22:20 Найти цитируемый пост)
, основной замес именно в подмене функции окна.  

вариантов прикрутить не много.. пару уже назвали,  это хранение this в отведенной области, и генерация машиного перехода... 
остался маппинг hwnd на this и заготовка достаточного объема статических функций со связанным аргументом.. 
У каждого из подходов есть свои недостатки..  Иногда бывает полезным смешать разные подходы.. 

Автор: Alexeis 6.4.2013, 17:15
mes, мне кажется самым экономным будет использование 
Цитата(mes @  6.4.2013,  17:24 Найти цитируемый пост)
остался маппинг hwnd на this и заготовка достаточного объема статических функций со связанным аргументом.. 

  Ммм...? А зачем иметь много статических функций? Разве недостаточно одной функции у общего базового класса? Функцию-член обработки сообщений окна делаем виртуальной и соответственно для каждого производного класса можно перекрывать обработку сообщений.

Автор: volatile 7.4.2013, 00:42
borisbn, привел хороший способ. Благо в WinApi, во многих местах присутствует что-то типа void *userdata
имхо самое место для указателя на класс.
Способ борланда, конечно некрасив, но им же не надо было думать о кроссплатформенности, и прочих табу ... наложенных на С++ прикладников.
Зато их способ, чуток должен быть эффективней. А вызов функций, это именно то место, где не помешает подумать об эффективности.
Так что, их способ, тоже имеет право на существование. и их можно понять.
С маппингом, думаю, будет совсем не эффективно.
короче, голосую за способ borisbn

Автор: mes 7.4.2013, 00:50
Цитата(Alexeis @  6.4.2013,  16:15 Найти цитируемый пост)
 Ммм...? А зачем иметь много статических функций? Разве недостаточно одной функции у общего базового класса? 

это два разных варианта )  маппинг hwnd и "маппинг" оконной функции ))

Добавлено через 4 минуты и 43 секунды
Цитата(volatile @  6.4.2013,  23:42 Найти цитируемый пост)
С маппингом, думаю, будет совсем не эффективно.
прям уж совсем ? на 1000 окон около 10 "лишних сравнений"...

Автор: volatile 7.4.2013, 01:14
Цитата(mes @  7.4.2013,  00:50 Найти цитируемый пост)
на 1000 окон около 10 "лишних сравнений"...

10 сравнений чтобы вызвать одну функцию, думаю что да, совсем не эффективно.
 smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)