CallWindowProc в WinXP

Программирование на Visual Basic, главный форум. Обсуждение тем программирования на VB 1—6.
Даже если вы плохо разбираетесь в VB и программировании вообще — тут вам помогут. В разумных пределах, конечно.
Правила форума
Темы, в которых будет сначала написано «что нужно сделать», а затем просьба «помогите», будут закрыты.
Читайте требования к создаваемым темам.
KiloGraf
Продвинутый пользователь
Продвинутый пользователь
 
Сообщения: 142
Зарегистрирован: 21.10.2004 (Чт) 7:37
Откуда: берутся такие глупые вопросы?

CallWindowProc в WinXP

Сообщение KiloGraf » 07.04.2005 (Чт) 18:17

Кто скажет, в чём я затупил? Почему в WinXP не работает сей код. Т.е. он работает, но по завершению CallWindowProc – RET куда-то возвращает, где Memory не может быть Read! И прогу, естественно вышибает...

Хотя в Win98 всё работает!

Вот, к примеру:
Код: Выделить всё
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Any, ByVal wParam As Any, ByVal lParam As Any) As Long

Dim ptrLib As Long
Dim ptrProc As Long
Dim lngRet As Long

Private Sub Form_Load()
  Me.AutoRedraw = True
End Sub

Private Sub Command1_Click()
  ptrLib = LoadLibrary("kernel32")
  Me.Print "Lib:  &H" & Hex(ptrLib)
  ptrProc = GetProcAddress(ptrLib, "Sleep")
  Me.Print "Proc: &H" & Hex(ptrProc)
  lngRet = CallWindowProc(ptrProc, 1000&, 0&, 0&, 0&)
  Me.Print "Ret:  &H" & Hex(lngRet)
  lngRet = FreeLibrary(ptrLib)
End Sub

Private Sub Command2_Click()
  ptrLib = LoadLibrary("user32")
  Me.Print "Lib:  &H" & Hex(ptrLib)
  ptrProc = GetProcAddress(ptrLib, "SetWindowTextA")
  Me.Print "Proc: &H" & Hex(ptrProc)
  lngRet = CallWindowProc(ptrProc, Me.hWnd, "Caption", 0&, 0&)
  Me.Print "Ret:  &H" & Hex(lngRet)
  lngRet = FreeLibrary(ptrLib)
End Sub
Nonsense in BASIC. © 1982 ...

xolod
Гуру
Гуру
 
Сообщения: 1162
Зарегистрирован: 15.01.2004 (Чт) 0:42
Откуда: Moscow

Сообщение xolod » 07.04.2005 (Чт) 19:00

KiloGraf
Вообще в XP не работает или только код в процедуре Command2_Click?

Constant ERROR_SUCCESS deprecated. I'm so happy.
Программирование и дизайн – http://www.macrointellect.ru

KiloGraf
Продвинутый пользователь
Продвинутый пользователь
 
Сообщения: 142
Зарегистрирован: 21.10.2004 (Чт) 7:37
Откуда: берутся такие глупые вопросы?

Сообщение KiloGraf » 07.04.2005 (Чт) 19:13

Я вот и привел разные процедуры, по причине сравнения...

...В первой RET куда-то возвращает, и приложение повисает на Sleep.
А во второй - Memory не может быть Read и вылетание как компиленого EXEшника, так и проекта вместе с VB. И это касается многих API... Но только под XP.
Nonsense in BASIC. © 1982 ...

Ennor
Конструктивный критик
Конструктивный критик
 
Сообщения: 2504
Зарегистрирован: 18.12.2001 (Вт) 3:58
Откуда: Калуга -> Москва

Сообщение Ennor » 07.04.2005 (Чт) 23:00

В опциях проекта поставь галку Include symbolic debug info, скомпиляй и запусти. Когда свалится, войди в дебаггер и посмотри, где именно ты валишься. Отсюда и начинай копать...

KiloGraf
Продвинутый пользователь
Продвинутый пользователь
 
Сообщения: 142
Зарегистрирован: 21.10.2004 (Чт) 7:37
Откуда: берутся такие глупые вопросы?

Сообщение KiloGraf » 08.04.2005 (Пт) 0:56

При вызове Sleep, все повисает в каком-то цикле по RETN в ntdll.KiFastSystemCallRet с выводом инфы:
LastErr ERROR_INVALID_WINDOW_HANDLE (00000578)

