Vba 使用VTable hacking使用标准模块中的方法重载COM类方法

Vba 使用VTable hacking使用标准模块中的方法重载COM类方法,vba,com,64-bit,vtable,iunknown,Vba,Com,64 Bit,Vtable,Iunknown,快速提问-我刚刚通过使用低级复制内存api更改VTable中的条目来测试重写类的方法 背景 我已经取得了一些成功,如果一个类的VTable中有两个条目具有相同的签名,那么可以交换它们。这样的类定义: Option Explicit Public Sub Meow() Debug.Print "Meow" End Sub Public Sub Woof() Debug.Print "Woof" End Sub 。。。生成如下所示的VT

快速提问-我刚刚通过使用低级复制内存api更改VTable中的条目来测试重写类的方法

背景 我已经取得了一些成功,如果一个类的VTable中有两个条目具有相同的签名,那么可以交换它们。这样的类定义:

Option Explicit

Public Sub Meow()
    Debug.Print "Meow"
End Sub

Public Sub Woof()
    Debug.Print "Woof"
End Sub 
。。。生成如下所示的VTable:

Option Explicit

Public Sub Meow()
    Debug.Print "Meow"
End Sub

Public Sub Woof()
    Debug.Print "Woof"
End Sub 

。。。我可以交换位置7和8处的条目,使
cls.Meow
print
Woof
,反之亦然。我还可以将一个类的VTable中的条目与另一个完全不同的VTable交换(前提是我不试图通过调用
Me.anythis来取消对隐式
this
指针的引用)

所以我可以再上一节课

Option Explicit

Public Sub Tweet()
    Debug.Print "Tweet"
End Sub
并将
Woof
的行为与
Tweet
的行为互换。不太复杂,如果人们需要,我可以分享代码

我不能做的是。。。 。。。但是,您是否知道如何将类方法与标准模块中的方法交换?

