COM in .NET world. Fixing problems
One of complications for STA COM interfaces that they have thread affinity and any function from that interface should be called only from that thread. For example, imagine that main thread actively using some COM interfaces. And in some moment GC thread started to release unused interfaces.
But GC cannot call Release function of that interface in GC finalizer thread. GC thread should switch to main thread to call Release. This is quite slow and main thread could be busy. In case when there are a lot of interfaces went on .NET side it can create huge queue of interfaces waiting for their release and there could be some other objects waiting for finalizer to be called.
As mitigate that, .NET team decided to periodically release COM interfaces on any COM interop. But these created own problems.
First problem that when your code calls IIntf2.Function2() and .NET executes Release function on unrelated object that implements IIntf1.
Also, in some cases releasing of COM interfaces will pump messages. It means that code will call PeekMessage or GetMessage. And if another thread called SendMessage to window that belong to thread that calling PeekMessage or GetMessage then window procedure will be called before these functions will process messages. Window procedure will be called regardless of filters passed to PeekMessage or GetMessage.
These two abovementioned cases create reentrancy problems and lead to strange bugs that happened very rarely and very hard to debug and fix. Microsoft realizes that this is not optimal solution and there is function Thread.DisableComObjectEagerCleanup to disable cleanup of RCW for current thread. But if you just call this function and hope that everything is fixed then you are wrong. Now we come back to original problem that GC has hard time releasing these interfaces from finalizers thread. As result it could lead to huge memory consumption if interfaces are used faster that GC able to release them. GC need some help and you can help it by calling Marshal.CleanupUnusedObjectsInCurrentContext from message pump function. Usually it is best to call on idle when there are no messages available. There are actually another function Marshal.AreComObjectsAvailableForCleanup that tells you that there is need to call Marshal.CleanupUnusedObjectsInCurrentContext.
Ok by this time you may thing that all this stuff applies to real COM objects that are registered in registry. But you want to interop with .NET and decided to just to pass interfaces between C++ code and .NET. You don’t want to use ATL or any other complex library. You just created pure virtual class implement it and pass it to .NET. And all of stuff I wrote before will apply to your object as well.
But say you created thread safe class and it is ok to use from any thread but .NET has own mind and doesn’t let you. What should you do? Well it is very simple. You have to implement support for IAgileObject interface. It is marker interface and has no methods. If you interface supports, it then it means that interface will be treated as free threaded. There will be no thread affinity and interface can be used from any thread and .NET will not do anything special about it. It will just call function from it. But keep in mind that your interface should be thread safe. So, all functions including AddRef, Release (You should use InterlockedIncrement and InterlockedDecrement) and QueryInterface should be thread safe. Specially it is important if you used Thread.DisableComObjectEagerCleanup and Marshal.CleanupUnusedObjectsInCurrentContext. In this case most of interfaces will be released in main thread because finalizer thread has no chance to run because you always cleaning interfaces. But sometimes you interface will be released in GC finalizer thread. And it could be last Release and destructor will be called and it could mess up some of your structures that are not thread safe. So be quite careful about this.
I hope this is helpful for some of you.