|
|
|
|
摘要:Visual Basic 的位操作功能较弱,甚至连最常用的移位运算都不支持,因此在使用VB开发诸如数据加密、压缩、通信之类的程序时往往困难重重。针对这一问题,本文详细地阐释了位操作的本质,并利用Win32 API函数实现了整型变量的拆分、合并、移位等VB不支持的位操作功能。 本文相关代码( 附件 ) 一 引言 笔者在编程实践中发现,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是目标内存的第一个字节地址,参数Source是被复制内存的第一个字节地址,参数Length是需要复制的字节数。
实现原理很简单:要实现拆分,就用CopyMemory函数把一个整形变量的一部分拷贝到另一个更小的整形变量的地址空间中;而实现合并,则利用CopyMemory函数把两个待合并的小变量拷贝到另一个大整形变量的地址空间中。见示例代码:
由于代码较长,故没有在文章中写出全部代码。
方法2:利用安全数组借用内存的方法实现 方法1虽然用起来简单方便,但是要执行API函数调用,函数调用时要保存现场、恢复现场,时间开销很大,效率太低,因此不适合大数据量密集运算的场合。笔者在开发加密软件时曾使用方法1来处理文件数据,效果很不理想,速度奇慢。其实有一种方法可以巧妙的骗过VB,让一个数组直接访问其他变量的内存空间,从而达到拆分、合并整形变量的目的。由于这种方法省去了API函数调用,因此效率非常高。下面就让我们认识一下VB中的安全数组。VB中的安全数组与C语言中的数组有很大的差别,虽然在VB和C语言中数组变量都是指针,但C语言中的数组变量直接指向数组元素,而 VB中的数组变量却是指向一个SafeArray结构,这个SafeArray结构中的pvData域才指向数组元素。 那么这个SafeArray结构是做什么用的呢?它存储着数组的上界、下界、维数、元素大小等一系列的信息,正是SafeArray结构的存在,使得VB程序能够对数组的访问做越界检查,这就是为什么VB中的数组叫做安全数组的原因,而C语言中的数组显然不具备越界检查的能力。当然安全数组的缺点就是没有C语言的数组灵活,但尽管如此,我们还是有办法操纵它,通过对安全数组的操纵,可以让它访问任意的内存位置,甚至包括其他变量的内存空间。对于一维数组来说,它的SafeArray结构如下: 2.移位运算的设计实现 在很多VB的资料和代码中都用乘以2的方法实现左移,除以2的方法实现右移。这是可行的,也是有理论依据的。下图是一个BYTE类型的权值表: 位序号 76543210 权值 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位的函数:
至此字节变量的移位问题已经得到解决,现在再来看单字和双字的移位,它们分别对应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类型变量的移位了吗。这种方法完全绕过了有符号数的符号位给我们带来的众多麻烦,顺利的实现了目的。用这种方法需要注意一点:如果是左移,要保证把低字节的最高位移入高字节的最低位,反之如果是右移,要把高字节的最低位移入低字节的最高位。从下面的代码中可以看到实现的过程: Integer类型的移位也可以用查表法,移位表占用 65535 * 15 * 2 * 2 个字节的内存空间。 移位表数组定义如下: aSHLW(0 to &Hffff&,1 to 15) as integer'单字的左移表 aSHRW(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下调试通过。
|

