About COM Interop, having a life et al.

Published Wed, Nov 3 2004 23:40 | girishb
Have you ever derived a .NET class off of a COM interop class and used it production? Well, I have. It turns out that it seems to be the easiest way to tack on my own methods onto an existing COM object interface without re-writing ~200 methods. It all worked well, until, our perf group tried to stress it.
That killed it. It died worse than those 80 odd extras died in Kill Bill Vol 1.
I have been fighting with this issue off an on for a little while and tackled it yesterday head on. The first thing I found was that I was not doing a ReleaseComObject on that original COM object anywhere. Well, it would have normally been ok if you were just using the interop object. In my case, since i was wrapping my .NET implementation (which had some memory resources such ArrayLists) associated with it, this was not enough. It worked ok to an extent but when the GC.Collect() occured, I got the infamous "Pure virtual Function Call" error [Footnote: infamous in COM days when you would have unloaded the library (usually a proxy) but holding on to an interface pointer and making a request on it]. This was because, you guessed it, while the actual COM object might have gone away due to my ReleaseComObject call, the actual wrapper managed to confuse(?) the infrastructure to call Release on my *nonexistent* COM object. Very bad.
So, At this point, I was desparate. I was hoping that I could come up with something that would work. I was getting ready to write delegate functions for each of those 200 methods on the COM object (so that I dont have to derive, instead make it an attribute), that I came across this ExtensibleClassFactory. This exposes one method RegisterObjectCreationCallback.
This is meant to register a callback to allow you to write your own hook to the creation of the unmanaged objects. This usually is done using CoCreateInstance with aggregation magic et al in the bowels of System.Runtime.InteropServices implementation.
It turns out that I can just register my own callback, and within that callback, I just had to return a new COM interop object for the base object. You are probably saying, but that is what should be happening when dont provide a hook. Well, not really. In my case, I just dont aggregate with the incoming pointer, I just return a new COM object every time. This seems to make my GC very happy and it managed to control a lot of OutOfMemory errors. To paraphrase the example in MSDN,
using System;
using System.Runtime.InteropServices;

public class CallBack
{
	public IntPtr Activate(IntPtr Aggregator)
	{
		return (IntPtr) System.Runtime.InteropServices.Marshal.GetIUnknownForObject(new MyCOMClass);
	}
}

public class MyInner : MyCOMClass
{
	static CallBack callbackInner;

	static void RegisterInner()
	{      
		callbackInner = new CallBack();
		System.Runtime.InteropServices.ExtensibleClassFactory.RegisterObjectCreationCallback(new System.Runtime.InteropServices.ObjectCreationDelegate(callbackInner.Activate));
	}
	//This is the static initializer.    
	static MyInner()
	{
		RegisterInner();
	}
}
is all I had to do.
You would think that would be the end of my story. But no, there was one more wrinkle. This object has been used in legacy ASP pages (~300) that have been ported to ASPX (ported not re-written) which basically ignores cleaning up the COM objects. Now, I needed a way to clean that mess without editing 300 pages. That would be disastrous. So, i basically wrote my own sorta GC which basically circumvents the real GC by keeping track of all COM interop objects created (since they are always created from my class) and stuffing them in a good ol' ArrayList and disposing them during the Session_OnEnd() event (and other key events such as LogOff).
All in all, very interesting day. I have not been blogging lately since all my sins are catching up to me and I did not have any coherent thoughts to write.