在VB 中调用动态连接库
 
2001-11-08· · ··yesky

1 2 3 4 5 6 7 8  下一页

  作为一种简单易用的Windows开发环境,Visual Basic从一推出就受到了广大编程人员的欢迎。它使 程序员不必再直接面对纷繁复杂的Windows消息,而可以将精力主要集中在程序功能的实现上,大大提高了编程效率。但凡事有利必有弊。VB中高度的封装和模块化减轻了编程者的负担,同时也使开发人员失去了许多访问低层API函数和直接与Windows交互的机会。因此,相比而言,VB应用程序的执行效率和功能比C/C++或Delphi生成的程序要差。为了解决这个问题,在一个大型的VB开发应用中,直接调用Windows API函数几乎是不可避免的;同时,还有可能需 要程序员自己用C/C++等开发一些动态连接库,用于在VB中调用。本文主要讨论在32位开发环 境Visual Basic 5.0中直接调用Windows 95 API函数或用户生成的32位动态连接库的方法 与规则。

  Windows动态连接库是包含数据和函数的模块,可以被其它可执行文件(EXE、DLL、OCX 等)调用。动态连接库包含两种函数:输出(exported)函数和内部(internal)函数。输出函数可以被其它模块调用,而内部函数则只能在动态连接库内部使用。尽管动态连接库也能输出 数据,但实际上它的数据通常是只在内部使用的。使用动态连接库的优点是显而易见的。将应 用程序的一部分功能提取出来做成动态连接库,不但减小了主应用程序的大小,提高了程序 运行效率,还使它更加易于升级。多个应用程序共享一个动态连接库还能有效地节省系统资 源。正因为如此,在Windows系统中,动态连接库得到了大量的使用。

  一般来说,动态连接库都是以DLL为扩展名的文件,如Kernel32.dll、commdlg.dll等。但也有例外,如16位Windows的核心部件之一GDI.exe其实也是一个动态库。编写动态连接库的工具很多,如VisualC++、BorlandC++、Delphi等,具体方法可以参见相关文档。下面只以Visual C++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中的编译选项"结构成员对齐方式(structure member alignment)" 应设成4字节,其原因将在后文详细介绍。

  4、由于在C中整型变量是4个字节,而VB中的整型变量依然只有2个字节,因此在C中声 明的整型(int)变量在VB中调用时要声明为长整型(long),而C中的短整型(short)在VB中则 要声明成整型(integer);下表针对最常用的C语言数据类型列出了与之等价的Visual Basic 类型(用于32位版本的Windows)。

A.处理使用字符串的系统Windows API过程

  如果调用的系统Windows API过程要使用字符串,那么声明语句中必须增加一个Alias 子句,以指定正确的字符集。包含字符串的系统Windows API函数实际有两种格式:ANSI和Unicode( 关于ANSI和Unicode两种字符集的区别将在后面详细阐述)。因此,在Windows头文件中,每 个包含字符串的函数都同时有ANSI版本和Unicode版本。例如,下面是SetWindowText函数 的两种C语言描述。可以看到,第一个描述将函数定义为SetWindowTextA,尾部的"A" 表明它是一个ANSI函数:

   WINUSERAPI BOOL WINAPI SetWindowTextA(HWND hWnd, LPCSTR lpString);

  第二个描述将它定义为 SetWindowTextW, 尾部的"W" 表明它是一个 Unicode 函数:

   WINUSERAPI BOOL WINAPI SetWindowTextW(HWND hWnd, LPCWSTR lpString);

  因为两个函数实际的名称都不是"SetWindowText",要引用正确的函数就必 须增加一个Alias子句:

Private Declare Function SetWindowText Lib "user32" _
Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal _
lpString As String) As Long

  应当注意,对于VB中使用的系统WindowsAPI函数,应该指定函数的ANSI版本,因为只 有WindowsNT才支持Unicode版本,而Windows95不支持这个版本。仅当应用程序只运行 在WindowsNT平台上的时候才可以使用Unicode版本。

  B.函数名是不标准的名称

  有时,个别的DLL过程的名称不是有效的标识符。例如,它可能包含了非法的字符(如连 字符),或者名称是VB的关键字(如GetObject)。在这种情况下,可以使用Alias关键字。例 如,操作环境DLLs中的某些过程名以下划线开始。尽管在VB标识符中允许使用标识符,但是 下划线不能作为标识符的第一个字符。为了使用这种过程,必须先声明一个名称合法的过程, 然后用Alias子句引用过程的真实名称:

Declare Function lopen Lib "kernel32" Alias "_lopen" _
(ByVal lpPathName As String, ByVal iReadWrite _
As Long) As Long

  在上例中,lopen是VB中使用的过程名称。而_lopen则是动态连接库中可以识别的名 称。

  C.使用序号标识DLL过程

  除了使用名称之外,还可以使用序号来标识DLL过程。某些动态连接库中不包含过程的名称,在声明它们包含的过程时必须使用序号。同使用名称标识的DLL过程相比,如果使用序号,在最终的应用程序中消耗的内存将比较少,而且速度会快些。但是,一个具体的API的序号 在不同的操作系统中可能是不同的。例如GetWindowsDirectory在Win95下的序号为432,而在WindowsNT4.0下为338。总而言之,如果希望应用程序能够在不同的操作系统下运行,那么最好不要使用序号来标识API过程。如果过程不属于API,或者应用程序使用的范围很有 限,那么使用序号还是有好处的。

  要使用序号来声明DLL过程,Alias子句中的字符串需要包含过程的序号,并在序号的 前面加一个数字标记字符(#)。例如,Windowskernel中的GetWindowsDirectory函数的序 号为432;可以用下面的语句来声明该DLL过程:

Declare Function GetWindowsDirectory Lib "kernel32" _
Alias "#432" (ByVal lpBuffer As String, _
ByVal nSize As Long) As Long

  在这里,可以使用任意的合法名称作为过程的名称,VB将用序号在DLL中寻找过程。

  为了得到要声明的过程的序号,可以使用Dumpbin.exe等实用工具(Dumpbin.exe是Microsoft VisualC++提供的一个实用工具,它的使用说明可以参见VC的文档)。利用Dumpbin,可以提取出.dll文件中的各种信息,例如DLL中的函数列表,它们的序号以及与代码有关的其它信息。

(2)、字符串参数的传递:

  与简单数据类型相比,字符串类型(String、String*n)的参数传递要复杂得多,这主要是Windows 95 API和VB使用的字符串类型不同的缘故。VB使用被称为BSTR的String数据类型,它是由自动化(以前被称为OLE Automation)定义的数据类型。一个BSTR由头部和字符串组成,头部包含了字符串的长度信息,字符串中可以包含嵌入的null值。大部分的BSTR是 Unicode的,即每个字符需要两个字节。BSTR通常以两字节的两个null字符结束。下图表示 了一个BSTR类型的字符串。

  (前缀)aTest/0
  头部BSTR指向数据的第一个字节

  另一方面,大部分的DLL过程(包括Windows 95 API中的所有过程)使用LPSTR类型字符串,这是指向标准的以null结束的C语言字符串的指针,它也被称为ASCIIZ字符串。LPSTR 没有前缀。下图显示了一个指向ASCIIZ字符串的LPSTR。

  aTest/0

  LPSTR指向一个以null结尾的字符串数据的第一个字节

  如果DLL过程需要一个LPSTR(指向以null结束的字符串的指针)作为参数,可以在VB 中将一个字符串以传值的方式传递给它。因为指向BSTR的指针实际指向以null值结束的字符串的第一个数据字节,所以对于DLL过程来说,它就是一个LPSTR。这样传入动态连接库的字符串,DLL过程也可以对它进行修改,尽管它是以传值方式传入的。只有当DLL过程需要一个指向LPSTR的指针时,才以传址的方式传入字符串,这时DLL过程得到的是一个指向字符串指针的指针(相当于C/C++中的char**),而不是通常所用的字符串的首地址(相当于C/C++中的char*)。

  当需要把一个字符串数组整个传入动态连接库时,情况就变得复杂多了,用传递简单数据类型数组的方式来传递字符串数组是行不通的。当我们以传值的方式将一个字符串数组的第一个元素传进动态连接库时,DLL过程得到的实际上是该元素压入堆栈段后的地址,而不是数据段中整个数组的首地址。也就是说,这时DLL过程只能得到数组的第一个元素,而无法访问整个数组。而以传址方式传入第一个元素时,DLL过程只能得到指向该元素在堆栈段中地址的指针,同样无法访问整个数组。这不能不说是VB的一个不足。因此,在程序设计中,如果确实需要将整个字符串数组传入动态库,就必须采取其它方法。

  我们知道,在VB中,有一种Byte数据类型。每个Byte型变量占一个字节,不含符号位,因 此所能表示的范围为0到255。这种数据类型是专门用于存放二进制数据的。为了将整个字符 串数组传进动态库,可以用字节数组来保存字符串。由于Byte是一种简单数据类型,因此字节 数组的传递是非常简单的。首先,需要把一个字符串正确地转变成一个字节数组。这要涉及一 些字符集的知识。Windows 95和VB使用不同的字符集,Windows 95 API使用的是ANSI或DBCS 字符集,而VB使用的则是Unicode字符集。所谓ANSI字符集,是指每个字符都用一个字节表示, 因此最多只能有28=256个不同的字符,这对于英语来说已经足够了,但不能完全支持其它语 言。DBCS字符集支持很多不同的东亚语言,如汉语、日语和朝鲜语,它使用数字0-255表示ASCII 字符,其它大于255或小于0的数字表明该字符属于非拉丁字符集;在DBCS中,ASCII字符的长 度是一个字节,而汉语、日语和其它东亚字符的长度是2个字节。而Unicode字符集则完全用 两个字节表示一个字符,因此最多可以表示216=65536个不同字符。也就是说,ANSI字符集中 所有的字符都只占一个字节,DBCS字符集中ASCII字符占一个字节,汉字占两个字节,Unicode 字符集中每个字符都占两个字节。由于VB与WindowsAPI使用的字符集不同,因此在进行字符 串到字节数组的转换时,当用Asc函数取得一个字符的字节码后,需要判断它是否是一个ASCII 字符;如果是ASCII字符,则在转换后的字节数组中就只占一个字节,否则要占两个字节。

  下面给出了转换函数:GetChar Byte得到一个字符的高字节或低字节,它的第一个参数 是一个字符的ASCII码,第二个参数是标志取高字节还是低字节;StrToByte按DBCS或ANSI格 式将一个字符串转换成一个字节数组,第一个参数是待转换的字符串,第二个参数是转换后的一个定长字节数组,若该数组长度不足以存放整个字符串,则截去超长的部分;ChangeStrAryToByte 利用前两个函数将字符串数组转换成字节数组,第一个参数是定长的字符串数组,其中每个元素都是一个字符串(各个元素包含的字符数可以不同),第二个参数是一个变长的字节数组, 保存转换后的结果。

Function GetCharByte(ByVal OneChar As Integer, ByVal IsHighByte As Boolean) As Byte ' 该函数获得一个字符的高字节或低字节

If IsHighByte Then
If OneChar >= 0 Then
GetCharByte = CByte(OneChar / 256)
'右移8位,得到高字节
Else
GetCharByte = CByte((OneChar
And &H7FFF) / 256) Or &H80
End If
Exit Function
Else
GetCharByte = CByte(OneChar And &HFF)
'屏蔽掉高字节,得到低字节
Exit Function
End If
End Function

Sub StrToByte(StrToChange As String, ByteArray() As Byte)
'该函数将一个字符串转换成字节数组
Dim LowBound, UpBound As Integer
Dim i, count, length As Integer
Dim OneChar As Integer

count = 0
length = Len(StrToChange)
LowBound = LBound(ByteArray)
UpBound = UBound(ByteArray)

For i = LowBound To UpBound
ByteArray(i) = 0 '初始化字节数组
Next

For i = LowBound To UpBound
count = count + 1
If count <= length Then
OneChar = Asc(Mid(StrToChange, count, 1))

If (OneChar > 255) Or (OneChar < 0) Then
'该字符是非ASCII字符
ByteArray(i) = GetCharByte(OneChar, True) '得到高字节
i = i + 1
If i <= UpBound Then ByteArray(i)
= GetCharByte(OneChar, False)
'得到低字节
Else
'该字符是ASCII字符
ByteArray(i) = OneChar
End If
Else
Exit For
End If
Next
End Sub

Sub ChangeStrAryToByte(StrAry()
As String, ByteAry() As Byte)
'将字符串数组转换成字节数组
Dim LowBound, UpBound As Integer
Dim i, count, StartPos, MaxLen As Integer
Dim TmpByte() As Byte

LowBound = LBound(StrAry)
UpBound = UBound(StrAry)
count = 0
ReDim ByteAry(0)

For i = LowBound To UpBound
MaxLen = LenB(StrAry(i))
ReDim TmpByte(MaxLen + 1)
ReDim Preserve ByteAry(count + MaxLen + 1)
Call StrToByte(StrAry(i), TmpByte) '转换一个字符串
StartPos = count
Do
ByteAry(count) = TmpByte(count - StartPos)
count = count + 1
If ByteAry(count - 1) = 0 Then Exit Do
Loop '将每一个字符串对应
的字节数组按顺序填入结果数组中
ReDim Preserve ByteAry(count - 1)
Next i
End Sub

  下面看一个转换的例子:

DimResultAry()asByte
DimSomeStr(2)asString
SomeStr(0)="测试1"
SomeStr(1)="测试222"
SomeStr(2)="测试33"
CallChangeStrAryToByte
(SomeStr,ResultAry)'转换字符串数组

  当转换完成以后,查看字节数组ResultAry,其中包含了21个元素,依次是:178,226,202,212,49,0,178,226,202,212,50,50,50,0,178,226,202,212,51,51,0。其中,[178,226]是"测"的字节码,[202,112]是"试"的字节码,49,50,51 分别为字符1、2、3的ASCII码。可见,经过转换后,字符串数组中的各个元素按顺序放在了字节数组中,相互间以终止符0分隔。

  这样,字符串数组就全部转换成了字节数组,然后只要将字节数组的第一个元素以传址的方式传入动态连接库,DLL过程就可以正确地访问数组中的所有字符串了。但是,使用这种方法,当DLL过程处理结束返回VB时,VB得到的仍然是字节数组。如果需要在VB中再次得到该字节数组表示的字符串,还要把整个字节数组重新以0为分割符分成多个子数组(每个子数组都对应原来字符串数组中的一个元素),然后使用VB函数StrConv将每个子数组转换成字符串(转换时第二个参数选vbUnicode),就可以显示或进行其它操作了。例如,其中一个子数组的名字是SubAry,则函数StrConv(SubAry,vbUnicode)就返回了它所对应的字符串。

  总之,VB应用程序和动态库间字符串参数的传递是一个比较复杂的过程,使用时要非常谨慎。同时应尽可能避免传递字符串数组类型的参数,因为这很容易引起下标越界、堆栈溢出等严重错误。

http://www.yesky.com/20011030/202753.shtml

在VB 中调用动态连接库相关推荐

  1. 怎么把dll库写成MATLAB接口,如何在Matlab中应用动态连接库接口技术

    1 引言 Matlab是当前应用最为广泛的数学软件,具有强大的数值计算.数据分析处理.系统 分析.图形显示甚至符号运算等功能.利用这一完整的数学平台,用户可以快速实现十分 复杂的功能,极大地提高工程分 ...

  2. CorelDRAWX4的VBA插件开发(三十一)使用C++制作动态连接库DLL辅助VBA构键强大功能-(5)在VBA中动态调用DLL文件

    我们先来看一下动态调用的截图 先上代码 '静态调用 Public Declare Function wodedll Lib "E:\VS-DLL\conglingkaishi\Debug\C ...

  3. c++与fortran混合语言编程中动态链接库的调用,[转载]C++与Fortran混合语言编程中动态连接库的调用...

    摘 要:介绍了C++和Fortran 90混合语言编程中,在C++中调用Fortran动态连接库(DLL,Dynamic Link Library)的方法以及参量传递.函数调用等的方法. 关键词:混合 ...

  4. CorelDRAWX4的VBA插件开发(二十七)使用C++制作动态连接库DLL辅助VBA构键强大功能-(1)前言和准备工作

    我们在使用VBA的时候会很多的局限性,包括调用库和递归函数,对指针结构体和类都不友好,对时间复杂度和空间复杂度都优化得不够,换言之VB就不是从性能出发的语言,所以难免力不从心 所以这里用C++为VBA ...

  5. 动态连接库的两种方式

    动态连接库的两种方式? 答案:调用一个DLL中的函数有两种方法: 1.载入时动态链接(load-time dynamic linking),模块非常明确调用某个导出函数,使得他们就像本地函数一样.这需 ...

  6. QT动态连接库的编写

    1.QT动态链接库的编写流程 1.1 首先在Qt Creator中创建一个动态连接库Matrix,添加类名,生成一个类(matrix.h和matrix.cpp)和一个matrix_global.h的文 ...

  7. 用g++编译生成动态连接库*.so的方法及连接

    用g++编译生成动态连接库*.so的方法及连接 1.动态库*.so的编译 这里我们用到4个文件,它们分别为:SoDemoTest.h.one.cpp.two.cpp.three.cpp.它们的内容如下 ...

  8. [转]Linux下的动态连接库及其实现机制

    摘 要:本文介绍了动态连接库的优点,详细阐述了x86体系结构上Linux系统的编译器 .连接器.加载器如何使用多种重定位方式来实现该功能 关键词:动态连接库:Linux:重定位 The Impleme ...

  9. C语言调用动态共享库

    目录 1加载sdk 2调用示例 3编译 4执行 方法一:修改LD_LIBRARY_PATH环境变量 方法二:修改/etc/ld.so.conf配置文件 1加载sdk 在程序包名下方引入动态库 #inc ...

最新文章

  1. redis trie
  2. (转载)网络抓包原理及常用抓包工具
  3. 一文盘点数据行业的动态演变
  4. 数学中的向量乘积和矩阵乘积总结
  5. 用一张白纸推导出 RAFT 算法
  6. 【独家】衣服上的铁锈去除简易方法
  7. implements Serializable
  8. Spring详解(四)------注解配置IOC、DI
  9. mysql实现自增函数
  10. C语言之结构体(2)
  11. jetson nano 相关设置(开机自动登录、取消休眠和屏保、开机自启动程序)
  12. Delphi2010
  13. hdu2553解题报告
  14. 获取select选中的值php,js如何获取select标签选中的值
  15. 半挂式洒水车的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  16. 【C#】打印机ZPL指令打印图片,将图片转成十六进制指令
  17. ads1115的程序,不知道问题出在哪里,一直读出数据都是0。大佬们帮忙看看,感激不尽
  18. LeetCode:714. 买卖股票的最佳时机含手续费(python)
  19. 以合力加速基础软件创新:拆解鲲鹏众智如何繁荣新计算生态
  20. 查看和调试core文件

热门文章

  1. 网站打开速度多少毫秒为正常_诠网科技|网站优化中跳出率多少才是合理的?...
  2. (仿头条APP项目)4.父类BaseFragment创建,用retrofit和gson获取并解析服务器端数据
  3. 【排序算法】图解桶排序
  4. hdu2147 kiki's game(巴什博弈java)
  5. JSTL 及 tablibs 的简单介绍和配置方法
  6. git stash pop冲突_这有一份 git 日常使用清单,你需要吗?
  7. datatables 无法 无法重新初始化datatable_伽巫塔罗:2020年9月运势占卜,摩羯没了热情,无法重新卡死...
  8. SSH运维总结-【liunx学习】
  9. 【干货】连交换机的攻击、防御都不懂,还做什么网络工程师
  10. html5支持原生js,HTML5怎么学原生的js?让你对前端有了新的认识