从C#访问COM Vtable

8

有没有一种方法在C#中访问COM对象的虚拟方法表以获取函数的地址?


1
你想要解决什么问题? - bmm6o
我正在尝试钩取一个COM对象的对象方法。为了做到这一点,我需要从COM对象的虚表中获取函数地址。 - lfalin
4个回答

9

经过大量的搜索和拼凑不同的部分解决方案,我找到了如何做到这一点。

首先,您需要为您要访问的对象定义COM coclass:

[ComImport, Guid("..."), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ISomeCOMInterface
{
   // Define interface methods here, using PInvoke conversion between types
}

接下来,您需要实例化COM对象。有几种方法可以做到这一点。由于我对DirectSound感兴趣,所以我使用了:

[DllImport("dsound.dll", EntryPoint = "DirectSoundCreate", ...]
static extern void DirectSoundCreate(IntPtr GUID, [Out, MarshalAs(UnmanagedType.Interface)] out IDirectSound directSound, IntPtr pUnkOuter);

IDirectSound directSound;
DirectSoundCreate(IntPtr.Zero, out directSound, IntPtr.Zero);

现在我已经有了我的COM对象,我可以使用Hans的建议Marshal.GetComInterfaceForObject()

IntPtr comPtr = Marshal.GetComInterfaceForObject(directSound, typeof(IDirectSound));
IntPtr vTable = Marshal.ReadIntPtr(comPtr);

作为额外的奖励,您可以像这样遍历vtable函数:
int start = Marshal.GetStartComSlot(typeof(IDirectSound));
int end = Marshal.GetEndComSlot(typeof(IDirectSound));

ComMemberType mType = 0;
for (int i = start; i <= end; i++)
{
    System.Reflection.MemberInfo mi = Marshal.GetMethodInfoForComSlot(typeof(IDirectSound), i, ref mType);
    Console.WriteLine("Method {0} at address 0x{1:X}", mi.Name, Marshal.ReadIntPtr(vTable, i * Marshal.SizeOf(typeof(IntPtr))).ToInt64());
}

额外阅读/参考资料:


我可能错了,但基于阅读MSDN文档,我认为你的迭代代码循环遍历插槽时应包括“end”插槽值。看起来你的代码会无法报告范围内最后一个可用的COM成员。要修复它,只需将循环条件从“i < end”更改为“i <= end”。 - Glenn Slayden
1
实际上,我刚刚验证了我的先前评论是正确的。尽管如此恭敬,我已经自行修改了代码以修复错误。 - Glenn Slayden

2
您无法从RCW中获得本机接口指针。但是,您可以调用Marshal.GetComInterfaceForObject()方法,只要请求正确的接口即可。从那里,您可以使用Marshal.ReadIntPtr()获取v-table项。偏移量为0是QueryInterface,4是AddRef,8是Release,12是第一个接口方法等等。对于x64代码,请将其加倍。Marshal.GetComSlotForMethodInfo()是一种选择。
实际调用方法需要使用Marshal.GetDelegateForFunctionPointer()方法。您需要使用COM接口方法的确切签名声明委托。这不会是您正常调用此方法时的签名,而是[PreserveSig]签名。换句话说,如果返回值,则为返回HRESULT和ref参数的函数。
有很多机会让您的程序崩溃。
更新后:您需要使用Marshal.GetFunctionPointerForDelegate和Marshal.WriteIntPtr来修补v-table槽条目。

这差不多就可以了,但我卡在如何告诉GetComInterfaceForObject我想要的接口上。我正在尝试使用DirectSound库,并尝试传入typeof(DirectX.PrivateImplementationDetails.IDirectSoundBuffer8),但这会导致ArgumentException异常。 - lfalin
我不知道,这是什么对象类型?这是哪种API? - Hans Passant
该对象是DirectSound缓冲区(或SecondaryBuffer)。COM对象提供了IDirectSoundBuffer8接口。 - lfalin
这听起来像是旧的.NET 1.1 Managed DirectX包装器。它的SecondaryBuffer类不是COM互操作类型。 DirectX没有类型库。 - Hans Passant

0
你确定你在问C#吗?
无论方法是否是虚方法,在C#中都不需要获取函数地址。你可以实例化一个“委托”,它持有对该方法的托管引用。如果你使用虚方法实例化委托,那么该方法将会按虚方式调用,即它将调用最具体的重写。
在C#中没有必要读取系统内部,如原始vtable。

0

我能想到的最接近的方法是Marshal.GetFunctionPointerForDelegate,尽管这至少比非托管COM方法多了一层(因为COM调用将被包装在.NET委托中)。

您需要这些信息做什么?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接