📄 感悟visualbasic(7).txt
字号:
感悟VisualBasic(7)
--------------------------------------------------------------------------------
广东 张鸿 时间:2003-11-28 10:07:14
第十话 初识地址与指针
VB与VC++、Delphi之间最大的不同之一,就是后两者都可以使用指针进行运算,而VB为了安全着想,被去除了这一功能,虽然因此避免了使用指针所带来的危险性,但却造成使用上的许多不便,特别是在速度上难以提高。API给VB带来了一些改变,它的介入使VB得到很大程度的扩展,包括带来了指针。其实指针在Windows中是一个32位变量(占用4个字节的内存),在VB中由于没有指针类型变量,所以存放指针应使用长型(长型也是32位的变量)。
说到这里,可能有的人还不知道什么是指针。这里要先说说内存与地址。
在VB程序中,我们的变量都是在定义后被分配内存的,比如长型分配4个字节的内存,整型分配2个字节的内存,除了变长字符串有点特别之外(分配4个字节的内存),其他类型都是这样的,这些变量在内存中的先后位置称为地址,而Windows对这些变量的存取,是通过记录它们的地址来存取的。这个用来记录地址的变量,就是指针变量。
或许你会问,指针可以为我们做什么?我们真的需要它吗?事实上,针对VB来说,除非特别情况,我们是完全不需要指针的,因为它并不能给我们带来明显的好处,特别是反而带来速度的明显下降(而不是普遍所说的提高速度)。但这个特别情况,却总是在使用API中出现,所以我们仍需要它。
这一话并不是要对指针进行深入研究,所以我只准备了一个不算复杂的例子来说明要讲的内容。这里用到一个API:
Private Declare Sub Copy Memory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any,Source As Any,By Val Length As Long)
CopyMemory把内存的一段内容复制到另一段中,可以用来赋值。Destination是目标对象(的地址),Source是源对象(的地址),Length是复制的长度(以字节算)。如果我们要对一对长型变量进行复制,可以这么写:
CopyMemroy lngValue2,lngValue1,4 "把lngValue1的内容复制到lngValue2中,复制4字节的内容
由于VB对按引用传递的参数做了特别处理(这和C++的引用有一点不同,VB中的引用实际上已经暗示了使用指针来传递值),实际上传递给API时,已经是lngValue2和lngValue1的地址了,所以我们不需要再多做工作。
对于用户定义类型的变量,你可以像这样使用:
Private Type Type1
str As String
value As Long
End Type
Dim s1 As String * 30,s2 As String * 30
Dim t1 As Type1,t2 As Type1
CopyMemory t2,t1,Len(t1)"复制t1的内容到t2,t1所占内存的大小
CopyMemory s2,s1,30"复制s1的内容到s2,30字节
这里用了Len来得到t1所占内存的大小,这是Len函数的另一个作用。本期的第一个示例源程序里演示了各种简单类型数据的复制,并包含了一个数组的复制,建议读者去看看。特别说一下,对数组的复制,应该传递第一个数组的成员,而不是数组名。
第十一话 检验CopyMemory
在上一话中,我说过除非特殊情况,否则并没有使用CopyMemory的需要,并且CopyMemory往往会使速度明显下降。那么,这一话我就告诉你这些问题的真相。
如果在我们的程序中,只是用来对变量进行复制,那么,VB本身的速度已经很快了。为了说明这一话的问题,我写了一个测试程序执行时间的示例,时间单位为毫秒。
Private Declare Function time Get Time Lib "winmm.dll" () As Long
这个timeGetTime返回从系统开始运行时的时间,主要作用是让我们计算一个时间间隔。
首先测试VB程序在我的计算机中(Win98SE中)循环500000次赋值所花的时间:
Const LOOPTIMES=500000
Dim t1 As Long,t2 As Long,tR1 As RECT,tR2 As RECT
Dim i As Long,lngAssign As Long,lngSrc As Long
Dim strSrc As String * 5000,strDest As String * 5000
lngSrc=10000000
t1=timeGetTime
For i=0 To LOOPTIMES
lngAssign=lngSrc
Next i
t2=timeGetTime
Debug.Print"Assignment:Long value=" & t2 - t1
t1=timeGetTime
Fori=0 To LOOPTIMES
tR1=tR2
Next i
t2=timeGetTime
Debug.Print"Assignment:User type =" & t2-t1
t1=timeGetTime
Fori=0 To LOOPTIMES
strDest=strSrc
Next i
t2=timeGetTime
Debug.Print"Assignment:String=" & t2-t1
这里第一个循环对长型进行赋值,第二个对(只包含4个长型成员的)用户定义类型进行赋值,第三个对5000字节的字符串进行赋值。所得的结果如下:
Assignment:Long value=175
Assignment:User type=254
Assignment:String=103850
长型、用户定义(只包含4个长型成员)分别花去175和254毫秒,但5000字节的字符串却花了103850毫秒,实在是够慢的了。那么CopyMemory的成绩又是怎样呢?
Dim t1 As Long,t2 As Long,tR1 As RECT,tR2 As RECT
Dim i As Long,lngAssign As Long,lngSrc As Long
Dim strSrc As String * 5000,strDest As String * 5000
lngSrc=10000000
t1=timeGetTime
Fori= 0 To LOOPTIMES
CopyMemory lngAssign,lngSrc,4
Next i
t2=timeGetTime
Debug.Print"CopyMemroy:Longvalue=" & t2-t1
t1=timeGetTime
Fori= 0 To LOOPTIMES
CopyMemoryt R2,tR1,Len(tR1)
Next i
t2=timeGetTime
Debug.Print"CopyMemory:User type=" & t2-t1
t1=timeGetTime
Fori= 0 To LOOPTIMES
CopyMemory strDest,strSrc,5000
Next i
t2=timeGetTime
Debug.Print"CopyMemory:String=" & t2-t1
运行结果是:
CopyMemroy:Long value=487
CopyMemory:User type=476
CopyMemory:String=3284319
更离奇了,花了487和476毫秒去给长型和只包含4个长型成员的用户定义类型变量,而字符串花了3284319毫秒,是VB本身的32倍!为了等这个测试完成,我花了近一个小时。这样的速度,能用它来提高速度吗?那么CopyMemory真的那么慢吗?
其实不是!但由于我们使用的是VB,所以就是这么慢。为什么?因为开销。
你有没有发现,在使用CopyMemory时,每一次都用到了Len运算用户定义的大小?这是第一个开销。我们调用API是调用外部的函数,而不是程序本身的函数,这时,如果函数是第一次调用,系统需要去访问磁盘上的文件,如果是第一次以后,所访问的虽然是内存,但却是一块共享区域,而且不是程序本身的,所以效率仍然很低,这是第二个开销。CopyMemory要用到参数,这些参数在传递时是需要花费时间的,这是第三个开销。在上一话中我说过VB本身会对按引用传递暗示按地址传递,这里暗中隐含了一个取地址的运算,是第四个开销。单单一个赋值运算,就要花费四个开销,这样程序的效率是非常低的,所以VB调用CopyMemory并不能给我们提高速度,它令我们十分失望!
但是,这里有个特殊情况,这种情况下我们可以优化我们的代码来改善这种情况,并且可能带来很大惊喜:
Dim t1 As Long,t2 As Long,tR1 As RECT,tR2 As RECT
Dim i As Long,lngAssign As Long,lngSrc As Long,lLen As Long
Dim strSrc As String * 5000,strDest As String * 5000
Dim v1 As Long,v2 As Long
lngSrc=10000000
lLen=Len(tR1)
v1=VarPtr(lngAssign)
v2=VarPtr(lngSrc)
t1=timeGetTime
For i= 0 To LOOPTIMES
CopyMemory ByVal v1,ByVal v2,4
Next i
t2=timeGetTime
Debug.Print"CopyMemroy:Long value=" & t2-t1
v1=VarPtr(tR1)
v2=VarPtr(tR2)
t1=timeGetTime
Fori= 0 To LOOPTIMES
CopyMemory ByVal v1,ByVal v2,lLen
Next i
t2=timeGetTime
Debug.Print"CopyMemory:User type=" & t2-t1
v1=StrPtr(strDest)
v2=StrPtr(strSrc)
t1=timeGetTime
Fori= 0 To LOOPTIMES
CopyMemory ByVal v1,ByVal v2,5000
Next i
t2=timeGetTime
Debug.Print"CopyMemory:String=" & t2-t1
这里又和前面有一点不同,不仅用到了VarPtr和StrPtr函数,还用到了ByVal。
VarPtr、StrPtr、ObjPtr是三个连MSDN也没提到的函数,它们分别是取得变量的地址、字符串的地址和对象的地址(在以前子类的文章中我曾提到过),由于CopyMemory规定传递的目标对象和源对象要用指针,但又为了考虑用户定义类型和对象的情况,所以用了Any类型(Any类型只能在API中使用,要注意)。但是就算是Any类型,也并不是说我们就可以直接使用,因为仍改变不了CopyMemory要用到指针的事实,所以VarPtr返回了变量的指针。而ByVal呢?因为VB隐含地把对象的地址传递给了函数,所以我们要“显式”的声明我们这一次传递的是对象的值(VB也因此省去运算变量的地址),而不是地址,要不然,将传递“存放变量地址的那个变量的地址”,结果将是两个长型变量之间的内容复制,而我们指定复制的大小又不是4字节,我们的程序将“非法操作”。
好,来看看我的结果:
CopyMemroy:Long value=472
CopyMemory:User type=525
CopyMemory:String=3018
噢,我知道你想说“我的天!这是什么结果?”,不过我没写错,是3秒!前面花了一个小时的赋值,在这里变成了3秒!这种优化对长型没有什么作用(每次运算时间都可能有不同程度的小偏差,这是正常的),但对字符串的作用是十分明显的,它也远远比VB本身的字符串赋值快(VB的字符串运算是比较慢的,因为经常暗含了一些不必要的转换)。我也在NT4中做了测试,所有结果的速度都有提高,如果你用比较新的计算机,那么结果会比我的快。
在这个特殊情况下,它要求在循环中每个源操作对象和目标操作对象的地址是相同的,Len的结果是同样大小。这样在每个循环中就省去了Len运算和取地址运算,去除了VB本身带来的两个开销。不过这种情况很少遇到,但我们还是可以从中看到CopyMemory是十分快的。
虽然如此,CopyMemory仍在许多时候要用到,因为我们使用的是API,我们不可能用API只是为了给程序中两个变量赋值。在许多情况下,用某个API取回的值是一个长型变量(比如SendMessage的一些应用场合),但它实际图1上却是一个其他变量的地址,当我们只得到这个地址的时候,我们无法用VB为我们取回它的对象,只能用CopyMemory。但因为操作的源对象不是我们程序中的对象,所以这里就不像我们上一话那么简单地使用了,应该把源对象的地址直接告诉CopyMemory,就如前面优化时所做的那样。
这一期主要是告诉你CopyMemory的使用方法,并把它的一些问题也告诉你。应该怎么去使用它,就要看你怎么把握你的程序了。由于篇幅原因,对于文中最后提到的有许多API返回值是变量的地址的情况,以后如果有机会,再向读者演示。在这之前,还需要读者自己遇到时,运用本期所讲的知识,不然一个不小心,就要去数“非法操作”一个小时有几次了。
文中程序在Win98/Win2000+VB6下调试通过。源程序下载地址是:http://www.cfan.net.cn/qikan/cxg/0207gwv.zip。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -