C# 通过COM接口访问底层托管对象

C# 通过COM接口访问底层托管对象,c#,com-interop,C#,Com Interop,我有一个第三方程序集,其中有一个实现某个COM接口的公共抽象类。大意是 [ComVisible(true)] public abstract class SomeClass: ISomeInterface { .... public void Method1() {...} } 实际对象是扩展SomeClass的内部对象,由第三方代码实例化 如果我只有对接口的CCW,是否有办法访问此类的公共方法 为了澄清我的问题: 我正在构建一个有特色的Visual Studio项目,扩展F#项目系统

我有一个第三方程序集,其中有一个实现某个COM接口的公共抽象类。大意是

[ComVisible(true)]
public abstract class SomeClass: ISomeInterface
{
  ....
  public void Method1() {...}
}
实际对象是扩展SomeClass的内部对象,由第三方代码实例化

如果我只有对接口的CCW,是否有办法访问此类的公共方法

为了澄清我的问题:


我正在构建一个有特色的Visual Studio项目,扩展F#项目系统。我的代码和F#项目系统都是托管的,但我们之间有很多非托管代码。在我的项目经理中,我得到了一个指向F#project manager的指针(IntPtr),我可以将它转换为F#project manager实现的许多接口,但我需要在项目经理本身上调用一个(public)方法,到目前为止,我通常找不到这样做的方法,不可以。您只能在此COM接口和同一对象上的其他COM接口中获取at方法。

这取决于您试图调用的未发布方法是否具有您看不到的内部COM(或C++)实现。COM接口要求实现对象提供固定位置的
vtables
,通常可以从托管代码中访问和解释这些内容,即使这些内容不打算公开使用

对于本机COM库,COM vtable方案通常与运行时内部设置方式的二进制映像相对应。因为这些表是不可移动的,并且与人们熟知的COM需求相关联,所以不安全的托管代码可以浏览并找出如何访问任何内容。为了举例说明,您可以在代码中定义一个接口
IFoo
,该接口与COM库的一个秘密接口并行——可能包含一个方法,如下所示:

[ComImport, SuppressUnmanagedCodeSecurity, Guid("00000000-0000-0000-0000-123456789ABC")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFoo
{
    int FooFunc(uint x);
}
请注意,如果您想为COM启用
IUnknown
和其他CLR支持,则应在此处使用
[ComImport]
,即使这是您自己的唯一接口声明,而不是从任何地方“导入”。现在假设这个接口与某个外部COM对象的运行时VTARE匹配,或者甚至是一个非COM本地C++对象(给定适当的调整),您可以做如下的事情:

var vtbl = *(IntPtr**)pObj; // ptr to a C++ style object, perhaps from ICustomMarshaller
// ...or...
var vtbl = *(IntPtr**)((IntPtr*)pUnk - 1); // or an IUnknown ptr instead

// since IUnknown methods are 0, 1, 2, vtable slot for FooFunc is likely '3'
// but to fetch it in a more "official way"...
var mi = typeof(IFoo).GetMethod(nameof(IFoo.FooFunc));
int slot_ix = Marshal.GetComSlotForMethodInfo(mi);

// fetch unmanaged function pointer to 'FooFunc'
IntPtr pfn = vtbl[slot_ix];
此时,您需要声明一个托管委托,以便通过此函数指针进行调用。由于
FooFunc
是一个实例,因此委托需要与相关的
this
实例绑定,在本例中该实例是
pObj
,这样每当调用
FooFunc
时,它都会按照函数的预期和要求将该值作为“不可见”的第一个参数插入。请注意,您不能利用将相关实例存储在
my_delegate.Target
属性中的常规CLR机制,因为这仅适用于托管对象,此处讨论的目标不是托管对象。因此,额外的参数变得不那么“无形”:

//重要。。。请注意这个关键属性及其参数--v
[ComVisible(true),非托管函数指针(CallingConvention.ThisCall)]
公共委托int-this-FooFunc([In]IntPtr-this[In]uint x);
//从上面为非托管函数指针创建托管委托
var\uuu FooFunc=Marshal.GetDelegateForFunctionPointer(pfn);
//叫它。。。但别忘了“这”
int i=uuu FooFunc(pObj,1234U);//从托管代码调用基于vtable的函数
不管怎样,这一切都是可行的,甚至没有那么粗略或有争议;我们基本上只是在COM规范中建立的结构,就像CLR的默认封送处理代码一样

说到这里,如果您感兴趣的COM功能源于托管环境,比如您所指出的F#,那么上述内容几乎不适用。托管运行时环境有机会在更复杂的基础上提供COM vtables,例如:

  • 仅适用于需要它们的对象
  • 仅适用于预期的特定接口
  • 仅在飞行中或需要时,和/或
  • 仅在要求或授权的时间内

虽然我不确定这些要点中的哪一点适用于您描述的案例,但似乎它们中的一些或全部可能会根据CLR主机的实现细节而有所不同。不幸的是,主机部署这些优化中的任何一个都足以阻止上述vtable窥探的类型。

这个对象用ComVisible(true)标记,AFAIK暗示它应该有类接口,不是吗?一个IntPtr?它甚至是一个COM接口指针吗?可能是Marshal.GetObjectFromIUnknown()。您是对的。我正在获取一个IntPtr,并使用Marshal.GetObjectForIUnknown方法将其转换为接口对象。该对象随后可以转换为其他接口,但不能转换为实现这些接口的托管对象
// important... note this crucial attribute and its argument ---v
[ComVisible(true), UnmanagedFunctionPointer(CallingConvention.ThisCall)]
public delegate int __this_FooFunc([In] IntPtr _this, [In] uint x);

// create managed delegate for unmanaged function pointer from above
var __FooFunc = Marshal.GetDelegateForFunctionPointer<__this_FooFunc>(pfn);

// call it...      v--- but don't forget the 'this'
int i = __FooFunc(pObj, 1234U);   // call vtable-based function from managed code