一、指针是什么?
不需要去找什么标准的定义,它就是一个32位整数,在C语言和在VB里都可以用Long类型来表示。在32位Windows平台下它和普通的32位长整型数没有什么不同,只不过它的值是一个内存地址,正是因为这个整数象针一样指向一个内存地址,所以就有了指针的概念。
有统计表明,很大一部分程序缺陷和内存的错误访问有关。正是因为指针直接和内存打交道,所以指针一直以来被看成一个危险的东西。以至于不少语言,如著名的JAVA,都不提供对指针操作的支持,所有的内存访问方面的处理都由编译器来完成。而象C和C++,指针的使用则是基本功,指针给了程序员极大的自由去随心所欲地处理内存访问,很多非常巧妙的东西都要依靠指针技术来完成。
关于一门高级的程序设计语言是不是应该取消指针操作,关于没有指针操作算不算一门语言的优点,我在这里不讨论,因为互联网上关于这方面的没有结果的讨论,已经造成了占用几个GB的资源。无论最终你是不是要下定决心修习指针技术《葵花宝典》,了解这门功夫总是有益处的。
注意:在VB里,官方是不鼓励使用什么指针的,本文所讲的任何东西你都别指望取得官方的技术支持,一切都要靠我们自己的努力,一切都更刺激!
让我们开始神奇的VB指针探险吧!
二、来看看指针能做什么?有什么用?
先来看两个程序,程序的功能都是交换两个字串:
【程序一】:
'标准的做法SwapStr Sub SwapStr(sA As String, sB As String) Dim sTmp As String sTmp = sA: sA = sB: sB = sTmp End Sub |
'用指针的做法SwapPtr Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (Destination As Any, Source As Any, ByVal Length As Long) Sub SwapPtr(sA As String, sB As String) Dim lTmp As Long CopyMemory lTmp, ByVal VarPtr(sA), 4 CopyMemory ByVal VarPtr(sA), ByVal VarPtr(sB), 4 CopyMemory ByVal VarPtr(sB), lTmp, 4 End Sub |
你是不是以为第一个程序要快,因为它看着简单而且不用调用API(调用API需要额外的处理,VB文档明确指出大量调用API将降低程序性能)。但事实上,在VB集成环境中运行,程序二要比程序一快四分之一;而编译成本机代码或p-code,程序二基本上要比程序一快一倍。下面是两个函数在编译成本机代码后,运行不同次数所花时间的比较:
运行100000次,SwapStr需要170毫秒,SwapPtr需要90毫秒。
运行200000次,SwapStr需要340毫秒,SwapPtr需要170毫秒。
运行2000000次,SwapStr需要3300毫秒,SwapPtr需要1500毫秒。
的确,调用API是需要额外指令来处理,但是由于使用了指针技术,它没有进行临时字串的分配和拷贝,因此速度提高了不少。
怎么样,想不到吧!C/C++程序员那么依赖指针,无非也是因为使用指针往往能更直接的去处理问题的根源,更有驾驭一切的快感。他们不是不知道使用指针的危险,他们不是不愿意开卫星定位无级变速的汽车,只是骑摩托更有快感,而有些地方只有摩托才走得过去。
和在C里类似,在VB里我们使用指针也不过三个理由:
一是效率,这是一种态度一种追求,在VB里也一样;
二是不能不用,因为操作系统是C写的,它时刻都在提醒我们它需要指针;
三是突破限制,VB想照料我们的一切,VB给了我们很强的类型检查,VB象我们老妈一样,对我们关心到有时我们会受不了,想偶尔不听妈妈的话吗?你需要指针!
但由于缺少官方的技术支持,在VB里,指针变得很神秘。因此在C里一些基本的技术,在VB里就变得比较困难。本文的目的就是要提供给大家一种简单的方法,来将C处理指针的技术拿到VB里来,并告诉你什么是可行的,什么可行但必须要小心的,什么是可能但不可行的,什么是根本就不可能的。
三、 程咬金的三板斧
是的,程序二基本上就已经让我们看到VB指针技术的模样了。总结一下,在VB里用指针技术我们需要掌握三样东西:CopyMemory,VarPtr/StrPtr/ObjPtr, AdressOf. 三把斧头,程咬金的三板斧,在VB里Hack的工具。
1、CopyMemory
关于CopyMemory和Bruce McKinney大师的传奇,MSDN的Knowledge Base中就有文章介绍,你可以搜索"ID: Q129947"的文章。正是这位大师给32位的VB带来了这个可以移动内存的API,也正是有了这个API,我们才能利用指针完成我们原来想都不敢想的一些工作,感谢Bruce McKinney为我们带来了VB的指针革命。
如CopyMemory的声明,它是定义在Kernel32.dll中的RtlMoveMemory这个API,32位C函数库中的memcpy就是这个API的包装,如MSDN文档中所言,它的功能是将从Source指针所指处开始的长度为Length的内存拷贝到Destination所指的内存处。它不会管我们的程序有没有读写该内存所应有的权限,一但它想读写被系统所保护的内存时,我们就会得到著名的Access Violation Fault(内存越权访问错误),甚至会引起更著名的general protection (GP) fault(通用保护错误) 。所以,在进行本系列文章里的实验时,请注意随时保存你的程序文件,在VB集成环境中将"工具"->"选项"中的"环境"选项卡里的"启动程序时"设为"保存改变",并记住在"立即"窗口中执行危险代码之前一定要保存我们的工作成果。
2、VatPtr/StrPtr/ObjPtr
它们是VB提供给我们的好宝贝,它们是VBA函数库中的隐藏函数。为什么要隐藏?因为VB开发小组,不鼓励我们用指针嘛。
实际上这三个函数在VB运行时库MSVBVM60.DLL(或MSVBVM50.DLL)中是同一个函数VarPtr(可参见我在本系列第一篇文章里介绍的方法)。
其库型库定义如下:
[entry("VarPtr"), hidden] long _stdcall VarPtr([in] void* Ptr); [entry("VarPtr"), hidden] long _stdcall StrPtr([in] BSTR Ptr); [entry("VarPtr"), hidden] long _stdcall ObjPtr([in] IUnknown* Ptr); |
Private Declare Function ObjPtr Lib "MSVBVM60" Alias "VarPtr" (var As Object) As Long Private Declare Function VarPtr Lib "MSVBVM60" (var As Any) As Long |
long VarPtr(void* pv){ return (long)pv; } |
mov eax,dword ptr [esp+4] ret 4 '弹出栈里参数的值并返回。 |
'体会ByVal和ByRef Sub TestCopyMemory() Dim k As Long k = 5 Note: CopyMemory ByVal VarPtr(k), 40000, 4 Debug.Print k End Sub |
Note2: CopyMemory ByVal VarPtr(k), ByVal 40000, 4 |
Note3: CopyMemory VarPtr(k), 40000, 4 |
'看看我们的东西被拷贝到哪儿去了 Sub TestCopyMemory() Dim i As Long, k As Long k = 5 i = VarPtr(k) NOTE4: CopyMemory i, 40000, 4 Debug.Print k Debug.Print i i = VarPtr(k) NOTE5: CopyMemory ByVal i, 40000, 4 Debug.Print k End Sub |
5 40000 40000 |
struct POINT{ int x; int y; }; int Compare(void* elem1, void* elem2){} void PtrDemo(){ //指针声明: char c = 'X'; //声明一个char型变量 char* pc; long* pl; //声明普通指针 POINT* pPt; //声明结构指针 void* pv; //声明无类型指针 int (*pfnCastToInt)(void *, void*);//声明函数指针: //指针赋值: pc = &c; //将变量c的地址值赋给指针pc pfnCompare = Compare; //函数指针赋值。 //指针取值: c = *pc; //将指针pc所指处的内存值赋给变量c //用指针赋值: *pc = 'Y' //将'Y'赋给指针pc所指内存变量里。 //指针移动: pc++; pl--; } |
Type POINT |
void *memcpy( void *dest, const void *src, size_t count ); |
'使用更安全的CopyMemory,明确的使用指针! Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Long) Sub SwapStrPtr2(sA As String, sB As String) Dim lTmp As Long Dim pTmp As Long, psA As Long, psB As Long pTmp = VarPtr(lTmp): psA = VarPtr(sA): psB = VarPtr(sB) CopyMemory pTmp, psA, 4 CopyMemory psA, psB, 4 CopyMemory psB, pTmp, 4 End Sub |
'有点象【程序四】,但将常量40000换成了值为1的变量. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, Length As Long) Sub TestCopyMemory() Dim i As Long,k As Long, z As Interger k = 5 : z = 1 i = VarPtr(k) '下面的语句会引起类型不符的编译错误,这是好事! 'CopyMemory i, z, 4 '应该用下面的 CopyMemory i, ByVal VarPtr(z), 2 Debug.Print k End Sub |
Function ObjPtr(obj as Object) as long Dim lpObj As Long CopyMemory lpObj, Obj, 4 ObjectPtr = lpObj End Function |
dim ab() As Byte , ret As long '传递Null值API会返回它所需要的缓冲区的长度。 ret = SomeApiNeedsBuffer(vbNullString) '动态分配足够大小的内存缓冲区 ReDim ab(ret) As Byte '再次把指针传给API,此时传字节数组第一个元素的指针。 SomeApiNeedsBuffer(ByVal VarPtr(ab(1))) |
'标准的方法,也是高效的方法,但不容易理解。 Function LoWord(ByVal dw As Long) As Integer If dw And &H8000& Then LoWord = dw Or &HFFFF0000 Else LoWord = dw And &HFFFF& End If End Function |
'用指针来做效率虽不高,但思想清楚。 Function LoWord(ByVal dw As Long) As Integer CopyMemory ByVal VarPtr(LoWord), ByVal VarPtr(dw), 2 End Function |
'标准的移动数组的做法 Private Sub ShitArray(ab() As MyType) Dim i As Long, n As Long n = CLng(UBound(ab) / 2) For i = 1 To n Value(n + i) = Value(i) Value(i).data = 0 Next End Sub |
'用指针的做法 Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (ByVal dest As Long, ByVal source As Long, ByVal bytes As Long) Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" _ (ByVal dest As Long, ByVal numbytes As Long) Private Declare Sub FillMemory Lib "kernel32" Alias "RtlFillMemory" _ (ByVal dest As Long, ByVal Length As Long, ByVal Fill As Byte) Private Sub ShitArrayByPtr(ab() As MyTpye) Dim n As Long n = CLng(UBound(ab) / 2) Dim nLenth As Long nLenth = Len(Value(1)) 'DebugBreak CopyMemory ByVal VarPtr(Value(1 + n)), ByVal VarPtr(Value(1)), n * nLenth ZeroMemory ByVal VarPtr(Value(1)), n * nLenth End Sub |
'交换两个字符串最快的方法 Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (Destination As Any, Source As Any, ByVal Length As Long) Sub SwapStrPtr3(sA As String, sB As String) Dim lTmp As Long Dim pTmp As Long, psA As Long, psB As Long pTmp = StrPtr(sA): psA = VarPtr(sA): psB = VarPtr(sB) CopyMemory ByVal psA, ByVal psB, 4 CopyMemory ByVal psB, pTmp, 4 End Sub |