封装回调函数——为对象方法(Object Method,参数中带this指针的函数) 构造 普通函数(参数中无this指针的函数)形式 的入口

文件:mFunEntry.bas
功能:封装回调函数——为对象方法(Object Method,参数中带this指针的函数) 构造 普通函数(参数中无this指针的函数)形式 的入口
作者:zyl910
版本:V1.0
日期:2005-6-24


  在VB使用回调函数很麻烦,得写在模块中,且很难封装。这个模块就是为了解决这个问题。

 

原理:VB对象的结构
~~~~~~~~~~~~~~~~~~



  Object变量        Object对象      接口函数指针表
┏━━━━━━━━┓  ┏━━━━━━━━━┓  ┏━━━[默认接口]━━━┓  ╭──────╮
① 0(4) 接口的指针┃─→② 0(4) *pVTable  ┃─→③ 0(4) QueryInterface  ┃─→│      │
┗━━━━━━━━┛  ┃ 4(?) 自定义数据 1┃  ┃ 4(4) AddRef          ┃─→│      │
            ┃ ?(?) 自定义数据 2┃  ┃ 8(4) Release         ┃─→│      │
            ┃ ?(?) 自定义数据 3┃  ┃ C(4) GetTypeInfoCount┃─→│      │
            ┃ ?(?) ……    ┃  ┃10(4) GetTypeInfo     ┃─→│      │
            ┃         ┃  ┃14(4) GetIDsOfNames   ┃─→│      │
            ┃         ┃  ┃18(4) Invoke          ┃─→│      │
            ┃  对象数据   ┃  ┃1C(4) 对象自定义函数 1┃─→│ 对象实现 │
            ┃         ┃  ┃20(4) 对象自定义函数 2┃─→│      │
            ┃         ┃  ┃24(?) ……      ┃  │      │
 接口的Object变量   ┃         ┃  ┗━━━━━━━━━━━┛  │      │
┏━━━━━━━━┓  ┣━━[接口 1]━━━┫  ┏━━━[接口 1]━━━━┓  │      │
① 0(4) 接口的指针┃─→② 0(4) *pVTable  ┃─→③ ?(?) ……      ┃  │      │
┗━━━━━━━━┛  ┃         ┃  ┗━━━━━━━━━━━┛  │      │
            ┣━━[接口 2]━━━┫  ┏━━━[接口 2]━━━━┓  │      │
            ┃   ……    ┃  ┃ ?(?) ……      ┃  ╰──────╯


地址① = VarPtr(Object变量)
地址② = ObjPtr(Object变量)  '// this指针
地址③:只能通过CopyMemory间接得到:Call CopyMemory(lngAddr, byval ObjPtr(Object变量), 4)


当使用Implements语句继承多个接口的时候
接口们的this指针形成一个表,都是存储在对象的内存区域中的
且接口们的接口函数指针表都是连在一起的





函数调用前的栈:
    ┃    ┃    ┃    ┃    ┃    ┃
    ┃    ┃    ┃    ┃    ┣━━━━┫
    ┃    ┃    ┣━━━━┫ ESP+00┃返回地址┃
    ┣━━━━┫ ESP+00┃返回地址┃ ESP+04┃this指针┃
 ESP+00┃返回地址┃ ESP+04┃this指针┃ ESP+08┃UserData┃
 ESP+04┃ 参数 0 ┃ ESP+08┃ 参数 0 ┃ ESP+0C┃ 参数 0 ┃
 ESP+08┃ 参数 1 ┃ ESP+0C┃ 参数 1 ┃ ESP+10┃ 参数 1 ┃
    ┃ …… ┃    ┃ …… ┃    ┃ …… ┃
   (a).普通函数    (b).成员函数  (c).带UserData参数


函数调用中的栈:
    ┃    ┃    ┃    ┃    ┃    ┃
    ┃    ┃    ┃    ┃    ┣━━━━┫
    ┃    ┃    ┣━━━━┫ EBP+00┃ 旧的EBP┃
    ┣━━━━┫ EBP+00┃ 旧的EBP┃ EBP+04┃返回地址┃
 EBP+00┃ 旧的EBP┃ EBP+04┃返回地址┃ EBP+08┃this指针┃
 EBP+04┃返回地址┃ EBP+08┃this指针┃ EBP+0C┃UserData┃
 EBP+08┃ 参数 0 ┃ EBP+0C┃ 参数 0 ┃ EBP+10┃ 参数 0 ┃
 EBP+0C┃ 参数 1 ┃ EBP+10┃ 参数 1 ┃ EBP+14┃ 参数 1 ┃
    ┃ …… ┃    ┃ …… ┃    ┃ …… ┃
   (a).普通函数    (b).成员函数  (c).带UserData参数


