Access COM vtable from C#

2019-03-27 15:14发布

Is there any way in C# to access a COM object's virtual method table in order to get a function's address?

标签: c# com interop
4条回答
再贱就再见
2楼-- · 2019-03-27 15:45

Are you sure you are asking about C#?

You shouldn’t need to get function addresses in C#, irrespective of whether a method is virtual or not. You can instantiate a delegate, which holds a managed reference to the method. If you instantiate a delegate with a virtual method, then the method will be called virtually, i.e. it will call the most derived override.

There is no need in C# to read system internals like the raw vtable.

查看更多
Summer. ? 凉城
3楼-- · 2019-03-27 15:49

You can't dig the native interface pointer out of the RCW. But you can call Marshal.GetComInterfaceForObject(), should be good as long as you ask for the right interface. From there, you can Marshal.ReadIntPtr() to get the v-table entries. Offset 0 is QueryInterface, 4 is AddRef, 8 is Release, 12 is the first interface method, etc. Double that for x64 code. Marshal.GetComSlotForMethodInfo() is an option.

Actually calling the method requires Marshal.GetDelegateForFunctionPointer(), you need to declare the delegate with the exact signature of the COM interface method. That won't be the signature it has when you call this method normally, it is the [PreserveSig] signature. In other words, a function that returns HRESULT and a ref argument if it returns a value.

Opportunities to bomb your program are plentiful.


After update: you need Marshal.GetFunctionPointerForDelegate and Marshal.WriteIntPtr to patch the v-table slot entry.

查看更多
Lonely孤独者°
4楼-- · 2019-03-27 15:50

The closest I can think of is Marshal.GetFunctionPointerForDelegate, although this is at least one level removed from the unmanaged COM method (since the COM call will be wrapped in a .NET delegate).

What do you need this information for?

查看更多
冷血范
5楼-- · 2019-03-27 16:07

After a lot of searching and piecing together different partial solutions, I figured out how to do it.

First you need to define the COM coclass for the object you're trying to access:

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

Next you need to instantiate the COM object. There are a couple of ways to do that. Since I was interested in DirectSound, I used:

[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);

Since I now had my COM object, I could then use Hans' suggestion of Marshal.GetComInterfaceForObject():

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

As an added bonus, you can then iterate through the vtable functions like this:

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());
}

Extra Reading / References:

查看更多
登录 后发表回答