How to dispose of a NET COM interop object on Rele

2019-02-02 16:57发布

问题:

I have a COM object written in managed code (C++/CLI). I am using that object in standard C++.
How do I force my COM object's destructor to be called immediately when the COM object is released? If that's not possible, call I have Release() call a MyDispose() method on my COM object?

My code to declare the object (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();
    };

My code to use the object (native 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().

If I put a destructor method on my COM object, it is never called. If I put a finalizer method, it is called when the garbage collector gets around to it. If I explicitly call my Release() override it is never called.

I would really like it so that when my native Bar::IFoo object goes out of scope it automatically calls my .NET object's dispose code. I would think I could do it by overriding the Release(), and if the object count = 0 then call MyDispose(). But apparently I'm not overriding Release() correctly because my Release() method is never called.

Obviously, I can make this happen by putting my MyDispose() method in the interface and requiring the people using my object to call MyDispose() before Release(), but it would be slicker if Release() just cleaned up the object.

Is it possible to force the .NET COM object's destructor, or some other method, to be called immediately when a COM object is released?

Googling on this issue gets me a lot of hits telling me to call System.Runtime.InteropServices.Marshal.ReleaseComObject(), but of course, that's how you tell .NET to release a COM object. I want COM Release() to Dispose of a .NET object.

回答1:

Actually neither the Dispose (or should i say ~Foo) not the Release will be called from the COM client when the last reference is released. It simply is not implemented. Here is some idea how such a thing could be done.

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

But the method is not advised even by author.

If you implement the COM Client as well the best option would be to query for IDisposable and call Dispose explicitly, iid to request is:

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

Other option I can think of is to implement some sort of own "COM Garbage Collector". Each object created by COM would be placed in a list (Provided that objects of your type are only creatable by COM - I cannot think of any method to distinguish from where the object is created). And then you would have to periodically check the list, and on each object call something like this:

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

but this is some crazy idea.



回答2:

I have a COM object written in managed code (C++/CLI). I am using that object in standard C++. How do I force my COM object's destructor to be called immediately when the COM object is released? If that's not possible, can I have Release() call a Dispose() (not MyDispose() - GBG) method on my (managed DotNet - GBG) COM object?

RE: Forcing a deterministic release of resources bound by a DotNet COM Server when the client is unmanaged code. These resource can be arranged to be released when the Garbage Collector collects the item, but that is not deterministic and such resources as file streams may not be released for hours or days for large memory systems where garbage collection is infrequent.

This is a common problem with COM Callable Wrappers (CCW's) as can be seen by another somewhat related thread at: Is it possible to intercept (or be aware of) COM Reference counting on CLR objects exposed to COM. in that case as in any case where one is writing their own COM client, no matter whether under managed or unmanaged code, is easy to solve just by a call to the IDisposable.Dispose() method as was done there. However, that method would not work for (say) a DotNet COM codec class whose client may be the operating system itself and which client shouldn't have to be aware that the COM server is unmanaged or managed (DotNet).

One can implement the IDisposable.Dispose() pattern on the DotNet COM server as per the MSDN link: http://msdn.microsoft.com/en-us/library/system.idisposable.aspx, but that won't do any good because the Dispose() method will never get called by the CCW. Ideally, the implementation of the CCW's in mscoree.dll should really check for and call the IDisposable.Dispose() method if implemented as part of the CCW release and/or finalization/destructor. I'm not sure why Microsoft did not do this, as having full access to the Assembly information they could easily determine if the DotNet COM Class supports IDisposable and just call Dispose() on final release if it does, and since this would be within the CCW, all the complexities about handling reference counting due to the extra interface reference could be avoided.

I can't see how this would "break" any existing code, as any client that is IDisposable aware could still call Dispose(), which if implemented according to the above template only effectively does anything on the first call. Microsoft may be worried about a class being Disposed while there are still managed references to it that wouldn't know about it being Disposed until there start to be exceptions thrown by trying to use already disposed resources, but that is a potential problem for any improper use of the IDisposable interface even with only DotNet clients: if there are multiple references to the same object instance and any one of them calls Dispose(), the others will find trying to use the required disposed resources cause exceptions. For such cases, one should always put in guards using the disposing boolean (as per the IDisposable pattern template) or only reference the object through a common wrapper.

Since Microsoft hasn't done the required few lines of code in the implementation of CCW's in mscoree.dll, I wrote a wrapper around mscoree.dll that adds this extra functionality. It is a little bit complex in that in order to control the creation of my wrapper around any instance of any DotNet COM Class, I need to also wrap the IClassFactory interface and aggregate the CCW instance in my "CCW_Wrapper" wrapper class. This wrapper also supports further levels of aggregation from yet another outer class. The code also does reference counting on instances of classes within the mscoree.dll implementation in use so as to be able to call FreeLibrary on mscoree.dll when there are no references (and LoadLibrary again if necessary later). The code should also be multi threading friendly as is required for COM under Windows 7. My C++ code is as follows:

EDITED 22 December 2010: Eliminated one unnecessary parameter of COM_Wrapper constructor:

#include <windows.h>

HMODULE g_WrappedDLLInstance = NULL;
ULONG g_ObjectInstanceRefCnt = 0;

//the following is the C++ definition of the IDisposable interface
//using the GUID as per the managed definition, which never changes across
//DotNet versions as it represents a hash of the definition and its
//namespace, none of which can change by definition.
MIDL_INTERFACE("805D7A98-D4AF-3F0F-967F-E5CF45312D2C")
    IDisposable : public IDispatch {
    public:
        virtual VOID STDMETHODCALLTYPE Dispose() = 0;
    };

class CCW_Wrapper : public IUnknown {
public:
    // constructor and destructor
    CCW_Wrapper(
        __in IClassFactory *pClassFactory,
        __in IUnknown *pUnkOuter) :
            iWrappedIUnknown(nullptr),
            iOuterIUnknown(pUnkOuter),
            iWrappedIDisposable(nullptr),
            ready(FALSE),
            refcnt(0) {
        InterlockedIncrement(&g_ObjectInstanceRefCnt);
        if (!this->iOuterIUnknown)
            this->iOuterIUnknown = static_cast<IUnknown*>(this);
        pClassFactory->CreateInstance(
            this->iOuterIUnknown,
            IID_IUnknown,
            (LPVOID*)&this->iWrappedIUnknown);
        if (this->iWrappedIUnknown) {
            if (SUCCEEDED(this->iWrappedIUnknown->QueryInterface(__uuidof(IDisposable), (LPVOID*)&this->iWrappedIDisposable)))
                this->iOuterIUnknown->Release(); //to clear the reference count caused by the above.
        }
        this->ready = TRUE; //enable destruction of the object when release decrements to zero.
        //OUTER IUNKNOWN OBJECTS MUST ALSO PROTECT THEIR DESTRUCTORS IN SIMILAR MANNERS!!!!!
    }
    ~CCW_Wrapper() {
        this->ready = FALSE; //protect from re-entering this destructor when object released to zero.
        if (this->iWrappedIDisposable) {
            //the whole reason for this project!!!!!!!!
            this->iWrappedIDisposable->Dispose();
            //the following may be redundant, but to be sure...
            this->iOuterIUnknown->AddRef();
            this->iWrappedIDisposable->Release();
        }
        if (this->iWrappedIUnknown)
            this->iWrappedIUnknown->Release();
        if (!InterlockedDecrement(&g_ObjectInstanceRefCnt)) {
            //clear all global resources including the mutex, multithreading safe...
            HMODULE m = (HMODULE)InterlockedExchangePointer((PVOID*)&g_WrappedDLLInstance, (LPVOID)0);
            if (m)
                FreeLibrary(m);
        }
    }

    // IUnknown Interface
    STDMETHOD(QueryInterface)(REFIID riid, void **ppv) {
        if (ppv) {
            *ppv = nullptr;
            if (riid == IID_IUnknown) {
                *ppv = static_cast<IUnknown*>(this);
                this->AddRef();
                return S_OK;
            }
            else if (this->iWrappedIUnknown) {
                return this->iWrappedIUnknown->QueryInterface(riid, ppv);
            }
            return E_NOINTERFACE;
        }
        return E_INVALIDARG;
    }

    STDMETHOD_(ULONG, AddRef)() {
        return InterlockedIncrement(&this->refcnt);    
    }

    STDMETHOD_(ULONG, Release)() {
        if (InterlockedDecrement(&this->refcnt))
            return this->refcnt;
        if (this->ready) //if not being constructed or destructed...
            delete this;
        return 0;
    }

private:
    IUnknown *iOuterIUnknown;
    IUnknown *iWrappedIUnknown;
    IDisposable *iWrappedIDisposable;
    BOOL ready;
    ULONG refcnt;
};

class ClassFactoryWrapper : public IClassFactory {
public:
    // constructor and destructor
    ClassFactoryWrapper(IClassFactory *icf) : wrappedFactory(icf), refcnt(0), lockcnt(0) {
        InterlockedIncrement(&g_ObjectInstanceRefCnt);
    }
    ~ClassFactoryWrapper() {
        if (wrappedFactory)
            wrappedFactory->Release();
        if (!InterlockedDecrement(&g_ObjectInstanceRefCnt)) {
            //clear all global resources, multithreading safe...
            HMODULE m = (HMODULE)InterlockedExchangePointer((PVOID*)&g_WrappedDLLInstance, (LPVOID)0);
            if (m)
                FreeLibrary(m);
        }
    }

    // IUnknown Interface
    STDMETHOD(QueryInterface)(REFIID riid, void **ppv) {
        if (ppv) {
            *ppv = nullptr;
            if (riid == IID_IUnknown) {
                *ppv = static_cast<IUnknown*>(this);
                this->AddRef();
            }
            else if (riid == IID_IClassFactory) {
                *ppv = static_cast<IClassFactory*>(this);
                this->AddRef();
            }
            else {
                return E_NOINTERFACE;
            }
            return S_OK;
        }
        return E_INVALIDARG;
    }

    STDMETHOD_(ULONG, AddRef)() {
        return InterlockedIncrement(&this->refcnt);    
    }

    STDMETHOD_(ULONG, Release)() {
        if (InterlockedDecrement(&this->refcnt) || this->lockcnt)
            return this->refcnt;
        delete this;
        return 0;
    }

    // IClassFactory Interface
    STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppv) {
        HRESULT result = E_INVALIDARG;

        if (ppv) {
            *ppv = nullptr;
            if (pUnkOuter && (riid != IID_IUnknown))
                return result;
            CCW_Wrapper *oipm = new CCW_Wrapper(wrappedFactory, pUnkOuter);
            if (!oipm)
                return E_OUTOFMEMORY;
            if (FAILED(result = oipm->QueryInterface(riid, ppv)))
                delete oipm;
        }

        return result;
    }

    STDMETHOD(LockServer)(BOOL fLock) {
        if (fLock)
            InterlockedIncrement(&this->lockcnt);
        else {
            if (!InterlockedDecrement(&this->lockcnt) && !this->refcnt)
                delete this;
        }
        return wrappedFactory->LockServer(fLock);
    }

private:
    IClassFactory *wrappedFactory;
    ULONG refcnt;
    ULONG lockcnt;
};


STDAPI DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, __deref_out LPVOID FAR* ppv) {
    HRESULT result = E_INVALIDARG;

    if (ppv) {
        *ppv = nullptr;
        if ((riid != IID_IUnknown) && (riid != IID_IClassFactory))
            return E_NOINTERFACE;
        HMODULE hDLL = LoadLibrary(L"mscoree.dll");
        if (!hDLL)
            return E_UNEXPECTED;
        typedef HRESULT (__stdcall *pDllGetClassObject) (__in REFCLSID, __in REFIID, __out LPVOID *);
        pDllGetClassObject DllGetClassObject = (pDllGetClassObject)GetProcAddress(hDLL, "DllGetClassObject");
        if (!DllGetClassObject) {
            FreeLibrary(hDLL);
            return E_UNEXPECTED;
        }
        IClassFactory *icf = nullptr;
        if (FAILED(result = (DllGetClassObject)(rclsid, IID_IClassFactory, (LPVOID*)&icf))) {
            FreeLibrary(hDLL);
            return result;
        }
        ClassFactoryWrapper *cfw = new ClassFactoryWrapper(icf);
        if (!cfw) {
            icf->Release();
            FreeLibrary(hDLL);
            return E_OUTOFMEMORY;
        }
        //record the HMODULE instance in global variable for freeing later, multithreaded safe...
        hDLL = (HMODULE)InterlockedExchangePointer((PVOID*)&g_WrappedDLLInstance, (LPVOID)hDLL);
        if (hDLL)
            FreeLibrary(hDLL);
        if (FAILED(result = cfw->QueryInterface(IID_IClassFactory, ppv)))
            delete cfw; //will automatically free library and the held class factory reference if necessary.
    }
    return result;    
}

