Using CopyMemory to Use Pointer in VB

  1. What is a pointer?

  There is no need to find any standard definition, it is a 32-bit integer, which can be represented by the Long type in both C language and VB. Under the 32-bit Windows platform, it is no different from the ordinary 32-bit long integer, except that its value is a memory address. It is precisely because this integer points to a memory address like a needle, so there is the concept of a pointer. .

  Statistics show that a large part of program bugs are related to incorrect access to memory. It is precisely because pointers deal directly with memory that pointers have always been seen as a dangerous thing. So many languages, such as the famous JAVA, do not provide support for pointer operations, and all memory access processing is done by the compiler. Like C and C++, the use of pointers is a basic skill. Pointers give programmers great freedom to handle memory access as they want. Many very ingenious things rely on pointer technology to complete.

  As to whether a high-level programming language should cancel pointer operations, and whether the absence of pointer operations is an advantage of a language, I will not discuss it here, because the inconsequential discussions on the Internet have already caused occupation Several GB of resources. Whether or not you end up making up your mind to practice pointer technique, The Sunflower Collection, it's always beneficial to know the skill.

  Note: In VB, the official does not encourage the use of pointers. You can't expect to get official technical support for anything mentioned in this article. Everything depends on our own efforts, and everything is more exciting!

  Let's start the magical VB pointer adventure!

  Second, let's see what the pointer can do? What is the use?

  Let's look at the two programs first. The function of the program is to exchange two strings:

  [Program 1]:

'Standard practice SwapStr
Sub SwapStr(sA As String, sB As String)
 Dim sTmp As String
 sTmp = sA: sA = sB: sB = sTmp
End Sub

  【Program 2】:

'用指针的做法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


  Do you think the first program is faster because it looks simple and does not need to call the API (calling the API requires extra processing, the VB documentation clearly states that a large number of calls to the API will reduce program performance). But in fact, running in the VB integrated environment, program two is a quarter faster than program one; and compiled into native code or p-code, program two is basically twice as fast as program one. Here's a comparison of how long it takes to run the two functions after compiling into native code for different times:

  100,000 runs, SwapStr takes 170ms, SwapPtr takes 90ms.

  Running 200000 times, SwapStr takes 340ms and SwapPtr takes 170ms.

  Running 2000000 times, SwapStr takes 3300ms and SwapPtr takes 1500ms.

  Indeed, calling the API requires additional instructions to process, but because of the use of pointer technology, it does not allocate and copy temporary strings, so the speed is much improved.

  How, can't think of it! C/C++ programmers rely so much on pointers because the use of pointers can often deal with the root cause of the problem more directly, and has the pleasure of controlling everything. It's not that they don't know the dangers of using pointers. It's not that they don't want to drive a car with a satellite positioning continuously variable transmission. It's just that riding a motorcycle is more enjoyable, and some places can only be passed by a motorcycle.
