Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Creating Small Setup Applications


August 1999/Creating Small Setup Applications


More and more software is being sold over the Internet these days, and since the size of the installable product is directly proportional to download time, creating a minimal-size executable is important. A variety of commercial products can create a compressed self-installing executable for you, but their code may add on order of 100KB to your total size — which may be a serious penalty if your own compressed product is roughly that size to start with. This article will demonstrate that it’s possible to create a self-extracting installation stub smaller than 5KB. Despite this small size, it can perform the most common tasks of a complete setup program: product files can be copied to destination directories, shortcuts can be added to the Start menu, and even uninstallation is fully supported. All you need is a C compiler, Microsoft’s compress.exe, and this article.

The Problem with CAB Archives

I wrote a software product (Janko’s Keyboard Generator) that creates customized keyboard layout files for Win95 and Win98. Until recently, I distributed it via the Internet in a .zip archive. I figured that anybody who dares to tweak system files knows how to work with archives. But that wasn’t true: one user sent me email asking for an unzip utility. When I built the Win98 version of my product, I decided to also use a new distribution format. I knew for some time that reviewers give better marks to software with automatic install and uninstall procedures. That’s why I decided to create a self-installing executable instead of distributing the product as a .zip file.

I looked for commercial software packages but I wasn’t satisfied — the overhead they introduced was simply too large. Zipped, my software was only around 130KB. Almost all the programs I found would at least double the size of my distribution. Another problem with these programs was that although they all claimed that using them was easy, I did not find them to be so. Some seemed to have a more complex environment than the state-of-the-art compiler I’m using. Finally, most of them were expensive. I found a link to one freeware product for creating a self-extracting setup, but then I discovered that it was distributed in a .zip archive. Therefore, I decided to build my own solution. My main goal was that the final self-executing installation program not be much larger than the .zip file format I had been using. Automatic installation, uninstallation, and updating of the Start menu were also a must.

After looking at commercial tools, I decided to research how Microsoft, who has used various software distribution techniques, distributes their software. Some of their updates were DOS self-extracting archives. The drawback with these archives was that users had to place them in the desired (presumably empty) directory before starting them; otherwise, they might accidentally clutter up an existing directory with installation files. Also, after unpacking such files, users had to use a .inf file to perform the actual installation.

Microsoft has also distributed software in the form of self-installable executables. When you look at the version information in these executables, you can see that it is always named WEXTRACT. Inside such packages are CAB archives. (To find out more about this format, search Microsoft’s website, www.microsoft.com.) Comparing the size of the CAB to the size of the whole package, I found that the executable stub that processes the CAB is around 90KB. This is not so bad, especially compared to some commercial solutions I’d already looked at. In online help there are Win32 API functions related to the CAB archives that work on both Win95 and NT. I thought these would be ideal for my needs.

I knew from experience that I could write a tiny stub program without using or linking with the C runtime library. Microsoft provides tools for creating a CAB archive. My only remaining problem seemed to be how to put the CAB archive in the stub program. First I considered using an idea from WEXTRACT: storing the CAB in the executable as a resource. However, I didn’t want to rebuild the self-installer program each time the CAB file changed, and the Win32 functions that let you programmatically modify the resources in an executable are not available under Win95. That’s why Microsoft used another solution to allow users to make “compressed office documents”: there the archive is simply concatenated to the end of the executable. The extra bytes of the .exe files are traditionally ignored by the system.

I wrote the self-installing stub program and tested it on Win95 and NT. It worked. Just one day later, I received a message from my potential user: “Your program requests setupapi.dll and there is no such file on my system.” That prompted me to more thoroughly examine the contents of original Win95 and OSR2 installations. Neither of them provided setupapi.dll, the DLL that provided the API my self-installation code needed to manipulate CAB files. Microsoft’s help for Win32 falsely claimed that these calls work under Win95. Why didn’t I notice this? After further investigation, I found that Internet Explorer since version 4 adds this file to the system. Unfortunately, I can’t ask the users that need my 130KB software to download at least 5MB of Internet Explorer.

Using LZ Files

Fortunately, there are uncompression Windows API functions available on all versions of Windows. Windows 3.1 was packaged in the so-called LZ files. Contrary to what happened with CAB, Microsoft left the LZ API available to all platforms. Compared to CAB, this API is much less capable — it works only with individual files rather than with an archive of multiple files. That meant I would have to write my own code to glue my installation files together, and then more code to separate them and hand them to the Windows LZ uncompress functions one at a time during installation.

