CabSdk Update, ClrSpy Love and geek teamwork!

As I mentioned previously, my team needed a managed wrapper around the CAB APIs in order to extract and test the contents our InfoPath templates programmatically*. We found a great series of articles on the subject by Jim Mischel, downloaded the sample code, and set to building our automated testing tool. Unfortunately, we ran into a snag once we began trying to decompress multiple XSN files. Luckily Adam Nathan’s CLRSpy came to the rescue. Here’s how I debugged the problem.

When our app got to the point of processing the third or fourth CAB, an Exception was thrown:

System.NullReferenceException: Object reference not set to an instance of an obj
at CabDotNet.CabSdk.FdiDestroy(IntPtr hfdi)
at CabWrapper.CabDecompressor.Dispose(Boolean disposing)
at CabWrapper.CabDecompressor.Dispose()
at CreateTemplatesAndViewers.testfdi.ExtractFiles(String cabinetFullPath, Str
ing destinationDirectory) in d:\visual studio projects\utilities\createtemplates
andviewers\testfdi.cs:line 70

Not knowing what the problem was, I dug through the source code looking for obvious managed code problems. Nada. At that point I started to suspect the interop portion of the code, so I fired up CLRSPy and ran the app again. Bingo! I saw the following in the CLRSPy log:

Collected Delegate in XSNInjector.exe (PID 5976): Unmanaged callback to garbage collected delegate: CabDotNet.FdiFileReadDelegate

Aha! The managed delegates Jim was creating were being Garbage Collected before the umanaged CAB API tried to call them. But why? Looking at the CabSdk.FdiCreate wrapper function, I noticed the call to the actual unmanaged FdiCreate function:

hfdi = CabSdk.FdiCreate(
new FdiMemAllocDelegate(MemAlloc),
new FdiMemFreeDelegate(MemFree),
new FdiFileOpenDelegate(FileOpen),
new FdiFileReadDelegate(FileRead),
new FdiFileWriteDelegate(FileWrite),
new FdiFileCloseDelegate(FileClose),
new FdiFileSeekDelegate(FileSeek),

The delegates being created (“new FdiMemAllocDelegate(MemAlloc)”, etc.) weren’t being refered to at all in the managed code! The GC doesn’t track unmanaged references to managed objects, so it felt free to collect them once there was enough memory pressure. The fix was relatively simple, I just created member variables to hold references to the delegates and our app was able to process any number of CABs without any problems.

Jim has since posted an updated article and code:

After the third article was published, I began receiving reports of intermittent unexplained failures of the CabCompressor and CabDecompressor classes when compressing and decompressing cabinet files. After much investigation, reader Gerrard Lindsay sent me the solution.

Ah, teamwork!