linux社区爱心援助Linux认证系列教程业界动态站务新闻公司招聘网络学院网址大全LPI专题CISCO专题
设为首页
加入收藏
管理团队
JSP  
JAVA  
PERL  
 您的位置:首页 > 开发语言 > Basic >
栏目导栏
  php
  JSP
  ASP
  asp.net
  JAVA
  c/c++/c#
  perl
  JavaScript
  Basic
  Delphi
资料搜索
热门文章
·VB读取文件内容的简单方法
·用VB查询数据库记录四法
·Visual Basic串口通信程序设计
·基于VB6.0射击游戏实例
·为你的VB程序程序加密(VB新手
·VB新手常见问题解答
·什么是BASIC语言
·VB的坐标系统综述
·Visual Basic变态用法之函数指
·如何编写高质量的VB代码
·VB COM基础之ActiveX EXEs
·菜鸟入门:VB编程规约
·Visual Basic代码优化的六条定
·在VB应用程序中调用Excel2000
·VB COM基础之创建第一个COM对象
最新文章
·VB2005中开发新一代控制台应用
·VB的坐标系统综述
·变量的作用域与存活期
·VBScript 数据类型
·为你的VB程序程序加密(VB新手
·VB新手常见问题解答
·用VB开发即时战略游戏
·窗体迁移大法
·VB编程的几个API函数的应用问题
·VB编程的一些基础的问题
·利用VB6.0开发基于IIS的应用程
·在VB应用程序中调用Excel2000
·菜鸟入门:VB编程规约
·Visual Basic 安装程序的制作
·.NET和VB编程创建SQL Server
Google
 
Visual Basic变态用法之函数指针
[ 作者:  加入时间:2007-10-19 13:12:05  来自:Linux联盟收集整理 ]
一、函数指针 hR6Linux联盟
hR6Linux联盟
   AddressOf得到一个VB内部的函数指针,我们可以将这个函数指针传递给需要回调这个函数的API,它的作用就是让外部的程序可以调用VB内部的函数。 hR6Linux联盟
hR6Linux联盟
   但是VB里函数指针的应用,远不象C里应用那么广泛,因为VB文档里仅介绍了如何将函数指针传递给API以实现回调,并没指出函数指针诸多神奇的功能,因为VB是不鼓励使用指针的,函数指针也不例外。 hR6Linux联盟
hR6Linux联盟
   首先让我们对函数指针的使用方式来分个类。 hR6Linux联盟
hR6Linux联盟
   1、回调。这是最基本也是最重要的功能。比如VB文档里介绍过的子类派生技术,它的核心就是两个API:SetWindowLong和CallWindowProc。 hR6Linux联盟
hR6Linux联盟
   我们可以使SetWindowLong这个API来将原来的窗口函数指针换成自己的函数指针,并将原来的窗口函数指针保存下来。这样窗口消息就可以发到我们自己的函数里来,并且我们随时可以用CallWindowProc来调用前面保存下来的窗口指针,以调用原来的窗口函数。这样,我们可以在不破坏原有窗口功能的前提下处理钩入的消息。 hR6Linux联盟
hR6Linux联盟
   具体的处理,我们应该很熟悉了,VB文档也讲得很清楚了。这里需要注意的就是CallWindowProc这个API,在后面我们将看到它的妙用。 hR6Linux联盟
hR6Linux联盟
   在这里我们称回调为让"外部调用内部的函数指针"。 hR6Linux联盟
hR6Linux联盟
   2、程序内部使用。比如在C里我们可以将C函数指针作为参数传递给一个需要函数指针的C函数,如后面还要讲到的C库函数qsort,它的声明如下: hR6Linux联盟
hR6Linux联盟
#define int (__cdecl *COMPARE)(const void *elem1, const void *elem2) hR6Linux联盟
void qsort(void *base, size_t num, size_t width, hR6Linux联盟
COMPARE pfnCompare);
hR6Linux联盟
   它需要一个COMPARE类型函数指针,用来比较两个变量大小的,这样排序函数可以调用这个函数指针来比较不同类型的变量,所以qsort可以对不同类型的变量数组进行排序。 hR6Linux联盟
hR6Linux联盟
   我们姑且称这种应用为"从内部调用内部的函数指针"。 hR6Linux联盟
hR6Linux联盟
   3、调用外部的函数 hR6Linux联盟
hR6Linux联盟
   也许你会问,用API不就是调用外部的函数吗?是的,但有时候我们还是需要直接获取外部函数的指针。比如通过LoadLibrary动态加载DLL,然后再通过GetProcAddress得到我们需要的函数入口指针,然后再通过这个函数指针来调用外部的函数,这种动态载入DLL的技术可以让我们更灵活的调用外部函数。 hR6Linux联盟
hR6Linux联盟
   我们称这种方式为"从内部调用外部的函数指针" hR6Linux联盟
hR6Linux联盟
   4、不用说,就是我们也可控制"从外部调用外部的函数指针"。不是没有,比如我们可以加载多个DLL,将其中一个DLL中的函数指针传到另一个DLL里的函数内。 hR6Linux联盟
hR6Linux联盟
   上面所分的"内"和"外"都是相对而言(DLL实际上还是在进程内),这样分类有助于以后我们谈问题,请记住我上面的分类,因为以后的文章也会用到这个分类来分析问题。 hR6Linux联盟
hR6Linux联盟
   函数指针的使用不外乎上面四种方式。但在实际使用中却是灵活多变的。比如在C++里继承和多态,在COM里的接口,都是一种叫vTable的函数指针表的巧妙应用。使用函数指针,可以使程序的处理方式更加高效、灵活。 hR6Linux联盟
hR6Linux联盟
   VB文档里除了介绍过第一方式外,对其它方式都没有介绍,并且还明确指出不支持“Basic 到 Basic”的函数指针(也就是上面说的第二种方式),实际上,通过一定的HACK,上面四种方式均可以实现。今天,我们就来看看如何来实现第二种方式,因为实现它相对来说比较简单,我们先从简单的入手。至于如何在VB内调用外部的函数指针,如何在VB里通过处理vTable接口函数指针跳转表来实现各种函数指针的巧妙应用,由于这将涉及COM内部原理,我将另文详述。 hR6Linux联盟
hR6Linux联盟
   其实VB的文档并没有说错,VB的确不支持“Basic 到 Basic”的函数指针,但是我们可以绕个弯子来实现,那就是先从"Basic到API",然后再用第一种方式"外部调用内部的函数指针"来从"API到BASIC",这样就达到了第二种方式从"Basic 到 Basic"的目的,这种技术我们可以称之为"强制回调",只有VB里才会有这种古怪的技术。 hR6Linux联盟
hR6Linux联盟
   说得有点绕口,但是仔细想想窗口子类派生技术里CallWindowProc,我们可以用CallWindowProc来强制外部的操作系统调用我们原来的保存的窗口函数指针,同样我们也完全可以用它来强制调用我们内部的函数指针。 hR6Linux联盟
hR6Linux联盟
   呵呵,前面说过要少讲原理多讲招式,现在我们就来开始学习招式吧! hR6Linux联盟
hR6Linux联盟
   考虑我们在VB里来实现和C里一样支持多关键字比较的qsort。完整的源代码见本文配套代码,此处仅给出函数指针应用相关的代码。 hR6Linux联盟
hR6Linux联盟
'当然少不了的CopyMemory,不用ANY的版本。 hR6Linux联盟
Declare Sub CopyMemory Lib "kernel32" Alias _ hR6Linux联盟
"RtlMoveMemory" (ByVal dest As Long, ByVal source As Long, _ hR6Linux联盟
ByVal numBytes As Long) hR6Linux联盟
hR6Linux联盟
'嘿嘿,看下面是如何将CallWindowProc的声明做成Compare声明的。 hR6Linux联盟
Declare Function Compare Lib "user32" Alias _ hR6Linux联盟
"CallWindowProcA" (ByVal pfnCompare As Long, ByVal pElem1 As Long, _ hR6Linux联盟
ByVal pElem2 As Long, ByVal unused1 As Long, _ hR6Linux联盟
ByVal unused2 As Long) As Integer hR6Linux联盟
'注:ByVal xxxxx As Long ,还记得吧!这是标准的指针声明方法。 hR6Linux联盟
hR6Linux联盟
'声明需要比较的数组元素的结构 hR6Linux联盟
Public Type TEmployee hR6Linux联盟
  Name As String hR6Linux联盟
  Salary As Currency hR6Linux联盟
End Type hR6Linux联盟
hR6Linux联盟
'再来看看我们的比较函数 hR6Linux联盟
'先按薪水比较,再按姓名比较 hR6Linux联盟
Function CompareSalaryName(Elem1 As TEmployee, _ hR6Linux联盟
      Elem2 As TEmployee, _ hR6Linux联盟
      unused1 As Long, _ hR6Linux联盟
      unused2 As Long) As Integer hR6Linux联盟
  Dim Ret As Integer hR6Linux联盟
  Ret = Sgn(Elem1.Salary - Elem2.Salary) hR6Linux联盟
  If Ret = 0 Then hR6Linux联盟
   Ret = StrComp(Elem1.Name, Elem2.Name, VBTextCompare) hR6Linux联盟
  End If hR6Linux联盟
  CompareSalaryName = Ret hR6Linux联盟
End Function hR6Linux联盟
hR6Linux联盟
'先按姓名比较,再按薪水比较 hR6Linux联盟
Function CompareNameSalary(Elem1 As TEmployee, _ hR6Linux联盟
      Elem2 As TEmployee, _ hR6Linux联盟
      unused1 As Long, _ hR6Linux联盟
      unused2 As Long) As Integer hR6Linux联盟
  Dim Ret As Integer hR6Linux联盟
  Ret = StrComp(Elem1.Name, Elem2.Name, VBTextCompare) hR6Linux联盟
  If Ret = 0 Then hR6Linux联盟
   Ret = Sgn(Elem1.Salary - Elem2.Salary) hR6Linux联盟
  End If hR6Linux联盟
  CompareNameSalary = Ret hR6Linux联盟
End Function
hR6Linux联盟
   最后再看看我们来看看我们最终的qsort的声明。 hR6Linux联盟
hR6Linux联盟
Sub qsort(ByVal ArrayPtr As Long, ByVal nCount As Long, _ hR6Linux联盟
ByVal nElemSize As Integer, ByVal pfnCompare As Long)
hR6Linux联盟
   上面的ArrayPtr是需要排序数组的第一个元素的指针,nCount是数组的元素个数,nElemSize是每个元素大小,pfnCompare就是我们的比较函数指针。这个声明和C库函数里的qsort是极为相似的。 hR6Linux联盟
hR6Linux联盟
   和C一样,我们完全可以将Basic的函数指针传递给Basic的qsort函数。 hR6Linux联盟
hR6Linux联盟
   使用方式如下: hR6Linux联盟
hR6Linux联盟
Dim Employees(1 To 10000) As TEmployee hR6Linux联盟
'假设下面的调用对Employees数组进行了赋值初始化。 hR6Linux联盟
Call InitArray() hR6Linux联盟
'现在就可以调用我们的qsort来进行排序了。 hR6Linux联盟
Call qsort(VarPtr(Employees(1)), UBound(Employees), _ hR6Linux联盟
LenB(Employees(1)), AddressOf CompareSalaryName) hR6Linux联盟
'或者先按姓名排,再按薪水排 hR6Linux联盟
Call qsort(VarPtr(Employees(1)), UBound(Employees), _ hR6Linux联盟
LenB(Employees(1)), AddressOf CompareNameSalary)
hR6Linux联盟
   聪明的朋友们,你们是不是已经看出这里的奥妙了呢?作为一个测验,你能现在就给出在qsort里使用函数指针的方法吗?比如现在我们要通过调用函数指针来比较数组的第i个元素和第j个元素的大小。 hR6Linux联盟
hR6Linux联盟
   没错,当然要使用前面声明的Compare(其实就是CallWindowProc)这个API来进行强制回调。 hR6Linux联盟
hR6Linux联盟
   具体的实现如下: hR6Linux联盟
hR6Linux联盟
Sub qsort(ByVal ArrayPtr As Long, ByVal nCount As Long, _ hR6Linux联盟
ByVal nElemSize As Integer, ByVal pfnCompare As Long) hR6Linux联盟
  Dim i As Long, j As Long hR6Linux联盟
  '这里省略快速排序算法的具体实现,仅给出比较两个元素的方法。 hR6Linux联盟
  If Compare(pfnCompare, ArrayPtr + (i - 1) * nElemSize, _ hR6Linux联盟
    ArrayPtr + (j - 1) * nElemSize, 0, 0) > 0 Then hR6Linux联盟
    '如果第i个元素比第j个元素大则用CopyMemory来交换这两个元素。 hR6Linux联盟
  End IF hR6Linux联盟
End Sub
hR6Linux联盟
   招式介绍完了,明白了吗?我再来简单地讲解一下上面Compare的意思,它非常巧妙地利用了CallWindowProc这个API。这个API需要五个参数,第一个参数就是一个普通的函数指针,这个API能够强马上回调这个函数指针,并将这个API的后四个Long型的参数传递给这个函数指针所指向的函数。这就是为什么我们的比较函数必须要有四个参数的原因,因为CallWindowProc这个API要求传递给的函数指针必须符合WndProc函数原形,WndProc的原形如下: hR6Linux联盟
hR6Linux联盟
LRESULT (CALLBACK* WNDPROC) (HWND, UINT, WPARAM, LPARAM);
hR6Linux联盟
   上面的LRESULT、HWND、UINT、WPARAM、LPARAM都可以对应于VB里的Long型,这真是太好了,因为Long型可以用来作指针嘛! hR6Linux联盟
hR6Linux联盟
   再来看看工作流程,当我们用AddressOf CompareSalaryName做为函数指针参数来调用qsort时,qsort的形参pfnCompare被赋值成了实参CompareSalaryName的函数指针。这时,调用Compare来强制回调pfnCompare,就相当于调用了如下的VB语句: hR6Linux联盟
hR6Linux联盟
Call CompareSalaryName(ArrayPtr + (i - 1) * nElemSize, _ hR6Linux联盟
ArrayPtr + (j - 1) * nElemSize, 0, 0)
hR6Linux联盟
   这不会引起参数类型不符错误吗?CompareSalaryName的前两个参数不是TEmployee类型吗?的确,在VB里这样调用是不行的,因为VB的类型检查不会允许这样的调用。但是,实际上这个调用是API进行的回调,而VB不可能去检查API回调的函数的参数类型是一个普通的Long数值类型还是一个结构指针,所以也可以说我们绕过了VB对函数参数的类型检查,我们可以将这个Long型参数声明成任何类型的指针,我们声明成什么,VB就认为是什么。所以,我们要小心地使用这种技术,如上面最终会传递给CompareSalaryName函数的参数"ArrayPtr + (i - 1) * nElemSize"只不过是一个地址,VB不会对这个地址进行检查,它总是将这个地址当做一个TEmployee类型的指针,如果不小心用成了"ArrayPtr + i * nElemSize",那么当i是最后一个元素时,我们就会引起内存越权访问错误,所以我们要和在C里处理指针一样注意边界问题。 hR6Linux联盟
hR6Linux联盟
   函数指针的巧妙应用这里已经可见一斑了,但是这里介绍的方法还有很大的局限性,我们的函数必须要有四个参数,更干净的做法还是在VC或Delphi里写一个DLL,做出更加符合要求的API来实现和CallWindowProc相似的功能。我跟踪过CallWindowProc的内部实现,它要做许多和窗口消息相关的工作,这些工作在我们这个应用中是多余的。其实实现强制回调API只需要将后几个参数压栈,再call第一个参数就行了,不过几条汇编指令而已。 hR6Linux联盟
hR6Linux联盟
   正是因为CallWindowProc的局限性,我们不能够用它来调用外部的函数指针,以实现上面说的第三种函数指针调用方式。要实现第三种方式,Matt Curland大师提供了一个噩梦一般的HACK方式,我们要在VB里凭空构造一个IUnknown接口,在IUnknown接口的vTable原有的三个入口后再加入一个新入口,在新入口里插入机器代码,这个机器代码要处理掉this指针,最后才能调用到我们给的函数指针,这个函数指针无论是内部的还是外部的都一样没问题。在我们深入讨论COM内部原理时我会再来谈这个方法。 hR6Linux联盟
hR6Linux联盟
   另外,排序算法是个见仁见智的问题,我本来想,在本文提供一个最通用性能最好的算法,这种想法虽好,但是不可能有在任何情况下都“最好”的算法。本文提供的用各种指针技术来实现的快速排序方法,应该比用对象技术来实现同样功能快不少,内存占用也少得多。可是就是这个已经经过了我不少优化的快速排序算法,还是比不了ShellSort,因为ShellSort实现上简单。从算法的理论上来讲qsort应该比ShellSort平均性能好,但是在VB里这不一定(可见本文配套代码,里面也提供了VBPJ一篇专栏的配套代码ShellSort,非常得棒,本文的思想就取自这个ShellSort)。 hR6Linux联盟
hR6Linux联盟
   但是应当指出无论是这里的快速排序还是ShellSort,都还可以大大改进,因为它们在实现上需要大量使用CopyMemroy来拷贝数据(这是VB里使用指针的缺点之一)。其实,我们还有更好的方法,那就是Hack一下VB的数组结构,也就是COM自动化里的SafeArray,我们可以一次性的将SafeArray里的各个数组元素的指针放到一个long型数组里,我们无需CopyMemroy,我们仅需交换Long型数组里的元素就可以达到实时地交换SafeArray数组元素指针的目的,数据并没有移动,移动的仅仅是指针,可以想象这有快多。 hR6Linux联盟
Linux联盟收集整理 ,转贴请标明原始链接,如有任何疑问欢迎来本站Linux论坛讨论
评论】【加入收藏夹】【 】【打印】【关闭
※ 相关链接
 ·Visual Basic 安装程序的制作  (2007-10-29 14:37:01)
 ·Visual Basic串口通信程序设计  (2007-10-29 14:33:03)
 ·Visual Basic6.0网络编程的五大散手  (2007-10-19 13:16:05)
 ·用Visual Basic创建多线程应用程序  (2007-10-19 13:13:59)
 ·Visual Basic COM基础之兼容性  (2007-10-19 13:07:40)
 ·Visual Basic COM基础之事件  (2007-10-19 13:01:19)
 ·Visual Basic COM基础之更多属性  (2007-10-19 12:59:38)
 ·Visual Basic COM基础之属性  (2007-10-19 12:58:54)
 ·Visual Basic COM基础之类的建立  (2007-10-19 12:58:10)
 ·Visual Basic COM基础讲座  (2007-10-19 12:57:22)