成员函数的返回值
~~~~~~~~~~~~~~~~

对于过程(VB语法):
Sub Proc(ByVal N As Long)

VB实际处理成(IDL语法):
HRESULT Proc([in] int N);

加上this指针后实际变成(C语法):
HRESULT Proc(void* This, int N);

函数返回值是放在EAX中,而过程调用者不理会返回值,所以该操作不会造成问题。




对于函数(VB语法):
Function Proc(byval N as long) As Long

VB实际处理成(IDL语法):
HRESULT Proc([in] int N, [out,retval] int*);

加上this指针后实际变成(C语法):
HRESULT Proc(void* This, [in] int N, [out,retval] int*);


由于现在多了一个返回值参数,使得堆栈不平衡,所以会出现非法操作。

成员函数的返回值
~~~~~~~~~~~~~~~~

对于过程(VB语法):
Sub Proc(ByVal N As Long)

VB实际处理成(IDL语法):
HRESULT Proc([in] int N);

加上this指针后实际变成(C语法):
HRESULT Proc(void* This, int N);

函数返回值是放在EAX中,而过程调用者不理会返回值,所以该操作不会造成问题。

 


对于函数(VB语法):
Function Proc(byval N as long) As Long

VB实际处理成(IDL语法):
HRESULT Proc([in] int N, [out,retval] int*);

加上this指针后实际变成(C语法):
HRESULT Proc(void* This, [in] int N, [out,retval] int*);


由于现在多了一个返回值参数,使得堆栈不平衡,所以会出现非法操作。

 

模块代码
~~~~~~~~

mFunEntry.bas
Option Explicit

'模块:函数入口
'功能:封装回调函数——为对象方法(Object Method,参数中带this指针的函数) 构造 普通函数(参数中无this指针的函数)形式 的入口
'作者:zyl910
'版本:V1.0
'日期:2005-6-24


'全局编译常量(请在工程属性对话框设置“条件编译参数”)
'~~~~~~~~~~~~
'IsRelease:是否是出于发布模式。若处于发布模式,程序不会检查FEF_CheckIDE常量
'APITypeLib:是否有API类型库。


'本模块编译常量
'~~~~~~~~~~~~~~
'#Const DebugASM = True '是否在指令中加入int3(为了便于调试)



'## 说明 ###################################################

'编写过程
'~~~~~~~~
'
'  使用AddressOf运算符编写回调函数太麻烦了,还需要在模块中
'编写代码,一点也不符合面向对象程序设计的精神。而且某些回调
'函数是不能在VB IDE的调试环境下中断的,如子类化、钩子、计时
'器等。
'
'  为了封装回调函数我试过很多方法,但都不太理想。比如上一
'次尝试:分别根据子类化、钩子、计时器、无返回值回调函数、带
'返回值回调函数编写功能,使用起来很简单。而且都是内嵌汇编代
'码,非常高效。但是存在这样的问题:各个功能都是分离的,不但
'编码复杂,而且体积庞大。比如子类化处理的类,每个对象的内嵌
'汇编代码就需要519字节的内存,再加上对象自身的内存占用,非常
'占内存。
'
'  于是我想这样改进:每个函数入口只有少量的跳转代码和一些
'参数,跳转代码用于跳转到通用入口函数。而通用入口函数根据参
'数,调整好函数调用协议来调用相应处理函数(比如可以给参数列
'中添加this指针以调用对象方法)
'
'  然后考虑到对象是从堆中分配的,速度慢,且VB对象占内存比
'较大。所以最后决定使用结构体来作为函数入口。
'  一个函数入口就是一个FunctionEntryBlock结构体变量,该结
'构体是由FunctionEntryHeader结构及FunctionEntryData结构组合
'而成的。请使用FunEntryFillBlock函数来初始化该结构。
'  FunctionEntryHeader结构位于FunctionEntryBlock结构的头部,
'该结构体的内容就是汇编跳转代码。由于汇编代码的最后一条是
'call指令,所以会将下一条指令的地址——FunctionEntryData结构
'的地址压入栈,就这样将参数传递了过去。请使用FunEntryFillHeader
'函数将该结构与入口函数绑定。