Similar to C, we use pointers in VB for three reasons:

  first, efficiency, which is an attitude and pursuit, and the same is true in VB;

  second, it cannot be used, because the operating system is written in C, It always reminds us that it needs pointers;

  the third is to break through the restrictions, VB wants to take care of everything for us, VB gives us a strong type check, VB is like our mother, and sometimes we can't stand it because of our concern. Do you sometimes not listen to your mother? You need pointers!

  But due to the lack of official technical support, pointers have become mysterious in VB. Therefore, some basic techniques in C become more difficult in VB. The purpose of this article is to provide you with a simple way to bring C's technique of dealing with pointers into VB and tell you what is possible, what is possible but must be careful, and what is possible but not feasible , what is simply impossible.

  3. Cheng Yaojin's three-pronged axe

  Yes, program 2 has basically let us see the appearance of VB pointer technology. To sum up, to use pointer technology in VB, we need to master three things: CopyMemory, VarPtr/StrPtr/ObjPtr, AdressOf. Three axes, Cheng Yaojin's three axes, and Hack tools in VB.

  1. CopyMemory

  About the legend of CopyMemory and Bruce McKinney, there is an article in MSDN's Knowledge Base, you can search for the article "ID: Q129947". It is this master who brought this API that can move memory to 32-bit VB. It is also with this API that we can use pointers to complete some work that we never thought of before. Thanks to Bruce McKinney for bringing us Here comes the pointer revolution of VB.

  As stated in CopyMemory, it is the API of RtlMoveMemory defined in Kernel32.dll, and the memcpy in the 32-bit C function library is the wrapper of this API. As stated in the MSDN document, its function is to move from the source pointed to by the Source pointer. The starting memory of length Length is copied to the memory pointed to by Destination. It doesn't care whether our program has the right to read and write the memory, once it wants to read and write the memory protected by the system, we will get the famous Access Violation Fault (memory unauthorized access error), or even Causes the more famous general protection (GP) fault. Therefore, when carrying out the experiments in this series of articles, please pay attention to save your program files at any time, and set the "When starting the program" in the "Environment" tab of "Tools" -> "Options" in the VB integrated environment. for "save changes" and remember to "immediately" Be sure to save our work before executing dangerous code in the window.

  2. VatPtr/StrPtr/ObjPtr

  They are the good treasures that VB provides us, they are hidden functions in the VBA function library. Why hide? Because of the VB development team, we are not encouraged to use pointers.

  In fact, these three functions are the same function VarPtr in the VB runtime library MSVBVM60.DLL (or MSVBVM50.DLL) (see the method I introduced in the first article of this series).

  Its library type library is defined as follows:

[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);

  Even though they are the same function in the VB runtime library, we can also redeclare these functions in VB using the API method, as follows:

Private Declare Function ObjPtr Lib "MSVBVM60" Alias "VarPtr" (var As Object) As Long
Private Declare Function VarPtr Lib "MSVBVM60" (var As Any) As Long

  (There is no StrPtr, because VB handles strings a little differently. There are too many problems in this area, and I will discuss them in another article. By the way, I heard that these functions are not available in VB.NET, but As long as the API can still be called, we can try the above declarations, so that we can also perform pointer operations in VB.NET. But please note that if you use VarPtr through API calls, the entire program 2 SwapPtr will be more than the original use. 6 times slower with the built-in VarPtr function.)

  If you like to get to the bottom of things, here's what the VarPtr function looks like in C and assembly language:

  In C it looks like this:

long VarPtr(void* pv){
 return (long)pv;
}

  The corresponding assembly code is only two lines:

mov eax,dword ptr [esp+4]
ret 4 'Pop the value of the parameter from the stack and return.

  The reason why I let you know the specific implementation of VarPtr is to tell you that its overhead is not large, because they are only two instructions. Even if parameter assignment, stack push and call instructions are added, the entire process of obtaining the pointer is only six instructions. Of course, the same function in C language only requires one instruction due to the direct support of the language. But in VB, it is already the fastest function, so we don't have to worry about using VarPtr to make us lose efficiency! Speed ​​is a fundamental requirement for using pointer technology.

  In a word, VarPtr returns the memory address where the variable is located. It can also be said that it returns a pointer to the memory location of the variable. It is one of the most important weapons for dealing with pointers in VB.

  3. ByVal and ByRef

  The parameter value passed by ByVal, and the address of the parameter passed by ByRef. Here, we don't need to distinguish between passing pointer/address/reference. In VB, they are simply three different ways of saying the same thing. Even in the VB documentation, there are places where these terms are mixed (but in C++ it is true To distinguish between pointers and references)

  For friends who first contacted the above program 2 SwapPtr, you must find out where to add ByVal and where not to add ByVal in the CopyMemory call inside (without ByVal is to use VB's default ByRef) , Accurately understanding the difference between pass-by-value and pass-by-address (pointer) is the basis for the correct use of pointers in VB.

  Now a simplest experiment to look at this problem, as shown in the following program 3:

  [Program 3]:

'体会ByVal和ByRef
Sub TestCopyMemory()
 Dim k As Long
 k = 5
 Note: CopyMemory ByVal VarPtr(k), 40000, 4
 Debug.Print k
End Sub

  The purpose of the statement at the label Note above is to assign k to 40000, which is equivalent to the statement k=40000. You can test it in the "immediate" window, and you will find that the value of k is indeed 40000.
In fact, the above statement, translated into vernacular, is to copy 4 bytes from the temporary variable that holds the constant 40000 to the memory where the variable k is located.

  Now let's change the statement at a Note, if it is changed to the following statement:

Note2: CopyMemory ByVal VarPtr(k), ByVal 40000, 4

  The meaning of this sentence becomes, copy 4 bytes from address 40000 to the memory where the variable k is located. Since we don't have access to the memory at address 40000, the OS will give us an Access Violation memory violation error telling us "There was an error trying to read the memory at location 0x00009c40, this memory cannot be 'Read'".

  Let's change it to the following statement.

Note3: CopyMemory VarPtr(k), 40000, 4

  The meaning of this sentence becomes, copy 4 bytes from the temporary variable that holds the constant 40000 to the temporary variable that holds the value of the memory address where the variable k is located. This does not give a memory unauthorized access error, but the value of k does not change.

  We can change the program to show the difference more clearly, as shown in the following program 4:

  [Program 4]:

'See where our stuff is copied to
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

  Program output:

5
40000
40000

  Since the default ByVal is used at NOTE4, the address of i (that is, the pointer to i) is passed, so the constant 40000 is copied to the variable i, so the value of i becomes 40000, but the value of k has not changed. However, before NOTE4: i=VarPtr(k), the intention is to use i itself as a pointer. At this time, we must use ByVal to pass the pointer i as in NOTE5. Since i is a pointer to the variable k, the last constant 40000 is copied into the variable k.

  I hope you understand this distinction, and I'll come back to it later in the discussion of the question.

  4. AddressOf

  It is used to get a pointer to the entry address of the VB function, but this pointer can only be passed to the API, so that the API can call back the VB function.

  This article is not going to discuss the function pointer in detail, please refer to the VB documentation for its use.

  5. Bringing Doctrine

  In fact , with the three axes of CopyMemory, VarPtr, and AddressOf, we can already take over the basic pointer operations in C.

  For example, the following C program includes most of the basic pointer-to-pointer operations:

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--;
}

  这些对指针操作在VB里都有等同的东西,前面讨论ByVal和ByRef时曾说过传指针和传地址是一回事,实际上当我们在VB里用缺省的ByRef声明函数参数时,我们已经就声明了指针。

  如一个C声明的函数:long Func(char* pc)

  其对应的VB声明是:Function Func(pc As Byte) As Long

  这时参数pc使用缺省的ByRef传地址方式来传递,这和C里用指针来传递参数是一样。

  那么怎么才能象C里那样明确地声明一个指针呢?

  很简单,如前所说,用一个32位长整数来表达指针就行。在VB里就是用Long型来明确地声明指针,我们不用区分是普通指针、无类型指针还是函数指针,通通都可用Long来声明。而给一个指针赋值,就是赋给它用VarPar得到的另一个变量的地址。具体见程序五。

  【程序五】:同C一样,各种指针。

Type POINT
 X As Integer
 Y As Integer
End Type

Public Function Compare(elem1 As Long, elem2 As Long) As Long
'
End Function

Function FnPtrToLong(ByVal lngFnPtr As Long) As Long
 FnPtrToLong = lngFnPtr
End Function

Sub PtrDemo()
 Dim l As Long, c As Byte, ca() As Byte, Pt As POINT
 Dim pl As Long, pc As Long, pv As Long, pPt As Long, pfnCompare As Long
 c = AscB("X")
 pl = VarPtr(l) '对应C里的long、int型指针
 pc = VarPtr(c) '对应char、short型指针
 pPt = VarPtr(Pt) '结构指针
 pv = VarPtr(ca(0)) '字节数组指针,可对应任何类型,也就是void*
 pfnCompare = FnPtrToLong(AddressOf Compare) '函数指针
 CopyMemory c, ByVal pc, LenB(c) '用指针取值
 CopyMemory ByVal pc, AscB("Y"), LenB(c) '用指针赋值
 pc = pc + LenB(c) : pl = pl - LenB(l) '指针移动
