Многие из вас знакомы с ATL thunks, например. для создания окна. Класс, который делает эту работу CStdCallThunk, нацелен на WindowProc вызов. По сути, он превращает глобальный обратный вызов в функцию-член объекта C ++.
Этот тип thunk не будет работать для SetWindowsHookEx обратный вызов который нуждается в первом параметре без изменений. Для 32-битных окон я нашел аккуратное решение в CAuxThunk, часть ATL / AUX библиотека. К сожалению, это не работает для собственного 64-битного исполняемого файла
Интересно, есть ли какой-нибудь гуру по сборке x64, который может исправить этот CAuxThunk для работы на 64-битных окнах или придумать какой-нибудь эквивалентный thunk, который бы превратил этот обратный вызов __stdcall в функцию-член?
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
Спасибо,
Никос
Я описал общий способ генерации любой код, который вы хотите в этот ответ. Давайте переделаем это для вашего случая, просто для удовольствия.
Предположим, что ваш класс определен как:
struct YourClass {
LRESULT YourMemberFunc(int nCode, WPARAM wParam, LPARAM lParam);
};
По сути, вы пишете свой блок в C ++ с заполнителями для фактических адресов:
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
YourClass *x = reinterpret_cast<YourClass*>(0x1122112211221122);
__int64 im = 0x3344334433443344;
LRESULT (YourClass::*m)(int,WPARAM,LPARAM) = *reinterpret_cast<LRESULT (YourClass::**)(int,WPARAM,LPARAM)>(&im);
return (x->*m)(nCode, wParam, lParam);
}
И вызовите это так, чтобы компилятор не включил вызов:
int main() {
LRESULT (CALLBACK *volatile fp)(int, WPARAM, LPARAM) = CallWndProc;
fp(0, 0, 0);
}
Компиляция в выпуске и просмотр сгенерированной сборки (в Visual Studio во время отладки посмотрите окно сборки и включите «Показать байты кода»):
4D 8B C8 mov r9,r8
4C 8B C2 mov r8,rdx
8B D1 mov edx,ecx
48 B9 22 11 22 11 22 11 22 11 mov rcx,1122112211221122h
48 B8 44 33 44 33 44 33 44 33 mov rax,3344334433443344h
48 FF E0 jmp rax
Это будет ваш толчок, с 44 33 44 33 44 33 44 33
заменено указателем на вашего участника (&YourClass::YourMemberFunc
) а также 22 11 22 11 22 11 22 11
заменяется указателем на фактический экземпляр объекта во время выполнения.
Объяснение того, что происходит в Thunk:
В соглашении о вызовах x64 (из которых только один в Windows) первые четыре параметра передаются в rcx, rdx, r8, r9
регистры, в этом порядке, слева направо. Так что, когда наш вызов называется, мы имеем
rcx = nCode, rdx = wParam, r8 = lParam
Для функций-членов существует неявное удержание первого параметра this
указатель, поэтому при входе YourMemberFunc
мы должны иметь
rcx = this, rdx = nCode, r8 = wParam, r9 = lParam
Сгенерированный компилятором код выполняет именно эту настройку: он сдвигается r8 -> r9, rdx -> r8, ecx -> edx
, а затем назначает наш заполнитель this = 1122112211221122
в rcx
, Теперь у него есть настроенные параметры, и он может продолжить косвенный переход к самой функции. rax
используется для хранения возвращаемого значения, поэтому его не нужно сохранять при вызовах функций. Вот почему он используется здесь для временного удержания адреса получателя, что дает возможность оптимизации хвостового вызова (пара вызов / возврат заменена на одиночный переход).
Почему мы должны сделать косвенный звонок? Потому что в противном случае мы получим относительный скачок. Но мы не можем использовать жестко запрограммированный относительный переход, потому что раздел будет скопирован по разным адресам в памяти! Поэтому мы прибегаем к установке абсолютного адреса во время выполнения и вместо этого делаем косвенный переход.
НТН
Поскольку в 64-битном режиме низкоуровневые соглашения одинаковы для всех типов (stdcall, cdecl и thiscall), вам не нужен ассемблерный код для достижения этой цели. Просто создайте статическую функцию, которая вызывает соответствующую функцию-член. Вам нужно будет выяснить правильное this
указатель для использования, например, путем связывания hwnd
в lparam
с объектом. Если у вас есть только один обратный вызов, это, конечно, не будет проблемой. Что-то вроде:
LRESULT CALLBACK static CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
Window w = GetWindowByHwnd(((CWPSTRUCT*)lParam)->hwnd);
return w->CallWndProc(nCode, wParam, lParam);
}
Обновлен Смотрите ниже
Взгляните на SetWindowLongPtr http://msdn.microsoft.com/en-us/library/windows/desktop/ms644898(v=vs.85).aspx
Это позволяет связать указатель с окном. Учитывая, что WindowProc выглядит так
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
Вы можете использовать параметр hwnd с GetWindowLongPtr, чтобы получить указатель this в вашем обратном вызове.
обновленный
Взгляни на Как выполнить функцию в x86 и x64? (Как std :: bind в C ++, но динамический)
Кажется, это то, что вы ищете