'使用方法
'~~~~~~~~
'
'一、基本应用
'  使用FEAttachProc可将FunctionEntryBlock结构体与你想调用
'的函数绑定起来。但我们一般使用接口来处理回调函数,这样可以
'实现面向对象编程:
'    1.定义好接口。使接口的函数原型与回调函数的原型一致(实
'际上多了this指针)。您可以用VB的类模块定义或通过类型库定义,
'只不过使用类型库定义时要注意带返回值的函数必须以retval形式
'处理返回值。
'    2.在类模块(或窗口模块、用户自定义控件模块)实现(implements)该接口。
'    3.在适当的位置定义FunctionEntryBlock结构体变量,并使用
'FEAttachIUnknow(对于直接从IUnknow继承的接口。此种接口只能
'用类型库声明)或FEAttachIDispatch(对于直接从IUnknow继承的
'接口。此种接口是可以通过VB类模块声明)来为该结构体填充内嵌
'汇编代码及一些关键数据。由于这些函数非常强大,所以一般情况
'下您只需调整fed.fFlags字段及fed.pfnOldProc字段,且只有在带
'有FEF_CheckIDE标志时才可能需要fed.pfnOldProc字段。
'    4.入口地址就是FunctionEntryBlock结构中的feh成员的首地址,
'可以通过VarPtr函数得到该地址。由于feh就是该结构的首个字段,
'所以可以以“VarPtr([FunctionEntryBlock变量])”的方式得到入
'口地址。同时为了方便,FEAttachProc、FEAttachIUnknow及
'FEAttachIDispatch均返回入口地址。



'技术资料
'~~~~~~~~
'1.EbMode函数
'  该函数是vba6的导出函数,可以通过调用该函数来得知是否处
'于VB IDE的调试模式。但是,该函数不能通过VB语句来调用,这是
'因为当执行VB语句的时候,VB IDE已不是出于中断模式了,而是因
'要解析执行VB语句而处于运行模式。
'
'函数原型:int EbMode(void);
'返回值:
'   0:编译后执行
'   1:在VB IDE中运行
'   2:在VB IDE中被中断




'参考文献
'~~~~~~~~
'1.Matthcw Curland著,涂翔云等译,《高级Visual Basic编程》(Advanced Visual Basic 6: Power Techniques for Everyday Programs)。中国电力出版社,2001.5
'2.李维著,《VCL架构分析》。电子工业出版社,2004.1
'3.罗云彬著,《Windows环境下32位汇编语言程序设计》。电子工业出版社,2002.10



#If APITypeLib = 0 Then
'## API ####################################################

'== 模块 ===================================================
Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long

Private Const STR_MODULE_VBA6           As String = "vba6"
Private Const STR_MODULE_VBA5           As String = "vba5"

Private Const STR_EBMODE                As String = "EbMode"


'== 内存 ===================================================
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)



#End If '#If APITypeLib = 0 Then
'###########################################################

'== FunctionEntry ==========================================
Public Enum FunctionEntryFlags
    FEF_CheckIDE = 1
        '是否检查VB IDE(通过调用pfnCheckIDE)。若处于发布模式(编译常量IsRelease),程序不会检查FEF_CheckIDE常量
        '若处于VB IDE的调试模式,则试图调用pfnOldProc。
        '若pfnOldProc不存在,则自己修正栈并返回。
    
    FEF_StructPtr = 2
        '是否传递FunctionEntryData结构的地址。
            '当存在该标志时,表示参数列中第一个参数是FunctionEntryData结构的地址,而不传送this指针。
            '该标志用于某些复杂设计,这样可以在标准模块中编写转发函数以处理复杂情况。
    
    FEF_RetVal = &H80000000
        '是否有返回值。
        'COM的返回值是在栈中的,需要特别处理(所以需要该标志)。注意只支持返回简单类型(既返回值能放入EAX寄存器)。
        '对于传统的通过EAX寄存器返回结果的函数,则不需要该标志。
    
End Enum

'函数入口的结构头。存储函数跳转代码
Public Type FunctionEntryHeader '16Byte
    ASMCodeJmp(0 To 3)  As Long '汇编跳转代码
End Type