End Sub


  我们看到,由于VB不直接支持指针操作,在VB里用指针取值和用指针赋值都必须用CopyMemory这个API,而调用API的代价是比较高的,这就决定了我们在VB里使用指针不能象在C里那样自由和频繁,我们必须要考虑指针操作的代价,在后面的"指针应用"我们会再变谈这个问题。

  程序五中关于函数指针的问题请参考VB文档,无类型指针void*会在下面"关于Any的问题"里说。

  程序五基本上已经包括了我们能在VB里进行的所有指针操作,仅此而已。

  下面有一个小测试题,如果现在你就弄懂了上面程咬金的三板斧,你就应该能做得出来。

  上面提到过,VB.NET中没有VarPtr,我们可以用声明API的方式来引入MSVBVM60.DLL中的VarPtr。现在的问题如果不用VB的运行时DLL文件,你能不能自己实现一个ObjPtr。答案在下一节后给出。


  四、指针使用中应注意的问题

  1、关于ANY的问题

  如果以一个老师的身份来说话,我会说:最好永远也不要用Any!是的,我没说错,是永远!所以我没有把它放在程咬金的三板斧里。当然,这个问题和是不是应该使用指针这个问题一样会引发一场没有结果的讨论,我告诉你的只是一个观点,因为有时我们会为了效率上的一点点提高或想偷一点点懒而去用Any,但这样做需要要承担风险。

  Any不是一个真正的类型,它只是告诉VB编译器放弃对参数类型的检查,这样,理论上,我们可以将任何类型传递给API。

  Any在什么地方用呢?让我们来看看,在VB文档里的是怎么说的,现在就请打开MSDN(Visual Studio 6自带的版本),翻到"Visual Basic文档"->"使用Visual Basic"->"部件工具指南"->"访问DLL和Windows API"部分,再看看"将 C 语言声明转换为 Visual Basic 声明"这一节。文档里告诉我们,只有C的声明为LPVOID和NULL时,我们才用Any。实际上如果你愿意承担风险,所有的类型你都可以用Any。当然,也可以如我所说,永远不要用Any。

  为什么要这样?那为什么VB官方还要提供Any?是信我的,还是信VB官方的?有什么道理不用Any?

  如前面所说,VB官方不鼓励我们使用指针。因为VB所标榜的优点之一,就是没有危险的指针操作,所以的内存访问都是受VB运行时库控制的。在这一点上,JAVA语言也有着同样的标榜。但是,同JAVA一样,VB要避免使用指针而得到更高的安全性,就必须要克服没有指针而带来的问题。VB已经尽最大的努力来使我们远离指针的同时拥有强类型检查带来的安全性。但是操作系统是C写的,里面到处都需要指针,有些指针是没有类型的,就是C程序员常说的可怕的void*无类型指针。它没有类型,因此它可以表示所有类型。如CopyMemory所对应的是C语言的memcpy,它的声明如下:

void *memcpy( void *dest, const void *src, size_t count );

  因memcpy前两个参数用的是void*,因此任何类型的参数都可以传递给他。

  一个用C的程序员,应该知道在C函数库里这样的void*并不少见,也应该知道它有多危险。无论传递什么类型的变量指针给上面memcpy的void*,C编译器都不会报错或给任何警告。

  在VB里大多数时候,我们使用Any就是为了使用void*,和在C里一样,VB也不对Any进行类型检查,我们也可以传递任何类型给Any,VB编译器也都不会报错或给任何警告。

  但程序运行时会不会出错,就要看使用它时是不是小心了。正因为在C里很多错误是和void*相关的,所以,C++鼓励我们使用satic_cast<void*>来明确指出这种不安全的类型的转换,已利于发现错误。

  说了这么多C/C++,其实我是想告诉所有VB的程序员,在使用Any时,我们必须和C/C++程序员使用void*一样要高度小心。 

  VB里没有satic_cast这种东西,但我们可以在传递指针时明确的使用long类型,并且用VarPtr来取得参数的指针,这样至少已经明确地指出我们在使用危险的指针。如程序二经过这样的处理就成了下面的程序:

  【程序五】:

'使用更安全的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

  注意,上面CopyMemory的声明,用的是ByVal和long,要求传递的是32位的地址值,当我们将一个别的类型传递给这个API时,编译器会报错,比如现在我们用下面的语句:

  【程序六】:

'有点象【程序四】,但将常量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

  编译会出错!是好事!这总比运行时不知道错在哪儿好!

  象程序四那样使用Any类型来声明CopyMemory的参数,VB虽然不会报错,但运行时结果却是错的。不信,你试试将程序四中的40000改为1,结果i的值不是我们想要的1,而是327681。为什么在程序四中,常量为1时结果会出错,而常量为40000时结果就不错?

  原因是VB对函数参数中的常量按Variant的方式处理。是1时,由于1小于Integer型的最大值32767,VB会生成一个存储值1的Integer型的临时变量,也就是说,当我们想将1用CopyMemroy拷贝到Long型的变量i时,这个常量1是实际上是Integer型临时变量!VB里Integer类型只有两个字节,而我们实际上拷贝了四个字节。知道有多危险了吧!没有出内存保护错误那只是我们的幸运!

  如果一定要解释一下为什么i最后变成了327681,这是因为我们将k的低16位的值5也拷贝到了i值的高16位中去了,因此有5*65536+1=327681。详谈这个问题涉及到VB局部变量声明顺序,CopyMemory参数的压栈顺序,long型的低位在前高位在后等问题。如果你对这些问题感兴趣,可以用本系列第一篇文章所提供的方法(DebugBreak这个API和VC调试器)来跟踪一下,可以加深你对VB内部处理方式的认识,由于这和本文讨论的问题无关,所以就不详谈了。到这里,大家应该明白,程序三和程序四实际上有错误!!!我在上面用常量40000而不用1,不是为了在文章中凑字数,而是因为40000这个常量大于32767,会被VB解释成我们需要的Long型的临时变量,只有这样程序三和程序四才能正常工作。对不起,我这样有意的隐藏错误只是想加深你对Any危害的认识。

  总之,我们要认识到,编译时就找到错误是非常重要的,因为你马上就知道错误的所在。所以我们应该象程序五和程序六那样明确地用long型的ByVal的指针,而不要用Any的ByRef的指针。

  但用Any已经如此的流行,以至很多大师们也用它。它唯一的魅力就是不象用Long型指针那样,需要我们自己调用VarPtr来得到指针,所有处理指针的工作由VB编译器来完成。所以在参数的处理上,只用一条汇编指令:push [i],而用VarPtr时,由于需要函数调用,因此要多用五条汇编指令。五条多余的汇编指令有时的确能我们冒着风险去用Any。

  VB开发小组提供Any,就是想用ByRef xxx As Any来表达void* xxx。我们也完全可以使用VarPtr和Long型的指针来处理。我想,VB开发小组也曾犹豫过是公布VarPtr,还是提供Any,最后他们决定还是提供Any,而继续隐瞒VarPtr。的确,这是个两难的决定。但是经过我上面的分析,我们应该知道,这个决定并不符合VB所追求的"更安全"的初衷。因为它可能会隐藏类型不符的错误,调试和找到这种运行时才产生的错误将花贵更多的时间和精力。

  所以我有了"最好永远不要用Any"这个"惊人"的结论。

  不用Any的另一个好处是,简化了我们将C声明的API转换成VB声明的方式,现在它变成了一句话:除了VB内置的可以进行类型检查的类型外,所以其它的类型我们都应该声明成Long型。

  2、关于NULL的容易混淆的问题

  有很多文章讲过,一定要记在心里:

  VbNullChar 相当于C里的'/0',在用字节数组构造C字串时常用它来做最后1个元素。

  vbNullString 这才是真正的NULL,就是0,在VB6中直接用0也可以。

  只有上面的两个是API调用中会用的。还有Empty、Null是Variant,而Nothing只和类对象有关,一般API调用中都不会用到它们。

  另:本文第三节曾提出一个小测验题,做出来了吗?现在公布正确答案:

  【测验题答案】

