在不知道coclass的情况下释放COM接口

时间:2012-07-15 18:17:14

标签: c# com interface interop

我有一个C#客户端,它使用来自本机C ++ COM服务器dll的接口。该DLL实现了4个接口。这4个接口由DLL中的4个coclasses实现。但是只有1个coclass暴露给客户端。接口2,3,4通过接口1中的一种方法返回给客户端。

C ++ COM服务器:

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

coclass ClassIFace1
{
    [default] interface IFace1;
};

C#客户端:

ClassIFace1 Face1Obj = new ClassIFace1();

IFace1 Face1Ctrl = (IFace1)Face1Obj; 

IFace2 Face2Ctrl = null;
IntPtr Face2IntPtr = new IntPtr();

Face1Ctrl.CreateOtherInterface(Face2Guid, out Face2IntPtr);
Face2Ctrl = (IFace2)Mashal.PtrToStructure(Face2IntPtr);

//Consume Face2Ctrl

if(Face1Obj != null)
{
    Marshal.ReleaseComObject(Face1Obj);
}

作为IFace2,IFace3和IFace4不与IFace1共享相同的coclass,我怀疑Marshal.ReleaseComObject(Face1Obj)行只会破坏ClassIFace1对象而不会破坏ClassIFace2,ClassIFace3,ClassIFace4对象并导致内存泄漏。有什么方法可以解决这个问题吗?或者Marshal.ReleaseComObject(Face1Obj)实际上也会破坏其他COM对象?

3 个答案:

答案 0 :(得分:1)

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

IFace1 face1Ctrl = new ClassIFace1();

方法CreateOtherInterface()对我来说有点奇怪,它与QueryInterface()具有相同的签名,所以我认为它应该做同样的事情(我不熟悉C ++) :

IFace2 face2Ctrl;
face1Ctrl.CreateOtherInterface(IFace2, out face2Ctrl);

我认为这应该做,只是尝试一下。如果它在正常的QueryInterface方法所在的位置,你应该能够获得如下界面:

IFace2 face2Ctrl = face1Ctrl as IFace2;

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

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

答案 1 :(得分:1)

就像汉斯所说,CreateOtherInterface看起来很奇怪。通常,您不需要自己创建它。您需要做的就是确保客户端可以访问所有四个coclass。然后,Activator.CreateInstance或原生CoCreateInstance将为您做正确的事。另一种选择是暴露一个单一的coclass并让那个单一的coclass支持所有四个接口。

但是,由于你提到只有1个coclass暴露给客户端,我想象有一些奇怪的原因,客户端使用的TLB文件没有看到其他3个coclass或其他3个coclass没有正确注册但是由第一个coclass以一些专有方式发现。我也假设您无法修改服务器端实现。

鉴于所有这些假设,这是我的答案。引用计数独立地保留在4个coclass中。因此,在第一个coclass上释放引用不会减少其他三个coclass中的引用计数。

还有一些事情你需要注意。您正在使用Marshal.ReleaseComObject(Face1Obj)来发布第一个coclass。你可以这样做,因为第一个coclass由Runtime Callable Wrapper(RCW)包装。就像Martin所说的那样,即使你不调用Marshal.ReleaseComObject(),.NET运行时也会在发生garbabge收集时为你做这件事。

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

如果CreateOtherInterface的实现与返回的接口上始终为QueryInterface的{​​{1}}类似,则在完成后,应在返回的接口上调用AddRef < EM> Face2Obj 。 Marshal.Release是不够的,因为它只是释放了RCW添加的引用计数,但在这种情况下,您需要在Marshal.ReleaseComObject()上进行更多的一次调用

答案 2 :(得分:0)

此外,如何获取界面也存在一些错误。以下是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;
}