VB 动态调用外部函数的方法

VB可以用Declare声明来调用标准DLL的外部函数,但是其局限性也很明显:利用Declare我们只能载入在设计时通过Lib和Alias字句指定的函数指针!而不能在运行时指定由我们自己动态载入的函数指针),不能用Declare语句来调用任意的函数指针。当我们想动态调用外部函数的时候,就必须考虑采用其他的辅助方法,来完成这个任务了。

在文章《VB真是想不到系列之三:VB指针葵花宝典之函数指针》、《Matthew Curland的VB函数指针调用》、《利用动态创建自动化接口实现VB的函数指针调用》等文献中对此问题都进行了一定程度上的讨论,但是头绪都很繁琐,对我这样的菜鸟还有点深奥,在资料搜索过程中,找到通过在VB中调入汇编程序,比较简便的实现了这个功能,下面就是实现原理:

1)使用LoadLibrary加载DLL;
2)GetProcAddress获得函数指针;

以上两步得到了预加载函数的指针,但是VB中没有提供使用这个指针的方法。我们可以通过一段汇编语言,来完成函数指针的调用!

3)通过汇编语言,把函数的所有参数压入堆栈,然后用Call待用函数指针就可以了。

实现以上功能的主要程序:

'加载Dll
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 &)

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

猜你喜欢

转载自yeuego.iteye.com/blog/947565
VB