'函数入口的标准数据。用于正确的调用函数。可以在cArgs项目后添加自己的项目以处理复杂情况
Public Type FunctionEntryData '32Byte
    ASMCodeRet(0 To 7)  As Byte '存放返回指令,因为需要从不同参数个数的函数中返回
    fFlags              As Long '标志常数
    pfnCheckIDE         As Long '检查VB IDE的函数(在调用CreateFunctionEntry()时自动设置该成员)
    pfnProc             As Long '欲调用的函数(会调整参数列,如插入this指针)
    pfnOldProc          As Long '原来的函数(不会调整参数列,与原来的函数原型一致)
    pObjThis            As Long 'this指针(VarPtr(对象变量)的返回值)
    cArgs               As Long '有多少个参数(函数的参数是多少个DWORD)
End Type

'函数入口块
Public Type FunctionEntryBlock '48Byte
    feh As FunctionEntryHeader
    fed As FunctionEntryData
End Type


'注意:结构体是经过仔细调整的,使其按16字节边界对其。


'== ASM Code: GenEntry =====================================

'详细代码请参考 asm/FunEntry.asm
Private Const ASMSize_GenEntry As Long = 168
#If IsRelease Then
Private Const ASM_GenEntry As String = "9058558BEC83EC08608BD88945FC33C08945F88B4308A901000000EB258B430C85C0741EFFD083F80275178B431485C0740C8945F8618B45F88BE55DFFE08D03FFE08B4308A900000080752B8B53108955F88B4308A90200000075068B43188945FC618BE55DFF34248B4424FC894424048B4424F8FFE08D45F8508B531C8BC2B102D3E08BCA2BE0FC8D75088BFCF3A58BD38B4308A90200000075038B531852FF53108D03FFE0CC"
#Else
Private Const ASM_GenEntry As String = "9058558BEC83EC08608BD88945FC33C08945F88B4308A90100000074258B430C85C0741EFFD083F80275178B431485C0740C8945F8618B45F88BE55DFFE08D03FFE08B4308A900000080752B8B53108955F88B4308A90200000075068B43188945FC618BE55DFF34248B4424FC894424048B4424F8FFE08D45F8508B531C8BC2B102D3E08BCA2BE0FC8D75088BFCF3A58BD38B4308A90200000075038B531852FF53108D03FFE0CC"
#End If
'注:Release版是将&H1B位置的 74 25(jz _no_stopped) 改成 EB 25(jmp _no_stopped)

'不能直接将代码放在模块级数组中。
'因为模块级数组的空间是在堆中分配的,会存在进程正在结束时释放内存后造成悬空指针的问题。
'所以应当将代码放在结构体中,这是由于模块级结构体的内存是在静态内存区(未初始化数据段)的
Private Type ASMCodeBlock_GenEntry
    Code(0 To ASMSize_GenEntry - 1) As Byte
End Type
Private m_ASMCode_GenEntry As ASMCodeBlock_GenEntry



'===========================================================
Private m_pfnGenEntry As Long   '通用入口函数的地址
Private m_pfnEbMode As Long     '检查VB IDE的函数

Private Sub pvInit()
    Dim hMod As Long
    Dim i As Long
    
    If m_pfnGenEntry <> 0 Then Exit Sub
    
    With m_ASMCode_GenEntry
        '读取指令
        For i = 0 To ASMSize_GenEntry - 1
            .Code(i) = CByte("&H" & Mid$(ASM_GenEntry, i * 2 + 1, 2))
        Next i
        
        '绑定地址
        m_pfnGenEntry = VarPtr(.Code(0))
        
    End With
    
    '得到EbMode函数的地址
    hMod = GetModuleHandle(STR_MODULE_VBA5) 'VB5?
    If hMod = 0 Then hMod = GetModuleHandle(STR_MODULE_VBA6) 'VB6?
    If hMod Then '处于VB IDE的调试模式
        m_pfnEbMode = GetProcAddress(hMod, STR_EBMODE)
    Else '编译后执行
        m_pfnEbMode = 0
    End If
    
End Sub

'填充FunctionEntryHeader结构的内嵌汇编代码
Public Sub FEFillHeader(ByRef feh As FunctionEntryHeader, Optional ByVal pfnCall As Long = 0)
    '默认函数是m_pfnGenEntry
    If pfnCall = 0 Then
        If m_pfnGenEntry = 0 Then pvInit
        pfnCall = m_pfnGenEntry
    End If
    '填充指令
    With feh
        #If DebugASM Then
            .ASMCodeJmp(0) = &H909090CC 'CC:int 3
        #Else
            .ASMCodeJmp(0) = &H90909090 '90:nop
        #End If
        .ASMCodeJmp(1) = &HB8909090
        .ASMCodeJmp(2) = pfnCall
        .ASMCodeJmp(3) = &HD0FF9090
    End With
    '-- Code --
    '90         nop
    '90         nop
    '90         nop
    'B8 imm32   mov eax, imm32
    '90         nop
    '90         nop
    'FF D0      call eax
