不过,当我们交换lStrPtrs里的两个Long型指针元素时,还要记得同时交换它们的索引,比如交换第0个和第3个元素,如下:
lTemp1 = lStrPtrs(0, 3) : lTemp2 = lStrPtrs(1, 3)
lStrPtrs(0, 3) = lStrPtrs(0, 0) : lStrPtrs(1, 3) = lStrPtrs(1, 0) lStrPtrs(0, 0) = lTemp1 : lStrPtrs(1, 0) = lTemp2
当我们排好序后,我们还要将这个lStrPtrs里的指针元素写回去,如下: For i = Lo To Hi
CopyMemory(ByVal VarPtr(MyArr(i)), lStrPtrs(0,i), 4) Next
我已经不想再把这个方法讲下去,虽然它肯定可行,并且也肯定比用CopyMemory来移动数据要快,因为我们实际上移动的仅仅是Long型的指针元素。但我心里已经知道下面有更好更直接的方法,这种转弯抹角的曲线救国实在不值得浪费文字。
四、HACK数组的BSTR结构,实时处理指针。
最精采的来了,实时处理指针动态交换数据,好一个响亮的说法。
我们看到,上一节中所述方法的不足在于我们的Long型指针数组里的指针是独立的,它没有和字串数组里的字串指针联系在一起,要是能联系在一起,我们就能在交换Long型指针的同时,实时地交换字串元素。 这可能吗?
当然,否则我花那么笔墨去写SafeArray干什么!
在上一节,我们的目的是要把字串数组里的BSTR指针数组拿出来放到一个Long型数组里,而在这一节我们的目的是要让我们Long型指针数组就是字串数组里的BSTR指针数组。拿出来再放回去的方法,我们在上一节看到了,现在我们来看看,不拿出来而直接用的方法。
这个方法还是要从字串数组的SafeArray结构来分析,我们已经知道SafeArray结构里的pvData指向的就是一个放实际数据的真数组,而一个字串数组如MyArr它的pvData指向的是一个包含BSTR指针的真数组。现在让我们想想,如果我们将一个Long型数组lStrPtrs的pvData弄得和字串数组MyArr的pvData一样时会怎样?BSTR指针数组就可以通过Long型数组来访问了,先看如何用代码来实现这一点: '模块级变量
Private MyArr() As String '要排序的字串数组
Private lStrPtrs() As Long '上面数组的字串指针数组,后面会凭空构造它 Private pSA As Long '保存lStrPtrs数组的SafeArray结构指针 Private pvDataOld As Long '保存lStrPtrs数组的SafeArray结构的原 ' pvData指针,以便恢复lStrPtrs
'功能: 将Long型数组lStrPtrs的pvData设成字串数组MyArr的pvData ' 以使Long指针数组的变更能实时反应到字串数组里 Private Sub SetupStrPtrs() Dim pvData As Long
' 初始化lStrPtrs,不需要将数组设得和MyArr一样大
33
' 我们会在后面构造它 ReDim lStrPtrs(0) As Long
'得到字串数组的pvData pvData = VarPtr(MyArr(0))
'得到lStrPtrs数组的SafeArray结构指针
CopyMemory pSA, ByVal VarPtrArray(lStrPtrs), 4
'这个指针偏移12个字节后就是pvData指针,将这个指针保存到pvDataOld ' 以便最后还原lStrPtrs,此处也可以用: ' pvDataOld = VarPtr(lStrPtrs(0)) CopyMemory pvDataOld, ByVal pSA + 12, 4
'将MyArr的pvData写到lStrPtrs的pvData里去 CopyMemory ByVal pSA + 12, pvData, 4
'完整构造SafeArray必须要构造它的rgsabound(0).cElements
CopyMemory ByVal pSA + 16, UBound(MyArr) - LBound(MyArr) + 1, 4 '还有rgsabound(0).lLbound
CopyMemory ByVal pSA + 20, LBound(MyArr), 4 End Sub
看不懂,请结合图一再看看,应该可以看出我们是凭空构造了一个lStrPtrs,使它几乎和MyArr一模一样,唯一的不同就是它们的类型不同。MyArr字串数组里的fFeatures包含FADF_BSTR,而lStrPtrs的fFeatures包含FADF_HAVEVARTYPE,并且它的VARTYPE是VT_I4。不用关心这儿,我们只要知道lStrPtrs和MyArr它们指向同一个真数组,管他是BSTR还是VT_I4,我们把真数组里的元素当成指针来使就行了。
注意,由于lStrPtrs是我们经过了我们很大的改造,所以当程序结束前,我们应该将它还原,以便于VB来释放资源。是的,不释放也不一定会引起问题,因为程序运行结束后,操作系统的确是会回收我们在堆栈里分配了却没有释放的lStrPtrs原来的野指针pvOldData,但当你在IDE中运行时,你有60%的机会让VB的IDE死掉。我们是想帮VB做点家务事,而不是想给VB添乱子,所以请记住在做完菜后,一定要把厨房打扫干净,东西该还原的一定要还原。下面看看怎么样来还原: '还原我们做过手脚的lStrPtr Private Sub CleanUpStrPtrs()
'lStrPtr的原来声明为:ReDim lStrPtrs(0) As Long ' 按声明的要求还原它
CopyMemory pSA, ByVal VarPtrArray(lStrPtrs), 4 CopyMemory ByVal pSA + 12, pvDataOld, 4 CopyMemory ByVal pSA + 16, 1, 4 CopyMemory ByVal pSA + 20, 0, 4 End Sub
好了,精华已经讲完了,如果你还有点想不通,看看下面的实验:
34
'实验
Sub Main()
'初始化字串数组 Call InitArray(6)
'改造lStrPtrs Call SetupStrPtrs
'下面说明两个指针是一样的 Debug.Print lStrPtrs(3)
Debug.Print StrPtr(MyArr(3)) Debug.Print
'先看看原来的字串 Debug.Print MyArr(0) Debug.Print MyArr(3) Debug.Print
'现在来交换第0个和第3个字串 Dim lTmp As Long lTmp = lStrPtrs(3) lStrPtrs(3) = lStrPtrs(0) lStrPtrs(0) = lTmp
'再来看看我们的字串,是不是觉得很神奇 Debug.Print MyArr(0) Debug.Print MyArr(3) Debug.Print
'还原
Call CleanUpStrPtrs End Sub
在我的机器上,运行结果如下: 1887420 1887420
OPIIU
WCYKOTC
WCYKOTC OPIIU
怎么样?如愿已偿!字串通过交换Long型数被实时交换了。 通过这种方式来实现字串数组排序就非常快了,其效率上的提高是惊人的,对冒泡排序这样交换字串次数很多的排序方式,其平均性能能提高一倍以上(要看我们字串平均长度,),对快速排序这样交换次数较少的方法也能有不少性能上的提高,用这种技术实现的快速排
35
序,可以看看本文的配套代码中的QSortPointers。
本道菜最关键的技术已经讲了,至于怎么做完这道菜,怎么把这道菜做得更好,还需要大家自己来实践。
四、我们学到了什么。
仅就SafeArray来说,你可能已经发现我根本就没有直接去用我定义了的SAFEARRAY结构,我也没有展开讲它,实际上对SafeArray我们还可以做很多工作,还有很多巧妙的应用。还有需要注意的,VarPtrArray不能用来返回字串数组和Variant数组的SafeArray结构的指针的指针,为什么会这样和怎样来解决这个问题?这些都需要我们了解BSTR,了解VARIANT,了解VARTYPE,这些也只是COM的冰山一角,要学好VB,乃至整个软件开发技术,COM还有很多很多东西需要学习,我也还在学,在我开始COM的专题之前,大家也应该自学一下。
COM的东西先放一放,下一篇文章,应朋友的要求,我准备来写写内存共享。
后记:
又花了整整一天的时间,希望写的东西有价值,觉得有用就来叫个好吧!
36