Форум программистов, компьютерный форум CyberForum.ru
Наши страницы

Visual Basic

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 121, средняя оценка - 4.64
Craw
235 / 46 / 4
Регистрация: 10.06.2012
Сообщений: 268
Записей в блоге: 1
#1

Работа с DLL в Visual Basic (статья) - VB

03.08.2013, 22:51. Просмотров 18685. Ответов 18
Метки нет (Все метки)

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

Основы

Итак, что же такое DLL? Этой английской аббревиатурой словосочетания Dynamic-Link Library, что переводится как "динамически подключая библиотека", называют в операционных системах семейства Microsoft Windows динамические библиотеки, содержащие процедуры и функции, которые могут многократно использоваться различными программными приложениями.

Ранние версии Windows работали в условиях жёсткого ограничения памяти, а потому использование DLL должно было позволить более активно использовать её, ведь один экземпляр DLL, загруженный в память, может использоваться сразу несколькими программами. Однако существенных преимуществ полностью получить не удалось. Причиной является такое явление, как DLL Hell ("DLL ад") - несколько программ работают с одинаковой библиотекой, однако с разными не полностью совместимыми версиями. Это приводило к большому количеству ошибок. Сейчас в Windows используется технология SxS, разрешающая параллельное использование разные версий библиотеки, что иногда сводит на нет её главное преимущество.

Тем не менее, DLL (к этому понятию также относят библиотеки ActiveX и драйверы) активно используются при разработке ПО.

Но в каких же случаях лучше использовать модули, а в каких DLL? Во-первых, модули, подключаемые к проекту, должны быть написаны на том языке, с которым работает среда разработки. DLL могут быть написаны на другом языке, хранятся они в уже откомпилированном виде. Во-вторых, использование DLL позволяет экономить память в том случае, если ею пользуются две или более программ одновременно (точнее, при использовании одной и той же версии). В-третьих, если функции не будут использоваться другими программами, то нет смысла создавать DLL. Тоже самое можно сказать про те случаи, когда нужно просто разбить для удобства большой код по модулям - удобнее работать с ними в одном проекте.

Соглашения вызова

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

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

Существует немало разных соглашений, но из них обратим внимание на stdcall и cdecl. Первый используют библиотеки WinAPI функций. Второй же пригодиться нам для использования функций, написанных на языке Си (cdecl - C Declaration). В обоих соглашениях аргументы передаются через стек, справа налево. Но в stdcall очистку стека производит вызываемая подпрограмма, а в cdecl - вызывающая программа.

Если что-то не поняли - не спешите ломать голову этими соглашениями, ведь для полного понимания работы с ними вам нужно знание основ Ассемблера. Не знаете? Вернетесь ещё к ним потом.
Внимание мы уделим stdcall и cdecl. Большинство библиотек у вас будут работать и с этим.

Использование

Каким же образом использовать DLL? Как воспользоваться функциями из них? Для этого их нужно подключить к программе. Различают два способа подключения: статический и динамический.

Статический. При использовании DLL загружается в память сразу при запуске программы, а выгружаются лишь при завершении работы программы. Преимущества: возможность вызова любой функции из библиотеки в любое время. Недостатки: нерациональное использование памяти в том случае, если какие-либо функции и процедуры из неё используются лишь 1 раз за всё время работы. Ведь она будет всё время в памяти, хотя логичнее её от туда выгрузить за ненадобностью.

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

Статическое подключение

При данном способе подключения нужные функции объявляют в разделе деклараций:

Visual Basic
1
2
Private Declare Function название_функции Lib "имя_библиотеки" Alias "название_функции_в_библиотеке"
(ByVal параметр_1 As тип_параметра, ..., ByVal параметр_n As тип_параметра) As тип_функции
Здесь:
Declare - ключевое слово, обозначающее, что дальше следует объявление функции из библиотеки
название_функции - название функции из библиотеки, которое будет использоваться у вас в программе. Может отличаться от названия функции в самой библиотеке (см. Alias)
Lib "имя_библиотеки" - название подключаемой библиотеки. Она должна находиться в папке System32, либо вместе с исполняемым файлом
Alias "название_функции_в_библиотеке" - необязательный параметр. Используется в том случае, если имя объявляемой функции в программе отличается от имени функции в библиотеке.

Пример подключения:

Visual Basic
1
Private Declare Function Symma Lib "test.dll" Alias "Summ" (ByVal a As Integer, ByVal b As Integer) As Integer
В библиотеке функция сложения двух чисел называется Summ, а у нас Symma.
Используются подключенные таким образом функции точно так же, как и обычные. Например:

Visual Basic
1
2
c = Symma(a,b)
d = Symma(1,3)

Ну, это всё описываются библиотеки с stdcall. А как же cdecl? Ну, в VB есть ключевое слово cDecl, причём использоваться оно должно так:

Visual Basic
1
Private Declare Function название_функции cDecl Lib "название_библиотеки"...
Однако вероятно вы получите Bad DLL Calling Convention (Error 49). Точнее, ваша программа будет работать лишь в Native (машинном) коде, т.е. уже полностью откомпилированная. А в режиме отладки (P-код), как и в самой IDE - нет. Дальнейшая разработка программы будет очень осложнена.
Мда, вот они какие - недокументированные возможности! Может есть шанс подключать cdecl библиотеки с динамическим подключением?

