完整版VB调用动态链接库DLL.docx
- 文档编号:24048423
- 上传时间:2023-05-23
- 格式:DOCX
- 页数:22
- 大小:25.19KB
完整版VB调用动态链接库DLL.docx
《完整版VB调用动态链接库DLL.docx》由会员分享,可在线阅读,更多相关《完整版VB调用动态链接库DLL.docx(22页珍藏版)》请在冰豆网上搜索。
完整版VB调用动态链接库DLL
VB调用动态链接库(DLL)
作为一种简单易用的Windows开发环境,VisualBasic从一推出就受到了广大编程人员的欢迎。
它使程序员不必再直接面对纷繁复杂的Windows消息,而可以将精力主要集中在程序功能的实现上,大大提高了编程效率。
但凡事有利必有弊。
VB中高度的封装和模块化减轻了编程者的负担,同时也使开发人员失去了许多访问低层API函数和直接与Windows交互的机会.因此,相比而言,VB应用程序的执行效率和功能比C/C++或Delphi生成的程序要差。
为了解决这个问题,在一个大型的VB开发应用中,直接调用WindowsAPI函数几乎是不可避免的;同时,还有可能需要程序员自己用C/C++等开发一些动态连接库,用于在VB中调用。
本文主要讨论在32位开发环境VisualBasic5。
0中直接调用Windows95API函数或用户生成的32位动态连接库的方法与规则。
Windows动态连接库是包含数据和函数的模块,可以被其它可执行文件(EXE、DLL、OCX等)调用。
动态连接库包含两种函数:
输出(exported)函数和内部(internal)函数.输出函数可以被其它模块调用,而内部函数则只能在动态连接库内部使用。
尽管动态连接库也能输出数据,但实际上它的数据通常是只在内部使用的。
使用动态连接库的优点是显而易见的.将应用程序的一部分功能提取出来做成动态连接库,不但减小了主应用程序的大小,提高了程序运行效率,还使它更加易于升级.多个应用程序共享一个动态连接库还能有效地节省系统资源。
正因为如此,在Windows系统中,动态连接库得到了大量的使用。
一般来说,动态连接库都是以DLL为扩展名的文件,如Kernel32.dll、commdlg.dll等.但也有例外,如16位Windows的核心部件之一GDI。
exe其实也是一个动态库。
编写动态连接库的工具很多,如VisualC++、BorlandC++、Delphi等,具体方法可以参见相关文档。
下面只以VisualC++5。
0为例,介绍一下开发应用于VisualBasic5.0的动态连接库时应注意的问题(本文中所有涉及C/C++语言或编译环境的地方,都以VC5为例;所有涉及VisualBasic的地方都以VB5为例)。
作为一种32位Windows应用程序的开发工具,VB5生成的exe文件自然也都是32位的,通常情况下也只能调用32位的动态连接库.但是,并不是所有的32位动态库都能被VB生成的exe文件正确地识别。
一般来说,自己编写用于VB应用程序调用的动态连接库时,应注意以下几个方面的问题:
1、生成动态库时要使用__stdcall调用约定,而不能使用缺省的__cdecl调用约定;__stdcall约定通常用于32位API函数的调用。
2、在VC5中的定义文件(。
def)中,必须列出输出函数的函数名,以强制VC5系统将输出函数的装饰名(decoratedname)改成普通函数名;所谓装饰名是VC的编译器在编译过程中生成的输出函数名,它包含了用户定义的函数名、函数参数及函数所在的类等多方面的信息。
由于在VC5中定义文件不是必需的,因此工程不包含定义文件时VC5就按自己的约定将用户定义的输出函数名修改成装饰名后放到输出函数列表中,这样的输出函数在VB生成的应用程序中是不能正确调用的(除非声明时使用Alias子句)。
因此需要增加一个.def文件,其中列出用户需要的函数名,以强制VC5不按装饰名进行输出。
3、VC5中的编译选项”结构成员对齐方式(structurememberalignment)”应设成4字节,其原因将在后文详细介绍。
4、由于在C中整型变量是4个字节,而VB中的整型变量依然只有2个字节,因此在C中声明的整型(int)变量在VB中调用时要声明为长整型(long),而C中的短整型(short)在VB中则要声明成整型(integer);下表针对最常用的C语言数据类型列出了与之等价的VisualBasic类型(用于32位版本的Windows).
C语言数据类型在VisualBasic中声明为调用时使用的表达式
ATOMByValvariableAsInteger结果为Integer类型的表达式
BOOLByValvariableAsLong结果为Long类型的表达式
BYTEByValvariableAsByte结果为Byte类型的表达式
CHARByValvariableAsByte结果为Byte类型的表达式
COLORREFByValvariableAsLong结果为Long类型的表达式
DWORDByValvariableAsLong结果为Long类型的表达式
HWND,HDC,HMENUByValvariableAsLong结果为Long类型的表达式等Windows句柄
INT,UINTByValvariableAsLong结果为Long类型的表达式
LONGByValvariableAsLong结果为Long类型的表达式
LPARAMByValvariableAsLong结果为Long类型的表达式
LPDWORDvariableAsLong结果为Long类型的表达式
LPINT,LPUINTvariableAsLong结果为Long类型的表达式
LPRECTvariableAstype自定义类型的任意变量
LPSTR,LPCSTRByValvariableAsString结果为String类型的表达式
LPVOIDvariableAsAny任何变量(在传递字符串的时候使用ByVal)
LPWORDvariableAsInteger结果为Integer类型的表达式
LRESULTByValvariableAsLong结果为Long类型的表达式
NULLAsAny或ByValNothing或
ByValvariableAsLongByVal0&或VBNullString
SHORTByValvariableAsInteger结果为Integer类型的表达式
VOIDSubprocedure不可用
WORDByValvariableAsInteger结果为Integer类型的表达式
WPARAMByValvariableAsLong结果为Long类型的表达式
5、VB中进行32位动态库的声明时,函数名是大小写敏感的.在获得了需要的动态连接库之后,就可以在VB中进行调用了。
但是,由于VB不能验证应用程序传递到动态连接库中的参数值是否正确,因此VB程序中大量的API调用可能会降低整个应用程序的稳定性,也会增加以后维护的难度。
所以,决定在VB程序中直接调用API函数时要慎重,但适当的使用API调用确实能够有效地提高VB程序的性能。
这之间的平衡需要编程人员根据实际情况来掌握。
下面就具体介绍一下在VB中调用API函数时需要做的工作.
要声明一个DLL过程,首先需要在代码窗口的"通用(General)”部分增加一个Declare语句。
如果该过程返回一个值,应将其声明为Function:
DeclareFunctionpublicnameLib"libname"[Alias"alias”][([[ByVal]variable[Astype][,[ByVal]variable[Astype]]...])]AsType
如果过程没有返回值,可将其声明为Sub:
DeclareSubpublicnameLib"libname"[Alias”alias”][([[ByVal]variable[Astype][,[ByVal]variable[Astype]]。
。
。
])]
缺省情况下,在标准模块中声明的DLL过程,可以在应用程序的任何地方调用它。
在其它类型的模块中定义的DLL过程则是模块私有的,必须在它们前面声明Private关键字,以示区分。
下面分别介绍声明语句的各个组成部分。
(一)、指定动态库:
Declare语句中的Lib子句用来告诉VisualBasic如何找到包含过程的。
dll文件。
如果引用的过程属于Windows核心库(User32、Kernel32或GDI32),则可以不包含文件扩展名,如:
DeclareFunctionGetTickCountLib"kernel32"Alias”GetTickCount”()AsLong
对于其它动态连接库,可以在Lib子句指定文件的路径:
DeclareFunctionlzCopyLib"c:
windowslzexpand.dll”_
(ByValSAsInteger,ByValDAsInteger)AsLong
如果未指定libname的路径,VisualBasic将按照下列顺序查找该文件:
①.exe文件所在的目录
②当前目录
③Windows系统目录
④Windows目录
⑤Path环境变量中的目录
下表中列出了常用的操作系统环境库文件。
动态链接库描述
Advapi32。
dll高级API服务,支持大量的API(其中包括许多安全与注册方面的调用)
Comdlg32.dll通用对话框API库
Gdi32.dll图形设备接口API库
Kernel32.dllWindows32位核心的API支持
Lz32.dll32位压缩例程
Mpr.dll多接口路由器库
Netapi32.dll32位网络API库
Shell32。
dll32位ShellAPI库
User32。
dll用户接口例程库
Version。
dll版本库
Winmm.dllWindows多媒体库
Winspool。
drv后台打印接口,包含后台打印API调用。
对于Windows的系统API函数,可以利用VB提供的工具APIViewer查找某一函数及其相关数据结构和常数的声明,并复制到自己的程序中。
(二)、使用别名:
Declare语句中的Alias子句是一个可选的部分,用户可以通过它所标识的别名对动态库中的函数进行引用。
例如,在下面的语句中,声明了一个在VB中名为MyFunction的函数,而它在动态库Mydll.dll中最初的名字是MyFunctionX。
PrivateDeclareFunctionMyFunctionLib”Mydll。
dll"_
Alias"MyFunctionX"()AsLong
需要注意的是,Alias子句中的函数名是大小写敏感的,也就是说,必须与函数在生成时的声明(如在C源文件中的声明)一致。
这是因为32位动态库与16位动态库不同,其中的函数名是区分大小写的。
同样道理,如果没有使用Alias子句,那么在Function(或Sub)后的函数名也是区分大小写的。
通常在以下几种情况时需要使用Alias子句:
A。
处理使用字符串的系统WindowsAPI过程
如果调用的系统WindowsAPI过程要使用字符串,那么声明语句中必须增加一个Alias子句,以指定正确的字符集。
包含字符串的系统WindowsAPI函数实际有两种格式:
ANSI和Unicode(关于ANSI和Unicode两种字符集的区别将在后面详细阐述)。
因此,在Windows头文件中,每个包含字符串的函数都同时有ANSI版本和Unicode版本。
例如,下面是SetWindowText函数的两种C语言描述。
可以看到,第一个描述将函数定义为SetWindowTextA,尾部的"A”表明它是一个ANSI函数:
WINUSERAPIBOOLWINAPISetWindowTextA(HWNDhWnd,LPCSTRlpString);
第二个描述将它定义为SetWindowTextW,尾部的"W"表明它是一个Unicode函数:
WINUSERAPIBOOLWINAPISetWindowTextW(HWNDhWnd,LPCWSTRlpString);
因为两个函数实际的名称都不是”SetWindowText",要引用正确的函数就必须增加一个Alias子句:
PrivateDeclareFunctionSetWindowTextLib”user32"_
Alias”SetWindowTextA"(ByValhwndAsLong,ByVal_
lpStringAsString)AsLong
应当注意,对于VB中使用的系统WindowsAPI函数,应该指定函数的ANSI版本,因为只有WindowsNT才支持Unicode版本,而Windows95不支持这个版本.仅当应用程序只运行在WindowsNT平台上的时候才可以使用Unicode版本。
B。
函数名是不标准的名称
有时,个别的DLL过程的名称不是有效的标识符.例如,它可能包含了非法的字符(如连字符),或者名称是VB的关键字(如GetObject).在这种情况下,可以使用Alias关键字。
例如,操作环境DLLs中的某些过程名以下划线开始。
尽管在VB标识符中允许使用标识符,但是下划线不能作为标识符的第一个字符。
为了使用这种过程,必须先声明一个名称合法的过程,然后用Alias子句引用过程的真实名称:
DeclareFunctionlopenLib"kernel32"Alias”_lopen"_
(ByVallpPathNameAsString,ByValiReadWrite_
AsLong)AsLong
在上例中,lopen是VB中使用的过程名称。
而_lopen则是动态连接库中可以识别的名称。
C。
使用序号标识DLL过程
除了使用名称之外,还可以使用序号来标识DLL过程。
某些动态连接库中不包含过程的名称,在声明它们包含的过程时必须使用序号.同使用名称标识的DLL过程相比,如果使用序号,在最终的应用程序中消耗的内存将比较少,而且速度会快些。
但是,一个具体的API的序号在不同的操作系统中可能是不同的。
例如GetWindowsDirectory在Win95下的序号为432,而在WindowsNT4。
0下为338。
总而言之,如果希望应用程序能够在不同的操作系统下运行,那么最好不要使用序号来标识API过程.如果过程不属于API,或者应用程序使用的范围很有限,那么使用序号还是有好处的。
要使用序号来声明DLL过程,Alias子句中的字符串需要包含过程的序号,并在序号的前面加一个数字标记字符(#)。
例如,Windowskernel中的GetWindowsDirectory函数的序号为432;可以用下面的语句来声明该DLL过程:
DeclareFunctionGetWindowsDirectoryLib”kernel32”_
Alias”#432”(ByVallpBufferAsString,_
ByValnSizeAsLong)AsLong
在这里,可以使用任意的合法名称作为过程的名称,VB将用序号在DLL中寻找过程.
为了得到要声明的过程的序号,可以使用Dumpbin.exe等实用工具(Dumpbin。
exe是MicrosoftVisualC++提供的一个实用工具,它的使用说明可以参见VC的文档).利用Dumpbin,可以提取出.dll文件中的各种信息,例如DLL中的函数列表,它们的序号以及与代码有关的其它信息。
(三)、使用值或引用传递
在缺省的情况下,VB以引用方式传递所有参数(ByRef)。
这意味着并没有传递实际的参数值,VB只传递了数据的32位地址。
另外有许多DLL过程要求参数以值方式传递(ByVal).这意味着它们需要实际的数据,而不是数据的内存地址。
如果过程需要一个传值参数,而传递给它的参数是一个指针,那么由于得到了错误的数据,该过程将不能正确地工作。
要使参数以使用值方式传递,在Declare语句中需要在参数声明的前面加上ByVal关键字。
例如InvertRect过程要求第一个参数用传值方式传递,而第二个用引用方式传递:
DeclareFunctionInvertRectLib"user32"Alias_
"InvertRectA"(ByValhdcAsLong,lpRectAsRECT)AsLong
动态连接库的参数传递是一个复杂的问题,也是VB中调用动态连接库时最容易出现错误的地方。
参数类型或传递方式的声明错误都可能导致应用程序出现GPF(通用保护错误),甚至使操作系统崩溃,因此我们将在后面专门详细地讨论这个问题.
(四)、灵活的参数类型
某些DLL过程的同一个参数能够接受多种数据类型。
如果需要传递多种类型的数据,可以将参数声明为AsAny,从而取消类型限制。
例如,下面的声明中的第三个参数(lpptAsAny)既可以传递一个POINT结构的数组,也可以传递一个RECT结构:
DeclareFunctionMapWindowPointsLib"user32”Alias_
"MapWindowPoints”(ByValhwndFromAsLong,_
ByValhwndToAsLong,lpptAsAny,_
ByValcPointsAsLong)AsLong
AsAny子句提供了一定的灵活性,但是,由于它不进行任何的类型检查,风险也随之增加。
因此在使用AsAny子句时,必须仔细检查所有参数的类型。
正确的函数声明是在VB中调用动态连接库的前提,但要想在VB中用对、用好动态库中的函数,仅仅有声明还是远远不够的。
前面已经说过,由于VB不能验证应用程序传递到动态连接库中的参数值是否正确,因此就要求程序员应对参数类型有非常详细的了解,否则很容易引起应用程序发生通用保护错或导致潜在的Bug,降低软件的可靠性。
下面将参数类型分为简单数据类型、字符串、和用户自定义类型三种分别进行讨论。
(1)、简单数据类型:
简单数据类型是指Numeric数据类型(包括Integer、Long、Single、Double、Currency类型)、Byte数据类型和Boolean数据类型。
它们的共同的特点是结构简单,操作系统在处理时不必进行特殊的转换。
简单数据类型参数的传递比较简单。
我们知道,在VB中传递参数的方式有两种:
传值(Byval)和传址(ByRef),缺省的方式是传址。
所谓传值,就是对一个变量的具体值进行传递;而传址则是传递变量的地址.例如,在VB程序中需要将一个整型变量m=10的值传进动态库,如果用传值方式,那么传进动态库的值就是10,而在传址方式下,传入的则是变量m的地址,相当于C/C++中&;m的值。
需要注意的是,以传值方式传进动态连接库的变量,其值在动态库中是不能被改变的;如果需要在动态连接库中修改传入参数的值,则必须使用传址方式。
一般来说,在VB和动态连接库之间传递单个的简单数据类型,只要注意了以上几个方面就可以了。
当需要将一个简单数据类型的整个数组传进动态库时,必须将相应参数声明为传址方式,然后把数组的第一个元素作为参数传入,这样在动态连接库中就得到了数组的首地址,从而可以对整个数组进行访问。
例如,声明了一个名为ReadArray的DLL过程,要求传入一个整型数组aArray:
DeclareFunctionReadArrayLib"mydll。
dll"_
(aArrayAsInteger)AsInteger
在调用时可以采用如下方式:
Dimret,I(5)asInteger
……
ret=ReadArray(I(0))注释:
将整个数组传入动态连接库
(2)、字符串参数的传递:
与简单数据类型相比,字符串类型(String、String*n)的参数传递要复杂得多,这主要是Windows98API和VB使用的字符串类型不同的缘故。
VB使用被称为BSTR的String数据类型,它是由自动化(以前被称为OLEAutomation)定义的数据类型。
一个BSTR由头部和字符串组成,头部包含了字符串的长度信息,字符串中可以包含嵌入的null值.大部分的BSTR是Unicode的,即每个字符需要两个字节。
BSTR通常以两字节的两个null字符结束。
下图表示了一个BSTR类型的字符串。
FunctionGetCharByte(ByValOneCharAsInteger,ByValIsHighByteAsBoolean)AsByte注释:
该函数获得一个字符的高字节或低字节
IfIsHighByteThen
IfOneChar>=0Then
GetCharByte=CByte(OneChar256)
注释:
右移8位,得到高字节
Else
GetCharByte=CByte((OneChar
And&H7FFF)256)Or&H80
EndIf
ExitFunction
Else
GetCharByte=CByte(OneCharAnd&HFF)
注释:
屏蔽掉高字节,得到低字节
ExitFunction
EndIf
EndFunction
SubStrToByte(StrToChangeAsString,ByteArray()AsByte)
注释:
该函数将一个字符串转换成字节数组
DimLowBound,UpBoundAsInteger
Dimi,count,lengthAsInteger
DimOneCharAsInteger
count=0
length=Len(StrToChange)
LowBound=LBound(ByteArray)
UpBound=UBound(ByteArray)
Fori=LowBoundToUpBound
ByteArray(i)=0注释:
初始化字节数组
Next
Fori=LowBoundToUpBound
count=count+1
Ifcount<=lengthThen
OneChar=Asc(Mid(StrToChange,count,1))
If(OneChar&gt;255)Or(OneChar<;0)Then
注释:
该字符是非ASCII字符
ByteArray(
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 完整版 VB 调用 动态 链接 DLL