Поиск

Полнотекстовый поиск:
Где искать:
везде
только в названии
только в тексте
Выводить:
описание
слова в тексте
только заголовок

Рекомендуем ознакомиться

'Документ'
ПОНЕДЕЛЬНИК Продолжаем путешествие, нас встречает загадочный и прекрасный Бухарест. Нас ждёт экскурсия по столице Румынии - Бухаресту. Расположенный н...полностью>>
'Книга'
Над «Прошу, убей меня» можно смело лить слезы из запаса «для непризнанных поэтов и наркоманов», но и посмеяться доведется вдоволь. Это честная, детал...полностью>>
'Документ'
Общеобразовательная школа должна формировать целостную систему универсальных знаний, умений и навыков, а также самостоятельной деятельности и личной ...полностью>>
'Документ'
В древности йога сложилась как система практических знаний, ведущая к духовному просветлению. Сейчас к йоге обращаются в основном для оздоровления тел...полностью>>

Visual Basic Основы работы с базами данных

Главная > Документ
Сохрани ссылку в одной из сетей:

Краткое резюме

На этом мы остановимся. К сожалению, ограничения журнального формата не позволяют затронуть в этой статье такие интересные темы как:

  • Разработка собственных источников данных;

  • Создание эффективного интерфейса пользователя;

  • Реализация OLAP средствами VB.

Возможно, они будут освещены в следующей статье.

В заключение: редакция требует от автора сказать свое слово в споре сторонников и противников VB как средства разработки СУБД-приложений. Что ж, попробуем перечислить сильные и слабые стороны VB в этом отношении, и указать область его применения. Итак, к основным плюсам VB можно отнести:

  • Наличие мощного объектного API-доступа к данным;

  • Наличие визуального конструктора объектов доступа к данным;

  • Мощные средства создания интерфейса пользователя;

  • Простоту отладки приложений;

  • Интеграцию с CASE-средствами.

Среди минусов я бы выделил два:

  • Обманчивая простота VB. VB действительно позволяет новичку сделать нечто, похожее на работающую программу. Однако необходимо понимать, что на самом деле VB - достаточно сложный инструмент, для адекватного применения которого требуется серьезный опыт. Иначе разочарования неизбежны ;) 

  • Наличие некоторого количества неприятных багов в самых, казалось бы, ровных местах. Хотя они, по большей части, документированы, однако, не все из них имеют простые обходные пути, что создает дополнительный барьер на пути написания сложных приложений:

Резюмируя: VB дает возможность чрезвычайно быстро разрабатывать и тестировать эффективные УБД-приложения, относящиеся как к front-end, так и к back-end. Единственное необходимое условие - мастерство программиста.

Вызов функций по указателю

Скачать код к статье

    Вы читали мою первую статью на эту тему? Надеюсь, что нет. Она мне страшно не нравилась уже сразу после опубликования. К тому же, я получил немало откликов на тему "мало информации", которые, конечно, считаю справедливыми. Я многое упустил из виду, исходя из мысли "так, это понятно, объяснять не нужно". Настало время всё-таки написать что-то относительно более полное. К тому же, название той статьи было "Ассемблер в VB", что не совсем (совсем не) соответствовало её содержанию. Теперь название правильное
При написании я предполагал только одно: вы знаете, что такое указатель.

Введение

    Как известно, прямой работы с указателями в VB нет. Однако есть функции для почти прямой работы с ними – частично скрытые (VarPtr, ObjPtr, StrPtr) и скрытые посильнее (см. статью про GetMem и PutMem). А вот вызова функций по указателю нет вообще, в то время как это мощнейший инструмент, очень удобный и простой. И привнести его в VB – деяние приятное и полезное. Многое из описанного в этой статье является весьма и весьма спорным с точки зрения переносимости, может, непереносимым вообще. Но возгласы "эй, а это будет работать в следующей версии VB?" не имеют силы: VB 7 уже вышел. VB 7 – это совсем не VB 6, это другой язык. Так что мы знаем, что VB 6 останется именно таким, какой он есть сейчас. И поэтому мы можем использовать даже самые непереносимые и несовместимые методики. При написании этой статьи я придерживался определённой хронологии и описывал решения в том порядке, в котором к ним приходил. Копирайт: сама идея вызова через CallWindowProc с использованием некоей ассемблерной вставки принадлежит не мне. Но реализация именно в таких вот видах и именно с такими вставками – моя. Взгляд изнутри Прежде всего, необходимо разобраться, что такое вызов функции на самом низком уровне, с точки зрения процессора. Как известно, у процессора есть набор инструкций, заложенных в него проектировщиками, которые он и исполняет. "Вызов" (функции) – это тоже просто одна из команд процессора. Их на самом деле несколько, но мы не будет углубляться в изучение ассемблера (во-первых, это выходит за рамки статьи, а во-вторых, я сам в этом не очень силён . Запомним пока, что есть такая команда. Теперь стек... Что такое стек? Это часть памяти (обыкновенной, оперативной), которая резервируется при запуске программы (нужный размер этой стековой памяти записывается компилятором прямо в exe при создании оного). Особенность работы со стеком описывается буквосочетанием LIFO (Last In, First Out; последним пришёл, первым вышел). Образно стек можно представить как трубу с одним открытым концом (а другой запаян). Можно положить что-то внутрь (при этом оно окажется на самом верху), если есть место, конечно. Можно убрать что-то (только самое верхнее, снизу нельзя). Можно посмотреть, что уже наличествует (смотреть можно и сверху, и глубже – представьте, что вся труба стеклянная). Принцип стека используется в программах (во всех программах) по двум причинам. Во-первых, память резервируется сразу при запуске программы, а значит, не будет расходов на её выделение в процессе работы (это обеспечивает высокую скорость). Во-вторых, это самый простой и удобный способ сохранения предыдущего состояния, чтобы сделать что-то другое и потом вернуться. Когда что-то помещается в верхушку стека, всё остальное остаётся неизменным, и потому после удаления верхнего элемента всё как-то само собой возвращается в исходное состояние, совершенно без лишних движений. Думаю, теперь можно переходить к описанию того, как на самом деле происходит вызов функции. Вызов функции – это помещение параметров в стек и выполнение процессорной команды call. Вот и всё. Существует несколько соглашений вызова (они определяют, как именно помещаются параметры в стек). Мы будем рассматривать только одно соглашение – StdCall. Во-первых, именно его используют все функции Windows, во-вторых, именно его использует VB. В соответствии с этим соглашением, параметры помещаются в стек в обратном порядке (справа налево), а функция сама удаляет их из стека по завершении работы. Допустим, у нас есть функция

Function Sum (ByVal p1 As Long, ByVal p2 As Long, ByVal p3 As Long) As Long

которая возвращает сумму своих аргументов. Когда мы вызываем её, выполняется такой код:

push p3

push p2

push p1

после чего происходит вызов функции Sum.
    Как вы догадались, команда процессора push означает "поместить в верхушку стека". Как нетрудно видеть, первый аргумент окажется в самой верхушке стека, ведь он помещён туда последним. Функция Sum всё это знает, и свои аргументы оттуда берёт (она пока не удаляет их! она пользуется тем, что труба стеклянная). Закончив работу, функция Sum должна вернуть управление туда, откуда её вызвали (иначе программа остановится… вернее, она рухнет, но это детали…). Но КУДА должна вернуть управление функция Sum?
Я написал "после чего происходит вызов функции Sum", но я не написал, как именно он происходит. Очень просто - выполняется единственная команда процессора Call. Эта команда передаёт управление функции Sum, но перед этим помещает в верхушку стека адрес, по которому должна вернуться эта самая Sum! В результате непосредственно перед передачей управления в функцию Sum стек приобретает вид:

Адрес возврата

Параметр p1

Параметр p2

Параметр p3

[всё, что глубже, помещено не нами и нас не интересует...]

Закончив работу, функция Sum вернёт управление по тому адресу, который лежит в самой верхушке стека. Непосредственно перед этим она удалит (на сей раз именно удалит, а не просмотрит) из стека и этот адрес, и все свои параметры.
Ну вот, теперь, кажется, мы знаем достаточно
Ну что, поехали…

Напрямую...

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

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, выпихнув из стека правильное количество параметров (четыре, четыре...). Делов-то! Всё это должно выглядеть так:

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).