Динамическое подключение

Как уже было сказано выше, при данном способе подключения библиотек кода будет намного больше.
Ну во-первых сразу скажу, что в Visual Basic нет возможности стандартными средствами подключать библиотеки динамическим способом.

Для этого нам нужно 3 WinAPI функции: LoadLibrary, GetProcAddress, FreeLibrary. Объявим их в разделе деклараций, а также 3 переменные, куда будем сохранять результаты:

Visual Basic
1
2
3
4
5
6
7
8
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 FreeLibrary Lib "kernel32" _
(ByVal hLibModule As Long) As Long
 
Dim handle As Long, address As Long, unload As Long
Как видите, используем мы эти функции из стандартной библиотеки kernel32.dll! Так что это статическое подключение, а значит как минимум эта библиотека у нас будет всегда в памяти.

Разберем подробнее эти функции, новые для нас. Как они работают, что делают и для чего нужны?

LoadLibrary

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

Единственный её параметр lpLibFileName (строковой тип) содержит путь либо имя подключаемой библиотеки.

Если функция завершается успешно, то она возвращает дескриптор модуля (библиотеки). Если происходит ошибка - 0.

GetProcAddress

Эта функция извлекает адрес нужной процедуры или функции из DLL.

Имеет два параметра: hModule ("хэндл", обычно целочисленный тип) и lpProcName (строковой тип). Первый параметр - дескриптор модуля. Получить его нам позволяет вышеописанная функция LoadLibrary. Второй параметр - имя нужной функции.

Если функция завершается успешно, то она возвращает адрес экспортируемой функции из модуля (библиотеки). Если происходит ошибка - 0.

FreeLibrary

Эта функция уменьшает итоговое число ссылок на DLL (если она используется одновременно несколькими приложениями), а если число ссылок становится равно 0, то выгружает библиотеку из памяти.

Имеет один параметр: hModule, т.е. дескриптор библиотеки (который, напомню, возвращает функция LoadLibrary).

Если функция завершается успешно, возвращаемой значение не равно нулю. Если происходит ошибка, то оно равно нулю.

Но основе этих функций составим программу, которая будет подключать динамически DLL (stdcall):

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
Option Explicit
' API-функции 
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 FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
 
Dim handle As Long, address As Long, unload As Long ' переменные с результатами функций
 
Private Sub Form_Load()
 
handle = LoadLibrary("test.dll") ' загружаем библиотеку
 
If handle <> 0 Then ' успешная загрузка
 
   MsgBox ("Библиотека успешно загружена.")
   address = GetProcAddress(handle, "Summ") ' получаем адрес функции
 
   If address <> 0 Then ' успешное получение адреса
 
      MsgBox ("Адрес функции успешно получен.") ' здесь можно использовать функцию
 
   ElseIf address = 0 Then ' ошибка при получении адреса
 
      MsgBox ("Ошибка при извлечении адреса функции из DLL!")
      Exit Sub 
 
   End If  
 
ElseIf handle = 0 Then ' ошибка при загрузке 
 
   MsgBox ("Ошибка при загрузке DLL!")
 
End If 
 
unload = FreeLibrary(handle) ' высвобождаем библиотеку из памяти 
 
If unload <> 0 Then ' успешно выгрузили 
 
   MsgBox ("Библиотека выгружена успешно.") 
 
ElseIf unload = 0 Then 'ошибка при выгрузке 
 
   MsgBox ("Ошибка при выгрузке библиотеки из памяти!") 
 
End If 
 
End Sub
Как вы сами видите, длина кода намного больше, чем при статической загрузке, поскольку мы включили сюда ещё и обработку ошибок при помощи конструкции if. Налицо - гибкость!

А теперь рассмотрим возможность динамического (впрочем, как я писал выше, работать со статическим очень сложно) подключения cdecl. Для этого, кроме упомянутых выше, нам понадобятся ещё функции CallWindowProc и CopyMemory. Сразу замечу, что необходимо знание Ассемблера для полного понимания работы такой программы.

GetProcWindow

Эта функция передает информацию сообщения процедуре заданного окна.

Имеет 5 параметров: lpFunc, который указывает на процедуру, и Param1-Param4, через которые передаются параметры функции. Все параметры имеют тип Long.

Возвращаемое значение определяет результат обработки и зависит от параметров.

RtlMoveMemory

Эта функция копирует содержимое одного блока памяти в другой.

Имеет 3 параметра: Dest - указатель в тот блок памяти, куда нужно произвести копирование, Src - указатель на тот блок памяти, который копируется, Length - количество байтов, которое должно передаваться.

Возвращаемое значение отсутствует.

В качестве примера использования мы будем вызывать функцию qsort из библиотеки ntdll.dll, написанной на Си (cdecl). Данная функция используется для быстрой сортировки массива.

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Option Explicit
 
Private Declare Sub qsort Lib "ntdll" (base As Any, ByVal num As Long, ByVal width As Long, ByVal compare As Long)
Private Declare Function VarCmp Lib "oleaut32" (ByVal pvarLeft As Variant, ByVal pvarRight As Variant, ByVal lcid As Long, ByVal dwFlags As Long) As Long
Public Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Public Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Public Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Dest As Any, Src As Any, ByVal Length As Long)
Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpFunc As Long, ByVal Param1 As Long, ByVal Param2 As Long, ByVal Param3 As Long, ByVal Param4 As Long) As Long
 