I compared compression of ZIP, CAB, and LZ. ZIP and CAB compressed my software from 320KB down to 132KB and 140KB, respectively. LZ was the least efficient compression technique, producing 170KB. But I had no choice — this was still the only API which, to the best of my knowledge, worked on all 32-bit platforms. Since I could make the installing program extremely small, the whole self-installing package was still going to be the smallest possible that will run on any version of Win32.

The main goal in the software I present here was to keep the listings small for publication. That’s why the code is much less robust than it would be in some commercial application.

Packing the Installation Files

I use three steps to create a self-installing executable. First, I compress all the installation files using Microsoft’s compress.exe; this is a good task for a batch file or makefile. Second, I run a custom program (described in this section) to pack all the compressed installation files into a single file. Finally, I attach this composite file to the self-installation executable.

The final step is the easiest, and requires no custom code. I decided to simply concatenate the “archive” to the executable stub:

copy /b stub.exe+filepack jankodi.exe

stub.exe is my custom self-installation program, filepack is the archive of compressed installation files, and jankodi.exe is the name of the resulting self-installing executable.

Creating the archive required a little more work. I didn’t want to modify the source code to stub.exe each time I changed the set of installation files, so I decided the archive should have a simple format that includes the name of each compressed installation file. In creating the archive, I write a “magic code” marker for each file, followed by file timestamps, then the NULL-terminated name of the file, and finally all the bytes of the compressed file. Note that I don’t store the sizes of the files, and that this layout allows me to produce the archive with extremely simple code.

The code to produce these archives is in creapack.c (Listing 1). The program assumes that you have created a file called filelist, which contains the names of the compressed installation files, one per line. main() opens filelist, reads the list of files to be put in the archive, and calls CopyToPack() for each file. CopyToPack() just copies one file with the described header to the output. RemoveEndWhite() strips trailing spaces from lines. Note that filenames can contain spaces as long as these spaces are not at the end of the filenames.

File Extraction

The main source code for the actual self-installation program is in lzinst.c (Listing 2). smallio.h (Listing 3) and smallio.c (Listing 4) contain some simple I/O routines I wrote to avoid accessing normal C runtime functions. The key to keeping the final executable size small is to avoid loading any part of the normal C runtime library. That’s why I wrote my own version of standard functions like strcpy().

In lzinst.c (Listing 2), ExtractFromModuleToFolder() extracts each compressed file in the self-installing executable and copies it to a destination folder. As described earlier, the archive of compressed files is just appended to the executable with the copy command, so ExtractFromModuleToFolder() reads the entire module into a memory buffer and scans from the beginning for a “magic string” that creapack.exe stores at the beginning of each archive member. The possibility that the magic code (I use the string “JSFHDR”) appears in the compressed file is very low, since the compressed bytes are quite random, so for the purposes of this article the solution is good enough.

However, I do have to ensure that this magic string does not appear in the source code for the self-installing executable — if lzinst.c contained the string constant “JSFHDR”, then ExtractFromModuleToFolder() would erroneously locate that and treat it as the beginning of a compressed file. To avoid that, the source code uses a string of “jsfhd”, and then dynamically converts each character to uppercase before comparing, as well as doing an extra check for the final ‘R’.

Each time ExtractFromModuleToFolder() locates a magic string that indicates the start of a compressed installation file, it calls PossiblyExtract(), which extracts the found file to the destination folder. This function is named “possibly” because it actually doesn’t extract a file every time it’s called. For example, the first time it gets called. Then only the first magic string marker has been found; it waits until the second call to extract the first file, when both the first and second magic string markers are available. Since creapack.exe doesn’t store any file size in the archive, PossiblyExtract() has to use the locations of two magic strings (or one string and the end of file) to determine the length of the file to extract.

Extraction is simple: PossiblyExtract() first writes the bytes of the file to a temporary folder using WriteWholeFile() from smallio.c (Listing 4). Then PossiblyExtract() calls PerformLZ(), which uncompresses the temporary file. In PerformLZ(), I had to use both OF_READ and OF_SHARE_EXCLUSIVE flags when calling Win32’s LZOpenFile() to open the compressed input file. No one else will be accessing this file, so I originally used only the OF_READ flag. The program worked flawlessly on NT, but when I tested it on Win95 it failed to open the file for reading. I still can’t explain this behavior. Another note of interest is that the LZ functions really need the OFSCTRUCT structures to exist. Some API functions will allocate this kind of structure for you if you just pass a NULL pointer, but not these functions.

