指向函数的指针:函数指针



  指针

   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声明如下:

# (__cdecl *COMPARE)(const void *elem1, const void *elem2)
void qsort(void *base, size_t num, size_t width,
COMPARE pfnCompare);
   它需要个COMPARE类型指针用来比较两个变量大小这样排序可以这个指针来比较区别类型变量所以qsort可以对区别类型变量进行排序

   我们姑且称这种应用为\"从内部内部指针\"

   3、外部

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

   我们称这种方式为\"从内部外部指针\"

   4、不用说就是我们也可控制\"从外部外部指针\"不是没有比如我们可以加载多个DLL将其中个DLL中指针传到另个DLL里

   上面所分\"内\"和\"外\"都是相对而言(DLL实际上还是在进程内)这样分类有助于以后我们谈问题请记住我上面分类以后文章也会用到这个分类来分析问题

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

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

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

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

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

   考虑我们在VB里来实现和C里样支持多关键字比较qsort完整源代码见本文配套代码此处仅给出指针应用相关代码

\'当然少不了CopyMemory不用ANY版本
Declare Sub CopyMemory Lib \"kernel32\" Alias _
\"RtlMoveMemory\" (ByVal dest As Long, ByVal source As Long, _
ByVal numBytes As Long)

\'嘿嘿看下面是如何将CallWindowProc声明做成Compare声明
Declare Function Compare Lib \"user32\" Alias _
\"CallWindowProcA\" (ByVal pfnCompare As Long, ByVal pElem1 As Long, _
ByVal pElem2 As Long, ByVal unused1 As Long, _
ByVal unused2 As Long) As Integer
\'注:ByVal xxxxx As Long 还记得吧!这是标准指针声明思路方法

\'声明需要比较元素结构
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指针传递给Basicqsort

   使用方式如下:

Dim Employees(1 To 10000) As TEmployee
\'假设下面对Employees进行了赋值
Call InitArray
\'现在就可以我们qsort来进行排序了
Call qsort(VarPtr(Employees(1)), UBound(Employees), _
LenB(Employees(1)), AddressOf CompareSalaryName)
\'或者先按姓名排再按薪水排
Call qsort(VarPtr(Employees(1)), UBound(Employees), _
LenB(Employees(1)), AddressOf CompareNameSalary)
   聪明朋友们你们是不是已经看出这里奥妙了呢?作为个测验你能现在就给出在qsort里使用指针思路方法吗?比如现在我们要通过指针来比较第i个元素和第j个元素大小

   没错当然要使用前面声明Compare(其实就是CallWindowProc)这个API来进行强制回调

   具体实现如下:

Sub qsort(ByVal ArrayPtr As Long, ByVal nCount As Long, _
ByVal nElemSize As Integer, ByVal pfnCompare As Long)
  Dim i As Long, j As Long
  \'这里省略快速排序算法具体实现仅给出比较两个元素思路方法
  If Compare(pfnCompare, ArrayPtr + (i - 1) * nElemSize, _
    ArrayPtr + (j - 1) * nElemSize, 0, 0) > 0 Then
    \'如果第i个元素比第j个元素大则用CopyMemory来交换这两个元素
  End IF
End Sub
   招式介绍完了明白了吗?我再来简单地讲解下上面Compare意思它非常巧妙地利用了CallWindowProc这个API这个API需要 5个参数个参数就是个普通指针这个API能够强马上回调这个指针并将这个API后 4个Long型参数传递给这个指针所指向这就是为什么我们比较必须要有 4个参数原因CallWindowProc这个API要求传递给指针必须符合WndProc原形WndProc原形如下:

LRESULT (CALLBACK* WNDPROC) (HWND, UINT, WPARAM, LPARAM);
   上面LRESULT、HWND、UINT、WPARAM、LPARAM都可以对应于VB里Long型这真是太好了Long型可以用来作指针嘛!

   再来看看工作流程当我们用AddressOf CompareSalaryName做为指针参数来qsort时qsort形参pfnCompare被赋值成了实参CompareSalaryName指针这时Compare来强制回调pfnCompare就相当于了如下VB语句:

Call CompareSalaryName(ArrayPtr + (i - 1) * nElemSize, _
ArrayPtr + (j - 1) * nElemSize, 0, 0)
   这不会引起参数类型不符吗?CompareSalaryName前两个参数不是TEmployee类型吗?在VB里这样是不行VB类型检查不会允许这样但是实际上这个是API进行回调而VB不可能去检查API回调参数类型是个普通Long数值类型还是个结构指针所以也可以说我们绕过了VB对参数类型检查我们可以将这个Long型参数声明成任何类型指针我们声明成什么VB就认为是什么所以我们要小心地使用这种技术如上面最终会传递给CompareSalaryName参数\"ArrayPtr + (i - 1) * nElemSize\"只不过是个地址VB不会对这个地址进行检查它总是将这个地址当做个TEmployee类型指针如果不小心用成了\"ArrayPtr + i * nElemSize\"那么当i是最后个元素时我们就会引起内存越权访问所以我们要和在C里处理指针样注意边界问题

   指针巧妙应用这里已经可见斑了但是这里介绍思路方法还有很大局限性我们必须要有 4个参数更干净做法还是在VC或Delphi里写个DLL做出更加符合要求API来实现和CallWindowProc相似功能我跟踪过CallWindowProc内部实现它要做许多和窗口消息相关工作这些工作在我们这个应用中是多余其实实现强制回调API只需要将后几个参数压栈再call第个参数就行了不过几条汇编指令而已

   正是CallWindowProc局限性我们不能够用它来外部指针以实现上面说第 3种指针方式要实现第 3种方式Matt Curland大师提供了个噩梦HACK方式我们要在VB里凭空构造个IUnknown接口在IUnknown接口vTable原有 3个入口后再加入个新入口在新入口里插入机器代码这个机器代码要处理掉this指针最后才能到我们给指针这个指针无论是内部还是外部样没问题在我们深入讨论COM内部原理时我会再来谈这个思路方法

   另外排序算法是个见仁见智问题我本来想在本文提供个最通用性能最好算法这种想法虽好但是不可能有在任何情况下都“最好”算法本文提供用各种指针技术来实现快速排序思路方法应该比用对象技术来实现同样功能快不少内存占用也少得多可是就是这个已经经过了我不少优化快速排序算法还是比不了ShellSortShellSort实现上简单从算法理论上来讲qsort应该比ShellSort平均性能好但是在VB里这不定(可见本文配套代码里面也提供了VBPJ篇专栏配套代码ShellSort非常得棒本文思想就取自这个ShellSort)

   但是应当指出无论是这里快速排序还是ShellSort都还可以大大改进它们在实现上需要大量使用CopyMemroy来拷贝数据(这是VB里使用指针缺点的)其实我们还有更好思路方法那就是Hack下VB结构也就是COM自动化里SafeArray我们可以次性将SafeArray里各个元素指针放到个long型我们无需CopyMemroy我们仅需交换Long型元素就可以达到实时地交换SafeArray元素指针数据并没有移动移动仅仅是指针可以想象这有快多


Tags:  delphi函数指针 typedef函数指针 函数指针数组 指向函数的指针

延伸阅读

最新评论

发表评论