Dim hModule1 As Long, EntryPoint As Long
Dim hModule2 As Long, VarCmp As Long
Dim Thunk(0 To 32) As Byte
Dim Comparer(0 To 24) As Byte
 
Public Sub Initialize()
    hModule1 = LoadLibrary("ntdll")
    EntryPoint = GetProcAddress(hModule1, "qsort")
    hModule2 = LoadLibrary("oleaut32")
    VarCmp = GetProcAddress(hModule2, "VarCmp")
' cDecl - ложим в стек параметры справа налево
CopyMemory Thunk(0), &H102474FF, 4  'FF742410   push dword ptr [esp+10h] - помещаем адрес функции VarCmp
    CopyMemory Thunk(4), &H102474FF, 4  'FF742410   push dword ptr [esp+10h] - помещаем размер (в байтах) одной переменной
    CopyMemory Thunk(8), &H102474FF, 4  'FF742410   push dword ptr [esp+10h] - помещаем кол-во элементов массива
    CopyMemory Thunk(12), &H102474FF, 4 'FF742410   push dword ptr [esp+10h] - помещаем указатель на буфер
    CopyMemory Thunk(16), &H6E8, 2      'E806000000 call $+6 - помещаем в стек, адрес следующей инструкции,
                                        ' после вызова qSort, выполнение начнется с нее. А также вызываем
                                        ' инструкцию расположенную на шесть байт после текущей
    CopyMemory Thunk(21), &H10C483, 3   '83C410     add esp, 10h - вызов cDecl чистим стек здесь, потому что
                                        ' VB не будет этого за нами делать
    CopyMemory Thunk(24), &H10C2, 2     'C21000     ret 10h - Тоже для CallWindowProc
    Thunk(27) = &H68                    '68xxxxxxxx push xxxxxxxx - здесь пишем адрес функции qSort в стек
    CopyMemory Thunk(28), EntryPoint, 4
    Thunk(32) = &HC3                    'C3         ret - удаляем из стека адрес функции qSort, и вызываем ее
' Для косвенного вызова через qSort функции VarCmp (сравнение двух Variant переменных)
    ' qSort сама чистит стек после выполнения этой вставки.
    ' stdCall - ложим в стек параметры справа налево
Comparer(0) = &H6A                  '6A00       push 0 - dwFlags
    Comparer(2) = &H6A                  '6A00       push 0 - lcid
    CopyMemory Comparer(4), &H102474FF, 4 'FF742410 push dword ptr [esp+10h] - pvarRight
    CopyMemory Comparer(8), &H102474FF, 4 'FF742410 push dword ptr [esp+10h] - pvarLeft
    CopyMemory Comparer(12), &H2E8, 2   'E802000000 call $+2 - Начнем выполнение с Comparer(19) (push xxxxxxxx -
                                        ' где xxxxxxxx- адрес функции VarCmp)
                                        ' Возвращаемое значение в регистре eax
    Comparer(17) = &H48                 '48         dec eax  - Вычитаем единицу для соответствия константам qSort'a
    Comparer(18) = &HC3                 'C3         ret - stdcall функция VarCmp сама почистила стек, поэтому просто выходим
Comparer(19) = &H68                 '68xxxxxxxx push xxxxxxxx - здесь пишем адрес функции VarCmp в стек
    CopyMemory Comparer(20), VarCmp, 4
    Comparer(24) = &HC3                 'C3         ret - переходим к выполнению VarCmp
End Sub
 
Public Sub Terminate()
    FreeLibrary hModule1
    FreeLibrary hModule2
End Sub
 
Public Sub SystemSort(SortArray())
    Initialize
    CallWindowProc VarPtr(Thunk(0)), VarPtr(SortArray(LBound(SortArray))), UBound(SortArray) - LBound(SortArray) + 1, 16, VarPtr(Comparer(0))
    Terminate
End Sub
 
Sub Main()
Dim i As Long, MyArr(-800 To 4000), StartTime As Single
Randomize Timer
  For i = LBound(MyArr) To UBound(MyArr)
    MyArr(i) = Rnd * 800
  Next
StartTime = Timer
  SystemSort MyArr
  Debug.Print Round(Timer - StartTime, 4)
End Sub

Подробное описание работы - в комментариях к коду.

Альтернативный код - с CallBack функцией. В отличие от предыдущего варианта, он гибче, т.к. позволяет менять критерий сравнения, а во-вторых здесь используется CopyMemory. Вместо функции RtlMoveMemory здесь используем API-функции GetMem4, PutMem4, GetMem2.

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
Option Explicit
 
Private Declare Function GetMem4 Lib "msvbvm60" (pSrc As Any, pDst As Any) As Long
Private Declare Function GetMem2 Lib "msvbvm60" (pSrc As Any, pDst As Any) As Long
Private Declare Function PutMem4 Lib "msvbvm60" (pDst As Any, NewValue As Any) As Long
 
