巧用Win32 API函数增强VB位操作功能

** 巧用 ** ** Win32 API ** ** 函数增强 ** ** VB ** ** 位操作功能 ** **

**

(作者:刘琦)

** 摘要: ** Visual Basic 的位操作功能较弱,甚至连最常用的移位运算都不支持,因此在使用VB开发诸如数据加密、压缩、通信之类的程序时往往困难重重。针对这一问题,本文详细地阐释了位操作的本质,并利用Win32 API函数实现了整型变量的拆分、合并、移位等VB不支持的位操作功能。

** 关键词: ** Visual Basic、位操作、移位

** 一 引言

**

笔者在编程实践中发现,VB对位操作的支持仅限于AND、OR、XOR几种位运算,远远不如其他的开发工具那样全面(如Visual C++、C++Builder、Delphi等开发工具都提供了整形变量的移位、拆分、合并的运算),因此在使用VB编写诸如加密之类的通用数据处理程序时往往困难重重。为了使以后的开发工作不再陷入僵局,我开始寻求增强VB位操作功能的通用方法,以达到一劳永逸的效果。

VB的数据类型不够丰富,整形数只包括Byte、Integer、Long三种类型,分别对应C++中的 unsigned char、short 和 long 类型,而我们常用的二字节无符号整形unsigned short(也叫 “ 字 ” 、Word)、四字节无符号整形unsigned long(也叫 “ 双字 ” 、DWord)在VB中却没有被支持。 但好在无符号数和有符号数在二进制的层次上没有任何差别,不同之处仅在于编译器对变量的理解。在进行位操作时我们只关心变量的二进制位,因此VB中的Integer类型可以当作Word类型使用,Long类型则对应DWord。 (此后文中提及的 Integer 类型均指 VB Integer 类型, Long 类型均指 VB Long 类型, Word 、DWord 类型则是不依赖于特定编译器的对二字节、四字节 整形 值的通用称呼)再来看位运算方面,可以看出 VB 不支持整型变量的左移、右移、拆分、合并等操作。

经过上述的分析之后,已经确定了工作的可行性和工作目标,于是笔者决定开发一个通用模块来增强 VB 的位操作功能,这个模块是可重用的,只要把这个模块加入工程中,就可以象使用 VB 的内置函数一样透明的使用模块中的函数,非常方便。如果使用大量的可重用模块来开发程序,则开发周期短,代码可读性好,易于维护,不容易出错。

** 二设计思路

**

** 1. ** ** 实现整形变量的拆分、合并

**

整型变量的拆分、合并是经常要用到的操作,比如 IP 地址就是一个四字节的双字,有时候为了以点分十进制的方式显示 IP 地址,就需要单独取出每个字节的值,而有时候为了把点分十进制的 IP 地址转换为计算机内部的双字,又需要把四个字节组合成一个双字。 VB 没有提供这样的功能,所以整型变量的拆分、合并也是我们这次要实现的功能。另外整型变量的拆分、合并也是实现 Integer 、 Long 类型变量移位的前提条件 ( 后面“分而治之策略”将会提到 ) ,只要实现了整型变量的拆分合并,移位问题就完全解决了。

** 方法 1:利用API函数Copymemory实现 **

在这里笔者利用 Win32 API 函数 CopyMemory 实现了整形变量的拆分、合并操作。在 VB 中使用 API 函数必须要声明, CopyMemory 函数的声明代码如下:

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _

(Destination As Any , Source As Any , ByVal Length As Long)

其中的参数 Destination 是目标内存的第一个字节地址,参数 Source 是被复制内存的第一个字节地址,参数 Length 是需要复制的字节数。

实现原理很简单:要实现拆分,就用 CopyMemory 函数把一个整形变量的一部分拷贝到另一个更小的整形变量的地址空间中;而实现合并,则利用 CopyMemory 函数把两个待合并的小变量拷贝到另一个大整形变量的地址空间中。见示例代码:

Public Function Hi(ByVal Word As Integer) As Byte

' 取一个字( Word )的高字节( Byte )

'INPUT-------------------------------------------

'Word 字( Word )

'OUTPUT------------------------------------------

' 返回值 Word 参数的高字节

'Last updated by Liu Qi 2004-3-20 。

Dim bytRet As Byte

CopyMemory bytRet , ByVal VarPtr(Word) + 1 , 1’ 把 Word 的高字节的内容烤入 bytRet 的地址处

Hi = bytRet’ 返回结果

End Function

根据数据类型的不同需要,笔者共设计了 6 个函数, HI ()函数用来获得一个单字的高字节, LO ()函数获得单字的低字节, HIWORD ()函数获得双字的高位字, LOWORD ()函数双字的低位字。CON()函数把两个字节组合成字, CONWORD ()函数把两个字组合成双字。只要把这6个函数组合应用就可以随意的拆分、组合各种整型变量。例如前面提到的IP地址,IP地址是用一个DWORD类型变量存储的,在VB中则对应 Long 类型变量,假设一个IP地址存储在长整型变量中,就可以这样提取一个IP地址的最高字节:HI(HIWORD( lng IP))。 **

**

由于代码较长,故没有在文章中写出全部代码,全部代码请到编辑部网站下载。 **

**

