用过VB写过点程序的朋友都知道,涉及到内存地址访问的时候,CopyMemory这个API是少他不了的。的确用了这个API给死板的VB带来了不少的灵活性,然而仅仅作为一个内存读写的指令,却要要用API函数来实现,其中的开销自然不言而喻。难道不用API,VB就没有别的办法访问内存了吗?当然不是!
在C里面,与指针这个东西最相似的当然就是数组,当你把数组的索引超过了其自身的长度,将会访问到数组以外的数据,甚至发生崩溃。但在VB里这种现象却不会发生——当你超越了数组的界限时,将会产生一个运行时错误以阻止这个操作的运行。这时因为VB为你程序的安全性考虑,以不至于产生一些安全性的问题,当然,这是以牺牲运行效率换来的。可见,正是这个多余的安全检查,阻碍了变量灵活访问的权限。事实上,数组的界限检验是可以关闭的!那么,当数组的索引超过了其界限,访问到的自然就是内存数据了。
新建一个只有模块的工程,在菜单工程->属性->编译->高级优化里,选上“取消数据绑定检查”,然后:
Option Explicit Dim MEM(0) As Byte Dim BASE As Long Sub Main() Dim src As Byte Dim pValue As Long BASE = -VarPtr(MEM(0)) src = 123 pValue = VarPtr(src) MsgBox MEM(BASE + pValue) End Sub
首先声明一个MEM的字节型数组,当作是内存,BASE变量保存MEM数组的地址,当作是一个基地址。因为变量顺序问题,这里把它取负。当知道某个变量的地址,比如pValue时,直接MEM(BASE + pValue)便可访问那个指针所指向的内存了。
编译EXE后运行,果然可以!不过按下F5调试,出错了?是的,VB那所谓的取消数组绑定只在编译以后才能起作用,编译的时候你还是得老老实实的写安全型的程序。那这样的程序岂不是不能调试了?当然不行,所以还得再开一条后路,让他在调试的时候也能乖乖的运行。
既然要给调试时留条后路,自然就要用到条件编译指令。在工程->属性->生成的条件编译参数里面,填上DEBUG_ = 1,然后再修改程序:
Option Explicit Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long) Dim BASE As Long #If DEBUG_ Then Property Get MEM(ByVal Addr As Long) As Byte Call CopyMemory(MEM, ByVal Addr, 1) End Property Property Let MEM(ByVal Addr As Long, ByVal val As Byte) Call CopyMemory(ByVal Addr, val, 1) End Property #Else Dim MEM(0) As Byte #End If Sub Main() Dim src As Byte Dim pValue As Long #If DEBUG_ Then #Else BASE = -VarPtr(MEM(0)) #End If src = 123 pValue = VarPtr(src) MsgBox MEM(BASE + pValue) End Sub
这里增加了一个名为MEM的属性过程,在调试过程中冒充那个MEM的数组,用CopyMemory来实现内存的访问。不过可要注意啦:调试时比编译后运行要慢上15倍之多!可见CopyMemory的开销确实不容乐观。
想必大家都在想一个问题,这样仅仅是在访问字节型的指针变量,能不能访问Long或更多的。答案当然是可以的,不过程序的风格可能变得很丑陋,例如MEM(BASE + pValue/4),因为MEM是Long型的了,每个元素都是4字节,不再想Byte()那样直接访问了。
不过当初考虑这个方法仅仅为了访问字节流而已,屡试不爽。