Public Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Public Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Public Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpFunc As Long, ByVal Param1 As Long, ByVal Param2 As Long, ByVal Param3 As Long, ByVal Param4 As Long) As Long
 
Dim hModule1 As Long, EntryPoint As Long
 
Dim Thunk(0 To 43) As Byte
 
Public Sub Initialize()
 
    hModule1 = LoadLibrary("ntdll")
    EntryPoint = GetProcAddress(hModule1, "qsort")
 
    ' cDecl - ложим в стек параметры справа налево
    
    Thunk(0) = &HB8                     'B800000000 - помещаем в eax адрес функции qSort
    GetMem4 EntryPoint, Thunk(1)
    Thunk(5) = &H5B                     'pop ebx
    GetMem2 &H1D89, Thunk(6)            'mov xxxxxxxx,ebx
    PutMem4 Thunk(8), Thunk(24)
    GetMem2 &HD0FF, Thunk(12)           'FFD0 - Call eax вызываем qSort
    GetMem2 &H1D8B, Thunk(14)           'mov ebx,xxxxxxxx
    PutMem4 Thunk(16), Thunk(24)
    Thunk(20) = &H53                    'push ebx
    GetMem2 &H10C2, Thunk(21)           'C21000     ret 10h - Тоже для CallWindowProc
 
    ' т.к. qSort сама чистит стек, Compare вызывается через вставку
    
    GetMem4 &H82474FF, Thunk(28)          'push   DWORD PTR [esp+0x8]  ' Дубликат параметра
    GetMem4 &H82474FF, Thunk(32)          'push   DWORD PTR [esp+0x8]  ' Дубликат параметра
    Thunk(36) = &HB8                      'mov eax,xxxxxxxx
    GetMem4 AddressOf Compare, Thunk(37)  'xxxxxxxx - Compare
    GetMem2 &HD0FF, Thunk(41)             'call eax - вызываем Compare
    Thunk(43) = &HC3                      'ret - Возвращаемся, стек почистит qSort
End Sub
 
Public Function Compare(ByVal lpOp1 As Long, ByVal lpOp2 As Long) As Long      ' CallBack - функция сравнения
    Dim Op1 As Long, Op2 As Long
    
    GetMem4 ByVal lpOp1, Op1
    GetMem4 ByVal lpOp2, Op2
    
    ' По возрастанию
    If Op1 < Op2 Then Compare = -1: Exit Function
    If Op1 = Op2 Then Compare = 0: Exit Function
    Compare = 1
    
    ' По убыванию
'    If Op1 < Op2 Then Compare = 1: Exit Function
'    If Op1 = Op2 Then Compare = 0: Exit Function
'    Compare = -1
    
End Function
 
Public Sub Terminate()
    FreeLibrary hModule1
End Sub
 
Public Sub SystemSort(SortArray() As Long)
    Initialize
    CallWindowProc VarPtr(Thunk(0)), _
                   VarPtr(SortArray(LBound(SortArray))), _
                   UBound(SortArray) - LBound(SortArray) + 1, _
                   LenB(SortArray(LBound(SortArray))), VarPtr(Thunk(28))
    Terminate
End Sub
 
Sub Main()
    Dim i As Long, MyArr(0 To 4000) As Long
    
    For i = 0 To UBound(MyArr)
        MyArr(i) = Rnd * 800
    Next
    
    SystemSort MyArr
 
End Sub

Создание

По каким-то причинам в Microsoft решили, что возможность создавать обычные DLL в Visual Basic будет лишней, а потому создавать их так просто мы не сможем, во всяком случае, без бубна. Да, я говорю про обычные DLL, потому что можно создавать лишь ActiveX DLL. В чём же разница обычных DLL от ActiveX?

ActiveX

ActiveX была внедрена в Windows компанией Microsoft в 1996 году как продолжение развития её технологий OLE (Object Linking and Embedding) и COM (Component Object Model).

OLE - технология связи и внедрения объектов в другие документы и объекты. Она позволяет передавать данные от одной программы для редактирования другой. Так, например, изображение или рисунок в программе Microsoft Word можно редактировать в Paint, и при сохранении он автоматически измениться и в документе Word.

COM - технология создания программного обеспечения на основе взаимосвязанных компонентов. Воплощение идей объектно-ориентированного программирования (ООП): инкапсуляции и полиморфизма.

И вот мы имеем фреймворк ActiveX, совмещающий эти две технологии. Он активно используется при разработке форм приложений. Все кнопки, списки, диалоговые окна, "этикетки" (Label), поля ввода (Textbox), их которых мы собираем форму - всё это ActiveX. Вообще многие продукты Microsoft используют управляющий элементы ActiveX, что позволяет использовать их функционал в других приложениях. Подробнее об этом смотрите в моей статье об использовании компонента Windows Media Player для создания музыкального проигрывателя.

Итак, приступим к созданию ActiveX DLL! Запускаем Visual Basic, в окне New Project выбираем ActiveX DLL (либо в меню File-New Project). И вот уже перед нами и редактор кода! Процесс написания кода здесь ничем не отличается от написания обычной программы. Пишете код, сохраняете проект, компилируете библиотеку.
Но не забывайте тот факт, что ActiveX DLL, в отличие от обычной DLL, не библиотека процедур и функций, а библиотека классов! Так что здесь забудьте про процедурное программирование - только ООП! Пишем классы: поля, свойства, методы, инкапсуляция, наследование (кстати, в Visual Basic нет обычного наследования, так что это делать нужно через наследование интерфейсов), полиморфизм. В принципе это и есть главное отличие. Хотя написать процедуру как метод не составит проблем.

