Russian English
   Basic.net
Среда, 23.09.2020, 03:33
Меню сайта
Категории раздела
Basic [44]
Помощь [0]
Облако тегов
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

Форма входа
Главная » Статьи » Basic

Вызов функций по указателю в Visual Basic (часть 2)
Share |

Напрямую...

А зачем нам это всё? Ну писали бы на ассемблере, а из VB-то мы не можем вызвать конкретные команды процессора... Да, не могли бы, если бы не одна единственная API-функция CallWindowProc (собственно, ей нужно ставить памятник, она действительно ОДНА...). Эта функция предназначена для вызова обработчика оконных сообщений, но суть её работы сводится к простой упаковке параметров в стек и передаче управления! Иными словами, она не проверяет, что именно её заставляют вызвать (да и не смогла бы проверить при всём желании). А раз она обычная API-функция, то мы можем её Declare. Ну и всё, можете вызывать что хотите, спасибо за внимание...
Нет, на самом деле проблемы только начинаются.

Проблема первая – количество параметров. Нетрудно видеть, что у CallWindowProc параметров ровно пять, из них один – адрес функции (или указатель на функцию; что-то давно не упоминалось мною это славное слово). Значит, вызываемая функция должна иметь ровно 4 параметра. Ведь у нас соглашение StdCall, помните? А оно требует, чтобы функция удаляла свои параметры из стека сама. Если функция была откомпилирована для работы с 3 параметрами, она удалит из стека ровно 3 параметра, и вы даже не можете себе представить, насколько ей безразлично, сколько их было туда помещено на самом деле. Результат – нарушение структуры стека (вызывающая сторона в недоумении, она-то уверена, что удалены 4 параметра, а тут...) и немедленный crash. Так что использовать функцию CallWindowProc напрямую можно лишь в одном случае: если вы уверены, что вызываемый код завершается командой процессора ret 0x0010 (это команда "возврат" с удалением из стека &H10 байт. &H10 – это 16, а 16 – это 4*4, то есть 4 параметра по 4 байта каждый. Они все 4 байта, параметры-то). В этом можно быть уверенным у в двух случаях: вы знаете, что у функции 4 параметра или же вы сами написали некий код, завершающийся командой ret 0x0010. Чувствуете, куда клоню? 

Даже если чувствуете, всё равно запомним промежуточный результат (промежуточных результатов будет несколько, и каждый имеет полное право на самостоятельное и независимое существование и использование):

Можно напрямую использовать функцию CallWindowProc для вызова другой функции. Для этого в качестве первого параметра нужно передать указатель на эту функцию, а в качестве остальных – параметры этой, вызываемой, функции. Жёсткое ограничение: у вызываемой функции должно быть ровно 4 параметра.
Приведём маленький код в подтверждение сказанного.
Код Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Option Explicit
 
Private Declare Function CallWindowProc Lib "user32.dll" _
Alias "CallWindowProcA" (ByVal lpPrevWndFunc _
As Long, ByVal hwnd As Long, ByVal msg As Long, ByVal wParam _
 As Long, ByVal lParam As Long) As Long
