微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 VTable hacking 使用来自标准模块的方法重载 COM 类方法

如何解决使用 VTable hacking 使用来自标准模块的方法重载 COM 类方法

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

背景

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

Option Explicit

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

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

... 生成这样的 VTable:

VTable

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

这样我就可以再上一堂课了

Option Explicit

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

并将 Woof 的行为从一个Tweet 的另一个交换。不太复杂,如果有人需要我可以分享代码

我不能做什么...

...但是,是否要弄清楚如何将类方法与标准模块中的方法交换?

基于 this 文章,似乎构建 VBA 的 COM 机器需要 VBA 隐藏的两个类方法

  • 它们有一个隐含的 this 指针
  • 他们返回 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 位计算机上的情况是否有所不同,或者标准模块函数是否对调用堆栈做了一些奇怪的事情。问题是我已经看到 examples of code 整个 VTable 是由标准模块函数组装而成的,所以我知道这是可能的,但只是不确定如何正确转换签名

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

解决方法

我对你的问题的评论只是部分正确。我仍然相信 Me 关键字在阻止类方法“重定向”到标准 .bas 模块中的方法方面发挥着作用。但这仅适用于早期绑定。

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

Public Function Meow(ByRef meObj As Class1) As Long

Class1 代码:

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

我使用 LibMemory 进行内存操作。

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

编辑 #1

我想到了下面评论中讨论的问题,我能想到的唯一原因是 IDispatch 仅适用于指向 IUnknown 接口的指针。

这意味着:

Public Function Meow(ByRef this As Class1) As Long

会使应用程序崩溃

但是,这是有效的:

Public Function Moew(ByVal this As Class1) As Long
    Debug.Print "Meow in .bas"
End Function

因为传递 ByVal 会在 IUnknown 上强制使用 QueryInterface 和 AddRef(退出作用域时使用 Release)

这也有效:

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 会失败?为什么 ByVal this As Class1ByRef this As IUnknown 有效?

ByRef this As Class1
我相信 VarPtr(this) 地址不能被 VB 访问,因此我们正在读取我们不应该读取的内存。这不像在 IUnknown 接口上有一个额外的 AddRef 或 Release,因为该方法永远不会使用这个声明被调用。当 Invoke 尝试调用该方法时,应用程序只会崩溃。

ByVal this As Class1
该方法只是创建一个 VB 变量(在 VB 内存空间上)并调用 AddRef

ByRef this As IUnknown
由于这不是双接口,因此调用了 QueryInterface 和 AddRef。 'this'的内存地址在本地内存空间,和第二个例子一样。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?