Коротко об использовании ActiveX DLL. Подключаются они точно так же, как и обычные, но с той разницей, что необходимо создать объект и вызывать его методы - это называют связыванием. Различают ранее и позднее связывание.

Ранее связывание осуществляется командой

Visual Basic
1
Set myObj = New имя_dll.myClass
где myObj - имя объекта, а myClass - название класса в DLL. При таком связывании вся информация о методах, свойствах и событиях класса известна во время компиляции, потому вызов методов происходит также быстро, как и обычных функций в
программе.
При позднем связывании нужно сначала создать объект:

Visual Basic
1
Set myObj=CreateObject("имя_dll.myClass")
после чего можно вызывать его метод:

Visual Basic
1
x=myObj.Summ(параметры)
Проверка методов/свойств происходит во время выполнения программы, так что мы проигрываем раннему связыванию во времени, но зато этот способ более гибкий.

Если вас заинтересовала технология ActiveX и её возможности, рекомендую вам ознакомится со статьей Б.Л. Файфеля "COM в действии": "Создаем ActiveX DLL": и "Вопросы" (часто задаваемые вопросы про ActiveX).

Обычные DLL


Вообще-то создавать обычные DLL в Visual Basic, как уже было сказано выше, нельзя без "танцев с бубном". Но энтузиастами был создан Add-In для среды разработки Visual Basic, который позволяет создавать почти полноценные DLL на Visual Basic. Единственный, и самый существенный минус - невозможность использования созданных таким образом библиотек в других языках программирования. Загрузить Add-In можно по этой ссылке К архиву прилагается файл ReadMe с подробным описанием установки дополнения и его использования, а также пара примеров создания DLL.

Каким же образом мы компилируем библиотеки, если такой возможности в VB нет? Весь секрет в недокументированных возможностях link.exe ("линкеры", как известно, выполняют компиляцию в всё с этим связанное). Если запустить его в DOS командой link.exe > c:/link.txt, то мы увидим множество различных ключиков. Их них наибольший интерес представляют /DLL и /DRIVER. Понятно, что компилятор Visual Basic 6 способен ещё и компилировать библиотеки с драйверами! Ну вот именно на первом ключе и основан принцип работы этой надстройки: перехватываем командную строку линкера, изменяем, вызываем настоящий линкер.
Только одно но... это скорее не продуманная реализация линкера, а баг. Он нарушает компоновку библиотеки, из-за чего она будет работать не стабильно. А именно - лишь в Visual Basic!

Если вас заинтересовало это, то подробнее ознакомиться с работой надстройки вы можете здесь.


Автор: Павел Смирнов (Craw)
Источник: basic.ucoz.net
Отдельная благодарность за помощь в написании статьи:
Catstail, SoftIce, locm, Pro_grammer, TheTrick
12
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
03.08.2013, 22:51
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Работа с DLL в Visual Basic (статья) (VB):

Создание процедурных DLL в Visual Basic (статья) - Visual Basic
Создание процедурных DLL в Visual Basic Несколько слов о резонах создания процедурных Dll на VB. Как известно, VB позволяет без...

Способы передачи данных с Visual Basic в Excel (статья) - Visual Basic
Еще по теме: 1. Тема: http://www.cyberforum.ru/visual-basic/thread681924.html 2. Тема:...

Visual Basic + VC++ DLL = ошибка Bad DLL calling convertation - Visual Basic
Попытался я написать DLL-ку на VC++, для последующего использования ее в VB. Но поскольку я в VC++ чайник - он мне выдает ошибку Bad DLL...

создание dll на основе visual basic - Visual Basic
Помогите решить рабочую задачу: создание dll на основе visual basic. Требуется трехмерная модель с указанием переменных размеров. Размеры...

Можно ли в Visual Basic использовать стандартные Windows DLL? - Visual Basic
Добрый день! Подскажите пожалуйста, можно ли в Visual Basic использовать стандартные Windows DLL? Если можно, подскажите где взять...

Visual Basic 6 DLL для работы с php файлами - Visual Basic
Есть ли такой? Если есть, дайте пож-та!

18
anny05
Телекомпания ВИD
1359 / 110 / 19
Регистрация: 14.10.2012
Сообщений: 100
19.08.2013, 17:10 #2
Комментарии к статье:

расположения параметров подпрограммы: они могут быть в стеке или в регистрах
Не только. Есть еще 3 других варианта. Также существует понятие способов передачи.

В обоих соглашениях аргументы передаются через стек, справа налево.
При вызове функции в отдельных случаях требуется выравнивание стека по 16-байтовой границе. Некоторые компиляторы возвращают простые структуры данных с длиной 2 регистра или менее в паре регистров EAX - EDX, а более крупные структуры и классы объектов, требующих обработки исключений, возвращаются в памяти, вследствие чего выделяется память и передается указатель как скрытый параметр.

