阿杰 发表于 2009-8-22 22:03:45

【开源】VB动态调用外部函数的方法

<font face="Verdana">
<p><font face="Verdana">'VB动态调用外部函数的方法<br/>'VB可以用Declare声明来调用标准DLL的外部函数,但是其局限性也很明显:利用Declare我们只能载入在设计时通过Lib和Alias字句指定的函数指针!而不能在运行时指定由我们自己动态载入的函数指针),不能用Declare语句来调用任意的函数指针。当我们想动态调用外部函数的时候,就必须考虑采用其他的辅助方法,来完成这个任务了。<br/>'<br/>'在文章《VB真是想不到系列之三:VB指针葵花宝典之函数指针 》、《Matthew Curland的VB函数指针调用》、《利用动态创建自动化接口实现VB的函数指针调用》等文献中对此问题都进行了一定程度上的讨论,但是头绪都很繁琐,对我这样的菜鸟还有点深奥,在资料搜索过程中,找到通过在VB中调入汇编程序,比较简便的实现了这个功能,下面就是实现原理:<br/>'<br/>'1)使用LoadLibrary加载DLL;<br/>'2)GetProcAddress获得函数指针;<br/>'<br/>'以上两步得到了预加载函数的指针,但是VB中没有提供使用这个指针的方法。我们可以通过一段汇编语言,来完成函数指针的调用!<br/>'<br/>'3)通过汇编语言,把函数的所有参数压入堆栈,然后用Call待用函数指针就可以了。</font></p>
<p><font face="Verdana">'实现以上功能的主要程序:<br/>'加载Dll<br/>Private Sub Form_Load()<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; LibAddr = LoadLibrary(ByVal "user32")<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; '获得函数指针<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; ProcAddr = GetProcAddress(LibAddr, ByVal "MessageBoxA")<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; '原型为MessageBox(hWnd, lpText, lpCaption, uType)<br/>&nbsp;&nbsp;&nbsp; '<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; '---以下为Assembly部分---<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; 'push uType<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; 'push lpCaption<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; 'push lpText<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; 'push hWnd<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; 'Call ProcAddr<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; '--------------------<br/>&nbsp;&nbsp;&nbsp; '&nbsp;&nbsp;&nbsp; FreeLibrary LibAddr&nbsp;&nbsp;&nbsp; '释放空间</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; '嘿,够简单吧!下面是动态调用MessageBoxA的源代码,上面的步骤被封装到RunDll32函数中,可放到模块(CallAPIbyName.bas)中:<br/>Dim s1() As Byte, s2() As Byte<br/>Dim ret As Long<br/>&nbsp;&nbsp;&nbsp; s1 = StrConv("Hello~World", vbFromUnicode)<br/>&nbsp;&nbsp;&nbsp; s2 = StrConv("VBNote", vbFromUnicode)<br/>&nbsp;&nbsp;&nbsp; ret = RunDll32("user32", "MessageBoxA", hWnd, VarPtr(s1(0)), VarPtr(s2(0)), 0&amp;)<br/>End Sub</font></p>
<p><font face="Verdana">'CallAPIbyName.bas中的源代码:</font></p>
<p><font face="Verdana">Option Explicit</font></p>
<p><font face="Verdana">Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long<br/>Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long<br/>Private Declare Function CallWindowProc Lib "User32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long<br/>Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long<br/>Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cBytes As Long)</font></p>
<p><font face="Verdana">Public m_opIndex As Long&nbsp;&nbsp;&nbsp; '写入位置<br/>Private m_OpCode() As Byte&nbsp;&nbsp;&nbsp; 'Assembly 的OPCODE</font></p>
<p><font face="Verdana">Public Function RunDll32(LibFileName As String, ProcName As String, ParamArray Params()) As Long<br/>Dim hProc As Long<br/>Dim hModule As Long</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; ReDim m_OpCode(400 + 6 * UBound(Params))&nbsp;&nbsp;&nbsp; '保留用来写m_OpCode<br/>&nbsp;&nbsp;&nbsp; '读取API库<br/>&nbsp;&nbsp;&nbsp; hModule = LoadLibrary(ByVal LibFileName)<br/>&nbsp;&nbsp;&nbsp; If hModule = 0 Then<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MsgBox "Library读取失败!"<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Exit Function<br/>&nbsp;&nbsp;&nbsp; End If</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; '取得函数地址<br/>&nbsp;&nbsp;&nbsp; hProc = GetProcAddress(hModule, ByVal ProcName)<br/>&nbsp;&nbsp;&nbsp; If hProc = 0 Then<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MsgBox "函数读取失败!", vbCritical<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FreeLibrary hModule<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Exit Function<br/>&nbsp;&nbsp;&nbsp; End If</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; '执行Assembly Code部分<br/>&nbsp;&nbsp;&nbsp; RunDll32 = CallWindowProc(GetCodeStart(hProc, Params), 0, 1, 2, 3)<br/>&nbsp;&nbsp;&nbsp; FreeLibrary hModule&nbsp;&nbsp;&nbsp; '释放空间<br/>End Function</font></p>
<p><font face="Verdana">Private Function GetCodeStart(ByVal lngProc As Long, ByVal arrParams As Variant) As Long<br/>&nbsp;&nbsp;&nbsp; '---以下为Assembly部分--<br/>&nbsp;&nbsp;&nbsp; '作用:将函数的参数压入堆栈</font></p>
<p><font face="Verdana">Dim lngIndex As Long, lngCodeStart As Long</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; '程序起始位址必须是16的倍数<br/>&nbsp;&nbsp;&nbsp; 'VarPtr函数是用来取得变量的地址<br/>&nbsp;&nbsp;&nbsp; lngCodeStart = (VarPtr(m_OpCode(0)) Or &amp;HF) + 1</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; m_opIndex = lngCodeStart - VarPtr(m_OpCode(0))&nbsp;&nbsp;&nbsp; '程序开始的元素的位置</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; '前面部分以中断点添满<br/>&nbsp;&nbsp;&nbsp; For lngIndex = 0 To m_opIndex - 1<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_OpCode(lngIndex) = &amp;HCC&nbsp;&nbsp;&nbsp; 'int 3<br/>&nbsp;&nbsp;&nbsp; Next lngIndex</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; '--------以下开始放入所需的程序----------</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; '将参数push到堆栈<br/>&nbsp;&nbsp;&nbsp; '由于是STDCall CALL 参数由最后一个开始放到堆栈<br/>&nbsp;&nbsp;&nbsp; For lngIndex = UBound(arrParams) To 0 Step -1<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AddByteToCode &amp;H68&nbsp;&nbsp;&nbsp; 'push的机器码为H68<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AddLongToCode CLng(arrParams(lngIndex))&nbsp;&nbsp;&nbsp; '参数地址<br/>&nbsp;&nbsp;&nbsp; Next lngIndex</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; 'call hProc<br/>&nbsp;&nbsp;&nbsp; AddByteToCode &amp;HE8&nbsp;&nbsp;&nbsp; 'call的机器码为HE8<br/>&nbsp;&nbsp;&nbsp; AddLongToCode lngProc - VarPtr(m_OpCode(m_opIndex)) - 4&nbsp;&nbsp;&nbsp; '函数地址 用call的定址</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; '-----------结束所需的程序--------------</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; '返回呼叫函數<br/>&nbsp;&nbsp;&nbsp; AddByteToCode &amp;HC2&nbsp;&nbsp;&nbsp; 'ret 10h<br/>&nbsp;&nbsp;&nbsp; AddByteToCode &amp;H10<br/>&nbsp;&nbsp;&nbsp; AddByteToCode &amp;H0</font></p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp; GetCodeStart = lngCodeStart<br/>End Function</font></p>
<p><font face="Verdana">Private Sub AddLongToCode(lData As Long)<br/>&nbsp;&nbsp;&nbsp; '将Long类型的参数写到m_OpCode中<br/>&nbsp;&nbsp;&nbsp; CopyMemory m_OpCode(m_opIndex), lData, 4<br/>&nbsp;&nbsp;&nbsp; m_opIndex = m_opIndex + 4<br/>End Sub</font></p>
<p><font face="Verdana">Private Sub AddIntToCode(iData As Byte)<br/>&nbsp;&nbsp;&nbsp; '将Integer类型的参数写道m_OpCode中<br/>&nbsp;&nbsp;&nbsp; CopyMemory m_OpCode(m_opIndex), iData, 2<br/>&nbsp;&nbsp;&nbsp; m_opIndex = m_opIndex + 2<br/>End Sub</font></p>
<p><font face="Verdana">Private Sub AddByteToCode(bData As Byte)<br/>&nbsp;&nbsp;&nbsp; '将Byte类型的参数写道m_OpCode中<br/>&nbsp;&nbsp;&nbsp; m_OpCode(m_opIndex) = bData<br/>&nbsp;&nbsp;&nbsp; m_opIndex = m_opIndex + 1<br/>End Sub</font></p>
<p><font face="Verdana"><br/></font>&nbsp;</p></font>
[此贴子已经被作者于2009-8-22 22:24:19编辑过]