.inf Files

So far, I’ve described how the self-installation program extracts and decompresses the installation file, but not how it accomplishes the many details of installation: copying files to the right place, creating a Start menu shortcut, providing an uninstall procedure, and so on. Fortunately, this is something that Windows can handle — once you learn how to create a .inf file.

The online help didn’t give me enough information, so I started to analyze the .inf files that happened to exist on my Win95 and NT systems. It amazed me that all these possibilities existed since the introduction of Win95 but nobody really explained how to use them. I found that Win95 and NT .inf processing is not identical, but that it is possible to write the file in a way which will work correctly on both platforms.

The .inf file is a list of instructions that tells Windows how to perform an installation. jankodem.inf (Listing 5) demonstrates most of the .inf files’ possibilities. It is designed to install an application called “Janko’s Demo App,” and it assumes that all the files to be installed exist in the same directory as the .inf file (where the self-installer placed them). As I’ll explain in more detail, this .inf file tells the system to do four things: copy installation files to a destination folder, add shortcuts to the startup menu, create uninstall information (so the user can go to Add/Remove Programs in the control panel to later uninstall this application), and update the registry.

The .inf file is divided into sections with a syntax similar to that used by .ini files. Sections are lines surrounded by brackets (“[]”), and each section contains a series of entries, often with the syntax “variable=value”. Sometimes the values are quoted strings. If you need a quoted string to contain a literal quote, use two double quotation characters.

Some sections are always required, and others are introduced by referring to them from the required sections. The first required section is [version]. This section must specify a “signature” of $CHICAGO$ if you want to be able to use the same .inf file for Win95, Win98, and NT. The second required section is [DefaultInstall]. To let a single .inf file perform differently under NT, you can also include a [DefaultInstall.NT] section. In these sections, you specify all the steps the installation is to perform. The [DefaultUninstall] section specifies actions required to uninstall the application. Section [SourceDisksNames] specifies the names of the disks containing the files to be installed; you have to include this section even if all the files will be installed from a hard disk. Similarly, the [SourceDisksFiles] section specifies which installation files exist on which source disks. Section [DestinationDirs] specifies destination directories for each group of files to be copied.

Section [Strings] is a kind of “macro definition” section. The macros defined there can be used throughout the rest of .inf file instead of more complex strings. Substitution will occur only for macro names bounded with percent signs. If the text inside the percent signs is not a defined macro, the text will remain unchanged. One of the reasons for introducing [Strings] macros was easier string localization in .inf files. Only strings in the [Strings] section must be considered for possible localization.

With these rules in mind, you can understand the contents of jankodem.inf (Listing 5) in more detail. In the [DefaultInstall] section, the CopyFiles line specifies that the groups of files to be copied during installation appear in sections named “Janko.Files1” and “Janko.Copy.Inf”. The destination directories for each of the groups is specified in the [DestinationDirs] section:

[DestinationDirs]
Janko.Files1=24,%Dest_Fold%
Janko.Copy.Inf=17

This odd syntax means that the files in the group named “Janko.Files1” should be copied to the predefined location 24, under the subdirectory “%Dest_Fold%” (which is defined in the [Strings] section to be “\Progra~1\JankoDem”). Table 1 lists the predefined locations that always exist on the user’s computer. The last line in this section will cause the “Janko.Copy.Inf” group (which consists of just the .inf file itself) to the Windows INF directory (predefined location 17). That’s where the control panel applet will look for it if the user asks to reinstall or uninstall this application.

The “UpdateInis” line in the [DefaultInstall] section refers to a custom section called “Janko.Inis” that contains lines of text that will be added to a file called setup.ini. After Windows processes the .inf file, it will process setup.ini, if it is present. Entries in setup.ini specify the groups to be added and the shortcuts to the files you want to appear in the Start menu. As you probably guessed, this technique has its roots in older Windows systems where the Program Manager contained the shortcuts to the installed application. The logic for deletion of the shortcuts is nearly the same as for deletion of the registry entries. When only the names of the group and shortcut are given without their contents, they will be deleted.