Статический. При использовании DLL загружается в память сразу при запуске программы
При данном способе подключения нужные функции объявляют в разделе деклараций:
Абсолютная глупость и ввод читателей в заблуждение. Вкратце: декларация как раз включает в себя использование апи-функций «динамического подключения» (в том числе указанных ниже) для загрузки в память и последующего нахождения адреса вызываемой функции, с той разницей, что это делает вбшный рантайм (а это, в свою очередь, открывает раздолье и для других нестандартных способов). Для неверующих – простейший поясняющий пример:
Visual Basic
1
2
3
Declare Function SetCursorPos1 Lib "user32" (ByVal x&, ByVal y&) As Long
Declare Function GetTickCount Lib "kernel321" () As Long
Sub Main(): MsgBox "qwe": SetCursorPos1 0, 0: MsgBox GetTickCount: End Sub
Под «статическим» (как он называется в 1 цитате) способом подразумевается использование библиотек типов.

Lib "имя_библиотеки" - название подключаемой библиотеки. Она должна находиться в папке System32, либо вместе с исполняемым файлом
Библиотеку можно объявить где угодно с указанием пути (в т.ч. относительном); расширение обычно не указывают для 3-х основных библиотек. Порядок поиска описан в MSDN.

Alias "название_функции_в_библиотеке" - необязательный параметр. Используется в том случае, если имя объявляемой функции в программе отличается от имени функции в библиотеке.
Есть еще одна роль – объявление по номеру, причем это дает выигрыш в быстродействии:
Visual Basic
1
2
Declare Function CU& Lib "user32" Alias "#53" (ByVal lpsz As String) 'WinXP
Sub Main(): t$ = "asd": CU t: MsgBox t: End Sub
Ну, в VB есть ключевое слово cDecl, причём использоваться оно должно так:
С точностью до наоборот (Ну,). Это ключевое слово ничего не даст, но если объявить cdecl-функцию как stdcall, и затем скомпилировать проект, она будет работать.

GetProcWindow
?

Альтернативный код - с CallBack функцией. В отличие от предыдущего варианта, он гибче
qSort сама чистит стек
По поводу кода.
1. Даже в официальной MSDN-документации сказано, что в фортрановском соглашении стек очищает вызываемая (с помощью RET), а при сишном это должна делать вызывающая (ADD ESP). А теперь подумайте, с какого перепугу cdecl «сама» чистит стек?
2. Сам посредник для вызова можно сократить до 9 байт:
Assembler
1
2
3
CALL Address
(ADD ESP, Arg) ;если это CDECL
RET
Данный подход универсален, т.к. позволяет еще вызывать stdcall (будет 6 байт) и даже fastcall, а также будет работать во всех режимах – P-code, Native, IDE.
3. Колбёк лучше вынести в отдельную функцию, сделав число аргументов динамическим. Это и есть «гибкость».
Да что обсуждать… сами смотрите аргументо-упрощенный пример:

Пример 1. CallEx_ASM.rar

4. Примечание. При большом числе передаваемых аргументов асмовый код, естественно, «раздуется», как и время на его «формирование» в циклах. Поэтому можно еще немного улучшить, возложив вбшные действия на сам асмовый код, задействовав для этого регистры. В результате он будет всегда иметь фиксированный размер 24 байта при любом числе параметров (см. Пример 2).

Пример 2. CallEx_ASM_v2.rar

5. Все это будет работать только на незащищенных машинах (имеется ввиду запуск байтового массива). В противном случае необходимо выделять кусок памяти для размещения посредника там.

Различают ранее и позднее связывание.

Ранее связывание осуществляется командой

Visual Basic
1
Set myObj = New имя_dll.myClass
При позднем связывании нужно сначала создать объект:

Visual Basic
1
Set myObj=CreateObject("имя_dll.myClass")
Это ложное предубеждение (относительно того, что New / CreateObject, либо способ подключения – означают раннее / позднее связывание соответственно).
New / CreateObject – это не типы связывания, а способы создания класса (причем самих способов создания больше, также как и подтипов связывания), различие между которыми незначительное, но в итоге они оба по CLSID-у и IID определяют лишь указатель на нужный интерфейс.
Так, CreateObject, но с переменной-интерфейсом в библиотеке типов – означает раннее, а New, но с объектной переменной и использованием библиотеки типов – позднее связывание.

Существуют различные технологии методов, но для COM характерна виртуализация, то есть получение указателей в специальной «виртуальной» таблице, выполняемое в момент обращения. Такой выбор метода в VB6 является ранним связыванием, поскольку *Ptr метода уже должен быть известен во время компиляции заранее полученного класса (и относится это исключительно к классам), но в других языках он будет являться поздним связыванием, т.к. они поддерживают более простое связывание методов с заранее предопределенными *Ptr-ами.

Далее, для COM характерен IDispatch интерфейс для работы с заранее неизвестными объектами, связывание по которому при помощи disp-идентификатора является поздним. В ранних версиях VB мог использоваться лишь disp-интерфейс, а в 6 версии классы являются дуальными, поддерживая оба типа связывания. В случае отсутствия поддержки таблицы указателей методов, но хранения disp-идентификаторов для исключения необходимости их запроса во время выполнения такой подтип IDispatch-связывания будет ранним, однако при каких-либо изменениях требуется повторная компиляция.

