GSerg как-то писал(а):CallWindowProcЕсть замечательная API-функция — CallWindowProc. Она делает именно то, что нам нужно — вызывает функцию по указателю. Её нужно использовать для вызова процедуры обработки оконных сообщений, но можно и для вызова любой другой процедуры, указатель на которую известен. Казалось бы, вопрос закрыт — но нет. Вызываемая функция должна иметь ровно 4 параметра. Ведь, как вы помните, по соглашению stdcall функция сама удаляет из стека свои параметры. Если функция удалит не 4 параметра, а, к примеру, 3, то структура стека будет нарушена и приложение рухнет. Однако вызвать функцию с 4 параметрами по 4 байта каждый мы можем уже сейчас. Идём дальше.
CallWindowProcExНет, такой функции в Windows нет (хотя кто его знает… недокументированных функций там до кучи…). Просто кое-что имею сказать по этому поводу.
Читатель, знакомый с KPD Team API-Guide (
http://www.allapi.net, хотя, скорее всего, уже
http://www.mentalis.org), наверняка видел там пример по регистрации любых компонентов через вызов DllRegisterServer, причём вызывается она именно по указателю, и именно через CallWindowProc. Вот, кстати, одна из отличных областей применения вызова по указателю: когда вы на стадии проектирования не знаете имя библиотеки, в которой будет функция. Ведь оператор Declare может содержать предложение Lib только в виде константы.
Но вернёмся к DllRegisterServer. Это стандартная функция, которая регистрирует тот компонент, из которого вызывается (она есть в любом OCX, в любой COM dll etc.). Единственная проблема: у неё нет параметров. Тем не менее, она прекрасно вызывается через CallWindowProc, при этом недостающие параметры просто дополняются нулями. Соглашение stdcall по-прежнему в силе. Значит, свои параметры из стека удаляет сама функция. Значит, DllRegisterServer ничего не должна удалять. А значит, структура стека должна быть нарушена. Тем не менее, всё работает.
Да, работает. А потом может перестать. Сейчас объясню.
Во-первых, имеет место контроль стека со стороны VB. Если там что не удалено, его это не смутит.
Во-вторых, имеет огромное значение то, каким способом скомпилирована сама CallWindowProc. Её задача ведь какая: взять 4 последних параметра и передать их в функцию, которая идёт первым параметром. А это ведь можно по-разному сделать.
Рассмотрим результат работы CallWindowProc под Win98. Объявляем её так:
- Код: Выделить всё
Declare Function CallWindowProc Lib “user32” Alias “CallWindowProcA” (ByVal prevproc As Long) As Long
Да, именно с одним параметром, хотя их и пять.
Выполняем:
- Код: Выделить всё
kernel = LoadLibrary(“kernel32”)
MsgBox CallWindowProc (GetProcAddress(kernel, “GetTickCount”))
FreeLibrary kernel
Работает.
Объявим CallWindowProc с двумя параметрами, вызовем через неё функцию, принимающую 2 параметра – работает. Объявим с тремя… работает… Работает и будет работать. С любым числом параметров. На win98. Проверьте. Из этого следует единственный вывод: CallWindowProc на win98 делает примерно следующее. Перед вызовом CWP стек имеет вид:
- Код: Выделить всё
Адрес возврата (того места, куда должна вернуться CWP)
Параметр 1 CWP (адрес вызываемой через CWP функции)
Параметр 2 CWP (якобы hWnd)
[Ну и остальные параметры, если мы их указали…]
Функция CallWindowProc модифицирует стек, приводя его к виду:
- Код: Выделить всё
Адрес возврата (того места, куда должна вернуться CWP)
Параметр 2 CWP (якобы hWnd)
[Ну и остальные параметры, если мы их указали…]
После этой модификации она выполняет не вызов желаемой функции (call), а переход на неё (jmp; запомним эту мнемонику. Она равносильна нашей команде GoTo). Раз вызов формально не выполнен, стек не меняется. И вызываемая функция получает этот самый стек именно в таком виде - и верхушка стека становится её адресом возврата! CallWindowProc просто передаёт адрес возврата дальше по цепочке, при этом не трогая параметры в стеке (не удаляя их никоим образом). Параметры удалит вызываемая функция, после чего вернётся по переданному адресу – к нам в VB-код.
А теперь сделайте то же самое где-нибудь на WinXP. Вы получите Bad Dll calling convention. Немедленный вывод: реализация CallWindowProc изменилась, и теперь она уже и помещает параметры в стек, и удаляет их оттуда. А раз так, вызывать под WinXP через CallWindowProc можно только функции, которые имеют 4 параметра или меньше, причём объявлять CWP нужно обязательно со всеми 5 параметрами (при вызове недостающие параметры заменять нулями). «Можно» в данном случае означает «теоретически можно». Оно вам надо, пользоваться методикой, которая непредсказуемо изменится в следующей версии Windows?