The section of registry entries to be added (“Janko.Add.Reg”) is specified in the “AddReg” line. Looking at the [DefaultInstall.NT] section, you will see that the only difference between Win9X and NT installations is what’s being put into the registry. This is not something that the demo application caused — uninstallation registry entries must be different for Win9X and NT. You can see this by comparing the “Janko.Add.Reg” and “Janko.Add.RegNT” sections. A Win9X uninstall invokes a 16-bit DLL called setupx.dll, while an NT uninstall invokes the 32-bit syssetup.dll. The entry points in these DLL are different, though the parameters are not. Programmers should be careful when specifying these lines. Just one superfluous space between the parameters can prevent proper functioning. What’s the meaning of the two lines put in the registry anyway? The first is the string that will be displayed in the Add/Remove Programs control panel applet, and the second one is what the system will perform when the user activates the given entry. On both systems, the action that will be performed is just processing the .inf file, but starting from the section [DefaultUninstall]. As you can see, the processing of this section will delete all the files copied during installation and then delete the registry entry that enables the uninstallation. To delete the registry entry, specify the name of the entry without the values.

If you want to use .inf files and allow the user to select the target installation directory, you must change the self-installing executable to modify the .inf file after it’s extracted and before it’s used. Be careful to always specify the destination directories and files with short filenames. Otherwise, you may find that the setup which works on Win98 does not work on Win95. More precisely, to be compatible with all systems you should install all your files using short filenames. If your application has to use files with long filenames later, you will have add an additional step to rename the files.

Processing .inf Files

Given that I’d created a .inf file that describes my installation, I now needed the self-extraction program to get Windows to process that .inf file at install time. A user can get Windows to process a .inf file by right-clicking on the file in Explorer, then selecting the Install menu item. I found that it is really easy to perform this programmatically. All the necessary code is in ExecuteInfAndWait() within lzinst.c (Listing 2).

ShellExecuteEx() can be used to perform registered actions on registered file of a given extension (.inf, .doc, .bat, etc.). What file types are registered on your system? What registered actions do they support? Most programmers would tell you that you can determine that by inspecting HKEY_CLASSES_ROOT in your registry. But even ordinary users can see and modify this information! In Windows Explorer, Folder Options, there is a tab File Types. The .inf files are among the other registered file types under the name “Setup Information”, which of course can be changed. There you will see the names of the actions (they are never localized), which you can pass to ShellExecuteEx(). In my case, I wanted ShellExecuteEx() to execute the “Install” action on a .inf file. You could eliminate the middleman and just directly execute the same program that ShellExecuteEx() will. (For example, under NT 4, this action executes rundll32.exe to do the actual work of passing the .inf file to the Windows setup API functions.) I use ShellExecuteEx() because it works the same on both Win95 and NT, and I believe this method is more likely to work the same with future versions of Windows.

ShellExecuteEx() will spawn a separate process to perform the installation, so ExecuteInfAndWait() uses WaitForSingleObject() on the process handle that ShellExecuteEx() returns. When the separate installation process terminates, the process handle will be signaled and WaitForSingleObject() returns.

Miscellaneous

The user interface in lzinst.c (Listing 2) is simple. The program displays its name and queries the user to proceed with the installation. This is very important because the user may not want to install the application, having clicked on the icon just to see what it was. After user confirmation, all the remaining steps of the installation will be fully automatic. The program creates a private temporary subdirectory in the system temporary folder to store all its temporary files. That way, it can just delete an entire folder instead of keeping a list of temporary files created.

Looking at DeleteFilesIn() in lzinst.c, you can see that Win32 doesn’t provide a simple way to delete a subdirectory. RemoveDirectory() works only if the directory is empty, and DeleteFile() does not accept wildcards. Thus, I had to manually traverse the files in the directory and delete them before calling RemoveDirectory(). At first, I thought that deleting the file during the iteration might be an unsafe operation. The Microsoft documentation didn’t say anything on this subject. But, in Microsoft’s documentation I found the sample code which deletes the file right in the middle of the FindFirstFile()/FindNextFile() loop.

In smallio.c (Listing 4), I wrote WinMainCRTStartup(), which is the actual entry point that calls WinMain(). This code substitutes for the entry point normally supplied by the C runtime library.

Recent user complaints have made me believe that an application such as this one should also perform integrity checking of the entire file before the installation is even attempted. If someone has an unsuccessful download that creates only the first portion of the file, the setup program presented here would not detect the problem. The proper solution would be addition of the four CRC bytes at the end of the whole file. Then, the setup stub would have to calculate the CRC and compare it to the one stored in the file originally. Such a routine would not significantly increase the size of the stub if it is properly written.

Janko Stamenovic is a software engineer in TeleTrader, a company that produces financial market realtime software. You can contact Janko at [email protected].

Get Source Code


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.