Наиболее простая, но медленная работа заключается в «позднем» связывании по имени с помощью каждый раз запрашивающегося идентификатора, а потом и обеспечения доступа к нужному методу. Следует заметить, что сами внутренние процедуры сопоставления по имени + доступ реализуются с помощью раннего связывания, но так как при обращении от объекта к методу это происходит 2 раза, связывание называется поздним. Частный случай: сопоставление «IDispatch – метод», также позднее.

Другой, достаточно нетривиальный подвид позднего связывания – отличается однократным получением идентификатора имени, а при повторных обращениях используется его сохраненное значение. Еще один подвид – позднее связывание по виртуальным таблицам, в котором через IDispatch происходит однократное определение типа объекта для получения класса, но без непосредственного доступа к методу, после чего все вызовы происходят по виртуальной таблице.

Раннее связывание по виртуальным таблицам использует исключительно виртуальные таблицы. В этом случае переменная может быть неизвестного типа, но не может быть объектной. Здесь нужные методы берутся из подключаемой библиотеки типов.

создавать обычные DLL в Visual Basic, как уже было сказано выше, нельзя без "танцев с бубном".
Можно.

Только одно но... это скорее не продуманная реализация линкера, а баг. Он нарушает компоновку библиотеки, из-за чего она будет работать не стабильно.
Ересь. Причины падений не из-за этого.

минус - невозможность использования созданных таким образом библиотек в других языках программирования
Минус – невозможность хождения пешком колёс у автомобиля внутри водителя.

1. У проблемы «хочу DLL, не хочу регистрацию ActiveX и другие языки» существует множество обходных путей.
2. Работать из VB будут лишь простейшие функции а-ля «сумма-разность-инкремент-декремент» и им подобные, к тому же не имеющие практической ценности. Но тестировать следует и на другом.
3. Библиотеки, как правило, пишутся на других языках (PB, C/C++, Delphi и т.д.) и вызываются из VB, а не наоборот. Но обратный вариант «с автомобилем», если захотеть, реализовать на самом деле можно (см. Пример 3 – расчет хеша md5 и вызов формы из DLL на VB). После получения формы с помощью различных языков – функции можно вызывать прямо из нее. Пример можно найти на указанном выше сайте.

Понятно, что компилятор Visual Basic 6 способен ещё и компилировать библиотеки с драйверами!
И делает это весьма успешно, а сам драйвер нормально загружается в Kernel (ядерное) пространство:


Проверено на XP/2003. Но экспериментировать все же лучше на VM (драйвер находится на том же сайте).

.
Работа с DLL в Visual Basic (статья)
7
Вложения
Тип файла: rar Пример 1. CallEx_ASM.rar (11.7 Кб, 97 просмотров)
Тип файла: rar Пример 2. CallEx_ASM_v2.rar (10.5 Кб, 63 просмотров)
Pro_grammer
Модератор
6019 / 2106 / 401
Регистрация: 24.04.2011
Сообщений: 3,599
Записей в блоге: 10
19.08.2013, 17:39 #3
Цитата Сообщение от anny05 Посмотреть сообщение
сам драйвер нормально загружается в Kernel (ядерное) пространство:
А можете прояснить, где в драйвере, скомпилированном на vb6 ссылка на MSVBVM60.DLL и вообще, есть исходник?
Какой прием без "танцев с бубном" или с ними заставил VB работать без MSVBVM60.DLL - иначе загрузить в (ядерное) пространство драйвер невозможно!
1
Catstail
Модератор
22712 / 11081 / 1795
Регистрация: 12.02.2012
Сообщений: 18,273
19.08.2013, 21:54 #4
Pro_grammer, присоединяюсь. Ссылка со слова "Можно" (создавать процедурные dll на vb) ведет на сайт, с которого скачать ничего нельзя. А было бы интересно!
0
locm
2026 / 783 / 75
Регистрация: 28.10.2011
Сообщений: 2,337
Записей в блоге: 2
19.08.2013, 22:18 #5
Хм, в "драйвере" (в кавычках, потому что это ИМХО не рабочий драйвер) должны быть вызовы функций ядра, а не MSVBVM60.DLL.
На бейсике можно написать и скомпилировать рабочий драйвер без танцев с бубном, только речь идет не о VB.
0
Dragokas
Эксперт WindowsАвтор FAQ
15997 / 6814 / 821
Регистрация: 25.12.2011
Сообщений: 10,554
Записей в блоге: 16
20.08.2013, 00:32 #6
Цитата Сообщение от anny05 Посмотреть сообщение
Но обратный вариант «с автомобилем», если захотеть, реализовать на самом деле можно (см. Пример 3 – расчет хеша md5 и вызов формы из DLL на VB). После получения формы с помощью различных языков – функции можно вызывать прямо из нее. Пример можно найти на указанном выше сайте.
Можете, пожалуйста, расписать по пунктам, как... Было бы весьма ценно и практично.
0
locm
2026 / 783 / 75
Регистрация: 28.10.2011
Сообщений: 2,337
Записей в блоге: 2
20.08.2013, 00:52 #7
Драйвер запускается, но это не драйвер, а хз что.
Его исполняемый код.
Assembler
1
2
3
EntryPoint:
        xor eax,eax
        retn    0008h
