Visual Studio 2010!

Read now >

View Now
DevSource RSS FEEDS
XML Want an easy way to keep up with breaking tech news? And the Get DevSource headlines delivered to your desktop with RSS.
ADVERTISEMENT
ADVERTISEMENT

 

DevSource.com: Your Source for Visual Studio on Facebook
ADVERTISEMENT
Cabinet Files Revisited
By Jim Mischel

Rate This Article: Add This Article To:

Cabinet Files Revisited - ' A Bug Fix '
( Page 2 of 4 )

Cabinet files are compressed files that Microsoft uses for product distribution, including Windows installation, ActiveX component downloads, and Microsoft Installer packages. A cabinet file is a compressed collection of multiple files, similar in concept to a zip file.

Two years ago, I created some code to read and write cabinet files from .NET programs. I published the cabinet file access code and a series of three articles on DevSource, explaining the code.

In Creating a CABINET.DLL Interface, I developed a C# class to interface with the Cabinet SDK. Using the CABINET.DLL Interface showed how to use the interface from the first article in some sample programs. Finally, in Creating an Object-Oriented Interface to CAB Files, I developed a full object-oriented wrapper around the bare interface to make it much easier to use. All three articles were written for .NET 1.1.

The most difficult part about writing the code was creating the managed callbacks for the unmanaged DLL functions. The Cabinet SDK expects all callbacks to use the Cdecl calling convention. Whereas the .NET Framework supports creating a Cdecl managed callback, there was no way in C# to create such a managed callback. My solution involved a custom attribute and some very messy post-processing of the compiled code, as I explained in the first of the three articles.

Note: Source code that accompanies this article is available here.

A Bug Fix

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.

The problem was in the way that I was creating and using the delegates. The problem lies in this bit of code in CabDecompressor:

public IntPtr FdiContext
{
    get
    {
        if (disposed)
        {
            throw new ObjectDisposedException("CabDecompressor");
        }

        if (hfdi == IntPtr.Zero)
        {
            hfdi = CabSdk.FdiCreate(
                new FdiMemAllocDelegate(MemAlloc),
                new FdiMemFreeDelegate(MemFree),
                new FdiFileOpenDelegate(FileOpen),
                new FdiFileReadDelegate(FileRead),
                new FdiFileWriteDelegate(FileWrite),
                new FdiFileCloseDelegate(FileClose),
                new FdiFileSeekDelegate(FileSeek),
                erf);
            if (hfdi == IntPtr.Zero)
            {
                throw new ApplicationException("Failed to create FDI context.");
            }
        }
        return hfdi;
    }
}

The highlighted code calls the FdiCreate function, passing newly created delegates that are the managed callbacks that the decompression functions will access in order to do their work. The problem is that the delegates, which are managed objects, have no managed references. As a result, the garbage collector is free to collect those managed objects during the next garbage collection pass. After the delegates are collected, any attempt to access the callbacks results in a null pointer reference.

The solution is to make the delegates private members of the CabDecompressor class and create the delegates in the constructor. That prevents the garbage collector from collecting them, because the class maintains a managed reference. The listing below shows the fixed code.

//gerrard: added private members for the delegates
private FdiFileReadDelegate FileReadDelegate;
private FdiFileOpenDelegate FileOpenDelegate;
private FdiMemAllocDelegate MemAllocDelegate;
private FdiFileSeekDelegate FileSeekDelegate;
private FdiMemFreeDelegate MemFreeDelegate;
private FdiFileWriteDelegate FileWriteDelegate;
private FdiFileCloseDelegate FileCloseDelegate;

public CabDecompressor()
{
	//gerrard: initializing new private members
	FileReadDelegate = new FdiFileReadDelegate(FileRead);
	FileOpenDelegate = new FdiFileOpenDelegate(FileOpen);
	MemAllocDelegate = new FdiMemAllocDelegate(MemAlloc);
	FileSeekDelegate = new FdiFileSeekDelegate(FileSeek);
	MemFreeDelegate = new FdiMemFreeDelegate(MemFree);
	FileWriteDelegate = new FdiFileWriteDelegate(FileWrite);
	FileCloseDelegate = new FdiFileCloseDelegate(FileClose);
}

public IntPtr FdiContext
{
    get
    {
        if (disposed)
        {
            throw new ObjectDisposedException("CabDecompressor");
        }

        if (hfdi == IntPtr.Zero)
        {
			//gerrard: using the new private members
			hfdi = CabSdk.FdiCreate(
				MemAllocDelegate,
				MemFreeDelegate,
				FileOpenDelegate,
				FileReadDelegate,
				FileWriteDelegate,
				FileCloseDelegate,
				FileSeekDelegate,
				ref erf);
			if (hfdi == IntPtr.Zero)
            {
                throw new ApplicationException("Failed to create FDI context.");
            }
        }
        return hfdi;
    }
}

A related error occurred in the public FdiCopy function, when the garbage collector would sometimes destroy the cabinet file path while the FdiCopy DLL function was still processing. The solution was to place a call to GC.KeepAlive in the function, referencing the cabPath variable and making it ineligible for garbage collection from the beginning of the function to the point where GC.KeepAlive is called. Examine the public FdiCopy function in cabsdk.cs to see how GC.KeepAlive is used.

Another reader with whom I spent considerable time debugging odd problems ran into a problem decompressing cabinet files that contain directories. He sent me a modified FileOpen function that creates subdirectories during decompression. The modified code is in the CabIO.cs source file.

I posted the fixed code on my Web site and made it available to all who wrote experiencing problems. You can download the most recent .NET 1.1 version of the CabDotNet code from www.mischel.com/pubs/cabdotnet/cabdotnet11.zip. Other than bug fixes, this is the final version of CabDotNet for .NET 1.1.



 
 
>>> More Using Microsoft Visual Studio Articles          >>> More By Jim Mischel