Function ObjPtr(obj as Object) as long
 Dim lpObj As Long
 CopyMemory lpObj, Obj, 4
 ObjectPtr = lpObj
End Function

  五、VB指针应用

  如前面所说VB里使用指针不象C里那样灵活,用指针处理数据时都需要用CopyMemory将数据在指针和VB能够处理的变量之间来回拷贝,这需要很大的额外开销。因此不是所有C里的指针操作都可以移值到VB里来,我们只应在需要的时候才在VB里使用指针。

  1、动态内存分配:完全不可能、可能但不可行,VB标准

  在C和C++里频繁使用指针的一个重要原因是需要使用动态内存分配,用Malloc或New来从堆栈里动态分配内存,并得到指向这个内存的指针。在VB里我们也可以自己

  用API来实现动态分配内存,并且实现象C里的指针链表。

  但我们不可能象C那样直接用指针来访问这样动态分配的内存,访问时我们必须用CopyMemory将数据拷贝到VB的变量内,大量的使用这种技术必然会降低效率,以至于要象C那样用指针来使用动态内存根本就没有可行性。要象C、PASCAL那样实现动态数据结构,在VB里还是应该老老实实用对象技术来实现。

  本文配套代码中的LinkedList里有完全用指针实现的链表,它是使用HeapAlloc从堆栈中动态分配内存,另有一个调用FindFirstUrlCacheEntry这个API来操作IE的Cache的小程序IECache,它使用了VirtualAlloc来动态分配内存。但实际上这都不是必须的,VB已经为我们提供了标准的动态内存分配的方法,那就是:

  对象、字符串和字节数组

  限于篇幅,关于对象的技术这里不讲,LinkedList的源代码里有用对象实现的链表,你可以参考。

  字符串可以用Space$函数来动态分配,VB的文档里就有详细的说明。

  关于字节数组,这里要讲讲,它非常有用。我们可用Redim来动态改变它的大小,并将指向它第一个元素的指针传给需要指针的API,如下:

dim ab() As Byte , ret As long
'传递Null值API会返回它所需要的缓冲区的长度。
ret = SomeApiNeedsBuffer(vbNullString)
'动态分配足够大小的内存缓冲区
ReDim ab(ret) As Byte
'再次把指针传给API,此时传字节数组第一个元素的指针。
SomeApiNeedsBuffer(ByVal VarPtr(ab(1)))

  在本文配套程序中的IECache中,我也提供了用字节数组来实现动态分配缓冲区的版本,比用VirtualAlloc来实现更安全更简单。

  2、突破限制

  下面是一个突破VB类型检查来实现特殊功能的经典应用,出自Bruce Mckinney的《HardCore Visual Basic》一书。

  将一个Long长整数的低16位作为Interger型提取出来,

  【程序七】

'标准的方法,也是高效的方法,但不容易理解。
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

  3、对数组进行批量操作

  用指针进行大批量数组数据的移动,从效率上考虑是很有必要的,看下面的两个程序,它们功能都是将数组的前一半数据移到后一半中:

  【程序九】:

'标准的移动数组的做法
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

  当数组较大,移动操作较多(比如用数组实现HashTable)时程序十比程序九性能上要好得多。

  程序十中又介绍两个在指针操作中会用到的API: ZeroMemory是用来将内存清零;FillMemory用同一个字节来填充内存。当然,这两个API的功能,也完全可以用CopyMemory来完成。象在C里一样,作为一个好习惯,在VB里我们也可以明确的用ZeroMemory来对数组进行初始化,用FillMemory在不立即使用的内存中填入怪值,这有利于调试。

  4、最后的一点

  当然,VB指针的应用决不止这些,还有什么应用就要靠自己去摸索了。对于对象指针和字符串指针的应用我会另写文章来谈,做为本文的结束和下一篇文章《VB字符串全攻略》的开始,我在这里给出交换两个字符串的最快的方法:

  【程序十一】

'交换两个字符串最快的方法
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

  对不起,为了一点点效率,又用了Any!关于StrPtr,下一篇文章我会来谈。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324125081&siteId=291194637