|
楼主 |
发表于 2009-12-31 15:39
|
显示全部楼层
<<<<<<<<上接1楼
5、拿来主义。
实际上,有了CopyMemory,VarPtr,AddressOf这三把斧头,我们已经可以将C里基本的指针操作拿过来了。
如下面的C程序包括了大部分基本的指针指针操作:- 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
复制代码 下接3楼>>>>>>>>>>>
[ 本帖最后由 lb_bn 于 2009-12-31 21:08 编辑 ] |
|