upring 发表于 2015-10-22 08:37:12


'VB动态调用外部函数的方法
'VB可以用Declare声明来调用标准DLL的外部函数,但是其局限性也很明显:利用Declare我们只能载入在设计时通过Lib和Alias字句指定的函数指针!而不能在运行时指定由我们自己动态载入的函数指针),不能用Declare语句来调用任意的函数指针。当我们想动态调用外部函数的时候,就必须考虑采用其他的辅助方法,来完成这个任务了。
'
'在文章《VB真是想不到系列之三:VB指针葵花宝典之函数指针 》、《Matthew Curland的VB函数指针调用》、《利用动态创建自动化接口实现VB的函数指针调用》等文献中对此问题都进行了一定程度上的讨论,但是头绪都很繁琐,对我这样的菜鸟还有点深奥,在资料搜索过程中,找到通过在VB中调入汇编程序,比较简便的实现了这个功能,下面就是实现原理:
'
'1)使用LoadLibrary加载DLL;
'2)GetProcAddress获得函数指针;
'
'以上两步得到了预加载函数的指针,但是VB中没有提供使用这个指针的方法。我们可以通过一段汇编语言,来完成函数指针的调用!
'
'3)通过汇编语言,把函数的所有参数压入堆栈,然后用Call待用函数指针就可以了。

