dcsimg
 

Cabinet File Utility Classes

Saturday Nov 7th 1998 by Levente Farkas

Cabinet File Utility Classes

-->

Cabinet files are multivolume compressed archives To view what's in a cabinet file, you can install a cabinet viewer utility from PowerToys. And also, the latest WinZip 7 Beta can view cabinet files.

If you ever wondered how to create those compressed cabinet (.cab) files that lots of setup kits use to pack files? Then you might have found out that you can actually create a cabinet file using a command line tool, named cabarc.exe that comes with Visual C++ 5.0 and higher (just search your MSDN CD for cabarc.exe).

However, if you want to build cabinet files from within your program, this tool is not of much help. Sure, you could write a wrapper around it and lauch it in the background whenever you need to manufacture a cab file. Not very elegant tough. In this article I'll show you how you can do this with some C++ classes and without the use of any external tool.

The cabinet files are in fact multivolume compressed (encrypted) archives. They have lots of features, including code signing (well, the cabinet file itself is actually signed, with the meaning that what it contains comes from a well-known provider) file spanning from one cabinet to another, etc. Thus, you won't be surprised that their internal format is quite complicated. However, there is a library available from MS that knows how to deal with those cab files. It can both create them and extract files from them. The library is distributed as a .dll (cabinet.dll) along with documentation on how to use it. You can download this Cabinet SDK from http://msdn.microsoft.com/workshop/management/cab/cabdl.asp. The file cabinet.dll is also included in the Windows core system file list and is part of the Win98 and Windows NT 4.0 operating systems (see the Windows Logo Requirements for more info).

Now, once you got that Cabinet SDK, you can stop reading this article if wou're a convinced C programmer. For everyone else, I wrote some C++ classes that wrap all those plain-vanilla (boring and compicated enough) C funtions the file cabinet.dll exports. There are two classes, one for creating cabinets (CCabinetBuilder) and one for exracting files from a cabinet file (CCabinetExtractor). These classes are declared and implemented in the files Cabinet.Hpp and Cabinet.Cpp respectively.

The cabinet classes were written so that they can be used from both MFC and non-MFC projects. Simply define the symbol __MFC__ if you're goin to use them from MFC stuff. If is also possible that only the cab creation or extraction code be used (however, this will only exclude the unused C++ classes but you'll still need the file cabinet.dll) by defining the symbols:

__CAB_BUILD__
__CAB_EXTRACT__

Only define the one that you'll actually use. E.g. if you only want to create cab files, define __CAB_BUILD__.

I'll show you how to use the classes to build and extract files from a cabinet file. First, let's create a cabinet file and put some files in it. Creating a cabinet file is dumb easy: you create an instance of the CCabinetBuilder class telling it the attbibutes of your new cab file, init it using Init and specifying where you actually want the cabinet file(s) to be created and then addding files in the cabinet.

If the accumulated (compressed) size of the files you add will grow larger that what would fit in a single cabinet file (you specified the volume size when you created the instance of the class) a new cabinet file will automatically be created. This is useful for creating cabinet files that would fit on floppy disks. So, you can get multiple volume cabinet files automatically!

And now let's see how it's done. We'll make a cabinet with ID 12345, volume size is 1.44M:

CCabinetBuilder cb(12345,1440000,1440000,250000);
if(cb.InitCabinet("D:\\Temp\\Test","Setup1","Disk"))
{
 // And now we add the files
 if(!cb.AddFile("D:\\Temp\\Reboot.Log") ||
 !cb.AddFile("D:\\Temp\\sysreport.bmp") ||
 !cb.AddFile("D:\\Temp\\levyaway.txt") ||
 !cb.AddFile("D:\\Temp\\Blackbug.avi") ||
 !cb.AddFile("K:\\Microsoft\\Outlook 98 Beta 2\\mpi95_2s.cab"))
 // Some error
  TRACE("Could not add file(s) ([d][%d][%d])\n",
   cb.GetErrorCode(),
   cb.GetErrorCodeEx(),
   errno);
}

After you've got a cabinet file it is absolutely normal that at some point you will want to extract the files from it. This operation is also easy, thanks to the CCabinetExtractor class. Just make an instance of it, tell it where you want the files to be extracted using SetDefaultExtractPath and extract the files from some cab file with ExtractFiles.