End Sub

'填充FunctionEntryData结构的内嵌汇编代码
Public Sub FEFillData(ByRef fed As FunctionEntryData, Optional ByVal cArgs As Long = -1)
    With fed
        '计算返回字节
        If cArgs < 0 Then '表示使用结构体中的cArgs字段
            cArgs = .cArgs
        Else
            .cArgs = cArgs
        End If
        cArgs = cArgs * 4 '将参数个数转为字节
        If (cArgs And &HFFFF0000) Then '超过WORD范围
            Err.Raise 6 '溢出
            Exit Sub
        End If
        
        '填充代码
            '61        popad
            '8B 45 F8  mov eax, dword ptr [ebp-8]
            'C9        leave
            'C2 34 12  ret 1234h
        .ASMCodeRet(0) = &H61
        .ASMCodeRet(1) = &H8B
        .ASMCodeRet(2) = &H45
        .ASMCodeRet(3) = &HF8
        .ASMCodeRet(4) = &HC9
        .ASMCodeRet(5) = &HC2
        .ASMCodeRet(6) = (cArgs And &HFF)
        .ASMCodeRet(7) = (cArgs And &HFF00&) / &H100&
        
        '填充pfnCheckIDE字段
        .pfnCheckIDE = m_pfnEbMode
        
    End With
End Sub

'填充FunctionEntryBlock结构的内嵌汇编代码(填充Header和Data结构)
Public Sub FEFillBlock(ByRef feb As FunctionEntryBlock, Optional ByVal cArgs As Long = -1)
    With feb
        '填充FunctionEntryHeader
        Call FEFillHeader(.feh)
        '填充FunctionEntryData
        Call FEFillData(.fed, cArgs)
    End With
End Sub

'取得对象方法函数的地址
'参数:
    'pObj: 对象的地址,既ObjPtr的返回值
    'iOffest: 该方法在VTable表中偏移。如果是基于IUnknow(只能用类型库声明)的,该值一般为4。如果是基于IDispatch(可以用VB类模块声明)的,该值一般为7。
Public Function FEGetMethodPtr(ByVal pObj As Long, Optional ByVal iOffest As Long = 7) As Long
    Dim pVTable As Long
    
    Call CopyMemory(pVTable, ByVal pObj, 4)
    Call CopyMemory(FEGetMethodPtr, ByVal (pVTable + iOffest * 4), 4)
    
End Function

'绑定函数入口到某个函数上
'参数:
    'feb    : FunctionEntryBlock结构体的地址
    'pObj   : 对象的地址,既ObjPtr的返回值
    'pfnProc: 欲调用的函数
    'cArgs  : 参数列中参数个数
'返回值: 入口地址(实质上是该结构体的.feh子结构首地址)
Public Function FEAttachProc( _
        ByRef feb As FunctionEntryBlock, _
        ByVal pObj As Long, _
        ByVal pfnProc As Long, _
        Optional ByVal cArgs As Long = -1) As Long
    With feb
        '填充FunctionEntryHeader
        Call FEFillHeader(.feh)
        '填充FunctionEntryData
        Call FEFillData(.fed, cArgs)
        '调整指针
        With .fed
            .pObjThis = pObj
            .pfnProc = pfnProc
        End With
        
        FEAttachProc = VarPtr(.feh)
        
    End With
    
End Function

'绑定函数入口到接口指针(IUnknow版)上
'参数:
    'feb    : FunctionEntryBlock结构体的地址
    'pObj   : 对象的地址,既ObjPtr的返回值
    'cArgs  : 参数列中参数个数
    'iOffest: 该方法在VTable表中偏移
'返回值: 入口地址(实质上是该结构体的.feh子结构首地址)
Public Function FEAttachIUnknow( _
        ByRef feb As FunctionEntryBlock, _
        ByVal pObj As Long, _
        Optional ByVal cArgs As Long = -1, _
        Optional ByVal iOffest As Long = 4) As Long
    FEAttachIUnknow = FEAttachProc(feb, pObj, FEGetMethodPtr(pObj, iOffest), cArgs)