'实现以上功能的主要程序:
'加载Dll
Private Sub Form_Load()
    '    LibAddr = LoadLibrary(ByVal "user32")
    '    '获得函数指针
    '    ProcAddr = GetProcAddress(LibAddr, ByVal "MessageBoxA")
    '    '原型为MessageBox(hWnd, lpText, lpCaption, uType)
    '
    '    '---以下为Assembly部分---
    '    'push uType
    '    'push lpCaption
    '    'push lpText
    '    'push hWnd
    '    'Call ProcAddr
    '    '--------------------
    '    FreeLibrary LibAddr    '释放空间

    '嘿,够简单吧!下面是动态调用MessageBoxA的源代码,上面的步骤被封装到RunDll32函数中,可放到模块(CallAPIbyName.bas)中:
Dim s1() As Byte, s2() As Byte
Dim ret As Long
    s1 = StrConv("Hello~World", vbFromUnicode)
    s2 = StrConv("VBNote", vbFromUnicode)
    ret = RunDll32("user32", "MessageBoxA", hWnd, VarPtr(s1(0)), VarPtr(s2(0)), 0&)
End Sub

'CallAPIbyName.bas中的源代码:

Option Explicit

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 Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cBytes As Long)

Public m_opIndex As Long    '写入位置
Private m_OpCode() As Byte    'Assembly 的OPCODE

Public Function RunDll32(LibFileName As String, ProcName As String, ParamArray Params()) As Long
Dim hProc As Long
Dim hModule As Long

    ReDim m_OpCode(400 + 6 * UBound(Params))    '保留用来写m_OpCode
    '读取API库
    hModule = LoadLibrary(ByVal LibFileName)
    If hModule = 0 Then
      MsgBox "Library读取失败!"
      Exit Function
    End If

    '取得函数地址
    hProc = GetProcAddress(hModule, ByVal ProcName)
    If hProc = 0 Then
      MsgBox "函数读取失败!", vbCritical
      FreeLibrary hModule
      Exit Function
    End If

    '执行Assembly Code部分
    RunDll32 = CallWindowProc(GetCodeStart(hProc, Params), 0, 1, 2, 3)
    FreeLibrary hModule    '释放空间
End Function

Private Function GetCodeStart(ByVal lngProc As Long, ByVal arrParams As Variant) As Long
    '---以下为Assembly部分--
    '作用:将函数的参数压入堆栈

Dim lngIndex As Long, lngCodeStart As Long

    '程序起始位址必须是16的倍数
    'VarPtr函数是用来取得变量的地址
    lngCodeStart = (VarPtr(m_OpCode(0)) Or &HF) + 1

    m_opIndex = lngCodeStart - VarPtr(m_OpCode(0))    '程序开始的元素的位置

    '前面部分以中断点添满
    For lngIndex = 0 To m_opIndex - 1
      m_OpCode(lngIndex) = &HCC    'int 3
    Next lngIndex

    '--------以下开始放入所需的程序----------

    '将参数push到堆栈
    '由于是STDCall CALL 参数由最后一个开始放到堆栈
    For lngIndex = UBound(arrParams) To 0 Step -1
      AddByteToCode &H68    'push的机器码为H68
      AddLongToCode CLng(arrParams(lngIndex))    '参数地址
    Next lngIndex

    'call hProc
    AddByteToCode &HE8    'call的机器码为HE8
    AddLongToCode lngProc - VarPtr(m_OpCode(m_opIndex)) - 4    '函数地址 用call的定址

    '-----------结束所需的程序--------------

    '返回呼叫函數
    AddByteToCode &HC2    'ret 10h
    AddByteToCode &H10
    AddByteToCode &H0

    GetCodeStart = lngCodeStart
End Function

Private Sub AddLongToCode(lData As Long)
    '将Long类型的参数写到m_OpCode中
    CopyMemory m_OpCode(m_opIndex), lData, 4
    m_opIndex = m_opIndex + 4
End Sub

Private Sub AddIntToCode(iData As Byte)
    '将Integer类型的参数写道m_OpCode中
    CopyMemory m_OpCode(m_opIndex), iData, 2
    m_opIndex = m_opIndex + 2
End Sub

Private Sub AddByteToCode(bData As Byte)
    '将Byte类型的参数写道m_OpCode中
    m_OpCode(m_opIndex) = bData
    m_opIndex = m_opIndex + 1
End Sub
页: [1]
查看完整版本: 【开源】VB动态调用外部函数的方法