Что получаем?

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, &H20C2&        '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

 

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

Далеко в обход...

Что может заставить нас идти дальше? Так это же интересно! Кому не интересно, остаётся на только что достигнутом результате, а мы продолжаем.
Что бросается в глаза - так это ParamArray. Вот если бы можно было задать его тип, чтобы не Variant... А так приходится делать CLng, да и вообще, Variant работает медленно. Жёстким волевым решением постановляем: Variant убрать, перейти на Long. Правда, придётся ввести дополнительный параметр, определяющий количество остальных параметров.
А как же удобно получать эти остальные параметры, спросит внимательный читатель, ведь теперь мы не можем обращаться к ним в цикле! Неужели многочисленные If ParamsCount < ... ? Конечно же нет! Вспомним, ведь наша "вызывалка" - это тоже функция! А раз так, она тоже подчиняется StdCall! А раз так, её параметры идут в стеке в обратном порядке! А раз так, мы можем их получить, если будем иметь "точку опоры"! А она у нас есть - это VarPtr первого параметра! Именно это великое число и показывает положение в стеке первого нашего "значимого" параметра. Последовательно прибавляя к нему 4, мы будем получать указатель на второй параметр, на третий...

В объектно-ориентированный обход...

Кто-то ещё в чём-то сомневается? Нет? Правильно. Возможно всё.
Знатоки технологии OLE наверняка уже в задумчивости. Функциональность любого объекта OLE определяется списком интерфейсов, которые реализует этот объект. А интерфейс – это что? Интерфейс – это указатель на таблицу указателей на функции. А вызывать функции по указателю мы уже худо-бедно умеем, разве нет? Следовательно, модифицировать CallFunction в CallInterface мы сможем очень легко. Для этого надо учесть лишь две вещи:
- указатель на интерфейс – это только первый шаг. Нужно найти, куда он указывает (а указывает он на таблицу функций), сместиться по этой таблице на нужное количество ячеек (одна ячейка – один указатель на метод) и посмотреть, что же в этой ячейке. Это и будет указатель на искомый метод.
- у каждого интерфейсного метода есть неявный первый параметр – указатель на сам интерфейс.
Теперь у нас снова достаточно информации Располагаем параметр pInterface непосредственно перед параметром p1, передаём ассемблерной вставочке не ParamsCount, а ParamsCount + 1, и не VarPtr(p1), а VarPtr(pInterface). Ну и указатель на функцию находим описанным выше способом. См. код!

"А зачем это нужно? Ведь VB может работать с объектами и просто так. Просто создаём объект через CreateObject, ставим точку и пишем имя метода – пусть списочка методов и нет, но VB разберётся!" VB, конечно, разберётся, но только если этот объект поддерживает интерфейс IDispatch. А если не поддерживает, то для работы с таким объектом позднее связывание (да, это оно работает, когда вы вызываете методы из ниоткуда) не работает. Для работы с таким объектом нужно иметь его описание – Type Library, библиотеку типов. Иметь её должен только разработчик, после компиляции она не нужна. Если же нет и библиотеки, то вызвать метод объекта можно только таким вот способом.
Ещё один промежуточный вариант. Теперь мы можем вызвать любой метод любого интерфейса, даже не имея его type library. Для этого нужно иметь указатель на интерфейс (это ObjPtr), а также знать номер вызываемого метода. Номера методов можно посмотреть в файлах .h или в документации. Лучше, конечно, иметь tlb, благо создать его не так уж сложно, но об этом я здесь не буду.

Слушайте! Идея пришла только что... А пусть получением параметров занимается сама ассемблерная вставочка! Во-первых, получится гораздо быстрее, во-вторых, можно будет сделать вставочку фиксированного размера (там будет цикл!). Подставляем только кол-во повторений, указатель на параметры и указатель на функцию! Ну-ка... Ну надо же... Нет, идея правда пришла только что!
И давайте, наконец-то, вынесем это дело в класс. Во-первых, у нас теперь есть необходимость в инициализации (вставочка-то теперь постоянного размера), а во-вторых, так мы сможем привязать экземпляр класса к библиотеке. Я не буду приводить полный код здесь, но в аттаче вы его найдёте и во всём разберётесь. А здесь - только обновлённый код CallFunction:

 