End Function

'绑定函数入口到接口指针(IDispatch版)上
'参数:
    'feb    : FunctionEntryBlock结构体的地址
    'pObj   : 对象变量
    'cArgs  : 参数列中参数个数
    'iOffest: 该方法在VTable表中偏移
'返回值: 入口地址(实质上是该结构体的.feh子结构首地址)
Public Function FEAttachIDispatch( _
        ByRef feb As FunctionEntryBlock, _
        ByVal pObj As Object, _
        Optional ByVal cArgs As Long = -1, _
        Optional ByVal iOffest As Long = 7) As Long
    FEAttachIDispatch = FEAttachProc(feb, ObjPtr(pObj), FEGetMethodPtr(ObjPtr(pObj), iOffest), cArgs)
End Function

asm/FunEntry.asm
	option casemap :none                        ;# Case sensitive
	.486                                        ;# Create 32 bit code
	.model flat, stdcall                        ;# 32 bit memory model
	.code

;调用该代码前的栈状态
; esp 内容
;   0 FunctionEntryData结构体的地址
;  +4 原函数的返回地址
;  +8 原函数的参数1
;  +C 原函数的参数2
;     ……

;调用该代码时的栈状态
; ebp 内容
;  -? pushad保存的所有32位寄存器
;  -8 函数返回值
;  -4 FunctionEntryData结构体的地址
;   0 旧的ebp
;  +4 原函数的返回地址
;  +8 原函数的参数1
;  +C 原函数的参数2
;     ……

;调用普通函数(非retval)前的栈
; esp 内容
;  -8 欲调用的函数
;  -4 this指针 或 FunctionEntryData结构体的地址
;   0 原函数的返回地址
;  +4 this指针 或 FunctionEntryData结构体的地址
;  +8 原函数的参数1
;  +C 原函数的参数2
;     ……


    FEF_CheckIDE = 1
        ;是否检查VB IDE(通过调用pfnCheckIDE)。
        ;若处于VB IDE的调试模式,则试图调用pfnOldProc。
        ;若pfnOldProc不存在,则自己修正栈并返回。
    
    FEF_StructPtr = 2
        ;是否传递FunctionEntryData结构的地址。
            ;当存在该标志时,表示参数列中第一个参数是FunctionEntryData结构的地址,而不传送this指针。
            ;该标志用于某些复杂设计,这样可以在标准模块中编写转发函数以处理复杂情况。
    
    FEF_RetVal = 80000000h
        ;是否有返回值。
        ;COM的返回值是在栈中的,需要特别处理(所以需要该标志)。注意只支持返回简单类型(既返回值能放入EAX寄存器)。
        ;对于传统的通过EAX寄存器返回结果的函数,则不需要该标志。


FunctionEntryData	struc
	ASMCodeRet	dq	?	;存放返回指令,因为需要从不同参数个数的函数中返回
	fFlags		dd	?	;标志常数
	pfnCheckIDE	dd	?	;检查VB IDE的函数(在调用CreateFunctionEntry()时自动设置该成员)
	pfnProc		dd	?	;欲调用的函数(会调整参数列,如插入this指针)
	pfnOldProc	dd	?	;原来的函数(不会调整参数列,与原来的函数原型一致)
	pObjThis	dd	?	;this指针(VarPtr(对象变量)的返回值)
	cArgs		dd	?	;有多少个参数(函数的参数是多少个DWORD)
FunctionEntryData	ends

;ASMCodeRet字段的代码:
;61        popad
;8B 45 F8  mov eax, dword ptr [ebp-8]
;C9        leave
;C2 34 12  ret 1234h


