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

发布时间 : 星期三 文章真是想不到系列文章(1-6) - VB6指针技术大揭秘更新完毕开始阅读449c59e79b89680203d82500

Value(i).data = 0 Next End Sub 【程序十】:'用指针的做法

Private Declare Sub CopyMemory Lib \ (ByVal dest As Long, ByVal source As Long, ByVal bytes As Long) Private Declare Sub ZeroMemory Lib \ (ByVal dest As Long, ByVal numbytes As Long)

Private Declare Sub FillMemory Lib \ (ByVal dest As Long, ByVal Length As Long, ByVal Fill As Byte)

Private Sub ShitArrayByPtr(ab() As MyTpye) Dim n As Long

n = CLng(UBound(ab) / 2) Dim nLenth As Long nLenth = Len(Value(1)) 'DebugBreak

CopyMemory ByVal VarPtr(Value(1 + n)), _ ByVal VarPtr(Value(1)), n * nLenth ZeroMemory ByVal VarPtr(Value(1)), n * nLenth End Sub

当数组较大,移动操作较多(比如用数组实现HashTable)时程序十比程序九性能上要好得多。

程序十中又介绍两个在指针操作中会用到的API: ZeroMemory是用来将内存清零;FillMemory用同一个字节来填充内存。当然,这两个API的功能,也完全可以用CopyMemory来完成。象在C里一样,作为一个好习惯,在VB里我们也可以明确的用ZeroMemory来对数组进行初始化,用FillMemory在不立即使用的内存中填入怪值,这有利于调试。 4、最后的一点

当然,VB指针的应用决不止这些,还有什么应用就要靠自己去摸索了。对于对象指针和字符串指针的应用我会另写文章来谈,做为本文的结束和下一篇文章《VB字符串全攻略》的开始,我在这里给出交换两个字符串的最快的方法: 【程序十一】'交换两个字符串最快的方法

Private Declare Sub CopyMemory Lib \Alias \_ (Destination As Any, Source As Any, ByVal Length As Long)

Sub SwapStrPtr3(sA As String, sB As String) Dim lTmp As Long

Dim pTmp As Long, psA As Long, psB As Long

pTmp = StrPtr(sA): psA = VarPtr(sA): psB = VarPtr(sB) CopyMemory ByVal psA, ByVal psB, 4 CopyMemory ByVal psB, pTmp, 4 End Sub

对不起,为了一点点效率,又用了Any!关于StrPtr,下一篇文章我会来谈。 自己来试试吧!欲练神功,赶快行动!

21

每次看大师的东西到了精彩之处,我就会拍案叫绝:\哇噻,真是想不到!\。在经过很多次这种感慨之后,我发现只要我们动了脑筋,我们自己也能有让别人想不到的东西。于是想到要把这些想不到的东拿出来和大家一起分享,希望抛砖引玉,能引出更多让人想不到的东西。

本系列文章可见:

http://www.csdn.net/develop/list_article.asp?author=AdamBear

VB真是想不到系列之三:VB指针葵花宝典之函数指针 关键字:VB、HCAK、指针、函数指针、效率、数组、对象、排序 难度:中级

要求:熟悉VB,了解基本的排序算法,会用VC更好。

引言:

不知大家在修习过本系列第二篇《VB指针葵花宝典》后有什么感想,是不是觉得宝典过于偏重内功心法,而少了厉害的招式。所以,今天本文将少讲道理,多讲招式。不过,还是请大家从名门正派的内功心法开始学起,否则会把九阴真经练成九阴白骨爪。 今天,我们重点来谈谈函数指针的实际应用。 接着上一篇文章,关于字串的问题,听CSDN上各位网友的建议,我不准备写什么《VB字符串全攻略》了,关于BSTR的结构,关于调用API时字串在UNICODE和ANSI之间的转换问题,请参考MSDN的Partial Books里的《Win32 API Programming with Visual Basic》里的第六章《Strings》。今天就让我们先忘掉字符串,专注于函数指针的处理上来。

一、函数指针

AddressOf得到一个VB内部的函数指针,我们可以将这个函数指针传递给需要回调这个函数的API,它的作用就是让外部的程序可以调用VB内部的函数。 但是VB里函数指针的应用,远不象C里应用那么广泛,因为VB文档里仅介绍了如何将函数指针传递给API以实现回调,并没指出函数指针诸多神奇的功能,因为VB是不鼓励使用指针的,函数指针也不例外。

首先让我们对函数指针的使用方式来分个类。

1、回调。这是最基本也是最重要的功能。比如VB文档里介绍过的子类派生技术,它的核心就是两个API:SetWindowLong和CallWindowProc。

我们可以使SetWindowLong这个API来将原来的窗口函数指针换成自己的函数指针,并将原来的窗口函数指针保存下来。这样窗口消息就可以发到我们自己的函数里来,并且我们随时可以用CallWindowProc来调用前面保存下来的窗口指针,以调用原来的窗口函数。这样,我们可以在不破坏原有窗口功能的前提下处理钩入的消息。

具体的处理,我们应该很熟悉了,VB文档也讲得很清楚了。这里需要注意的就是CallWindowProc这个API,在后面我们将看到它的妙用。 在这里我们称回调为让\外部调用内部的函数指针\。