Public Function CallFunction(ByVal FuncName As String,_
ByRef FuncPointer As Long, ByVal ParamsCount As Long,
_ Optional ByVal p1 As Long = 0,_
Optional ByVal p2 As Long = 0, Optional _
ByVal p3 As Long = 0, _
Optional ByVal p4 As Long = 0, Optional _
ByVal p5 As Long = 0, Optional _
ByVal p6 As Long = 0, Optional _
ByVal p7 As Long = 0, Optional ByVal _
p8 As Long = 0, Optional_
ByVal p9 As Long = 0, Optional ByVal _
p10 As Long = 0) As Long
 
  'Сделаем так:
  '   - если FuncPointer = 0, то проиходит вызов FuncName, а через
  '     FuncPointer возвращается указатель на эту функцию.
  '   - если FuncPointer <> 0, то происходит вызов этого самого FuncPointer,
  '     а FuncName игнорируется.
 
  If ParamsCount < 0 Then Err.Raise 5 'invalid call
 
  If FuncPointer = 0 Then
    FuncPointer = GetProcAddress(m_hLib, FuncName)
    If FuncPointer = 0 Then Err.Raise 453 'function not found
  End If
 
  If ParamsCount = 4 Then
    CallFunction = CallWindowProc(FuncPointer, p1, p2, p3, p4)
  Else
    PutMem4 hGlobal + 1, ParamsCount
    PutMem4 hGlobal + 6, VarPtr(p1)
    PutMem4 hGlobal + 22, FuncPointer - (hGlobal + 22) - 4
    CallFunction = CallWindowProc(hGlobal, 0, 0, 0, 0)
  End If
End Function

 