Private Declare Function FreeLibrary Lib "kernel32.dll" (ByVal _
hLibModule As Long) As Long
Private Declare Function GetProcAddress Lib "kernel32.dll"_
 (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function LoadLibrary Lib "kernel32.dll" _
Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
 
Private Sub Form_Load()
  Dim user As Long
  
  user = LoadLibrary("user32.dll")
  CallWindowProc GetProcAddress(user, "MessageBoxA"), _
Me.hwnd, StrPtr(StrConv("Ну что, работает!", vbFromUnicode)), _
StrPtr(StrConv("Заголовок", vbFromUnicode)), 0
  FreeLibrary user
End Sub
Пока не обращайте внимания на StrPtr и StrConv, потом всё скажу

И в обход…

Вспомним те два случая, в которых можно быть уверенными Не случай номер 1 мы повлиять не можем (на самом деле, при известном шаманстве возможно всё, но я не буду соблазнять вас на модификацию самой команды Ret в теле откомпилированной вызываемой функции; да и что если параметров больше 4, а не меньше?), так что вплотную займёмся вариантом номер два.
Да, мы будем писать код. Но мы будем писать машинный код. Не бойтесь, это несложно Машинные коды, соответствующие инструкциям Push, Call и Ret легко посмотреть в соответствующих мануалах от Intel или ещё от кого. И их всего три! Поехали.

Задача: мы должны создать в памяти (а больше негде) участок готового машинного кода, который бы завершался командой ret 0x0010, и при этом был бы способен вызывать функцию с любым количеством параметров. Тогда мы сможем передать управление на этот участочек с помощью CallWindowProc, участочек вызовет функцию, управление вернётся на участочек, а он вернёт его в CallWindowProc, выпихнув из стека правильное количество параметров (четыре, четыре...). Делов-то! Всё это должно выглядеть так:

Код ASM
1
2
3
4
5
6
7
push параметрN
push параметр(N-1)
...
push параметр2
push параметр1
call function
ret 0x0010
Как видно, нам не удастся сделать участочек неизменяемым: он будет зависеть от количества параметров. Где-то я прочитал, что начало функции должно быть на границе двойного слова... Честно говоря, я не знаю, так ли это, и не знаю, зачем это нужно Но я следую этому правилу, и потому память для участочка выделяю через GlobalAlloc – эта функция выделяет память сразу по искомой границе. Обращение к этой памяти реализовано через GetMem и PutMem (вы ведь знаете про них, правда?). Итак: выделяем участок памяти, заносим в нужные места этого участка команды push (это просто байт &H68), заносим после каждого байта push один параметр (четыре байта), добавляем команду Call (это байт &HE8) и команду Ret 0x0010 (это три байта: C2 10 00).
Что получаем?
Код Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Private Const GMEM_FIXED As Long = &H0
Private Const MAX_PARAMS As Long = 10
 
Public Function CallFunction(ByVal FuncPointer As Long, ParamArray p()) As Long
  Dim i As Long
  Dim hGlobal As Long, hGlobalOffset As Long
  
  'Учтём совпадение числа параметров:
  If UBound(p) - LBound(p) + 1 = 4 Then
    CallFunction = CallWindowProc(FuncPointer, CLng(p(0)), CLng(p(1)), CLng(p(2)), CLng(p(3)))
  Else
    hGlobal = GlobalAlloc(GMEM_FIXED, 5 * MAX_PARAMS + 5 + 3 + 1)
    'Заполняем всё подряд, ZEROINIT не нуно.
    If hGlobal = 0 Then Err.Raise 7 'insuff. memory
    hGlobalOffset = hGlobal
    
    For i = LBound(p) To UBound(p) 
'если параметров нет, то ubound<lbound, и цикл не выполнится вообще
      PutMem2 hGlobalOffset, &H68 'asmPUSH_imm32
      hGlobalOffset = hGlobalOffset + 1
      PutMem4 hGlobalOffset, CLng(p(i))
      hGlobalOffset = hGlobalOffset + 4
    Next
    
    'Добавляем вызов функции
    PutMem2 hGlobalOffset, &HE8 ' asmCALL_rel32
    hGlobalOffset = hGlobalOffset + 1
    PutMem4 hGlobalOffset, FuncPointer - hGlobalOffset - 4
    hGlobalOffset = hGlobalOffset + 4
    
    PutMem4 hGlobalOffset, &H10C2&        'ret 0x0010
    
    CallFunction = CallWindowProc(hGlobal, 0, 0, 0, 0)
    
    GlobalFree hGlobal
  End If
End Function
 
'Вызывающий код. Я его больше не буду повторять, ладно?
Private Sub Form_Load()
  Dim user As Long
  
  user = LoadLibrary("user32.dll")
  MsgBox CallFunction(GetProcAddress_
(user, "GetWindowTextLengthA"), Me.hwnd)
  FreeLibrary user
End Sub
Вот и ещё один промежуточный результат. В принципе, это готовое решение для вызова чего угодно с любым числом параметров. Мы, конечно, пойдём дальше, но в такой форме оно тоже может использоваться, а порой и удобнее всего.
Категория: Basic | Добавил: Admin (14.06.2012)
Просмотров: 2619 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск
Наш опрос
Какую версию Basic вы предпочитаете?
Всего ответов: 2028

© Basic.ucoz.net, 2020