根据文章,VBA所基于的COM机制似乎需要VBA隐藏的两种类方法:

  • 它们有一个隐式
    这个
    指针
  • 它们返回一个HRESULT(
    typedef long
所以我想

Public Sub Meow()
在类模块中,
Class1
相当于

Public Function Meow(ByVal this As LongPtr) As Long
我也试过了

Public Function Meow(ByRef meObj As Class1) As Long
Public Function Meow(ByRef meObj As Class1) As LongPtr 'but HResult is 32 bit int
Public Sub Meow(ByVal this As LongPtr)
但是当我试图从VTable调用该方法时,VBA总是崩溃。所以我有点不知所措。我想知道64位计算机上的情况是否有所不同,或者标准模块函数是否会对调用堆栈产生奇怪的影响。问题是我看到了整个VTable是从标准模块函数组装而成的,所以我知道这是可能的,但只是不确定如何正确转换签名


如何使用标准模块中定义的方法覆盖VTable条目?

我对您的问题的评论仅部分正确。我仍然相信
Me
关键字在防止类方法“重定向”到标准.bas模块内的方法方面起到了一定作用。但这只适用于早期绑定

IDispatch::Invoke实际上可以毫无问题地调用.bas模块内的方法。您的初始方法签名是正确的:

Class1
code:

Option Explicit

Public Sub Meow()
    Debug.Print "Meow"
End Sub

Public Sub Woof()
    Debug.Print "Woof"
End Sub
标准bas模块中的代码:

Option Explicit

Sub Test()
    Dim c As Object 'Must be late-binded!
    Dim vTblPtr As LongPtr
    Dim vTblMeowPtr As LongPtr
    Dim originalMeow As LongPtr
    '
    Set c = New Class1
    c.Meow 'Prints "Meow" to the Immediate Window
    '
    'The address of the virtual table
    vTblPtr = MemLongPtr(ObjPtr(c))
    '
    'The address of the Class1.Meow method within the virtual table
    vTblMeowPtr = vTblPtr + 7 * PTR_SIZE
    '
    'The current address of the Class1.Meow method
    originalMeow = MemLongPtr(vTblMeowPtr)
    '
    'Replace the address of Meow with the one in a .bas module
    MemLongPtr(vTblMeowPtr) = VBA.Int(AddressOf Moew)
    '
    c.Meow 'Prints "Meow in .bas" to the Immediate Window
    '
    'Revert the original address
    MemLongPtr(vTblMeowPtr) = originalMeow
    '
    c.Meow 'Prints "Meow" to the Immediate Window
End Sub

Public Function Moew(ByVal this As Class1) As Long
    Debug.Print "Meow in .bas"
End Function
我已经使用了内存操作

如果将
Meow
类方法更改为
Function
而不是
Sub
,则需要在.bas模块中
Meow
方法的参数列表末尾添加一个
ByRef
参数

编辑#1

我想到了下面评论中讨论的问题,我能想到的唯一原因是IDispatch只能与指向IUnknown接口的指针一起工作

这意味着:

将使应用程序崩溃

但是,这是可行的:

Public Function Moew(ByVal this As Class1) As Long
    Debug.Print "Meow in .bas"
End Function
因为传递
ByVal
会在IUnknown上强制执行查询接口和AddRef(退出作用域时释放)

这也适用于:

Public Function Moew(ByRef this As IUnknown) As Long
    Debug.Print "Meow in .bas"
End Function
编辑#2

抱歉再次编辑

Invoke方法无法使用指向IUnknown的指针。它正在使用指向IDispatch的指针。这可以通过以下方式进行检查:

Public Function Moew(ByVal this As LongPtr) As Long
    Debug.Print this
    Debug.Print "Meow in .bas"
End Function
将ptr打印到IDispatch接口。那么,为什么
ByRef this As Class1
失败了呢?为什么将它定义为Class1和IUnknown起作用

ByRef this As Class1

我相信VB无法访问VarPtr(this)地址,因此我们正在读取不应该读取的内存。这不像在IUnknown接口上有额外的AddRef或Release,因为该方法从未使用此声明调用过。当Invoke试图调用该方法时,应用程序就会崩溃

ByVal将其作为Class1

该方法只需创建一个VB变量(在VB内存空间上)并调用AddRef

将其称为IUnknown


由于这不是一个双重接口,因此会调用QueryInterface和AddRef。“this”的内存地址位于本地内存空间,与第二个示例中相同。

在x64上的行为完全相同。但是,您可以将IUnknown和IDispatch方法“重定向”到.bas模块内的函数(以及链接示例中的IEnumVariant方法)。我假设这些是有效的,因为它们不是VB。我认为,由于VB类的工作方式,您无法重定向VB类方法。考虑<代码> Me/Cux>关键字(它的行为类似于函数/属性get)。您不可能在.bas模块中复制
Me
行为。一定有更多的事情发生在幕后。也许电话会议也不一样。谢谢你的跟进!一个问题;您使用ByVal this而不是ByRef;那是打字错误吗?您是否有幸调用了
this
的任何方法或属性?抱歉。我甚至没有意识到。我这样做是出于习惯。很久以前,在挂接IUnknown::Release时,我发现当非VB函数调用VB函数时,最好将实例参数传递给
ByVal
,以避免崩溃。我会想出一个解释,如果我提出一个像样的解释,我会相应地修改答案。是的,使用答案中的代码可以访问.bas模块的
Meow
方法中类的所有方法。提出错误也能正常工作。@Greedo编辑了答案,希望我的假设是正确的。顺便说一句,你似乎总是问正确的问题。干得好@我又做了一次编辑。希望这次我答对了。@Greedo您的问题帮助我更新了逻辑中的逻辑(请参见编辑#1)。谢谢你!看起来,在早期绑定中,类的入口点确实有一些extr
Public Function Moew(ByVal this As LongPtr) As Long
    Debug.Print this
    Debug.Print "Meow in .bas"
End Function