真是想不到系列文章(1-6) - VB6指针技术大揭秘

不过,当我们交换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

联系客服:779662525#qq.com(#替换为@)