首页 > 代码库 > VB6.0调用DLL
VB6.0调用DLL
目录
第1章 VB6.0调用DLL 1
1 VC++编写DLL 1
1.1 使用__stdcall 1
1.2 使用 .DEF 文件 1
2 简单数据类型 2
2.1 传值(ByVal) 2
2.2 传址(ByRef) 3
2.3 传址(VarPtr) 4
2.4 转换为Variant 4
3 String 6
3.1 BSTR内存布局 6
3.2 StrPtr、VarPtr 7
3.3 示例代码 7
3.4 转换为Variant 12
3.5 小结 13
4 结构 14
4.1 定义 14
4.2 示例代码 14
5 数组 17
5.1 简单数据类型数组 17
5.2 转换为Variant 18
5.3 结构数组 19
6 Object 21
7 函数 21
第1章 VB6.0调用DLL
1 VC++编写DLL
VB6.0或VBA可以调用DLL里的导出函数。可以使用VC++编写DLL,供VB6.0调用。VC++编写DLL时,需要注意以下几点:
1.1 使用__stdcall
C函数在默认情况下,其调用约定为__cdecl。为了让VB6.0调用此函数,需要特别设置调用约定为__stdcall(或 WINAPI)。如下面的C代码:
void __stdcall Test() { } |
1.2 使用 .DEF 文件
使用VC++编写DLL,可以使用 __declspec(dllexport) 导出一个函数。如:
__declspec(dllexport) void __stdcall Test() { } |
使用VC++6.0的C编译器编译上述代码,则导出函数名为_Test@0。此时,VB6.0声明Test函数的代码如下:
Private Declare Sub Test Lib "Test.dll" Alias "_Test@0" () |
"Alias "_Test@0""明确说明了导出函数的名称。
为了简化VB6.0里Test函数的声明,可使用DEF文件,具体如下:
1、使用DEF文件。下面是DEF文件的内容,它表示导出Test函数:
EXPORTS Test |
2、VC代码里不使用__declspec(dllexport),如下面的代码
void __stdcall Test() { } |
3、VB6.0声明Test函数时无需Alias,如下面的声明
Private Declare Sub Test Lib "Test.dll" () |
2 简单数据类型
VB6.0中,常见的数据类型请见下表
VB6.0 |
C |
说明 |
Boolean |
VARIANT_BOOL |
就是short VARIANT_TRUE 等于-1表示 TRUE VARIANT_FALSE 等于0表示 FALSE |
Byte |
BYTE |
就是unsigned char |
Currency |
CY CURRENCY |
就是 __int64 如:1234567表示货币值123.4567@ |
Date |
DATE |
就是double 表示1899年12月30日到当前时刻的天数,对应于MFC的COleDateTime |
Decimal |
DECIMAL |
VB6.0里Decimal用Variant表示 |
Double |
double |
|
Integer |
short |
|
Long |
long |
|
Single |
float |
|
String |
BSTR |
Unicode字符串 |
Variant |
VARIANT |
上表中,除了Decimal、String、Variant之外其它均为简单数据类型。
VB6.0传递简单数据类型给DLL时,分为传值和传址两种方式:
2.1 传值(ByVal)
VC++代码:
long WINAPI TestSimpleV(VARIANT_BOOL b,BYTE bt,__int64 c ,DATE dt,double d,short i,long l,float s) { b = VARIANT_TRUE; //改变形参,不会影响VB6.0里的实参 return 0; } |
VB6.0声明代码:
Public Declare Function TestSimpleV Lib "Test.dll" _ (ByVal b As Boolean, ByVal bt As Byte, ByVal c As Currency, _ ByVal dt As Date, ByVal d As Double, ByVal i As Integer, _ ByVal l As Long, ByVal s As Single) As Long |
VB6.0调用代码:
Dim b As Boolean,bt As Byte,c As Currency,dt As Date Dim d As Double,i As Integer,l As Long,s As Single Dim nRet As Long nRet = TestSimpleV(b, bt, c, dt, d, i, l, s) |
说明:
1、VB6.0声明函数时,必须使用ByVal,表示把实参的数值传递给形参;
2、因为传递的是数值,所以VC函数里改变形参的数值不会影响到实参。
2.2 传址(ByRef)
VC++代码:
long WINAPI TestSimpleR(VARIANT_BOOL*b,BYTE*bt,__int64*c ,DATE*dt,double*d,short*i,long*l,float*s) { if(b) { *b = VARIANT_TRUE; } if(bt) { *bt = 2; } if(c) { *c = 654321; } //货币值 65.4321@ if(d) { *d = 3.4; } if(i) { *i = 5; } if(l) { *l = 6; } if(s) { *s = 7.8f; } if(dt) { SYSTEMTIME tmSys; GetLocalTime(&tmSys); SystemTimeToVariantTime(&tmSys,dt); } return 1; } |
VB6.0声明代码:
Public Declare Function TestSimpleR Lib "Test.dll" _ (ByRef b As Boolean, ByRef bt As Byte, ByRef c As Currency, _ ByRef dt As Date, ByRef d As Double, ByRef i As Integer, _ ByRef l As Long, ByRef s As Single) As Long |
VB6.0调用代码:
Dim b As Boolean, bt As Byte, c As Currency, dt As Date Dim d As Double, i As Integer, l As Long, s As Single Dim nRet As Long nRet = TestSimpleR(b, bt, c, dt, d, i, l, s) |
说明:
1、VB6.0声明函数时,应使用ByRef,表示把实参的地址传递给形参;
2、因为传递的是地址,所以VC函数里改变形参的数值将影响到实参。
2.3 传址(VarPtr)
使用ByRef无法将NULL指针传递给DLL,为此可使用函数VarPtr。示例代码如下:
VB6.0声明代码:
Public Declare Function TestSimpleP Lib "Test.dll" Alias "TestSimpleR" _ (ByVal b As Long, ByVal bt As Long, ByVal c As Long, _ ByVal dt As Long, ByVal d As Long, ByVal i As Long, _ ByVal l As Long, ByVal s As Long) As Long |
VB6.0调用代码:
Dim b As Boolean,bt As Byte,c As Currency,dt As Date Dim d As Double,i As Integer,l As Long,s As Single Dim nRet As Long nRet = TestSimpleP(VarPtr(b), VarPtr(bt), VarPtr(c), VarPtr(dt), _ VarPtr(d), VarPtr(i), VarPtr(l), VarPtr(s)) |
VarPtr(b)将返回变量b的地址,被VB6.0当做long传递给DLL。若要传递NULL指针,可修改VarPtr(...)为0。
VB.NET不支持VarPtr函数,下面是从网上找到的代码,不知是否稳定。
Public Function VarPtr(ByVal e As Object) As Integer Dim GC As GCHandle = GCHandle.Alloc(e, GCHandleType.Pinned) Dim GC2 As Integer = GC.AddrOfPinnedObject.ToInt32 GC.Free() Return GC2 End Function |
2.4 转换为Variant
VB6.0里可以将简单数据类型转换为Variant,然后再传递给DLL。下面是示例代码:
VC++代码:
void WINAPI TestSimpleVariant(VARIANT v,VARIANT*r) { if(v.vt == VT_R8) { v.dblVal = -1.2; //不会影响实参 } if(r && r->vt == (VT_R4 | VT_BYREF)) { *r->pfltVal = -3.4f; //会影响实参 } } |
VB6.0声明代码:
Public Declare Sub TestSimpleVariant Lib "Test.dll" _ (ByVal v As Variant, ByRef r As Variant) |
VB6.0调用代码:
Dim d As Double, s As Single d = 1.2 s = 3.4! MsgBox Hex$(VarPtr(d)) & " = " & d & vbCrLf & _ Hex$(VarPtr(s)) & " = " & s Call TestSimpleVariant(d, s) MsgBox Hex$(VarPtr(d)) & " = " & d & vbCrLf & _ Hex$(VarPtr(s)) & " = " & s |
运行VB6.0代码,将依次出现:
1、首先显示如下界面
说明:变量d的首地址为0x18F374,数值为1.2;变量s的首地址为0x18F370,数值为3.4;
2、VB6.0将 d、s 转换为Variant,然后传递给VC函数TestSimpleVariant。注意:r->pfltVal就是实参s的首地址;
3、VB6.0中Call TestSimpleVariant(d, s)执行完毕后,d的数值未变,s的数值被改变,d、s的地址均未改变,如下图所示:
3 String
可以认为VB6.0的String就是C语言的BSTR。BSTR可以表示宽字符串(Unicode),也可以表示窄字符串(ANSI)。在VB6.0或EVB3.0里,String对应的BSTR是一个宽字符串。
3.1 BSTR内存布局
BSTR s表示了一个字符串。s是一个指针,指向了一个ANSI或UNICODE字符串。此外,BSTR还包含了字符串的长度信息。具体结构如下:
字符串所占字节数 共4字节 |
字符串 首地址就是s |
结束符 |
如:字符串"123"以ANSI的BSTR表示(假定s的值为0x1000)
地址 |
内容 |
说明 |
0x0FFC |
03H |
字符串所占字节数,这里为3 |
0x0FFD |
00H |
|
0x0FFE |
00H |
|
0x0FFF |
00H |
|
0x1000 |
31H |
字符串123 |
0x1001 |
32H |
|
0x1002 |
33H |
|
0x1003 |
00H |
结束符 |
字符串"123"以UNICODE的BSTR表示(假定s的值为0x1000)
地址 |
内容 |
说明 |
0x0FFC |
06H |
字符串所占字节数,这里为6 |
0x0FFD |
00H |
|
0x0FFE |
00H |
|
0x0FFF |
00H |
|
0x1000 |
31H |
字符串123 |
0x1001 |
00H |
|
0x1002 |
32H |
|
0x1003 |
00H |
|
0x1004 |
33H |
|
0x1005 |
00H |
|
0x1006 |
00H |
结束符 |
0x1007 |
00H |
所以,当BSTR s表示ANSI字符串时,可以把s当作char*;当BSTR s表示UNICODE字符串时,可以把s当作wchar_t*
3.2 StrPtr、VarPtr
StrPtr返回的是字符串首字符的地址(可看做wchar_t*),VarPtr返回的是字符串变量的地址(可看做wchar_t**)。参考下表,可以更好的理解:
VB6.0代码 |
C代码 |
Dim s As String |
BSTR s; |
Dim n As Long |
|
n = StrPtr(s) |
wchar_t*n = (wchar_t*)s; |
n = VarPtr(s) |
wchar_t**n = (wchar_t**)&s; |
3.3 示例代码
VB6.0里将String变量传递给DLL的示例代码如下:
VC++代码:
BSTR WINAPI TestString(BSTR v,BSTR*r,BSTR sp,BSTR*vp) { if(v) {//VB6.0 传过来 vbNullString,则 v 为 NULL char*p = (char*)v; //ANSI 字符串首地址 UINT& nLen = ((UINT*)p)[-1]; //ANSI 字符串长度 if(nLen > 0) { p[0] = ‘v‘; //允许修改字符串 nLen = 1; //允许将字符串长度变小 p[nLen] = ‘\0‘; //修改字符串长度后,这句是必需的 } } { char* p = *(char**)r; //ANSI 字符串首地址 if(p) {//VB6.0 传过来 vbNullString,则 r 不为 NULL,p 为 NULL UINT&nLen = ((UINT*)p)[-1]; //ANSI 字符串长度 if(nLen > 0) { p[0] = ‘r‘; //允许修改字符串 nLen = 1; //允许将字符串长度变小 p[nLen] = ‘\0‘; //修改字符串长度后,这句是必需的 } } else {//重新定义字符串 char*szStr = "C 函数生成的字符串"; SysFreeString(*r); //释放以前的字符串 *r = SysAllocStringByteLen(szStr,strlen(szStr)); //修改字符串 } } { wchar_t*p = (wchar_t*)sp; //Unicode 字符串首地址 if(p) {//VB6.0 传过来 vbNullString,则 sp 不为 NULL,p 为 NULL UINT&nLen2 = ((UINT*)p)[-1]; //Unicode 字符串字节数 if(nLen2 >= 2) { p[0] = L‘s‘; //允许修改字符串 nLen2 = 2; //允许将字符串长度变小 p[nLen2>>1] = L‘\0‘; //修改字符串长度后,这句是必需的 } } } { wchar_t*p = *(wchar_t**)vp; //Unicode 字符串首地址 if(p) {//VB6.0 传过来 vbNullString,则 vp 不为 NULL,p 为 NULL UINT&nLen2 = ((UINT*)p)[-1]; //Unicode 字符串字节数 if(nLen2 >= 2) { p[0] = L‘v‘; //允许修改字符串 nLen2 = 2; //允许将字符串长度变小 p[nLen2>>1] = L‘\0‘; //修改字符串长度后,这句是必需的 } } } {//返回值,允许返回 NULL char*szStr = "C 函数返回的字符串"; return SysAllocStringByteLen(szStr,strlen(szStr)); } } |
VB6.0声明代码:
Public Declare Function TestString Lib "Test.dll" _ (ByVal v As String, ByRef r As String, _ ByVal sp As Long, ByVal vp As Long) As String |
VB6.0调用代码:
Dim v As String Dim r As String Dim sp As String Dim vp As String v = txtV.Text r = txtR.Text sp = txtSP.Text vp = txtVP.Text txtReturn.Text = TestString(v, r, StrPtr(sp), VarPtr(vp)) txtV.Text = v txtR.Text = r txtSP.Text = sp txtVP.Text = vp |
下面是对上述代码的详细说明:
3.3.1 ByVal v As String
VB6.0里的变量v是一个String,其实就是一个Unicode的BSTR。
调用TestString之前,VB6.0会将v转换为Ansi的BSTR,并将这个BSTR传递给DLL。
DLL的处理代码如下:
if(v) {//VB6.0 传过来 vbNullString,则 v 为 NULL char*p = (char*)v; //ANSI 字符串首地址 UINT& nLen = ((UINT*)p)[-1]; //ANSI 字符串长度 if(nLen > 0) { p[0] = ‘v‘; //允许修改字符串 nLen = 1; //允许将字符串长度变小 p[nLen] = ‘\0‘; //修改字符串长度后,这句是必需的 } } |
首先要判断v是否为NULL,因为VB6.0传递字符串vbNullString时,该参数将为NULL。接下来获得ANSI字符串的首地址p和长度nLen。允许修改ANSI字符串的内容和长度,但是不能加长字符串。
VC++的TestString函数返回后,VB6.0会将ANSI字符串转换为Unicode字符串,并由此改变实参v。
3.3.2 ByRef r As String
VB6.0里的变量r是一个String,其实就是一个Unicode的BSTR。
调用TestString之前,VB6.0会将r转换为Ansi的BSTR,并将这个BSTR传递给DLL。
DLL的处理代码如下:
{ char* p = *(char**)r; //ANSI 字符串首地址 if(p) {//VB6.0 传过来 vbNullString,则 r 不为 NULL,p 为 NULL UINT&nLen = ((UINT*)p)[-1]; //ANSI 字符串长度 if(nLen > 0) { p[0] = ‘r‘; //允许修改字符串 nLen = 1; //允许将字符串长度变小 p[nLen] = ‘\0‘; //修改字符串长度后,这句是必需的 } } else {//重新定义字符串 char*szStr = "C 函数生成的字符串"; SysFreeString(*r); //释放以前的字符串 *r = SysAllocStringByteLen(szStr,strlen(szStr)); //修改字符串 } } |
即:可以修改ANSI字符串内容和长度,也可以重新生成字符串。
VC++的TestString函数返回后,VB6.0会将ANSI字符串转换为Unicode字符串,并由此改变实参r。
3.3.3 ByVal sp As Long
VB6.0里的变量sp是一个String,其实就是一个Unicode的BSTR。
调用TestString时使用StrPtr(sp)将字符串的首地址传递给了DLL,DLL的处理代码如下:
{ wchar_t*p = (wchar_t*)sp; //Unicode 字符串首地址 if(p) {//VB6.0 传过来 vbNullString,则 sp 不为 NULL,p 为 NULL UINT&nLen2 = ((UINT*)p)[-1]; //Unicode 字符串字节数 if(nLen2 >= 2) { p[0] = L‘s‘; //允许修改字符串 nLen2 = 2; //允许将字符串长度变小 p[nLen2>>1] = L‘\0‘; //修改字符串长度后,这句是必需的 } } } |
上述代码处理的不再是ANSI字符串,而是Unicode字符串。不过,修改字符串时只能减小长度,不能增大长度。
3.3.4 ByVal vp As Long
VB6.0里的变量vp是一个String,其实就是一个Unicode的BSTR。
调用TestString时使用VarPtr(vp)将字符串变量的地址传递给了DLL,DLL的处理代码如下:
{ wchar_t*p = *(wchar_t**)vp; //Unicode 字符串首地址 if(p) {//VB6.0 传过来 vbNullString,则 vp 不为 NULL,p 为 NULL UINT&nLen2 = ((UINT*)p)[-1]; //Unicode 字符串字节数 if(nLen2 >= 2) { p[0] = L‘v‘; //允许修改字符串 nLen2 = 2; //允许将字符串长度变小 p[nLen2>>1] = L‘\0‘; //修改字符串长度后,这句是必需的 } } } |
与"ByVal sp As Long"一样:修改字符串时只能减小长度,不能增大。
3.3.5 返回值
DLL函数返回一个ANSI编码的BSTR,如下所示:
{//返回值,允许返回 NULL char*szStr = "C 函数返回的字符串"; return SysAllocStringByteLen(szStr,strlen(szStr)); } |
上述代码执行完毕后,VB6.0负责将其转换为Unicode编码的字符串。
3.4 转换为Variant
VB6.0里可以将字符串转换为Variant后传递给DLL。下面是示例代码:
VC++代码:
void WINAPI TestStringVariant(VARIANT v,VARIANT*r) { if(v.vt == VT_BSTR) { wchar_t*p = v.bstrVal; //Unicode 字符串首地址 if(p) { UINT& nLen2 = ((UINT*)p)[-1]; //Unicode 字符串字节数 if(nLen2 >= 2) { p[0] = L‘值‘; } } } if(r && r->vt == (VT_BSTR | VT_BYREF)) { SysFreeString(*r->pbstrVal); *r->pbstrVal = SysAllocString(L"字符串2,C 函数生成"); } } |
VB6.0声明代码:
Public Declare Sub TestStringVariant Lib "Test.dll" _ (ByVal v As Variant, ByRef r As Variant) |
VB6.0调用代码:
Dim s1 As String Dim s2 As String s1 = "字符串1" s2 = "字符串2" MsgBox Hex$(VarPtr(s1)) & "," & Hex$(StrPtr(s1)) & " = " & s1 & _ vbCrLf & Hex$(VarPtr(s2)) & "," & Hex$(StrPtr(s2)) & " = " & s2 Call TestStringVariant(s1, s2) MsgBox Hex$(VarPtr(s1)) & "," & Hex$(StrPtr(s1)) & " = " & s1 & _ vbCrLf & Hex$(VarPtr(s2)) & "," & Hex$(StrPtr(s2)) & " = " & s2 |
说明:
1、Call TestStringVariant(s1, s2)时VB6.0会将s1、s2转换为Variant,然后传递给DLL;
2、DLL里的参数v:v.bstrVal就是StrPtr(s1),修改v.bstrVal会影响s1。不过这种修改只能修改内容、减小长度,不能增大长度;
3、DLL里的参数r:r->pbstrVal、*r->pbstrVal分别是VarPtr(s2)、StrPtr(s2)。允许为*r->pbstrVal分配一个新的Unicode字符串,Call TestStringVariant(s1, s2)返回后StrPtr(s2)仍然是*r->pbstrVal。
3.5 小结
1、最常用的就是ByVal v As String
如果DLL里的函数只是读取字符串,不修改字符串,使用比较简单。如下面的代码调用SetWindowText修改窗口标题栏文本:
Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String) As Long
Private Sub Form_Load() SetWindowText Me.hwnd, "窗口标题" End Sub |
如果DLL里的函数要修改字符串,就需要VB6.0分配内存,然后还要修改字符串长度。如下面的代码将获得Windows的安装目录:
Private Declare Function GetWindowsDirectory Lib "kernel32" Alias "GetWindowsDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long) As Long
Private Sub Command1_Click() Dim s As String, n As Long s = Space$(260) ‘分配内存 GetWindowsDirectory s, 260 n = InStr(1, s, Chr$(0), vbBinaryCompare) If n >= 1 Then s = Left$(s, n - 1) ‘把‘\0‘及其后面的字符删除 End Sub |
2、若要处理ANSI字符串,并想加长,请使用ByRef r As String;
3、若要处理Unicode字符串,并想加长,请使用ByRef r As Variant。
4 结构
4.1 定义
VB6.0的结构定义与C的结构定义,请参考下表:
VB6.0 |
C |
Public Type vb6Type vByte As Byte vBool As Boolean vInt As Integer vLng As Long vSng As Single vDbl As Double vStr As String End Type |
#pragma pack(push,4) struct vb6Type { BYTE vByte; VARIANT_BOOL vBool; short vInt; long vLng; float vSng; double vDbl; BSTR vStr; }; #pragma pack(pop) |
注意:为了与VB6.0的结构相对应,C里的结构必须按4字节对齐。
4.2 示例代码
VB6.0里将结构传递给DLL的示例代码如下:
VC++代码:
#pragma pack(push,4) struct vb6Type { BYTE vByte; VARIANT_BOOL vBool; short vInt; long vLng; float vSng; double vDbl; BSTR vStr; }; #pragma pack(pop)
vb6Type WINAPI TestStruct(vb6Type*r,vb6Type*vp) { { r->vByte = 255; SysFreeString(r->vStr); const char*szStr = "VC 字符串(ANSI)"; r->vStr = SysAllocStringByteLen(szStr,strlen(szStr)); } { vp->vDbl = 55.0; SysFreeString(vp->vStr); vp->vStr = SysAllocString(L"VC 字符串(Unicode)"); } {//返回值 vb6Type ret; ret.vByte = 0; ret.vBool = VARIANT_TRUE; ret.vInt = 2; ret.vLng = 3; ret.vSng = 4.0f; ret.vDbl = 5.0; const char*szStr = "VC 字符串"; ret.vStr = SysAllocStringByteLen(szStr,strlen(szStr)); return ret; } } |
VB6.0声明代码:
Public Type vb6Type vByte As Byte vBool As Boolean vInt As Integer vLng As Long vSng As Single vDbl As Double vStr As String End Type
Public Declare Function TestStruct Lib "Test.dll" _ (ByRef r As vb6Type, ByVal vp As Long) As vb6Type |
VB6.0调用代码:
Dim vt As vb6Type vt.vByte = 0 vt.vBool = True vt.vInt = 2 vt.vLng = 3 vt.vSng = 4! vt.vDbl = 5# vt.vStr = "VB 字符串" Dim vr As vb6Type vr = TestStruct(vt, VarPtr(vt)) |
下面是对上述代码的详细说明:
4.2.1 ByVal vp As Long
VB6.0的代码"vr = TestStruct(vt, VarPtr(vt))"中,VarPtr(vt) 将返回结构vt的首地址。这个地址传递给了 C 函数里的形参vp,对vp的处理代码如下:
{ vp->vDbl = 55.0; SysFreeString(vp->vStr); vp->vStr = SysAllocString(L"VC 字符串(Unicode)"); } |
上述代码修改vDbl为55.0;修改vStr为"VC 字符串(Unicode)"。注意:修改字符串前需要释放已有的字符串;vp->vStr是Unicode编码的BSTR。
因为传递的是地址,所以形参的改变会引起VB6.0中实参的改变。
4.2.2 ByRef r As vb6Type
这个参数要分两种情况讨论:
若结构里只有简单数据类型,没有字符串成员变量vStr,则直接传递结构变量的首地址,即VarPtr(vt);
若结构里有字符串成员变量vStr,则VB6.0会生成一个临时结构变量。将实参数值复制给这个临时结构变量。数值复制时需要注意的是字符串,复制到临时变量里的字符串是ANSI编码的。调用完DLL里的函数后,VB6.0会将临时变量的数值再复制给实参。
对形参r的 C 处理代码如下:
{ r->vByte = 255; SysFreeString(r->vStr); const char*szStr = "VC 字符串(ANSI)"; r->vStr = SysAllocStringByteLen(szStr,strlen(szStr)); } |
最重要的就是:r->vStr是一个ANSI编码的字符串。
4.2.3 返回值
DLL里的TestStruct函数返回了一个结构,这个结构里的字符串成员变量也是ANSI编码的。
5 数组
VB6.0里的数组对应于VC++里的SAFEARRAY。下面举例说明:
5.1 简单数据类型数组
数组的元素为简单数据类型时,示例代码如下:
VC++代码,导出函数 TestArrayDouble
void ReadArray(SAFEARRAY*sa) { if(NULL == sa || sa->cDims != 2 //二维数组 || sa->rgsabound[0].lLbound != 3 //第一维下标从 3 开始 || sa->rgsabound[0].cElements != 3 //第一维有 3 个元素 || sa->rgsabound[1].lLbound != 0 //第二维下标从 0 开始 || sa->rgsabound[1].cElements != 4 //第二维有 4 个元素 ) {//数组是二维的。 //第一维下标从3至5(有3个),第二维下标从0至3(有4个) //注意:VB6.0 里数组 a 的第一、二维与 sa 的第一、二维正好逆序 return; } {//读取单个元素 a(1,4),也就是读取 sa 数组中的元素(4,1) long n[2] = {1,4}; //(4,1)的逆序 double d = 0.0; SafeArrayGetElement(sa,n,&d); } }
void WINAPI TestArrayDouble(SAFEARRAY**r) { ReadArray(*r); } |
VB6.0声明代码:
Public Declare Sub TestArrayDouble Lib "Test.dll" (ByRef r() As Double) |
VB6.0调用代码:
Dim a(0 To 3, 3 To 5) As Double a(1, 4) = 10# TestArrayDouble a |
5.2 转换为Variant
数组的元素为简单数据类型时,数组可以转换为Variant。请参考示例代码:
VC++代码,导出函数TestArrayVariant
void ReadArray(SAFEARRAY*sa) { if(NULL == sa || sa->cDims != 2 //二维数组 || sa->rgsabound[0].lLbound != 3 //第一维下标从 3 开始 || sa->rgsabound[0].cElements != 3 //第一维有 3 个元素 || sa->rgsabound[1].lLbound != 0 //第二维下标从 0 开始 || sa->rgsabound[1].cElements != 4 //第二维有 4 个元素 ) {//注意:VB6.0 里数组 a 的第一、二维与 sa 的第一、二维正好逆序 return; } {//读取单个元素 a(1,4),也就是读取 sa 数组中的元素(4,1) long n[2] = {1,4}; //(4,1)的逆序 double d = 0.0; SafeArrayGetElement(sa,n,&d); } }
VARIANT WINAPI TestArrayVariant(VARIANT v,VARIANT*r) { { SAFEARRAY*sa = NULL; if(v.vt == (VT_ARRAY | VT_R8)) { sa = v.parray; ReadArray(sa); } } { SAFEARRAY*sa = NULL; if(r->vt == (VT_ARRAY | VT_R8 | VT_BYREF)) {//实参是一个变量,如:a sa = *r->pparray; } else if(r->vt == (VT_ARRAY | VT_R8)) {//实参是一个临时变量,如:(a) sa = r->parray; } ReadArray(sa); } {//返回值 VARIANT vRet; VariantInit(&vRet); VariantCopy(&vRet,&v); return vRet; } } |
VB6.0声明代码:
Public Declare Function TestArrayVariant Lib "Test.dll" _ (ByVal v As Variant, ByRef r As Variant) As Variant |
VB6.0调用代码:
Dim a(0 To 3, 3 To 5) As Double a(1, 4) = 10# Dim vr As Variant vr = TestArrayVariant(a, a) |
5.3 结构数组
VB6.0里将结构数组传递给DLL的示例代码如下:
VC++代码,导出函数TestArrayStruct
#pragma pack(push,4) struct vb6Type { BYTE vByte; VARIANT_BOOL vBool; short vInt; long vLng; float vSng; double vDbl; BSTR vStr; }; #pragma pack(pop)
void WINAPI TestArrayStruct(SAFEARRAY**r) { if(r) { SAFEARRAY* psa = *r; if(psa) { vb6Type*pType = (vb6Type*)psa->pvData; if(pType && psa->cDims == 1 //一维数组 && psa->cbElements == sizeof(vb6Type)) //单个元素字节 { int n = (psa)->rgsabound->cElements; //元素个数 const wchar_t*pStr = pType[0].vStr; //宽字符串 SysFreeString(pType[1].vStr); pType[1].vStr = SysAllocString(L"VC 字符串"); } } } } |
VB6.0声明代码:
Public Type vb6Type vByte As Byte vBool As Boolean vInt As Integer vLng As Long vSng As Single vDbl As Double vStr As String End Type
Public Declare Sub TestArrayStruct Lib "Test.dll" (ByRef r() As vb6Type) |
VB6.0调用代码:
Dim a(0 To 3) As vb6Type a(0).vStr = "VB 字符串" Call TestArrayStruct(a) |
执行完毕后a(1).vStr变为"VC 字符串"。
6 Object
VB6.0里将Object传递给DLL的示例代码如下:
VC++代码:
void WINAPI TestObject(IDispatch*v,IDispatch**r,IDispatch**vp) { CComDispatchDriver sp(v); sp.PutPropertyByName(L"Caption",&_variant_t(L"VC 设置")); } |
VB6.0声明代码:
Public Declare Function TestObject Lib "Test.dll" (ByVal v As Object, _ ByRef r As Object, ByVal vp As Long) As Variant |
VB6.0调用代码:
Private Sub cmdObject_Click() Call TestObject(cmdObject, cmdObject, VarPtr(cmdObject)) End Sub |
执行完毕后按钮cmdObject内的文本将变为"VC设置"。
7 函数
VB6.0里将函数传递给DLL的示例代码如下:
VC++代码:
void WINAPI TestFunc(void (WINAPI*vb6Func)(BSTR sv,BSTR*sr,int nv,int*nr)) { if(vb6Func) { BSTR sv = SysAllocString(L"参数 sv"); BSTR sr = SysAllocString(L"参数 sr"); int nv = 1; int nr = 2; (*vb6Func)(sv,&sr,nv,&nr);//调用 VB6.0 函数,sr 和 nr 的值可变 SysFreeString(sv); SysFreeString(sr); } } |
VB6.0声明代码:
Public Declare Sub TestFunc Lib "Test.dll" (ByVal v As Long) |
VB6.0调用代码(请放在 Module 模块里)
Private Sub vb6Func(ByVal sv As String, ByRef sr As String, _ ByVal nv As Long, ByRef nr As Long) MsgBox sv & "," & sr & "," & nv & "," & nr sv = "A" sr = "B" nv = 10 nr = 20 End Sub
Public Function vb6TestFunc() Call TestFunc(AddressOf vb6Func) End Function |
执行VB6.0函数vb6TestFunc,会调用C函数TestFunc,其参数为VB6.0函数vb6Func的地址。
VB6.0调用DLL