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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read