extern "C"
HRESULT __stdcall DllCanUnloadNow(void) {
    if (g_ObjectInstanceRefCnt)
        return S_FALSE;
    return S_OK;
}

extern "C"
BOOL APIENTRY DllMain( HMODULE hModule,
                                                DWORD  ul_reason_for_call,
                                                LPVOID lpReserved ) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

A '.def' file is also required for the DLL as follows:

LIBRARY mscoreeCOM_DisposeWrapper

EXPORTS
    DllCanUnloadNow         PRIVATE
    DllGetClassObject       PRIVATE

To use this source code, compile it into a DLL and install the DLL into the Windows SYSTEM folder, then have your installation program or your [COMRegisterFunction] method in your DotNet COM server modify the class registry entry for the InprocServer32 from mscoree.dll to the name of this wrapper (say mscoreeWrapper.dll). It can be compiled under 32 and/or 64 bit, and for installation on 64-bit systems should have the 64-bit version put into the System folder and the 32-bit version put into the SysWOW64 folder; also, both the normal CLSID registration and the virtualized WOW6432 versions should be modified as to the InprocServer32 entries. Some applications may require this wrapper DLL to be digitally signed to work seamlessly, which is a whole other subject. If someone wants, I'll make a link to my compiled versions of these DLL's available here.

As I said, the required few lines (excluding the wrapper requirements) technique should really be incorporated into mscoree.dll. Does anyone know how to contact someone within the appropriate department within Microsoft to make this suggestion?

EDITADD: I have submitted a suggestion for the DotNet Framework to Microsoft Connect. This seems to be the best way of giving Microsoft feedback.

EDITADD2: In implementing a workaround for this problem, I realized why MIcrosoft likely will not implement this "Auto calling of Dispose, if supported, when the CCW reference count drops to zero". In writing the workaround, I had to obtain a reference pointer to a COM interface on a managed object in order to pass it to a pure unmanaged COM method, and then had to Release() that reference count in order to not have the CCW strongly referencing the object and therefore causing a memory leak by never having it available for garbage collection. I did this because I know that decreasing a reference count to zero on a managed object currently only makes the CCW drop its strong reference to the object, making it eligible for garbage collection if there are no other references. However, if Microsoft implemented the Auto Dispose fix as I have suggested or if this code was in place wrapping the mscoree.dll functionality, this would trigger a Dispose() on the managed object when it wasn't wanted. For this particular case, I could "guard" the Dispose(bool disposing) virtual method to prevent the Dispose() happening, but for any existing code that uses this behaviour with the same assumption, including Microsoft's implementation of the DotNet Runtime Libraries, implementing this "fix" on the CCW would break that existing code. This wrapper fix still applies for COM Servers one writes themselves and is aware of this side effect, as they can put the "guards" in place on the Dispose().

EDITADD 3: In further work with this, I see that my suggestion to Microsoft is still valid and the problems of "breaking" existing code can be avoided by the fix that would call the IDisposable.Dispose() method on the object instance implementing the managed COM server if the interface exists only if a new custom attribute such as [AutoComDispose(true)], whose default value is false, is applied to the managed COM server class. In this way, the programmer would choose to implement the functionality and the documentation on the new attribute would caution its use regarding having to "guard" the Dispose() method such as with a "artificial reference count" when there is a possibility of the Marshal.Release() method being called explicitly by in code used by the managed server or implicitly by calls to methods such as Marshal.GetObjectForIUnknown(), which in some cases can call QueryInterface and Release of the reference point if the ComObject is a managed object.

The main problem with this Answer is the complexity of installing it for use, as noted above.



回答3:

The code to declare the object (C++/CLI) corrected for VS 2010 (GBG):

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() {};
        };
}

The code is corrected as follows:

  1. The Release method does not override the hidden IUnknown::Release() method which the CLI compiler knows nothing about, if it were corrected to actually return a ULONG value,

  2. It is recommended that the ~Foo() destructor just call the !Foo() finalizer to avoid repeating what it needs to do which is release unmanaged resources,

  3. The destructor ~Foo() should dispose both managed and unmanaged resources but the finalizer !Foo() should only dispose unmanaged resources as implemented here,

  4. There is no need for any methods to be public other than the implemented interface methods, and

  5. All interface methods should be made [PreserveSig] for maximum compatiblity with COM.

The code to use the object (native C++) corrected for VS 2010 (GBG), with the corrections commented as follows (Note that this contains the answer to the question when one writes the COM client!):

    #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();
    ...

It seems the question opener doesn't really understand the relationship between the Release reference count method of COM servers and the absolute absence of reference counting in managed code other than as simulated by the CCW's:

If I put a destructor method on my COM object, it is never called. If I put a finalizer method, it is called when the garbage collector gets around to it. If I explicitly call my Release() override it is never called.

The ~Foo() destructor and !Foo() finalizer behaviour have been explained above; the so-called Release() override never gets called because it isn't an override to anything, especially the hidden IUnknown interface provided by the CCW. However, these coding errors don't mean that the secondary question isn't valuable and there are a few workarounds to make it possible, as I cover in other Answers.

You can do this through IDisposable and Finalize. weblogs.asp.net/cnagel/archive/2005/04/27/404809.aspx

This answer doesn't directly answer the question, as the IDisposable and Finalize are already implemented on the C++/CLI ~Foo() and !Foo(); the questioner merely doesn't understand how to call the Dispose() method, which I have shown above.



回答4:

Gordon,

I'm very interested in the answer to this. did you have any luck with your implementation

Charlie

Charlie, I take it that you are mostly interested in my Answers and in 2. or 3. below. My Answer 1. just works as I describe as does 2. However, there are serious problems, not with implementing 3. but in keeping it working as the patching done gets unpatched by some event in checking for availability for garbage collection, even when the COM object isn't eligible for collection. The methods are as follows, but I have since found a way to use the DotNetFramework version 4 to work around the major problems with 3.:

  1. The succinct answer showing how to call IDisposable.Dispose() from a native unmanaged client written in C++, which answers the direct question. Of course this works, and it was only the inexperience of the original question writer in writing for managed COM that made this difficult for him. However, the secondary question of whether this can be done automatically is what I am really answering here.

  2. A workaround using a C++ wrapper for the COM Callable Wrapper (CCW) as implemented by the Microsoft COM Runtime Execution Environment in mscoree.dll. This works as posted and you are welcome to use it, with the main limitations as noted: you need to install the DLL file in your System directory (or both of them if on a 64 bit system) and you need to re-write the registry entries for the InProcServer32 to link to this wrapper DLL (again for both the 32 and 64 bit versions of the registry). The DLL file should also be digitally signed to be acceptable for some built-in Microsoft COM clients such as Windows Imaging Component (WIC) in order to be fully functional.

  3. An alternate workaround done purely from the managed code side that works by "patching"/"hooking"/"swizzling" the virtual method tables of the CCW interfaces to take control and add the desired functionality of automatically calling IDisposable::Dispose() on derived classes when their pseudo reference counts provided by the CCW decrement to zero. This looks like it will work as I have it working for the non-aggregated instantiation case but am trying to support aggregation before I submit it here. Hopefully I will have it working within a few days. I am writing in C#, but of course it can also be written in managed/CLI C++, and will eventually be supplying links to both implementations. This is much more complex to write than the wrapper, but has the advantage that it is very easy to use as one make very few changes to the COM server class to use it, as I will be showing, and there is no extra work for installation at all. This appears to be unreliable as the patching I need to do gets undone by some event in the garbage collection sweeps so that I lose control of tracking of the reference counts.

Thus the advantages are it is easy to use. However, there are some disadvantages as follows:

  1. The code patches virtual method tables by direct writing to them in the cases of the simulated managed interface ones, which has been able to be done for the past many years but there is no guaranty that Microsoft may not change that in the future with a new memory model for a new version of the operating system. I have tried doing the changes to copies of the virtual method tables in unmanaged memory, but can't easily get certain key functionality working - the Marshal.GetObjectForIunknown() method call no longer works as expected which requires some very messy static look up tables... I'm not using that at present and patching directly for the managed vtables. If Microsoft change the write restrictions, I can likely get the "messy" method working. Doesn't look possible other than using new functionality provided by the DotNetFramework version 4.0.

  2. To keep it reasonably simple, I currently modify the QueryInterface implementation hooks so that the "extra" IMarshal...IConnectionContainer interfaces are ignored and not accessible from COM. If these are required, one can either implement all the required interfaces in managed code - quite a big job - or find a way to "hook" these interfaces as well, which is likely possible as I already "hook" the inner IUnknown unmanaged interface as was necessary to support aggregation. Still quite a big programming task, though.

  3. The code likely isn't very fast due to all the marshalling between unmanaged and managed code, and probably unsuitable for a managed COM server where the calls to the COM server methods are fast and frequent and "fine-grained" meaning that each call doesn't do all that much. If the required managed COM server can do quite a lot of the work with few method calls, then it should be fine and the performance cost of implementing it in managed code rather than unmanaged C++ should be minimal.

This project in more in the nature of a "white knight" hacking project in working around the current implementation!

When finished, I should really submit it as an article to some website such as "The Code Project", as I would think that it would be of use to many.

Anyway, watch this space for one more Answer using facilities from DotNetFramework 4.0...



回答5:

It seems to me that there are three approaches so solving the Question(s) posed here, as follows:

  1. When one has direct control of the native unmanaged client, one can call IDisposable.Dispose() directly when supported as I have shown clearly in on Answer; this is the easiest solution, but one does not always have control of the client.

  2. As per yet another Answer to the secondary question of how to make the Dispose() method be called automatically when the CCW reference count is released to zero, I have shown that one can wrap the unmanaged code implementation of COM Callable Wrappers (CCW's) with an unmanaged code DLL to support this functionality. This solution is not hard to write, but is difficult to install.

  3. In a previous Answer, I tried to present a third alternative to the solution of the secondary question above by using "patching"/"hacking"/"hooking"/"swizzling" techniques so that the functionality can be supported completely by managed code, thus not requiring any extra installation other than as normally required for managed DotNet COM servers. I have pretty much given up on this solution, as after completing it, including detecting and implementing support for client aggregation, I found that some event linked to the garbage collection scanning process causes the patched pointers to be unpatched, meaning that my routines lose control of tracking of the reference counts and thus the disposal process.

  4. Here I offer my last Answer to the secondary question using the new ICustomQueryInterface interface only available with DotNetFramework version 4.0 (and presumably newer), which avoids having to do most of the risky patching of existing virtual method tables. Using this new interface, one still creates virtual method tables, but only needs to be concerned with controlling the inner IUnknown interface only used when the COM server is aggregated by the client. The method has a slight but not serious limitation which I will cover after the implementation has been presented.

The secondary question is really related to why the actual Dispose() associated with the ~Foo() destructor doesn't automatically get called when the COM reference counter goes to zero, for which the reason is that this functionality is not implemented by the COM Callable Wrapper (CCW), at least not currently. This secondary question that needs to be answered is related to the problem that when one doesn't write the COM client, which clients in the general case are written assuming unmanaged native code COM servers, but the COM server requires deterministic disposal of resources as in the question text as follows:

I would really like it so that when my native Bar::IFoo object goes out of scope it automatically calls my .NET object's dispose code.

Here, the questioner doesn't really understand that native C++ doesn't have a mechanism for determining when stack based variables go "out of scope", unlike C++/CLI using "Stack Semantics", and that one has to manually call the Release() method as I have shown. Thus, the correct answer is given as follows:

Actually neither the Dispose (or should i say ~Foo) nor the Release will be called from the COM client when the last reference is released. It simply is not implemented. Here is some idea how such a thing could be done.

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

But the method is not advised even by author.

The above is correct that the unmanaged Com Callable Wrapper (CCW) is not IDisposable interface aware (the interface that is automatically implied by the ~Foo() C++/CLI syntax), and thus does not call the (actual) Dispose() method which would trigger everything to happen as the OP (and everyone else) desires. However, there are workarounds to cause this, as I have contributed here.

I got thinking about the idea expressed by "MadQ" as per the page linked above near the bottom of the page and his ideas about implementing a fix in the managed code by "hooking"/"swizzling" the vTable pointers, and thought that this idea may actually work with some modifications. While it is true that this idea may not be completely advisable in that one is effectively writing self modifying code in building one's own vTable entries, it isn't anything different than the interface proxies built by the CCW in order to be able to support DotNet COM servers. There are quite a few omissions and problems with the basic premise as outlined by "MadQ" as follows:

  1. It was proposed that the IUnknown interface be hooked or "swizzled" so as to be able to check when the reference count falls to zero, but for the more common non-aggregated case, all of the other interfaces also can change the reference count of the object instance, including all of the managed interfaces and also the unmanaged ones automatically provided by the CCW. These last can include IMarshal, IProvideClassInfo, ISupportErrorInfo, possibly the supporting interfaces for these last two in ItypeInfo and IErrorInfo respectively, IDispatch if the class uses the AutoDispatch or AutoDual settings which will also create a coclass interface, IConnectionPointContainer and possibly IConnectionPoint if COM events are implemented, IDispatchEx if the IExpando interface is implemented on the managed class, and IEnumVARIANT if the managed class implements the IEnumerable interface as for collections (there may be other undiscovered interfaces).

  2. For the non-aggregated case once detected, one could add "swizzling"/hooking for all the managed Release() pointers just as was proposed for IUnknown; however, because the other unmanaged interfaces are implemented in unmanaged C++, they are in code space and the vTables can not be written/patched. This would be solved by creating copies of the vTable's in allocated Co task memory, but then the routines themselves detect that the objects have become detached from the true vTable's. The work around for this would be to use a static dictionary of object instances and the pointers they belong to so that the actual objects can be looked up, then the patched interface vTable pointers unpatched, the method called, and the pointers re-patched for every method in every one of the unmanaged interfaces - a lot of very messy work, and not so nice in all of the flows in back and forth marshalling between unmanaged and managed code. Also, there are major potential problems supporting multi-threading doing this, so other workarounds to the problem must be found. I thus tried to stick to patching for the managed interfaces and only new simulated virtual method tables for the inner IUnknown interface used for aggregation; however I ran into the severe problem described below in 4.

  3. I noticed in the Microsoft documentation at http://msdn.microsoft.com/en-us/library/aa720632(VS.71).aspx] that the .Net class can supply its own implementations of these interfaces which override these native code implementations, so all interfaces could appear to be managed classes, with the implementations appearing to replace the unmanaged interfaces. This is a workable solution, but would require a lot of work in implementing all of these interfaces in managed code, if required.

  4. Alternatively, one could just patch the QueryInterface methods so that the unmanaged interfaces aren't supported by the resulting managed COM server, which is all most require as in the original question here. In fact, this makes the above unmanaged implementations of interfaces a complete non-issue, as the hooked managed code version of QueryInterface() can only respond to the implemented managed classes and reject any queries for those other interfaces not required or implemented as managed versions of the interfaces. This is the major restriction to implementing this using DotNetFramework less than version 4.0, as other than the garbage collector unpatching/restoring the virtual method tables, if the original IUnknown interface is patched then one can no longer use Marshal.GetObjectForIUnknown() and Marshal.GetUniqueObjectForIUnknown() on this server, which would be necessary if a DotNet managed COM server were to be passed back as a parameter to another managed COM server object by other unmanaged code. For instance, parts of the unmanaged Windows Imaging Component (WIC) codec system may pass instances of a managed IBitmapDecoder object to managed WPF code, which will fail in calling methods from this class through a Runtime Callable Wrapper (RCW) unless these Marshal methods work. I was unsuccessful in tricking the system into thinking the implementation was a unmanaged interface to cause the support of a full RCW as for unmanaged code.

  5. Yet another alternative to controlling this unmanaged "extra" interfaces is to write the managed implementations of these interfaces to just call through to the unmanaged code already implemented in the CCW, which is likely the easiest way to control these interfaces if they are required.

  6. Finally, "MadQ" missed a trick in that there is nothing wrong with the CCW reference count being increased and reduced to zero before the linkages are "hooked" as it does not cause a C++ type delete and only takes away the strong reference to the managed object instance keeping it from being finallized and garbage collected. In this way, one does not depend on the managed class only being usable for unmanaged COM clients because the reference count of at least one never allowing it to be garbage collected for .Net clients, as on contruction the reference count can be left at zero in spite of some references to the various instance interfaces.

What's good about this technique is the following:

This alternate workaround is implemented purely from the managed code side that works by "patching"/"hooking"/"swizzling" the virtual method tables of the CCW interfaces to take control and add the desired functionality of automatically calling IDisposable.Dispose() on derived classes when their pseudo reference counts provided by the CCW decrement to zero. This is much more complex to write than the wrapper, but has the advantage that it is very easy to use as one makes very few changes to the COM server class to use it as I will be showing, and there is no extra work for installation at all.

Thus the advantages are it is easy to use. However, there are some disadvantages as follows:

  1. Even using the new ICustomQueryInterface interface available with DotNet 4.0, one cannot control/track the normal non-aggregated IUnknown interface for the object instance, and while one can prevent queries on this interface from finding any other interfaces that one doesn't want to expose to COM, one cannot track the reference counting done through this interface. Fortunately, it appears that one can control the tracking of reference counting for the normally hidden inner IUnknown interface only used for use when the object is aggregated by the client.

  2. To keep it reasonably simple, I currently modify the QueryInterface implementation hooks so that the "extra" IMarshal...IConnectionContainer interfaces are ignored and not accessible from COM. If these are required, one can either implement all the required interfaces in managed code - quite a big job - or find a way to "hook" these interfaces as well, which also can be done but with a lesser amount of extra code and research.

  3. The code likely isn't very fast due to all the marshalling between unmanaged and managed code, and probably unsuitable for a managed COM server where the calls to the COM server methods are fast and frequent and "fine-grained" meaning that each call doesn't do all that much. If the required managed COM server can do quite a lot of the work with few method calls, then it should be fine and the performance cost of implementing it in managed code rather than unmanaged C++ should be minimal.

This project is more in the nature of a "white knight" hacking project in working around the current implementation!

In reverse order to the Question, to start, the implementation of the unmanaged COM client only changes as to not requiring the call to the IDisposable.Dispose() method (just as for the wrapper Answer) as follows:

    #import "..\\Bar\\Bar.tlb" //raw_interfaces_only

    ...
    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 IS NOT REQUIRED AND IS COMMENTED OUT!!!
            //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();
        }
    ...
    CoUninitialize();
    ...

Next, the only change to the managed COM server is to make the COM server class inherit (directly or indirectly) from the new AutoComDisposenC base class. I have also added the added functionality of "guarding" the destructor code to make it only effective the first time it's called for those cases where this matters, as in files only closed once, etcetera, as follows:

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

namespace Bar {

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

        [Guid("417E5293-blahblah")]
        [ClassInterface(ClassInterfaceType::None)]
        [ComVisible(true)]
        public ref class Foo : AutoComDisposenC, IFoo
        {
        //these don't need to be seen...
        private:
            bool disposed;
            Object ^disposedlock;
            void DisposeManaged() {};
            void DisposeUnmanaged() {};
            ~Foo() // called directly or by Dispose() on this instance
            {
                //multi-threading safe, only effective first time it's called...
                bool d;
                Monitor::Enter(this->disposedlock);
                    //in critical section = only one thread at a time
                    d = this->disposed;
                    this->disposed = false;
                Monitor::Exit(this->disposedlock);
                if (!d)
                {
                    DisposeManaged();
                    this->!Foo();
                }
            }
            !Foo() {DisposeUnmanaged();} // This is called by the garbage collector and the above.
        public:
            Foo() : disposed(FALSE), disposedlock(gcnew Object()) {};
            //THE FOLLOWING IS WRONG AS ONE CANNOT OVERRIDE THE HIDDEN IUNKNOWN RELEASE() METHOD!!!
//      virtual ULONG Release() {MyDispose(); return 0;} // This is never called automatically!!!
            [PreserveSig]
            [ComVisible(true)]
            virtual void Doit() {};
        };
}

And finally, to come, will be the actual implementation of the AutoComDisposenC class:

When finished, I should really submit this alternative Answer as an article to some website such as "The Code Project", as I would think that it would be of use to many.



回答6:

Short of creating a wrapper in C++ (without .NET), I don't know of a way. The problem is that when a call to Release drops the COM reference count to 0, the .NET Framework doesn't know whether there are still managed references that aren't included in the COM reference count.

The underlying limitation is that .NET doesn't have the concept of an object that is only accessible from COM. And since references from .NET objects can't be determined until garbage collection, there's no deterministic way to dispose on release, like there is in pure COM.