About COM Interop, having a life et al.
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.