2、程序内部使用。比如在C里我们可以将C函数指针作为参数传递给一个需要函数指针的C函数,如后面还要讲到的C库函数qsort,它的声明如下:

#define int (__cdecl *COMPARE)(const void *elem1, const void *elem2) void qsort(void *base, size_t num, size_t width, COMPARE pfnCompare);

它需要一个COMPARE类型函数指针,用来比较两个变量大小的,这样排序函数可以调用

22

这个函数指针来比较不同类型的变量,所以qsort可以对不同类型的变量数组进行排序。 我们姑且称这种应用为\从内部调用内部的函数指针\。 3、调用外部的函数

也许你会问,用API不就是调用外部的函数吗?是的,但有时候我们还是需要直接获取外部函数的指针。比如通过LoadLibrary动态加载DLL,然后再通过GetProcAddress得到我们需要的函数入口指针,然后再通过这个函数指针来调用外部的函数,这种动态载入DLL的技术可以让我们更灵活的调用外部函数。

我们称这种方式为\从内部调用外部的函数指针\

4、不用说,就是我们也可控制\从外部调用外部的函数指针\。不是没有,比如我们可以加载多个DLL,将其中一个DLL中的函数指针传到另一个DLL里的函数内。 上面所分的\内\和\外\都是相对而言(DLL实际上还是在进程内),这样分类有助于以后我们谈问题,请记住我上面的分类,因为以后的文章也会用到这个分类来分析问题。

函数指针的使用不外乎上面四种方式。但在实际使用中却是灵活多变的。比如在C++里继承和多态,在COM里的接口,都是一种叫vTable的函数指针表的巧妙应用。使用函数指针,可以使程序的处理方式更加高效、灵活。

VB文档里除了介绍过第一方式外,对其它方式都没有介绍,并且还明确指出不支持“Basic 到 Basic”的函数指针(也就是上面说的第二种方式),实际上,通过一定的HACK,上面四种方式均可以实现。今天,我们就来看看如何来实现第二种方式,因为实现它相对来说比较简单,我们先从简单的入手。至于如何在VB内调用外部的函数指针,如何在VB里通过处理vTable接口函数指针跳转表来实现各种函数指针的巧妙应用,由于这将涉及COM内部原理,我将另文详述。

其实VB的文档并没有说错,VB的确不支持“Basic 到 Basic”的函数指针,但是我们可以绕个弯子来实现,那就是先从\到API\,然后再用第一种方式\外部调用内部的函数指针\来从\到BASIC\,这样就达到了第二种方式从\到 Basic\的目的,这种技术我们可以称之为\强制回调\,只有VB里才会有这种古怪的技术。

说得有点绕口,但是仔细想想窗口子类派生技术里CallWindowProc,我们可以用CallWindowProc来强制外部的操作系统调用我们原来的保存的窗口函数指针,同样我们也完全可以用它来强制调用我们内部的函数指针。

呵呵,前面说过要少讲原理多讲招式,现在我们就来开始学习招式吧!

考虑我们在VB里来实现和C里一样支持多关键字比较的qsort。完整的源代码见本文配套代码,此处仅给出函数指针应用相关的代码。 '当然少不了的CopyMemory,不用ANY的版本。 Declare Sub CopyMemory Lib \

\ ByVal numBytes As Long)

'嘿嘿,看下面是如何将CallWindowProc的声明做成Compare声明的。 Declare Function Compare Lib \

\ ByVal pElem2 As Long, ByVal unused1 As Long, _ ByVal unused2 As Long) As Integer

'注:ByVal xxxxx As Long ,还记得吧!这是标准的指针声明方法。

23

'声明需要比较的数组元素的结构 Public Type TEmployee Name As String Salary As Currency End Type

'再来看看我们的比较函数 '先按薪水比较,再按姓名比较

Function CompareSalaryName(Elem1 As TEmployee, _

Elem2 As TEmployee, _ unused1 As Long, _

unused2 As Long) As Integer Dim Ret As Integer

Ret = Sgn(Elem1.Salary - Elem2.Salary) If Ret = 0 Then

Ret = StrComp(Elem1.Name, Elem2.Name, vbTextCompare) End If

CompareSalaryName = Ret End Function

'先按姓名比较,再按薪水比较

Function CompareNameSalary(Elem1 As TEmployee, _ Elem2 As TEmployee, _ unused1 As Long, _

unused2 As Long) As Integer Dim Ret As Integer

Ret = StrComp(Elem1.Name, Elem2.Name, vbTextCompare) If Ret = 0 Then

Ret = Sgn(Elem1.Salary - Elem2.Salary) End If

CompareNameSalary = Ret End Function

最后再看看我们来看看我们最终的qsort的声明。

Sub qsort(ByVal ArrayPtr As Long, ByVal nCount As Long, _

ByVal nElemSize As Integer, ByVal pfnCompare As Long)

上面的ArrayPtr是需要排序数组的第一个元素的指针,nCount是数组的元素个数,nElemSize是每个元素大小,pfnCompare就是我们的比较函数指针。这个声明和C库函数里的qsort是极为相似的。

和C一样,我们完全可以将Basic的函数指针传递给Basic的qsort函数。 使用方式如下:

Dim Employees(1 To 10000) As TEmployee

'假设下面的调用对Employees数组进行了赋值初始化。 Call InitArray()

'现在就可以调用我们的qsort来进行排序了。

24