Во втором случае:
Код: Выделить всё
           File 'C:\Project1.exe'
           New process with ID 00000804 created
004011D0   Main thread with ID 000009F4 created
00400000   Module C:\Project1.exe
             Debugging information (DIA format) available
66000000   Module C:\WINDOWS\system32\MSVBVM60.DLL
77110000   Module C:\WINDOWS\system32\OLEAUT32.dll
774D0000   Module C:\WINDOWS\system32\ole32.dll
77C00000   Module C:\WINDOWS\system32\msvcrt.dll
77D30000   Module C:\WINDOWS\system32\USER32.dll
77DC0000   Module C:\WINDOWS\system32\ADVAPI32.dll
77E70000   Module C:\WINDOWS\system32\RPCRT4.dll
77F10000   Module C:\WINDOWS\system32\GDI32.dll
7C800000   Module C:\WINDOWS\system32\kernel32.dll
7C900000   Module C:\WINDOWS\system32\ntdll.dll
004011D0   Program entry point
746E0000   Module C:\WINDOWS\system32\MSCTF.dll
75E60000   Module C:\WINDOWS\system32\SXS.DLL
002801DE   Access violation when writing to [00000001]


Но мне это ни о чем не говорит, правда я смотрел Олей (OllyDbg), а там дебри...

...Причем, на разных XP SP1 и SP2 – одинаково! А в 98 рулит во всю и с разными библами, и при разном кол-ве входных параметров!
Может в XP так и должно быть, или я глючю?...
Nonsense in BASIC. © 1982 ...

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 08.04.2005 (Пт) 4:57

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?



Писано это некоторое время назад :) И, исходя из новооткрывшихся обстоятельств, предлагаю слова "только функции, которые имеют 4 параметра или меньше" заменить на "только функции, которые имеют 4 параметра или 0" :)
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

KiloGraf
Продвинутый пользователь
Продвинутый пользователь
 
Сообщения: 142
Зарегистрирован: 21.10.2004 (Чт) 7:37
Откуда: берутся такие глупые вопросы?

Сообщение KiloGraf » 08.04.2005 (Пт) 11:30

Да уж... Не очень то утешительный вывод – четыре или ноль параметров.

Но я нашел выход для себя...
...Дело в том, что есть такой бейсик, как "PureBasic", и в нем реализован ряд следующих функций, использующих тот же интерфейс:

Код: Выделить всё
OpenLibrary
CloseLibrary

IsFunction
IsFunctionEntry

CallCFunction
CallCFunctionFast
CallFunction
CallFunctionFast

CountLibraryFunctions
ExamineLibraryFunctions

IsLibrary
LibraryFunctionAddress
LibraryFunctionName
LibraryID
NextLibraryFunction


...И с возвратом никаких проблем не возникает.
Но для написания GUI приложений, PureBasic достаточно убогий (ИМХО). Что нельзя сказать про то, как он компилит Native DLL.

Из этого следует, что на нём можно создать интерфейсную библиотеку, как своеобразного посредника при вызове API функций с разным количеством входных параметров (0-4).

И DLLки он компилит очень маленькие и что самое главное – целиком самостоятельные. Т.е. независящих от дополнительных библиотек, не входящих в ядро Windows.
Nonsense in BASIC. © 1982 ...

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 08.04.2005 (Пт) 12:18

Дык переходники на асме давно написаны :)
Статьи читай :) Про вызов функций по указателю в VB :)
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

KiloGraf
Продвинутый пользователь
Продвинутый пользователь
 
Сообщения: 142
Зарегистрирован: 21.10.2004 (Чт) 7:37
Откуда: берутся такие глупые вопросы?

Сообщение KiloGraf » 08.04.2005 (Пт) 13:05

Так я вот только сейчас сам попробовал переходник в виде DLLки сделать для эксперимента...
...Работает!!!

Причем в PureBasic'е функции CallCFunction и CallCFunctionFast работают по принципу CallWindowProc, но могут иметь от нуля до 20(!) входных параметра типа Any

И DLLка переходника, которую я скомпилял для эксперимента, вышла в 6,5 kb!
Nonsense in BASIC. © 1982 ...


Вернуться в Visual Basic 1–6

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 181

    TopList