The very simple sample code is here:

CCabinetExtractor ce;
ce.SetDefaultExtractPath("D:\\Temp\\Extract");

if(!ce.ExtractFiles("D:\\Temp\\Test\\Setup1.cab"))
 // Some error
 TRACE("Could not add file(s) ([d][%d][%d])\n",
  cb.GetErrorCode(),
  cb.GetErrorCodeEx(),
  errno);

With this simple samples you can create and extract files from cabinet file in a snap. However, the cabinet library sends notifications for almost every operation it does, so that clients of the library can alter its behaviour. It does this by calling some C funtions that clients (you, actually the cabinet classes) register with it. These notifications are wired to static members of the cabinet classes.

Because the notifications the compression library sends are quite complicated, I added code to handle them all resonably and then designed a few simple virtual notifications that you will probably want to use and override. I'll shortly descrube these simple notifications below.

For class CCabinetBuilder, the simple notifications are:

// Miscellaneous helpers
public:
 virtual void ManufactureDiskName(PCCAB cabinfo);
 virtual void ManufactureCabinetName(PCCAB cabinfo);

ManufactureDiskName and ManufactureCabinetName are called when the cabinet engine is about to create a new cabinet volume because it already reached the volume limit with the current one. You simply supply the name of the next cabinet file and its internal (disk) name in the passed structs.

For class CCabinetExtractor, the simple notifications are:

// Miscellaneous helpers
protected:
 virtual BOOL NotifyCabinetInfo(USHORT nID, LPCTSTR lpcszCabPath, LPCTSTR lpcszCabName, LPCTSTR lpcszDiskName, USHORT nCabIndex);
 virtual BOOL NotifyPartialFile(LPCTSTR lpcszFileName, LPCTSTR lpcszFirstCab, LPCTSTR lpcszFirstDisk);
 virtual BOOL NotifyFileCopy(LPCTSTR lpcszFileName, ULONG cbSize, string &strExtractTo, BOOL &bSkipFile);
 virtual BOOL NotifyFileCopied(LPCTSTR lpcszFileName, USHORT &nDate, USHORT &nTime, USHORT &nAttribs);
 virtual BOOL NotifyFileCopiedAndClosed(LPCTSTR lpcszFileName); 
 virtual BOOL NotifyEnumerate(USHORT nID, long &nCurrentPosition, USHORT &nFilesRemaining);
 virtual BOOL NotifyNextCabinet (LPCTSTR lpcszNextCab, LPCTSTR lpcszNextDisk, string &strNextCabPath, int nErrorCode);

NotifyCabinetInfo is called whenever the cab engine wants info about a cabinet file it is about to extract from. If the first file in the cabinet is continuation from some previous cabinet, then you're informed via NotifyPartialFile. NotifyFileCopy is called for each file in the cabinet so that you can decide whether or not to extract it. The default implementation of this member simply answers with "Yes, extract it" for every file. NotifyFileCopied and NotifyFileCopiedAndClosed are called when the cabinet engine finished extracting some file from the cabinet and when it closed it too, respectively. With NotifyEnumerate you can enumerate files in the cabinet and with NotifyNextCabinet you can tell the cabinet library the name of the cab file where the one currently being extracted (actually some file in it, that spans across cab file boundary) is continued.

By having some static functions in a class isn't really a very extensible solution, so I added virtual functions to each of the cabinet classes that return addresses of these statix. If you want to do something that I didn't designed provision for, you could derive your class from mine, write a whole new static funtion to handle a notification from the cabinet engine (you have all the necessary documentation in the Cabinet SDK), then return its address via the virtual function. Very poverful indeed!

If you, by any reason (possibly you derived your classes from mine and suddenly compression/decompression doesn't work anymore - nice, eh?), want to see how compression and/or decompression progresses, you might want to define one of these symbols:

__TRACE_CAB_MEMORY__
__TRACE_CAB_COMPRESSION__
__TRACE_CAB_EXTRACTION__

Download source - 142 KB

Home
Mobile Site | Full Site
Copyright 2018 © QuinStreet Inc. All Rights Reserved