1回顶部
摘要:Visual Basic 的位操作功能较弱,甚至连最常用的移位运算都不支持,因此在使用VB开发诸如数据加密、压缩、通信之类的程序时往往困难重重。针对这一问题,本文详细地阐释了位操作的本质,并利用Win32 API函数实现了整型变量的拆分、合并、移位等VB不支持的位操作功能。 关键词:Visual Basic、位操作、移位 2回顶部 二设计思路 1. 实现整形变量的拆分、合并 整型变量的拆分、合并是经常要用到的操作,比如IP地址就是一个四字节的双字,有时候为了以点分十进制的方式显示IP地址,就需要单独取出每个字节的值,而有时候为了把点分十进制的IP地址转换为计算机内部的双字,又需要把四个字节组合成一个双字。VB没有提供这样的功能,所以整型变量的拆分、合并也是我们这次要实现的功能。另外整型变量的拆分、合并也是实现Integer、Long类型变量移位的前提条件(后面“分而治之策略”将会提到),只要实现了整型变量的拆分合并,移位问题就完全解决了。 方法1:利用API函数Copymemory实现 在这里笔者利用Win32 API 函数CopyMemory实现了整形变量的拆分、合并操作。在VB中使用API函数必须要声明,CopyMemory函数的声明代码如下: Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ 其中的参数Destination是目标内存的第一个字节地址,参数Source是被复制内存的第一个字节地址,参数Length是需要复制的字节数。 实现原理很简单:要实现拆分,就用CopyMemory函数把一个整形变量的一部分拷贝到另一个更小的整形变量的地址空间中;而实现合并,则利用CopyMemory函数把两个待合并的小变量拷贝到另一个大整形变量的地址空间中。见示例代码: Public Function Hi(ByVal Word As Integer) As Byte 根据数据类型的不同需要,笔者共设计了6个函数,HI()函数用来获得一个单字的高字节,LO()函数获得单字的低字节,HIWORD()函数获得双字的高位字,LOWORD()函数双字的低位字。CON()函数把两个字节组合成字,CONWORD()函数把两个字组合成双字。只要把这6个函数组合应用就可以随意的拆分、组合各种整型变量。例如前面提到的IP地址,IP地址是用一个DWORD类型变量存储的,在VB中则对应Long类型变量,假设一个IP地址存储在长整型变量中,就可以这样提取一个IP地址的最高字节:HI(HIWORD(lngIP))。 由于代码较长,故没有在文章中写出全部代码。 3回顶部 方法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 定义 如果显式的给一个数组变量赋值,让它指向我们自己创建的SafeArray结构,就可以通过设置SafeArray结构的pvData域来访问任意内存位置。请看示例代码: Public Declare Function VarPtrArray Lib "msvbvm60.dll" _Alias "VarPtr" (ptr() As Any) As Long 从代码中可以看到我们用一个字节数组借用了长整形变量i的地址空间,从而可以通过数组元素访问变量i的各个字节。这样也实现了拆分、组合整形变量的目的,和方法1殊途同归,但很显然方法2不需要函数调用,不需要数据复制,因此效率非常高。用这种方法,我专门构筑了一个模块:FastBitEx模块,实现了方法1中提及的6个函数的Fast版本,代码很长,不在这里给出,请读者参阅代码。 4回顶部 2.移位运算的设计实现 在很多VB的资料和代码中都用乘以2的方法实现左移,除以2的方法实现右移。这是可行的,也是有理论依据的。下图是一个BYTE类型的权值表:
可以看出每一位的权值都是比它低一位的那一位的权值的2倍,对一个BYTE变量左移一位相当于每一个二进制位都向高位移动,则每一位的权值变为原来的两倍(最高位除外),由于BYTE变量的十进制值等于它的每个二进制位的值和该位权值的乘积的总和,所以把一个BYTE变量左移和把它的十进制值乘以2是等效的,唯一的区别就是如果BYTE变量的最高位为 1,乘以2会溢出,我们要使用一个小技巧防止溢出:先把最高位屏蔽为0,再乘以2就不会溢出了。据此我们可以写出把BYTE类型变量左移1位的函数: Private Function ShLB_By1Bit(ByVal Byt As Byte) As Byte 类似的把BYTE类型变量右移1位时采用除以2的方法 ,这时要注意舍去小数位,以免VB按照四舍五入的方法处理小数位而引起结果不正确。据此我们可以写出把BYTE类型变量右移1位的函数: Private Function ShRB_By1Bit(ByVal Byt As Byte) As Byte 有了移一位的函数,那么移任意位数的函数就不难写出了:只要反复的调用ShLB_By1Bit()或ShRB_By1Bit()就可以了,参见代码中的函数ShLB() 和 ShRB()。 5回顶部 至此字节变量的移位问题已经得到解决,现在再来看单字和双字的移位,它们分别对应VB中的Integer和Long类型。用乘以2和除以2的方法还行吗?用几个数试验一下就会发现,这个方法失灵了。请看各种运算结果的对比: A=1001’0111’1110’1100 右移一位: 0100’1011’1111’0110 (A/2):1100’1011’1111’0110 问题好象变的有点复杂了,其实导致这个方法失灵的最根本的原因是VB把Integer和Long类型当做有符号数理解,把一个有符号数乘以2或除以2,最高位(即符号位)根本就没有参与运算,这一点从上面的运算结果对比就可以看出来:把A除以2 以后最高位还是1,根本就没有变,而右移一位后最高位补入的是0,两种运算的结果自然是相去甚远。不只是符号位的问题,如果选用其它的数据来对比还会发现更多的问题,这里就不再赘述了。难道就真的没有办法了吗?办法当然是有的,既然已经实现了字节的移位操作,那么可以 用“分而治之”的策略,把Integer变量一分为二,拆成两个字节,把这两个字节交给ShLB()或ShRB(),把它俩各移一位,最后把移位后的两字节重新组合成一个Integer变量就是移位后的结果了,这不就实现了Integer类型变量的移位了吗。这种方法完全绕过了有符号数的符号位给我们带来的众多麻烦,顺利的实现了目的。用这种方法需要注意一点:如果是左移,要保证把低字节的最高位移入高字节的最低位,反之如果是右移,要把高字节的最低位移入低字节的最高位。从下面的代码中可以看到实现的过程: Private Function ShLW_By1Bit(ByVal Word As Integer) As Integer 至于Long类型,和Integer类型一样,属于有符号数,也不能用乘以2和除以2的方法实现移位。我们只好和处理Integer类型一样如法炮制,用分而治之的方法实现移位。具体过程不再赘述,请参看代码。 6回顶部 3.移位运算的性能优化 本文中的移位实现方法偏重于代码的可读性,没有优化代码的性能,因此不适用于对性能要求苛刻的场合。为了优化性能,可以用查表法来优化执行速度,这是一种拿空间换时间的方案,移位结果可以事先都计算出来,保存在移位表中,用的时候查表,比用*2,/2快多了。比如,字节类型的移位表数组定义如下: dim aSHLB(0 to 255,1 to 7) as byte'字节左移表 使用方法也很简单,比如想要求字节变量x左移一位的结果,只需使用aSHLB(x,1)就可得到,和函数调用很相似。当然,与函数调用不同的是,使用移位表之前一定要初始化移位表的所有元素,否则会得到错误的结果。 Integer类型的移位也可以用查表法,移位表占用 65535 * 15 * 2 * 2 个字节的内存空间。 移位表数组定义如下: aSHLW(0 to &Hffff&,1 to 15) as integer'单字的左移表 注意:Integer是有符号类型,造表的时候要用它的无符号值来造表,同样查表的时候也要用它的无符号值来查表。(因为数组下标是不允许负数的。) 求Integer类型无符号值的方法是:(Int and &hFFFF&),注意,不等同于CLng(Int) 遗憾的是,Long类型无法造表,因为Long类型的值的范围是 4 GB,如果对它造表,那么表的大小就会超出总的内存地址空间。 查表移位的代码请参见本文附带的代码,这里就不给出了。 三 结语 要想实现本文所述的那些位操作函数其实有很多方法,本文所用的方式未必是最好的,主要是为了提供一种解决问题的思路:在编程过程中遇到难以解决的问题时,想一想能不能把大问题分解成能解决或已解决的小问题,这就是“分而治之”的策略。因笔者水平有限,本文难免会有疏漏和不足之处,欢迎批评指正,有意见或建议给我发电子邮件liuqi5521@sina.com。 本程序在 Win2000+VB6.0下调试通过。 |
闂佽 鍋撻柟顖滃椤ユ垿鏌熺€涙ê濮囧┑顕嗘嫹闂佸搫琚崕鍐诧耿閸涙潙缁╅柟顖滃椤ワ拷>>
正在阅读:巧用Win32 API函数增强VB位操作功能巧用Win32 API函数增强VB位操作功能
2005-09-15 10:14
出处:
责任编辑:moningfeng