如何在Release()上处理NET COM互操作对象

时间:2009-11-12 19:22:24

标签: .net com interop c++-cli release

我有一个用托管代码(C ++ / CLI)编写的COM对象。我在标准C ++中使用该对象 当COM对象被释放时,如何强制立即调用COM对象的析构函数?如果那是不可能的,请调用我的Release()调用我的COM对象上的MyDispose()方法?

我的代码声明对象(C ++ / CLI):

    [Guid("57ED5388-blahblah")]
    [InterfaceType(ComInterfaceType::InterfaceIsIDispatch)]
    [ComVisible(true)]
    public interface class IFoo
    {
        void Doit();
    };

    [Guid("417E5293-blahblah")]
    [ClassInterface(ClassInterfaceType::None)]
    [ComVisible(true)]
    public ref class Foo : IFoo
    {
    public:
        void MyDispose();
        ~Foo() {MyDispose();} // This is never called
        !Foo() {MyDispose();} // This is called by the garbage collector.
        virtual ULONG Release() {MyDispose();} // This is never called
        virtual void Doit();
    };

我使用该对象的代码(本机C ++):

#import "..\\Debug\\Foo.tlb"
...
Bar::IFoo setup(__uuidof(Bar::Foo)); // This object comes from the .tlb.
setup.Doit();
setup->Release(); // explicit release, not really necessary since Bar::IFoo's destructor will call Release().

如果我在我的COM对象上放置析构函数方法,则永远不会调用它。如果我放置一个终结器方法,它会在垃圾收集器到达时调用它。如果我明确调用我的Release()覆盖,则永远不会调用它。

我真的很喜欢这样,当我的原生Bar :: IFoo对象超出范围时,它会自动调用我的.NET对象的dispose代码。我想我可以通过覆盖Release()来实现它,如果对象count = 0则调用MyDispose()。但显然我没有正确地覆盖Release(),因为我的Release()方法永远不会被调用。

显然,我可以通过在接口中放置MyDispose()方法并要求使用我的对象的人在Release()之前调用MyDispose()来实现这一点,但是如果Release()刚刚清理了它,它会更加顺畅。对象

是否有可能在释放COM对象时立即强制调用.NET COM对象的析构函数或其他方法?

谷歌搜索这个问题让我有很多命中,告诉我调用System.Runtime.InteropServices.Marshal.ReleaseComObject(),但当然,这就是告诉.NET释放COM对象的方法。我想要COM Release()来处理.NET对象。

6 个答案:

答案 0 :(得分:10)

答案 1 :(得分:3)

实际上,当最后一个引用被释放时,不会从COM客户端调用Dispose(或者我应该说~Foo)。它根本没有实现。这里有一些想法可以做到这一点。

http://blogs.msdn.com/oldnewthing/archive/2007/04/24/2252261.aspx#2269675

但即使是作者也不建议这种方法。

如果您实现COM客户端,最好的选择是查询IDisposable并显式调用Dispose,iid请求是:

{805D7A98-D4AF-3F0F-967F-E5CF45312D2C}

我能想到的其他选择是实现某种自己的“COM垃圾收集器”。 COM创建的每个对象都将放在一个列表中(假设您的类型的对象只能由COM创建 - 我想不出任何方法可以区别于创建对象的位置)。然后你必须定期检查列表,并在每个对象上调用如下内容:

IntPtr iUnk = Marshal.GetIUnknownForObject(@object);
int refCount = Marshal.Release(iUnk);
if (refCount == 0)
    @object.Dispose();

但这是一个疯狂的想法。

答案 2 :(得分:1)

声明为VS 2010(GBG)更正的对象(C ++ / CLI)的代码:

using namespace System;
using namespace System::Runtime::InteropServices;

namespace Bar {

        [Guid("57ED5388-blahblah")]
        [InterfaceType(ComInterfaceType::InterfaceIsIDispatch)]
        [ComVisible(true)]
        public interface class IFoo
        {
                void Doit();
        };