;参数
_retEIP$ = 4	;返回地址
_pThis$ = 4	;this指针 或 FunctionEntryData结构体的地址
_Arg0$ = 8	;参数0
;局部变量
_pData$ = -4	;FunctionEntryData结构体的地址 或 this指针(仅非retval函数最后调用pfnProc函数时)
_lResult$ = -8	;返回值。仅retval函数
_pfnCall$ = -8  ;调用的函数的地址(由于popad,只能通过栈传递数据)。仅非retval函数及处理FEF_CheckIDE时
start:
	;为调试作准备
	nop

	;恢复栈
	pop	eax	;将Data结构的地址放入eax寄存器中

	;保存环境
	push	ebp
	mov	ebp, esp

	;分配局部变量
	sub	esp, 8

	;所有寄存器进栈
	pushad

	;初始化局部变量
	mov	ebx, eax	;以后通过ebx来访问Data结构
	mov	DWORD PTR _pData$[ebp], eax	;将Data结构的地址写入内存
	xor	eax, eax	;初始化返回值
	mov	DWORD PTR _lResult$[ebp], eax

	;检查FEF_CheckIDE常数(无标志则跳转)
	mov	eax, [ebx + FunctionEntryData.fFlags]
	test	eax, FEF_CheckIDE
	jz	_no_stopped

	;检查pfnCheckIDE函数
	mov	eax, [ebx + FunctionEntryData.pfnCheckIDE]
	test	eax, eax
	jz	_no_stopped

	;调用pfnCheckIDE函数
	call	eax
	cmp	eax, 2
	jne	_no_stopped

;处于VB IDE运行环境的中断模式
_is_stopped:
	;检查pfnOldProc函数
	mov	eax, [ebx + FunctionEntryData.pfnOldProc]
	test	eax, eax
	jz	_no_oldproc
	mov	DWORD PTR _pfnCall$[ebp], eax	;将函数地址放入栈中

	;调用
	popad
	mov	eax, DWORD PTR _pfnCall$[ebp]
	mov	esp, ebp
	pop	ebp
	jmp	eax

;没有默认处理函数
_no_oldproc:
	;调用返回代码
	lea	eax, [ebx + FunctionEntryData.ASMCodeRet]
	jmp	eax



;不是处于VB IDE运行环境的中断模式
_no_stopped:
	;检查FEF_RetVal常数(带标志则跳转)
	mov	eax, [ebx + FunctionEntryData.fFlags]
	test	eax, FEF_RetVal
	jnz	_is_retval_fun

;普通函数(无返回值 或 返回值能放在在eax寄存器中)
_is_normal_fun:
	;将调用函数的地址保存在栈中
	mov	edx, [ebx + FunctionEntryData.pfnProc]
	mov	DWORD PTR _pfnCall$[ebp], edx

	;检查FEF_StructPtr常数(带标志则跳转)
	mov	eax, [ebx + FunctionEntryData.fFlags]
	test	eax, FEF_StructPtr
	jnz	_call_normal_fun

	;将this指针保存在栈中
	mov	eax, [ebx + FunctionEntryData.pObjThis]
	mov	DWORD PTR _pData$[ebp], eax

;调用普通函数
_call_normal_fun:
	;恢复栈
	popad
	mov	esp, ebp
	pop	ebp

	;填充this指针
	push	[esp]	;重复栈顶元素(返回地址)
	mov	eax, DWORD PTR _pData$[esp]
	mov	DWORD PTR _pThis$[esp], eax

	;调用
	mov	eax, DWORD PTR _pfnCall$[esp]
	jmp	eax



;带COM返回值的函数
_is_retval_fun:
	;将返回值的地址压进栈
	lea	eax, DWORD PTR _lResult$[ebp]
	push	eax

	;调整ecx,使其等于要复制的DWORD数

	;计算要复制的字节数(eax)、双字数(ecx)
	mov	edx, [ebx + FunctionEntryData.cArgs]
	mov	eax, edx
	mov	cl, 2	;现在是32位代码,每个参数占4字节——既左移2位
	shl	eax, cl
	mov	ecx, edx

	;空出栈(等下用串指令将参数复制过来)
	sub	esp, eax	;空出栈

	;复制参数
	cld					;清方向位,使地址正向增加
	lea	esi, DWORD PTR _Arg0$[ebp]	;计算源指针
	mov	edi, esp			;设置目的指针
	rep	movsd				;复制

	;检查FEF_StructPtr常数(带标志则跳转)
	mov	edx, ebx	;默认为FEF_StructPtr模式
	mov	eax, [ebx + FunctionEntryData.fFlags]
	test	eax, FEF_StructPtr
	jnz	_call_retval_fun

	;将edx改成this指针
	mov	edx, [ebx + FunctionEntryData.pObjThis]

_call_retval_fun:
	;调用
	push	edx
	call	DWORD PTR [ebx + FunctionEntryData.pfnProc]

	;返回
	lea	eax, [ebx + FunctionEntryData.ASMCodeRet]
	jmp	eax

end start

 

打包下载
~~~~~~~~

下载(注意修改下载后的扩展名)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值