** 方法 2:利用安全数组借用内存的方法实现

**

方法 1虽然用起来简单方便,但是要执行API函数调用,函数调用时要保存现场、恢复现场,时间开销很大,效率太低,因此不适合大数据量密集运算的场合。笔者在开发加密软件时曾使用方法1来处理文件数据,效果很不理想,速度奇慢。其实有一种方法可以巧妙的骗过VB,让一个数组直接访问其他变量的内存空间,从而达到 拆分、合并整形变量的目的 。由于这种方法省去了 API函数调用,因此效率非常高。下面就让我们认识一下VB中的安全数组。VB中的安全数组与C语言中的数组有很大的差别,虽然在VB和C语言中数组变量都是指针,但C语言中的数组变量直接指向数组元素,而 VB中的数组变量却是指向一个SafeArray结构,这个SafeArray结构中的pvData域才指向数组元素。见图

那么这个 SafeArray结构是做什么用的呢?它存储着数组的上界、下界、维数、元素大小等一系列的信息,正是SafeArray结构的存在,使得VB程序能够对数组的访问做越界检查,这就是为什么VB中的数组叫做安全数组的原因,而C语言中的数组显然不具备越界检查的能力。当然安全数组的缺点就是没有C语言的数组灵活,但尽管如此,我们还是有办法操纵它,通过对安全数组的操纵,可以让它访问任意的内存位置,甚至包括其他变量的内存空间。对于一维数组来说,它的SafeArray结构如下:

Type SafeArray1d '1维数组的 SafeArray 定义

cDims As Integer '维数

fFeatures As Integer '标志

cbElements As Long '单个元素的字节数

clocks As Long '锁定计数

pvData As Long '指向数组元素的指针

cElements As Long '维定义,该维的元素个数

Lbound As Long '该维的下界

End Type

如果显式的给一个数组变量赋值,让它指向我们自己创建的 SafeArray结构,就可以通过设置SafeArray结构的pvData域来访问任意内存位置。请看示例代码:

Public Declare Function VarPtrArray Lib "msvbvm60.dll" _

Alias "VarPtr" (ptr() As Any) As Long

Private Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Private Sub Command2_Click()

Dim pBytesInLong() As Byte

Dim SA1D As SafeArray1d

Dim i As Long

With SA1D

.cDims = 1

.fFeatures = 17

.cbElements = 1

.clocks = 0

.pvData = VarPtr(i) '使数组的数据指针指向长整形变量 i

.cElements = 4

.Lbound = 0

End With

'使数组变量(其实就是个指针)指向我们自己创建的 SafeArray1d 结构

CopyMemory ByVal VarPtrArray(pBytesInLong), VarPtr(SA1D), 4

i = &HFFFFFFFF

MsgBox pBytesInLong(1) '访问长整形变量的第2个字节(从低处开始数)

pBytesInLong(3) = 0 '把全部数组元素设为0

pBytesInLong(2) = 0

pBytesInLong(1) = 0

pBytesInLong(0) = 0

MsgBox i '你会发现 i 也变成了 0

'把数组变量(其实就是个指针)指向 0,既 C 语言中的 NULL

CopyMemory ByVal VarPtrArray(pBytesInLong), 0&, 4

End Sub

从代码中可以看到我们用一个字节数组借用了长整形变量 i的地址空间,从而可以通过数组元素访问变量i的各个字节。这样也实现了拆分、组合整形变量的目的,和方法1殊途同归,但很显然方法2不需要函数调用,不需要数据复制,因此效率非常高。用这种方法,我专门构筑了一个模块:FastBitEx模块,实现了方法1中提及的6个函数的Fast版本,代码很长,不在这里给出,请读者参阅代码。

** 2.移位运算的设计实现

**

在很多 VB 的资料和代码中都用乘以 2 的方法实现左移,除以 2 的方法实现右移。这是可行的,也是有理论依据的。下图是一个 BYTE 类型的权值表:

位序号

|

7

|

6

|

5

|

4

|

3

|

2

|

1

|

0

---|---|---|---|---|---|---|---|---

权值

|

2 7

|

2 6

|

2 5

|

2 4

|

2 3

|

2 2

|

2 1

|

2 0

可以看出每一位的权值都是比它低一位的那一位的权值的2倍,对一个 BYTE 变量左移一位相当于每一个二进制位都向高位移动,则每一位的权值变为原来的两倍(最高位除外),由于 BYTE 变量的十进制值等于它的每个二进制位的值和该位权值的乘积的总和,所以把一个 BYTE 变量左移和把它的十进制值乘以2是等效的,唯一的区别就是如果 BYTE 变量的最高位为 1,乘以2会溢出,我们要使用一个小技巧防止溢出:先把最高位屏蔽为0,再乘以2就不会溢出了。据此我们可以写出把 BYTE 类型 变量左移1位的函数:

Private Function ShLB_By1Bit(ByVal Byt As Byte) As Byte

‘ 把 BYTE 类型 变量左移1位的函数,参数 Byt是待移位的字节,函数返回移位结果

‘ <span lang="EN-US" style="FONT-FAMILY: 宋体; mso-hansi-font-family: 'Times New

Published At
Categories with Web编程
Tagged with
comments powered by Disqus