Этот драйвер бесполезен и состоит всего из двух асм. инструкций! И наверняка данный участок написан на асме, а не на VB (возможно даже что асм. вставка добавлена после компиляции). У него даже нет нормальной функции DriverEntry(), не говоря об остальном.
1
Catstail
Модератор
22712 / 11081 / 1795
Регистрация: 12.02.2012
Сообщений: 18,273
20.08.2013, 09:34 #8
locm, а где Вы взяли исходник? Меня не драйвер интересует, а процедурная dll на vb.
0
locm
2026 / 783 / 75
Регистрация: 28.10.2011
Сообщений: 2,337
Записей в блоге: 2
20.08.2013, 10:02 #9
Цитата Сообщение от Catstail Посмотреть сообщение
locm, а где Вы взяли исходник?
Дизассемблировал драйвер.
0
Catstail
Модератор
22712 / 11081 / 1795
Регистрация: 12.02.2012
Сообщений: 18,273
20.08.2013, 10:05 #10
Цитата Сообщение от locm Посмотреть сообщение
Дизассемблировал драйвер.
- а как Вам удалось что-либо скачать с сайта?
0
locm
2026 / 783 / 75
Регистрация: 28.10.2011
Сообщений: 2,337
Записей в блоге: 2
20.08.2013, 10:08 #11
Над статьей слева, есть ссылка "Скачать удаленно". По ней нужно кликнуть и скачаете файл.
0
The trick
Модератор
7200 / 2432 / 741
Регистрация: 22.02.2013
Сообщений: 3,501
Записей в блоге: 74
20.08.2013, 14:05 #12
Цитата Сообщение от anny05 Посмотреть сообщение
Даже в официальной MSDN-документации сказано, что в фортрановском соглашении стек очищает вызываемая (с помощью RET), а при сишном это должна делать вызывающая (ADD ESP). А теперь подумайте, с какого перепугу cdecl «сама» чистит стек?
qSort вызывает Compare и сама чистит стек после ее вызова, поэтому во вставке мы просто передаем управление к ней с помощью Ret, а все передаваемые параметры уже в qSort будут очищены (ADD ESP).
0
Craw
235 / 46 / 4
Регистрация: 10.06.2012
Сообщений: 268
Записей в блоге: 1
22.08.2013, 21:26  [ТС] #13
За критику спасибо!

Цитата Сообщение от anny05 Посмотреть сообщение
Можно.

Не по теме:

На сайте: "Читать миллион раз 6 снизу строку комментариев! Примера больше не будет.", и что-то про то, что ваш IP в логах (хотя кому надо, будет прокси использовать).


Не будет ещё одной ссылки, чтобы все это хорошо протестировать и проверить? Ведь в статье написано, что без бубна здесь не обойтись, а вы говорите, что ещё как можно и обойтись, так что нужно изучить и написать.


По поводу драйвера - не уж то вы и в правду полноценный сделать смогли? И без "танцев", опять-таки?
2
Catstail
Модератор
22712 / 11081 / 1795
Регистрация: 12.02.2012
Сообщений: 18,273
22.08.2013, 21:43 #14
Цитата Сообщение от Craw Посмотреть сообщение
На сайте: "Читать миллион раз 6 снизу строку комментариев! Примера больше не будет.", и что-то про то, что ваш IP в логах (хотя кому надо, будет прокси использовать).
- на этом сайте делать нечего!
Тем временем, я нашел хорошую статью на английском на тему "Как сделать полноценную процедурную DLL на VB". Эту статью я уже почти перевел и выполнил все, что описал автор. Процедурные DLL действительно получаются! Когда закончу перевод - выложу сюда (с примером и дополнением от себя). Постараюсь побыстрее.
1
locm
2026 / 783 / 75
Регистрация: 28.10.2011
Сообщений: 2,337
Записей в блоге: 2
22.08.2013, 21:58 #15
Цитата Сообщение от Craw Посмотреть сообщение
По поводу драйвера - не уж то вы и в правду полноценный сделать смогли?
Я писал по этому поводу.
Драйвер не полноценный и кроме того, содержит импорт WinAPI и VB-рантайма, что не допустимо в режиме ядра.
Наверняка его еще допиливали после компиляции.
0
22.08.2013, 21:58
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
22.08.2013, 21:58
Привет! Вот еще темы с ответами:

Visual Basic вылетает с ошибкой при попытке вызова функции из DLL - Visual Basic
Declare Function testdll Lib &quot;cfg.dll&quot; () As String Private Sub Form_Load() Me.Caption = testdll() End Sub сама dll...

Где бесплатно скачать учебник по Visual Basic 6 и Visual Basic .Net ? - Visual Basic
Где бесплатно скачать учебник по Visual Basic 6 и Visual Basic .Net

Вычисление значений функции двух переменных в Visual Basic - Visual Basic - Visual Basic
Помогите пожалуйста! В среде VB написать программу вычисления значений функции двух переменных. Ориентировочный вид окна программы и...

Работа с Visual Basic - Visual Basic
Всем привет! Программа которую я использую: ```````````````````````````````````````````````````````````````````````````````````` ...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
15
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru