Sam Gentile

Sponsors

The Lounge

Syndication

News

  • This Blog has moved to samgentile.com. If you have subscribed via FeedBurner, you do not have to do a thing, feed has been re-pointed

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
A Beasty COM Interop Problem

This is one reason why Scott Hanselman calls me an "Interop Beast" -)). We had a thorny COM Interop issue that was causing a crash in certain machine types.  Every single pair on the team had worked on it for many hours for the last three weeks, in our Ship Cycle without success. It should have been solved since I was feeding them all the usual Marshal.ReleaseComObject crap again but it refused to be cleaned up. If you remember back from my posts in 2002, while working with 10 million+ plus lines of COM to interoperate with while I was at Groove, I had run into many thorny lifetime issues.

As a review, (for more information read my MSDN articles here and here), the Runtime Callable Wrapper is a "bridge" between the two very different worlds of managed CLR code and unmanaged code. Its job is to hide all that ugly COM crap from the goodness that is the CLR and feel, smell and taste like any other CLR object. To do this it must perform a tough job as the COM world is completely deterministic and the CLR is completly not as the GC runs when it feels like it. The RCW (usually) has one and only one unmanaged COM reference to the underlying COM object. The problem comes that when you are done using the RCW on the CLR side, you are marking the RCW class for GC but when that GC runs is complelty non-determinstic and probably not going to happen that split second. The issue is that COM wants references cleaned up right away (ref counts-- until 0) and killing off that object or bad things happen, like crashes. So, the RCW is still holding on to that reference while waiting for GC. BOOM, comes the crash in many cases, like we faced.

In the case, we were trying to interoperate with some old VB6 COM objects from an old legacy previous version of our product that was being driven by a command line script. The first thing is that IDisposable was not completly implemented and so I did this:

public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~V4Repository() {
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing) {
            if (disposing) {
                if (modUtil != null) {
                    Marshal.FinalReleaseComObject(modUtil);
                    modUtil = null;
                }
                
                if (sentryDB != null) {
                    CloseDatabase();
                    Marshal.FinalReleaseComObject(sentryDB);
                    sentryDB = null;
                }

                 GC.Collect();
                GC.WaitForPendingFinalizers();
            }
        }
Notice, that evil GC.Collect and WaitForPendingFinalizers. I never really had to do this in the past as one had to do Marshal.ReleaseComObject in a loop since that one reference count sometimes became 2, 3 or 4 across the bounday. Whidbey introduced FinalReleaseComObject which eliminates the need to do the loop. The only place I have ever had to do the Collect and Wait is with Outlook and Excel as they are really hard to kill off in Interop.
Anyway, Steve and I were on this for 4-5 hours into the night last night and nothing was working. We were getting really fustrated and tired but we wouldn't let it go. I said it had to work, something else was afoot. I first thought it had to be the underlying VB COM object as they always forget to close the underlying ADO connection (Oh, the VB runtime does that for you (sic)) but that wasn't it either. We then noticed that the above was working on certain threaded machines or hyperthreaded machines and not on single threaded machines. Steve and I hit at the problem solution at the same time: Stupid VB6 only does STA apartment threaded COM objects and .NET is inherently multi-threaded. We were on the wrong thread in certain situations. We had to force a single thread with the [STATHREAD] attribute in the console application and then everything was finally working on all types of processes. Did I tell you how much COM Interop sucks? Steve and I left with a lot of joy though when it was over!

Posted 12-02-2006 10:51 AM by Sam Gentile
Filed under: ,

[Advertisement]