4

我有一个 C# 客户端,它使用来自本机 C++ COM 服务器 dll 的接口。DLL 实现了 4 个接口。这 4 个接口由 DLL 中的 4 个组件类实现。但是只有 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 对象?

4

3 回答 3

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 运行时,也会在发生垃圾收集时为您执行此操作。

但是,Face2Ctrl的获取方式不同。它没有被 RCW 包裹。您将返回的指针直接视为结构。这对我来说听起来不对,因为您可能在内存对齐和数据编组方面遇到问题。您想要做的可能是调用Marshal.GetObjectForIUnknown它将为您返回 RCW。一旦你拿到你的 RCW,你可以打电话Marshal.ReleaseComObject()及时释放你的 RCW。

如果实现CreateOtherInterface是 like QueryInterface,它总是AddRef在返回的接口上,你应该Marshal.Release在完成Face2Obj后调用返回的接口。 Marshal.ReleaseComObject()还不够,因为它只是释放了 RCW 添加的引用计数,但在这种情况下,您需要多次调用IUnknown.Release

于 2012-07-16T02:13:13.350 回答
1

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

IFace1 face1Ctrl = new ClassIFace1();

该方法CreateOtherInterface()对我来说有点奇怪,它具有与 a 相同的签名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 并返回它的接口,或者它可以返回自己并增加引用计数器。

于 2012-07-15T20:20:41.767 回答
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;
}
于 2012-07-17T07:27:56.097 回答