我的文章可见:
真想不到之五:高效字串指针类
关键字:VB、HCAK、字串指针、BSTR、效率、内存共享
难度:中级或高级
参考文章:
1、2000年7月VBPJ Black Belt专栏文章《Modify a Varialbe´s Pointer》
作者:Bill McCarthy
2、1998年4月VBPJ Black Belt专栏文章《Play VB´s Strings》
作者:Francesco Balena
引言:
本想以内存共享做为VB指针专题的最后一篇,写着写着发现字串的问题应该单独谈谈。在内存共享的问题上,我尤其关心的是字串的共享,因为在我一个多月前发布的源码里用的是《HardCore VB》里Bruce Mckinney提供的CShareStr类,它实现了字串的内存共享。但是Bruce也没有突破局限,对字串的处理依然是CopyMemory的乾坤大挪移,尤其是还要进行讨厌的ANSI/DBCS和Unicode的转换。我在readme里说过它效率极低,应该采用Variant或Byte数组来实现,才能避免转换。后来又想到可以用StrPtr来做,并在VC里用DLL共享节实现了可以不进行转换的字串内存共享。不过在VC里我仍然需要用SysAllocString来建立VB能使用的BSTR。这都不是我想要的,我想要的东西要象VC里的CString的一样,只要字串够大,对其赋值就不用重新分配内存,还要象VC里CComBSTR类一样可以Attach到一个特定BSTR。
知道该怎么做,是在看了VBPJ上Bill McCarthy和Francesco Balena的两篇文章之后。Bill用修改SafeArray描述结构实现了数组的内存共享,而Francesco则对字串指针进行深入的探讨。但是Bill和Francesco的东西都没有实现我想要的字串类。
方法知道了,实现并不难,所以我决定自己来包装一个这样的东西。
正文:
使用VB里的字串类型String有两大不足:第一、它的分配是由VB运行时控制,我们不能将其分配在指定内存处;第二,任何一次对字串的赋值操作都要进行内存重新分配。要实现高效、灵活的字串处理,我们必须克服这两大不足。
对于第一个问题,通过修改String变量里放着的BSTR描述符指针可以实现;对于第二个问题,可以用Mid语句(注意是语句而不是函数)来赋值。不详细讲了,直接看下面的这个类:
Option Explicit
´********************************************************
´clsBSTR.cls
´作者: 熊超 ID: AdamBear 2002年3月18日
´http://www.csdn.net/Author/AdamBear
´ 你可以自由使用本类模块,不过请保留本声明
´********************************************************
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
´不要直接对sString赋值(可以用MID语句),将其设为公有仅为提高效率。
Public sString As String ´BSTR描述符指针
Private pStr As Long ´BSTR地址
Private nMaxLen As Long ´BSTR最大字节数
´让本字串指向特定地址
Public Sub Attach(Addr As Long, Optional nLen As Long)
pStr = Addr
´修改BSTR描述符指针,使其指向Addr
CopyMemory ByVal VarPtr(sString), Addr, 4
If IsMissing(nLen) Then Exit Sub
´设定最大字串字节数
nMaxLen = nLen
End Sub
´还原本字串原BSTR描述符
Public Sub Detach()
CopyMemory ByVal VarPtr(sString), 0&, 4
End Sub
´让本字串指向源字串
Public Sub AttachStr(sStr As String)
Attach StrPtr(sStr), LenB(sStr)
End Sub
´data为缺省属性
Public Property Let data(sVal As String)
Dim c As Long
c = LenB(sVal)
´超过最大字串数,抛出错误。
If c > nMaxLen Then Err.Raise vbObjectError + 3000, _
"CString::Let Data", "溢出"
´写字串长度
CopyMemory ByVal (pStr - 4), c, 4
´写字串
Mid(sString, 1) = sVal
End Property
´可以通过公有变量sString来读字串,效率更高
Public Property Get data() As String
data = sString
End Property
Private Sub Class_Terminate()
Call Detach
End Sub
用法如下,假设我们已通过VitualAlloc,HeapAlloc,MapViewOfFile这样的内存管理API得到了一个4k个字节的可读写的内存地址baseAddr:
Dim sShare As New clsBSTR
´留下前4个字节用于BSTR保存字串字节数
sShare.Attach(baseAddr+4, 4096-4)
´下面的字串"Test"会直接写到baseAddr+4字节处
sShare = "Test"
Dim y As String
´读字串时可以用sString属性或缺省属性
y = sShare.sString
´用AttachStr方法Attach到一个字串。
´必须要先Detach
sShare.Detach
sShare.AttachStr(y)
sShare = "Hahaha"
Debug.Print y
´一旦AttachStr到字串y后,对sShare的修改就相当于对y的修改。
´并且以后对y的修改也只能用Mid语句
Mid(y, 1) = "xxxxx"
´不能直接赋值,这样VB会将原来y所指(也是sShare所指)内存释放,
´ 重新分配y。这样在访问sShare时会出错。
´y = "Test"
我也不在这里讲这个类的详细原理,可以参考我前面说的两篇文章。
使用这个类有几个需要注意的地方。
1、读字串时可以用sString属性来读,更快。
读sShare有两种方法,一种是用缺省属性Data来读,一种是直接用sString属性来读。用sString属性不重新分配内存,要快得多。
2、不要直接给sString赋值,应使用缺省的data属性来赋值。
之所以把sString属性暴露出来,是为了效率和方便。我们可以用Mid语句对其进行修改,但不要直接用"="来赋值。
3、注意Attach的第二个参数,表示字串的最大字节数,不要让它超过已经分配的内存。
4、用AttachStr将本字串对象Attach到某个字串(比如上面的y)上后,不能再对这个字串y重新赋值,也不能将其传递到会对其重新赋值的过程。
哇,这么多需要注意的问题,用起来岂不是更不方便。的确,用它的之前要考虑是不是必须的。因为建立这个类也一样有开销。所以还有一个需要注意的问题:
5、它主要的应用还是在于将字串安放在指定内存处。虽然它也可以让同一个进程内几个的字串达到共享的目的,但是如果只是两三个很小的字串这样时做反而慢了。
后计:
数组指针和字串指针我们已经谈过了,对于普通的数值类型变量的指针没有什么Hack的必要,但是它关系到一个有用的技术,下篇文章再谈。
本文和下篇文章的代码,以及用这个类来实现的共享内存的代码,我会发布到CSDN共享软件上,名字是《内存共享和指针》。