        [Guid("417E5293-blahblah")]
        [ClassInterface(ClassInterfaceType::None)]
        [ComVisible(true)]
        public ref class Foo : IFoo
        {
        //these don't need to be seen...
        private:
            void DisposeManaged() {};
            void DisposeUnmanaged() {};
            ~Foo() {DisposeManaged(); this->!Foo();} // Only called by Dispose() on object instance or direct call and delete in C++/CLI
            !Foo() {DisposeUnmanaged();} // Called by the garbage collector and by the above.
        public:
        //THE FOLLOWING IS WRONG, ONE CANNOT OVERRIDE THE HIDDEN IUNKNOWN RELEASE() METHOD IN THIS WAY!!!
//      virtual ULONG Release() {MyDispose(); return 0;} // This is never called automatically!!!
            [PreserveSig];
            virtual void Doit() {};
        };
}

代码更正如下:

  1. Release方法不会覆盖CLI编译器一无所知的隐藏IUnknown :: Release()方法,如果它被更正为实际返回ULONG值,

  2. 建议~Foo()析构函数只调用!Foo()终结器,以避免重复它需要做的事情,即释放非托管资源,

  3. 析构函数~Foo()应该处理托管和非托管资源,但是终结器!Foo()应该只处理这里实现的非托管资源,

  4. 除了实现的接口方法之外,不需要公开任何方法,

  5. 所有接口方法都应该[PreserveSig]以实现与COM的最大兼容性。

  6. 使用针对VS 2010(GBG)更正的对象(本机C ++)的代码,更正如下所示进行了修改(请注意,这包含了编写COM客户端时问题的答案!):

        #import "..\\Bar\\Bar.tlb" //raw_interfaces_only
    
        //C++ definition of the managed IDisposable interface...
        MIDL_INTERFACE("805D7A98-D4AF-3F0F-967F-E5CF45312D2C")
            IDisposable : public IDispatch
        {
        public:
            virtual VOID STDMETHODCALLTYPE Dispose() = 0;
        }
    
        ...
        CoInitialize(NULL);
        ...
            //the syntax for a "Smart Pointer" is as follows:
            Bar::IFooPtr pif(__uuidof(Bar::Foo)); // This object comes from the .tlb.
            if (pif)
            {
                //This is not stack based so the calling syntax for an object instance is as follows:
                pif->Doit();
                //THE FOLLOWING ANSWERS THE QUESTION: HOW TO DISPOSE ON RELEASE:  when one controls the COM client!!!
                IDisposable *id = nullptr;
                if (SUCCEEDED(pif->QueryInterface(__uuidof(IDisposable), (LPVOID*)&id)) && id)
                {
                    id->Dispose();
                    id->Release();
                }
                //The Release on the IUnknown is absolutely necessary, as without it the reference count stays as one!
                //This would result in a memory leak, as the Bar::Foo's destructor is never called,
                //and knows nothing about the IUnknown::Release() even if it were!!!
                pif->Release(); // explicit release, not really necessary since Bar::IFoo's destructor will call Release().
            }
        ...
        CoUninitialize();
        ...
    

    似乎问题揭幕者并不真正理解COM服务器的发布引用计数方法与托管代码中绝对没有引用计数之间的关系,而不是CCW所模拟的:

      

    如果我在我的COM对象上放置析构函数方法,则永远不会调用它。如果我放置一个终结器方法,它会在垃圾收集器到达时调用它。如果我明确调用我的Release()覆盖,则永远不会调用它。

    上面已经解释了~Foo()析构函数和!Foo()终结器行为;所谓的Release()覆盖永远不会被调用,因为它不是对任何东西的覆盖,尤其是CCW提供的隐藏的IUnknown接口。但是,这些编码错误并不意味着次要问题没有价值,并且有一些解决方法可以实现,正如我在其他答案中所述。

      

    您可以通过IDisposable和Finalize执行此操作。   weblogs.asp.net/cnagel/archive/2005/04/27/404809.aspx

    这个答案没有直接回答这个问题,因为IDisposable和Finalize已经在C ++ / CLI~Foo()和!Foo()上实现了;提问者只是不明白如何调用我在上面显示的Dispose()方法。

答案 3 :(得分:1)

答案 4 :(得分:1)

答案 5 :(得分:0)

如果没有在C ++中创建包装器(没有.NET),我不知道如何。问题是,当对Release的调用将COM引用计数降为0时,.NET Framework不知道是否仍存在未包含在COM引用计数中的托管引用。

潜在的限制是.NET没有只能从COM访问的对象的概念。而且由于.NET对象的引用在垃圾收集之前无法确定,因此在发布时没有确定的处理方式,就像在纯COM中一样。