• 获取VB类模块成员函数指针(转)


    最近在做一些VB6、VBA的项目,被如何获取类模块中的函数指针这个问题所困扰,收集整理后,有2分资料值得收藏,特将关键部分留存,以备后续查找。

    参照连接1:http://www.cnblogs.com/pctgl/articles/1352916.html

    参照连接2:http://blog.csdn.net/lyserver/article/details/4224676

    以下是链接1中的部分内容:

    1. 函数地址 = GetClassProcAddress ( 指定为哪个函数 [上面解释],想获取的函数的参数个数 )

    2. 这个函数的实现前提,要求有个局部数组,也就是在类模块的声明部分声明一个数组,由于我这个是在写一个热键类中完成的,所以这个数组名就叫了 TimerCallBack,你可以随便修改为你想要的 100% 免责开源...

    3. 函数返回值是一个 Long 型数据,并不是你指定的函数的真正地址,而是 TimerCallBack 数组的首元素地址, TimerCallBack中是一个小型的用汇编写的函数。

    4. 返回值就是你可以进行子类化的函数地址,返回值可直接用于 SetWindowLong x,x,这里

    下面这个GetClassProcAddress函数是作者修改后的版本,我调整了一下格式,以便阅读:

    Private Function GetClassProcAddress(ByVal SinceCount As Long, ByVal ParamsCount As Long) As Long
        Dim mePtr As Long
        Dim i As Long
        Dim jmpAddress As Long
        
        mePtr = ObjPtr(Me)
        CopyMemory i, ByVal mePtr, 4
        CopyMemory i, ByVal i + (SinceCount - 1) * 4 + &H1C, 4
        CopyMemory jmpAddress, ByVal i + 1, 4
        jmpAddress = i + jmpAddress + 5
        
        ReDim TimerCallback(33)
        TimerCallback(0) = &H55
        TimerCallback(1) = &H8B
        TimerCallback(2) = &HEC
        TimerCallback(3) = &H83
        TimerCallback(4) = &HEC
        TimerCallback(5) = &H10
        TimerCallback(6) = &H8B
        TimerCallback(7) = &HFC
        TimerCallback(8) = &H8D
        TimerCallback(9) = &H75
        TimerCallback(10) = &H8
        TimerCallback(11) = &H33
        TimerCallback(12) = &HC9
        TimerCallback(13) = &HB1
        TimerCallback(15) = &HFC
        TimerCallback(16) = &HF3
        TimerCallback(17) = &HA5
        TimerCallback(18) = &H68
        TimerCallback(23) = &HB8
        TimerCallback(28) = &HFF
        TimerCallback(29) = &HD0
        TimerCallback(30) = &HC9
        TimerCallback(33) = &H0
        
        TimerCallback(32) = ParamsCount * 4
        TimerCallback(14) = ParamsCount             ' mov cl,4(params)   >>> for rep movs
        CopyMemory TimerCallback(19), mePtr         ' objptr(me)
        CopyMemory TimerCallback(24), jmpAddress    ' call eax
        
        If ParamsCount = 0 Then
            TimerCallback(31) = &HC3
        Else
            TimerCallback(31) = &HC2
        End If
        
        GetClassProcAddress = VarPtr(TimerCallback(0))
    End Function
    

      

    以上代码,其中有一部分是作者参考的,原始函数代码如下

    Private Function GetClassProcAddress(ByVal SinceCount As Long) As Long
        '***************************************************************************************************
        '   VB6 历史上最简单的获取类中指定函数地址的函数诞生了,can be get address of property to value ,too
        '***************************************************************************************************
        Dim i As Long, jmpAddress As Long
        
        CopyMemory i, ByVal ObjPtr(Me), 4                                ' get vtable
        CopyMemory i, ByVal i + (SinceCount - 1) * 4 + &H1C, 4           ' 查表
        CopyMemory jmpAddress, ByVal i + 1, 4                            ' 获取的函数地址实际还是一个表,是一个跳转表
        GetClassProcAddress = i + jmpAddress + 5                         ' 计算跳转相对偏移取实际地址
    End Function
    

    调用方法:

    类模块中指定的函数地址 = GetClassProcAddress( 第几个函数 )

    参数 SinceCount:是从某个类模块中最顶端的函数或属性算起,他是第几个函数

    1. 当被查找的函数为 公用函数时,它的值就是自顶端算起的第几个函数,比如你在类模块中最顶端写的一个公用函数 WndProc,那么就传 1 如果是第2个公用函数或属性那么就传 2 依次... 注意,计算的时候要算上公用属性,公用属性也要算上,当他是函数,算做一个

    2. 当被查找的函数为 局部函数时,也就是说如果是 Private 修饰的函数,则此参数值为 所有公用函数个数 + 这是第 N 个私有函数 也是从顶端算起,同样包括属性

    原理:

    对象是什么? 对象实际就是一个结构,VB,甚至 C++ 都不一定能让你真正深刻的理解最底层的对象构造,如果说 VB 能让你懂得什么叫继承 则 C++ 能让你知道对象还可以变异....对象原来是那么简单实现了那么高级的技术 再向底层看,用汇编构造对象,你就可以看到,对象原来就是一个结构,结构中包括所有公用函数,属性的地址指针,和连接,销毁函数指针等 那么,在返回到 VB,ObjPtr 可以得到对象的 vTable 指针,通过查询 vTable 就可以得到我们想要的函数指针,前提是我们要知道编译器是 按照什么样的顺序放置属性函数指针的,现在经过查询资料和测试,已经知道了,那就是 基址 + &H1C 所谓的基址其实就是vTable, &H1C就 是VB给结构添加的和必要的函数指针所占用的空间, 从vTable+&H1C 开始存储我们的函数地址,存储顺序如何,可以参照上面对 GetClassProcAddress 的参数 SinceCount 的解释.VB 把所有模块都单独的建立了一个表,每个表中又有单独的表表示他所包含的函数地址. 好了,函数和原理解释已经差不多了,再说说应用 很不幸的我要说,直接应用价值基本 = 0 , 郁闷啊... 为什么呢? 因为... 对象的函数他的第1个参数是vTable指针,第2个(暂时忘了,想起来再补) 于是你构造的函数有4个参数,但编译后该函数将有6个参数,那如果直接交给别人用,比如 APi 那还不出事吗... 会出事,但又不是不能弥补,加上少量的内嵌汇编代码,从新构造一个小函数,就可以完美的运行了,o... 还是很不错的选择 说了很多, 我也累了,就先打住了,总结起来,就是成功的用最简单的代码获取了类模块中指定的函数地址,从这个角度来说此文应该还是一精华文章吧? 等我有时间了,我会将弥补的汇编函数和 GetClassProcAddress 相结合,构造一个最简单化的代码,实现真正的类模块回调函数

      

    以下是链接2中的部分内容:

    参考2链接的作者构建了一个类模块,可以直接使用该类模块子类化指定窗体,其窗体新的消息处理过程名是WindowProc

    该作者还构建过一个自定义计时器类,其中也用到了这个获取类模块函数指针的函数,链接http://blog.csdn.net/lyserver/article/details/4230045

    以下是类模块的代码:

    Option Explicit
    
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
    Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
    Private Declare Function IsWindow Lib "user32" (ByVal hWnd As Long) As Long
    Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Private Const GWL_WNDPROC = (-4)
    Private Const WM_NCDESTROY = &H82
    
    Public Event WindowProc(ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long, bCallNext As Boolean, lReturn As Long)
    Dim m_hwnd As Long, m_NewProc As Long, m_OldProc As Long
    Private Sub Class_Initialize()
        m_NewProc = GetClassProcAddr(Me, 5, 4, True)
    End Sub
    
    Private Sub Class_Terminate()
        Call Unbind
    End Sub
    
    Public Function Bind(ByVal hWnd As Long) As Boolean
        Call Unbind
        If IsWindow(hWnd) Then m_hwnd = hWnd
        m_OldProc = SetWindowLong(m_hwnd, GWL_WNDPROC, m_NewProc)
        Bind = CBool(m_OldProc)
    End Function
    
    Public Function Unbind() As Boolean
        If m_OldProc <> 0 Then Unbind = CBool(SetWindowLong(m_hwnd, GWL_WNDPROC, m_OldProc))
        m_OldProc = 0
    End Function
    
    Private Function WindowProcCallBack(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
        Dim bCallNext As Boolean, lReturn As Long
        bCallNext = True
        RaiseEvent WindowProc(Msg, wParam, lParam, bCallNext, lReturn)
        If bCallNext Then
            WindowProcCallBack = CallWindowProc(m_OldProc, hWnd, Msg, wParam, lParam)
        Else
            WindowProcCallBack = lReturn
        End If
        If hWnd = m_hwnd And Msg = WM_NCDESTROY Then Call Unbind
    End Function
    
    Private Function GetClassProcAddr(obj As Object, ByVal Index As Long, _
        Optional ByVal ParamCount As Long = 4, Optional ByVal HasReturnValue As Boolean) As Long
        Static lReturn As Long, pReturn As Long
        Static AsmCode(50) As Byte
        Dim i As Long, pThis As Long, pVtbl As Long, pFunc As Long
    
        pThis = ObjPtr(obj)
        CopyMemory pVtbl, ByVal pThis, 4
        CopyMemory pFunc, ByVal pVtbl + (6 + Index) * 4, 4
        pReturn = VarPtr(lReturn)
    
        For i = 0 To UBound(AsmCode)                                '填充nop
            AsmCode(i) = &H90
        Next
        AsmCode(0) = &H55                                           'push   ebp
        AsmCode(1) = &H8B: AsmCode(2) = &HEC                        'mov    ebp,esp
        AsmCode(3) = &H53                                           'push   ebx
        AsmCode(4) = &H56                                           'push   esi
        AsmCode(5) = &H57                                           'push   edi
        If HasReturnValue Then
            AsmCode(6) = &HB8                                       'mov    offset lReturn
            CopyMemory AsmCode(7), pReturn, 4
            AsmCode(11) = &H50                                      'push   eax
        End If
        For i = 0 To ParamCount - 1                                 'push   dword ptr[ebp+xx]
            AsmCode(12 + i * 3) = &HFF
            AsmCode(13 + i * 3) = &H75
            AsmCode(14 + i * 3) = (ParamCount - i) * 4 + 4
        Next
        i = i * 3 + 12
        AsmCode(i) = &HB9                                           'mov    ecx,this
        CopyMemory AsmCode(i + 1), pThis, 4
        AsmCode(i + 5) = &H51                                       'push ecx
        AsmCode(i + 6) = &HE8                                       'call 相对地址
        CopyMemory AsmCode(i + 7), pFunc - VarPtr(AsmCode(i + 6)) - 5, 4
        If HasReturnValue Then
            AsmCode(i + 11) = &HB8                                  'mov    eax,offset lReturn
            CopyMemory AsmCode(i + 12), pReturn, 4
            AsmCode(i + 16) = &H8B                                  'mov    eax,dword ptr[eax]
            AsmCode(i + 17) = &H0
        End If
        AsmCode(i + 18) = &H5F                                      'pop    edi
        AsmCode(i + 19) = &H5E                                      'pop    esi
        AsmCode(i + 20) = &H5B                                      'pop    ebx
        AsmCode(i + 21) = &H8B: AsmCode(i + 22) = &HE5              'mov    esp,ebp
        AsmCode(i + 23) = &H5D                                      'pop    ebp
        AsmCode(i + 24) = &HC3                                      'ret
        GetClassProcAddr = VarPtr(AsmCode(0))
    End Function
    
  • 相关阅读:
    AS出现Connection timed out
    关于eclipse出现The selection cannot be launched,and there are no recent launches
    第十周周赛题解
    FJUT 2401 尼克的任务
    关于3月份的学习总结
    人生的第一题图论
    第六周周赛题解
    Drupal8 社区文档之积极的Drupal版本
    Drupal8 社区文档之Drupal安全吗
    Drupal8 社区文档之技术堆栈
  • 原文地址:https://www.cnblogs.com/alexywt/p/5880993.html
Copyright © 2020-2023  润新知