VB动态调用外部函数的方法
{5 t, k( {9 U: F 导读:VB可以用Declare声明来调用标准DLL的外部函数,但是其局限性也很明显:利用Declare我们只能载入在设计时通过Lib和Alias字句指定的函数指针!而不能在运行时指定由我们自己动态载入的函数指针),不能用Declare语句来调用任意的函数指针。当我们想动态调用外部函数的时候,就必须考虑采用其他的辅助方法,来完成这个任务了。
; d+ |! u6 X2 M% q% [; U 在文章《VB真是想不到系列之三:VB指针葵花宝典之函数指针 》、《Matthew Curland的VB函数指针调用》、《利用动态创建自动化接口实现VB的函数指针调用》等文献中对此问题都进行了一定程度上的讨论,但是头绪都很繁琐,对我这样的菜鸟还有点深奥,在资料搜索过程中,找到通过在VB中调入汇编程序,比较简便的实现了这个功能,下面就是实现原理:
+ g" O0 d( ]7 d1 f! { 1)使用LoadLibrary加载DLL;
, n3 k) K3 w0 ]3 G: d: w) H8 g 2)GetProcAddress获得函数指针;
5 K9 I, [6 @- u( Y- E 以上两步得到了预加载函数的指针,但是VB中没有提供使用这个指针的方法。我们可以通过一段汇编语言,来完成函数指针的调用!1 u8 B n5 m0 a4 }
3)通过汇编语言,把函数的所有参数压入堆栈,然后用Call待用函数指针就可以了。; U' g: K3 ]3 U2 k" ^9 ]
实现以上功能的主要程序:: K0 t* w- F. v; [, I* [* A6 n
′加载Dll
& f0 K5 G9 @' b& t, F8 f# X9 u LibAddr = LoadLibrary(ByVal "user32")$ ?% _# [ a$ P( w8 L
′获得函数指针
$ W4 ?, H8 w( i1 P ProcAddr = GetProcAddress(LibAddr, ByVal "MessageBoxA")1 s( o* i' R) ?0 Z
′原型为MessageBox(hWnd, lpText, lpCaption, uType)9 t9 n0 R- z4 R
′---以下为Assembly部分---0 u. U, \+ g$ e* _( P
push uType
) R$ ~' x5 j5 c push lpCaption
$ v% j$ J0 F- t+ T push lpText
, B) I! L, C$ B' B+ H push hWnd, M$ l# x3 D" G! {! b
call ProcAddr
# J0 J& f a4 b ′--------------------
' V7 ^4 n4 _: Y0 V& l FreeLibrary LibAddr′释放空间4 j) x8 U3 O! m! o
嘿,够简单吧!下面是动态调用MessageBoxA的源代码,上面的步骤被封装到RunDll32函数中,可放到模块(CallAPIbyName.bas)中:
8 K+ P* Y2 b, M6 @& P$ r$ f/ t Dim s1() As Byte, s2() As Byte
( }5 G, {4 i& o5 T6 C1 n/ k( P% I Dim ret As Long
. i9 Y4 n- }6 j& L4 | s1 = StrConv("Hello~World", vbFromUnicode)9 ^7 H5 I# N- R1 Q: Q _
s2 = StrConv("VBNote", vbFromUnicode)
4 t; m7 I' G$ b ret = RunDll32("user32", "MessageBoxA", hwnd, VarPtr(s1(0)), VarPtr(s2(0)), 0&)& _; D! I# T- M& K6 N
CallAPIbyName.bas中的源代码:
3 ]( E x5 z" l0 J% |# E! E2 q' O Option Explicit
/ M3 Y: _; k2 X0 u% w* K+ F Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long9 _! K/ h3 D. B6 c, i
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long0 Z/ b1 u9 N c
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
* N' S7 O U/ b4 n8 a: B& I Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long# D3 X2 P: W* `2 k
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cBytes As Long)
9 I" A, }* e( ]7 {2 ~ Public m_opIndex As Long ′写入位置- F3 d* ^" n$ n7 r
Private m_OpCode() As Byte ′Assembly 的OPCODE
) T1 _5 Z2 _" M" L& n3 n; J Public Function RunDll32(LibFileName As String, ProcName As String, ParamArray Params()) As Long3 G& u2 F% y$ u' A$ _7 P
Dim hProc As Long
8 j( W7 Y1 b) G/ H0 _ Dim hModule As Long2 x' {! r- o0 H' q* a4 y" f
ReDim m_OpCode(400 + 6 * UBound(Params)) ′保留用来写m_OpCode
+ W! O( y- s/ c, _% Q$ ^ ′读取API库, S1 u: U$ B. s# t; b" s
hModule = LoadLibrary(ByVal LibFileName)* z2 I( b- s* g! y0 _* V
If hModule = 0 Then' f* s0 m' Q0 f; v
MsgBox "Library读取失败!"
9 d; d! t/ i9 c2 R8 Q- P, r# X" R Exit Function
% A7 Q% w! |. s; V5 N End If
! w k0 d$ ~; k- ]! ^" W q ′取得函数地址/ X, a; O3 f- w7 M
hProc = GetProcAddress(hModule, ByVal ProcName)
7 C2 f# y+ L( J; l, ^: V If hProc = 0 Then6 ^: Y$ c# W! ^5 y( ~4 o7 v
MsgBox "函数读取失败!", vbCritical
9 s: d( R, N1 [8 j/ a! F FreeLibrary hModule
. ]8 }% U; |+ m* y0 \$ y Exit Function( G: R7 U; n2 s0 x9 ]; g
End If
( b" q& X4 Y9 j8 X; b& ` ′执行Assembly Code部分
: ?( t8 w# l( u- O RunDll32 = CallWindowProc(GetCodeStart(hProc, Params), 0, 1, 2, 3), J6 L d, d' p0 `; X
FreeLibrary hModule ′释放空间# w/ d3 G# b3 c7 g. P
End Function
$ P4 E1 j- _& F& A Private Function GetCodeStart(ByVal lngProc As Long, ByVal arrParams As Variant) As Long
9 q1 K9 N5 | L' F1 b& x, u; h2 B; r( f9 Y/ R( ? }
′---以下为Assembly部分--
# }* H7 u! l' K8 G w. g ′作用:将函数的参数压入堆栈
: e% \& y8 u: I2 j. P! c! i Dim lngIndex As Long, lngCodeStart As Long) M9 Q# ]) A7 \
′程序起始位址必须是16的倍数! F1 a% g: O q: n8 [) u1 C
′VarPtr函数是用来取得变量的地址% C' N. b( N Q7 P1 \" P
lngCodeStart = (VarPtr(m_OpCode(0)) Or &HF) + 1
+ Z/ _' N3 I; H* g* t m_opIndex = lngCodeStart - VarPtr(m_OpCode(0)) ′程序开始的元素的位置
! Y8 @2 e8 I! h$ A8 Y8 ]7 y9 w ′前面部分以中断点添满' D% K* U9 ?9 ~6 l8 D; K* E- S
For lngIndex = 0 To m_opIndex - 1
$ c! Y/ b! d. O m_OpCode(lngIndex) = &HCC ′int 3' t$ }6 h7 t0 q: U9 g% ?& A1 a
Next lngIndex) g( g7 Y$ _% a2 g
′--------以下开始放入所需的程序----------
% Q" t: c+ U* q ′将参数push到堆栈
* y6 c9 [: n9 d" i ` ′由于是STDCall CALL 参数由最后一个开始放到堆栈
7 u: L& ?5 G, J W. u6 r For lngIndex = UBound(arrParams) To 0 Step -1
, G1 s& c Y% \ AddByteToCode &H68 ′push的机器码为H68
' E& h3 S& Z+ r( y1 e AddLongToCode CLng(arrParams(lngIndex)) ′参数地址
$ G2 X. K" `: { M/ _# z Next lngIndex
0 d4 f- x& k1 j; `& w ′call hProc
& W" {: s' z% p* x) O AddByteToCode &HE8 ′call的机器码为HE8
* Q% g, F1 n' n& w; m# F$ q7 A% r" y3 n AddLongToCode lngProc - VarPtr(m_OpCode(m_opIndex)) - 4 ′函数地址 用call的定址 |