C# 在不了解coclass的情况下发布COM接口 我有一个C客户端,它从本地C++ Client DLL中消耗接口。DLL实现了4个接口。这4个接口由DLL中的4个类实现。但是只有1个coclass向客户机公开。接口2、3、4通过接口1中的方法之一返回给客户机

C# 在不了解coclass的情况下发布COM接口 我有一个C客户端,它从本地C++ Client DLL中消耗接口。DLL实现了4个接口。这4个接口由DLL中的4个类实现。但是只有1个coclass向客户机公开。接口2、3、4通过接口1中的方法之一返回给客户机,c#,com,interface,interop,C#,Com,Interface,Interop,C++COM服务器: interface IFace1: IUnknown{ HRESULT CreateOtherInterface([in] REFIID iidFace, [out, iid_is(iidFace)] void** ppOut); }; coclass ClassIFace1 { [default] interface IFace1; }; 客户: 由于IFace2、IFace3和IFace4与IFace1不共享同一个类,我怀疑Marshal.ReleaseCo

C++COM服务器:

interface IFace1: IUnknown{
HRESULT CreateOtherInterface([in] REFIID iidFace, [out, iid_is(iidFace)] void** ppOut);
};

coclass ClassIFace1
{
    [default] interface IFace1;
};
客户:


由于IFace2、IFace3和IFace4与IFace1不共享同一个类,我怀疑Marshal.ReleaseComObject(Face1Object)行只会破坏ClassFace1对象,而不会破坏ClassFace2、ClassFace3和ClassFace4对象,并导致内存泄漏。有什么办法解决这个问题吗?或者Marshal.ReleaseComObject(Face1Object)实际上也会破坏其他COM对象?

COM对象的正常行为是,您只能通过其接口访问它们。只有在创建COM对象的新实例时才需要coclass,您不能直接访问coclass。所以我怀疑你的例子应该是这样的:

IFace1 face1Ctrl = new ClassIFace1();
IFace2 face2Ctrl = face1Ctrl as IFace2;
方法
CreateOtherInterface()
在我看来有点奇怪,它与
QueryInterface()具有相同的签名,因此我认为它应该做同样的事情(我不熟悉C++):

我想这个应该可以,试试看。如果使用普通的QueryInterface方法,您应该能够获得如下接口:

IFace1 face1Ctrl = new ClassIFace1();
IFace2 face2Ctrl = face1Ctrl as IFace2;
COM对象是引用计数的,一旦销毁对接口的最后一个引用,它们就会被释放。一旦垃圾收集器通过对COM对象的引用销毁了变量,COM对象就会释放自己。这在C#中可能是个问题,因为您必须等待垃圾收集器,并且无法确定释放顺序。如果需要在给定时刻释放COM对象,可以使用
Marshal.ReleaseComObject()
,但通常您只需等待垃圾收集器减少引用计数器


只要您不知道COM对象的实现,您就不知道每个接口是否都有自己的coclass,或者一个coclass是否实现了多个接口。当您使用COM对象时,不应该需要这些知识。查询接口可以创建一个新的coclass并返回其接口,也可以返回自身并增加引用计数器。

就像Hans所说的,
CreateOtherInterface
看起来很奇怪。通常,您不需要自己创建它。您需要做的就是确保客户端可以访问所有四个coclass。然后,
Activator.CreateInstance
或本机
CoCreateInstance
将为您做正确的事情。另一种选择是公开一个coclass,并使该coclass支持所有四个接口

然而,由于您提到只有1个coclass向客户机公开,我认为有一些奇怪的原因,客户机使用的TLB文件没有看到其他3个coclass,或者其他3个coclass没有正确注册,而是由第一个coclass以某些专有方式发现的。我还假设您不能修改服务器端实现

考虑到所有这些假设,下面是我的答案。参考计数独立地保持在4个CoClass内。因此,释放第一个coclass上的引用不会减少其他三个coclass中的引用计数

还有一些事情你需要注意。您正在使用
Marshal.ReleaseComObject(Face1Obj)
来释放第一个coclass。您可以这样做,因为第一个类是由运行时可调用包装器(RCW)包装的。正如Martin所说,即使您不调用
Marshal.ReleaseComObject()
,.NET运行时也会在Garbadge集合发生时为您执行此操作

然而,Face2Ctrl的获得方式不同。它没有被RCW包裹。您直接将返回的指针视为一个结构。我觉得这听起来不对,因为您可能在内存对齐和数据编组方面存在问题。您要做的可能是调用
Marshal.GetObjectForIUnknown
,它将为您返回一个RCW。获得RCW后,可以调用
Marshal.ReleaseComObject()
及时释放RCW


如果
CreateOtherInterface
的实现类似于
QueryInterface
,它总是在返回的接口上执行
AddRef
,那么在使用Face2Obj之后,您应该在返回的接口上调用
Marshal.Release
Marshal.ReleaseComObject()
是不够的,因为它只是释放由RCW添加的引用计数,但在这种情况下,您需要对
IUnknown.Release进行多个调用。此外,在如何获取接口方面存在一些错误。以下是C#client的完整解决方案:


也许我应该在这里说得更具体一些。COM服务器将与设备通信。而不同的设备会暴露出一组不同的功能。每个功能都实现为功能接口,如IFace2、IFace3等。客户端只需提供他们感兴趣的功能的iid,如果设备支持,COM服务器将返回功能接口,否则调用将失败。客户端不知道IFace2,IFace3 etc是否使用与IFace1相同的类实现(实际上它们是不同的类),它们也不关心。他们只需从IFace1获得所有的特性接口,并在完成后单独发布特性接口。对于C++客户端来说,这种方法在所有这些年都很有用,直到我们需要实现一个C客户端来与COM服务器对话。是的,CealAtter接口()与QueIrrIdFrice()完全一样:在接口IID中传递,它将返回接口指针。区别在于QueryInterface()只返回在同一个coclass上强制执行的接口(除非您自己覆盖QueryInterface()),而不是CreateOtherInterface()c
//======Create IFace1 and IFace2 interface===============
Type consoleType = Type.GetTypeFromCLSID(Face1CoCLSID);
Object Face1Obj = Activator.CreateInstance(consoleType);
IFace1 Face1Ctrl = (IFace1)Face1Obj;

Guid IFace2Guid = typeof(IFace2).GUID;
IntPtr Face2IntPtr = IntPtr.Zero;

//Face2 object's ref count will go up 1
Face1Ctrl.CreateOtherInterface(Face2Guid, out Face2IntPtr); 

//Face2 object's ref count will go up 2. One by "GetObjectForIUnknown()" 
//and one by "as", since the "as" will trigger .Net to call QueryInterface()
IFace2 Face2Ctrl = Marshal.GetObjectForIUnknown(Face2IntPtr) as IFace2; 

//=============Consume Face2Ctrl=========================

//======Destroy IFace1 and IFace2 interface===============

if (Face2Ctrl != null)
{
//Release 3 times as there were 3 RefCount obtained.

    Marshal.Release(Face2IntPtr);
    Marshal.Release(Face2IntPtr);
    Marshal.Release(Face2IntPtr);
    Face2Ctrl = null;     
}

if(Face1Obj != null)
{
//both Face1 object and Face2 object will get FinalRelease() after
//this line.
    Marshal.ReleaseComObject(Face1Obj); 
    Face1Obj = null;
}