Вот и ещё один промежуточный результат. Для вызова нужно явно указать число параметров, а затем и перечислить их. Параметры помещаются в стек ассемблерным циклом, поэтому их количество может быть любым. Если вам не хватит 10, добавляйте ещё (в форме "optional byval p# as long", где # - номер очередного параметра). Ни код функции, ни инициализация класса от этого не изменятся.
Большинство команд заполнения ассемблерной вставки перенесено в инициализацию класса, благодаря чему возросла скорость вызова.
Запомним и этот вариант; а какой где использовать, разберёмся в конце.

Сусанин отдыхает...

Что, можно придумать что-то ещё?! А вы как думали!Согласитесь, вызов функции через CallFunction довольно удобен, а поначалу просто шикарен... Но когда попривыкнешь, начинаешь думать о чём-то ещё более удобном... Вот если бы совместить обычный синтаксис VB с вот этим всем... Ну так в чём же дело?
Помещаем в модуль функцию. Задаём ей список параметров, соответствующий списку параметров вызываемой по указателю функции. В тело функции помещаем единственную команду – возвращение нуля. Берём адрес этой функции (AddressOf). Адрес функции – это место в памяти, где расположена первая команда функции. Так вот пишем в это место совсем другую команду, а именно команду безусловного перехода на нужный адрес – jmp. Команда jmp не меняет содержимого стека. В результате цепочка будет такой:
- мы пишем по адресу VB-функции команду jmp. Мы делаем это 1 раз, потом будем пользоваться сколько угодно
- вызываем эту VB-функцию (обычным синтаксисом VB, как обычную функцию модуля)
- VB думает, что мы правда хотим именно эту функцию, упаковывает параметры в стек (сам, без нас) и передаёт управление на свой модуль
- а там команда jmp! Сразу и без вопросов получается переход на тот адрес, который нам на самом деле нужен. Очень быстрый переход.
- управление переходит в действительно нужную нам функцию. С её точки зрения всё прекрасно: параметры упакованы в стек как нужно, а команда jmp не поменяла ни их, ни адрес возврата. Поэтому при завершении работы управление будет возвращено не в модульную функцию VB, а в то место, откуда была вызвана модульная функция
- VB вообще об этом не подозревает. Он думает, что модульная функция успешно завершилась.

Позвольте, а почему я решил, что в AddressOf функции хватит места для записи команды перехода и адреса перехода? А потому что я скомпилировал проект с пустой функцией с одним параметром, возвращающей ноль, и нашёл её в exe. Код следующий:

xor eax, eax

ret 4

Кто-то ещё сомневается в крутизне компилятора VB? Но не отвлекаемся: указанные две команды занимают ровно 5 байт. Именно столько нам и нужно. Фантастика. Даже лишних растрат памяти на функции-пустышки не будет.
Вот! Наконец-то! Вызов любых функций по указателю синтаксисом VB!
Единственная проблема – работает это только в exe по причинам, которые выходят за рамки этой статьи. Раз так, провести отладку не сможем. Потеря отладки – серьёзный недостаток, и мириться с ним я бы не стал. Но позвольте! Первые варианты прекрасно работают и в exe, и в IDE. Но они существенно медленнее и не позволяют осуществлять вызов синтаксисом VB... Но это не имеет значения, потому что мы объединим их через условную компиляцию. При работе из IDE будет использоваться медленный, но совместимый с IDE способ, а при компиляции в exe будет записан быстрый.
А как же права записи? PutMem4 тут не поможет, нет права на запись в эту часть кода. А вот WriteProcessMemory пофигист куда больший.
А как же синтаксис вызова? Он же разный? Шерстить всю программу? Ну уж нет... Тут пришлось немного постараться, но в результате вызов "совместимым" способом стал с точки зрения внешнего пользователя неотличим от вызова "несовместимым". Для настройки функции на нужный указатель используется SetFunction, а вызов настроенной функции – как всегда вызывали функцию модуля.
Логика тут такая.
Если #ReleaseBuild = False, то:
- модуль содержит коллекцию, хранящую служебную информацию, а также функцию CallFunction.
- вызов SetFunction приводит к занесению в коллекцию адреса реального вызова, ключом для которого является строковое представление адреса функции-пустышки. Переписывание тела пустышки не производится.
- Будучи вызванной, функция-пустышка извлекает из коллекции тот адрес, с которым она ассоциирована, и вызывает CallFunction по этому адресу.
- Код пустышки несколько усложняется, писать его вручную уже не с руки (об этом ниже). Не огорчайтесь, в exe попадёт единственная команда xor eax, eax...
- Да, всё это очень медленно! Но это только для отладки.

Если #ReleaseBuild = True, то:
- Модуль не содержит ничего, кроме пустышек. Пустышки не содержат ничего, кроме команды возвращения нуля.
- Вызов SetFunction приводит к прямой записи по указанному адресу, без всяких проверок (предполагается, что всё уже отлажено. Вы же не будете отлаживать в режиме ReleaseBuild = True? А раз вы всё уже отладили, зачем проверки?).
- Максимальная скорость! Серьёзно.
Вот такая вот логика
Спокойно пишем свой рулезный софт, используем вызов по указателю, всё тестируем, и т.д. и т.п. Убедившись в работоспособности, ставим ReleaseBuild = True и компилируем. Всё, опять-таки, работает, но на гораздо большей скорости.
Самые ленивые, однако, уже отметили, что придётся писать усложнённый код пустышек. Придётся! Не хотите – пожалуйста, с потерей отладки... Ну ладно, ладно В конце концов, можно переложить эту работу на IDE. Для этого нужно всего лишь написать add-in (стандартный шаблон оной есть в окне создания нового проекта). Используя события среды, надстройка будет отслеживать активное окно с кодом. Если это окно является модулем (а пустышки могут быть только там), начинается сабклассинг оного (дело в том, что у кодовых окон нет нужных нам событий). Перехватывается нажатие Enter. Если Enter нажат на строке, содержащей кодовую последовательность

#func MyFunction(paramlist)

то происходит замена этой последовательности на тело соответствующей пустышки! Paramlist – список параметров функции, по тем же правилам, что и всегда. Код пустышки будет сгенерирован с подстановкой VarPtr, ObjPtr и StrPtr в тех местах, где это необходимо. Обратите внимание – используется полное имя функции в форме ИмяМодуля.ИмяФункции. Это связано с тем, что иначе нельзя получить адрес функции из самой этой функции.

Применимость

Применять при вызове функций по указателю! Теперь чуть более подробно. Все параметры должны быть размером 4 байта. Целые числа можно передавать по значению. Целые и нецелые числа можно передавать по указателю (т.е. в качестве параметра нужно указать VarPtr).
Строки нужно передавать либо по значению (StrPtr), либо по ссылке (VarPtr). На самом деле, в обоих случаях будет передан указатель, в первом случае – на строковые данные, во втором – на переменную VB, содержащую указатель на эти строковые данные.
Объекты нужно передавать по значению (ObjPtr) или по ссылке (VarPtr). Правила те же, что и со строкой.
Неявное конвертирование из Unicode в Ansi не происходит! Поэтому если вы вызываете функцию, ожидающую Ansi, вы должны передать ей что-то вроде StrPtr(StrConv("string", vbFromUnicode)). Если же функция ожидает Unicode, передавайте StrPtr("string").
Используйте прямой вызов CallWindowProc, если у вызываемой функции ровно 4 параметра, а больше ничего вы и не вызываете. Ну бывают такие совпадения.
Используйте CallFunction с ParamArray в том случае, когда вы не хотите включать в проект модуль с пустышками, когда для вас не критична скорость, и когда вы не хотите каждый раз указывать число параметров явно.
Используйте CallFunction с явным указанием числа параметров в том случае, когда вы не хотите включать в проект модуль с пустышками, когда скорость гораздо более важна (тогда почему вы пустышки-то не хотите?) и когда вам нужна большая жёсткость при передаче параметров. В предыдущем варианте вы могли передать любое выражение в качестве параметра, а здесь – только Long. Теперь вы не забудете о том, что строки передаются через StrPtr, переменные по ссылке – через VarPtr. Иначе просто не будет компилироваться. В принципе, этот метод предпочтительнее, во избежание ошибок.
Используйте функции-пустышки, когда вы хотите вызывать что угодно по указателю синтаксисом VB или когда для вас критична скорость вызова. Не обращайте внимания на медленный вызов из IDE, он всё равно не попадёт в exe. Надстройка, следящая за вводом кодовой последовательности шаблона, будет генерировать код пустышек, подставляя VarPtr, StrPtr и ObjPtr там, где это нужно. Главное – перед компиляцией не забудьте установить ReleaseBuild = True.



Скачать документ

Похожие документы:

  1. Работа с базами данных

    Документ
    База данных — это организованная структура, предназначенная для хранения информации. Внимательный читатель, знающий из первой главы этого пособия о том, что данные и информация — понятия взаимосвязанные, но не тождественные, должен
  2. Задачи: Изучить среду Visual Basic; Подобрать задачи исследовательского характера для визуализации экспериментального процесса; Разработать программы и исследовать информационные процессы

    Исследовательская работа
    Visual Basic; (VB) - это среда разработки программ, которая позво­ляет быстро и легко создавать приложения (прикладные программы) для Windows. В нее включено все, что необходимо для создания, модифика­ции, тестирования, корректирования
  3. «Обработка баз данных при помощи Microsoft Access»

    Документ
    Когда необходимо хранить и использовать большие объемы данных, необходим инструмент для простой и быстрой обработки информации. В семействе Microsoft Office таким инструментом является MS Access.
  4. Базы данных (2)

    Документ
    В основе решения многих задач лежит обработка информации. Для облегчения обработки информации создаются информационные системы (ИС). Автоматизированными называют ИС, в которых применяют технические средства, в частности ЭВМ.
  5. Visual basic for applications (vba)

    Документ
    Visual Basic является мощным программным средством, с помощью которого можно реализовать широкий спектр прак­тических задач. Основное достоинство этого языка програм­мирования состоит в том, что он оптимально сочетает в себе простоту

Другие похожие документы..