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

.NET

Installing Windows 95 Programs


SEP95: Installing Windows 95 Programs

Installing Windows 95 Programs

Your own installation program for Windows 95, Windows 3.1, and Windows NT

Al Williams

Al is the author of several books, including OLE 2.0 and DDE Distilled and Commando Windows Programming (both from Addison-Wesley). Look for his latest book, Steal This Code! in bookstores soon. You can contact Al on CompuServe at 72010,3574.


Gone are the days when applications could get by with simple batch scripts for installing programs. Today, you must have a full-blown installation program to keep Windows users happy. While there are a number of commercially available installation packages--Stirling's InstallShield, Jetstream's InstallWizard, Knowledge Dynamics' Winstall, Sax's Setup Wizard, and Microsoft's SetWizard (included with Visual C++ and Visual Basic) come to mind--sometimes you'll need to write your own installation programs. When it comes to this, however, there's good and bad news.

The good news is that Windows provides reasonable support for installation, including version checking and decompression. The bad news is these functions are quirky and poorly documented. In this article, I'll present a Windows 95 toolkit you can use to write high-quality installation programs in C, C++, and other languages. You won't need to learn a new scripting language--this installer uses C or C++. In the process, I'll examine tabbed dialogs, Microsoft's new property sheets.

Windows Support

The VER.DLL library (a standard part of Windows) has three ways to directly support installation programs:

  • The VERSIONINFO resource statement, which records information about EXE-format files (including DLLs).
  • VerFindFile(), which locates appropriate places to install files.
  • VerInstallFile(), which decompresses and copies files to their destinations.
The VERSIONINFO resource is a special entry in an RC file that specifies information about an executable module (EXEs, DLLs, and so on). Among the items you can store are the version number, language the module uses, file type, original filename, and copyright notice.

Listing One shows a typical VERSIONINFO entry. Notice that the information is in a simple, text-based format. There is a fixed portion of data and a varying portion enclosed in a BLOCK keyword. Each block has its own keywords and syntax; see Table 1.

VerFindFile() recommends a location for a file you want to install. You supply several parameters, and the function returns the recommended path and a status word; see Table 2. The status word is 0 on success; otherwise, one or more bits may be set:

  • VFF_CURNEDEST--a previous version of the file resides in a nonrecommended directory.
  • VFF_FILEINUSE--a previous version of the file is in use; you won't be able to remove it.
  • VFF_BUFFTOOSMALL--an input buffer was too small.
VerInstallFile() takes the information that VerFindFile() returns and uses it to copy the file to its new location. You can compress the source file with Microsoft's Compress utility (a DOS program) or leave it uncompressed. Table 3 shows more details about VerInstallFile().

The Ver... functions examine the version resource in your files, if it exists. Any file that has resources can contain a version resource (DLL, EXE, FON, and so on). Listing One shows a typical version resource containing copyright information, version numbers, language identifiers, and other information. To add a version resource to your files, simply place a version-resource block in your RC file and compile as usual. The version resource contains version information, other fixed values, and a variable part that begins with the BLOCK keyword. These blocks contain string information that varies from language to language.

Install-Program Characteristics

An install program must perform these basic steps:

  1. Determine the directory where you will install and the location of the installation disk, and collect any options. Typically, you'll present a dialog (with default values) where the user can enter this information. However, some programs might install to a fixed location (the Windows directory, for example).
  2. Verify that there is enough disk space available.
  3. Create any necessary directories and subdirectories (this may vary depending on the options the user selects in step 1).
  4. Make the first file to install the current file.
  5. Call VerFindFile() for the current file. If many applications share the file (for example, a common DLL), use the VFFF_ISSHAREDFILE flag. Set the source filename to the complete path of the source file and the application-directory parameter to your program's install directory. This call will return the directory that currently holds the file (if applicable) and the directory that VerFindFile() recommends for installation.
  6. Call VerInstallFile() for the current file. Set the source to the complete path of the file on the install disk and the destination path to the one recommended in step 5. You also pass the current file's location from step 5 to VerInstallFile() so it can remove the old file. This function will decompress the file (if required) and copy it to the proper location.
  7. Check the return value from VerInstallFile(). If the VIF_TEMPFILE bit is set, you must delete the temporary file that VerInstallFile() uses. You may report other errors to the user or force the installation by repeating step 6 with VIFF_FORCEINSTALL.
  8. If there are more files, make the next one current and go to step 5.
  9. Make any entries required in INI files or the system registry (optional).
  10. Create icons for your program in the user's shell (optional).
While these steps appear straightforward, you can have problems. First, much of the documentation for the Ver... functions is just wrong. Secondly, there are problems when installing from multiple diskettes. Finally, compressed hard drives can be difficult to manage.

The main problem with the VerFindFile() and VerInstallFile() documentation is the description of the lpszFileName field. The documentation states that you should not include the path for the file in this parameter--only the filename and extension--but this is wrong. The culprit is LzOpenFile(), the underlying function that opens the file (which may be compressed).

COMPRESS.EXE (the Windows SDK compression utility) changes a file's extension by appending an underscore to it (or replacing the last extension character with an underscore). For example, READ.ME becomes READ.ME_ and README.TXT becomes README.TX_. When the Ver... functions use LzOpenFile() to open the file README.TXT, it looks for the filename in the current directory. Since the file is now README.TX_, it doesn't find it. It then looks for the file in the Windows directory, Windows system directory, all the directories on the path, and any mapped network drives. If it doesn't find the file, it then looks for README.TX_, which it will find.

Unfortunately, it is a good bet that the file README.TXT does exist in one of the myriad places LzOpenFile() searches. Then, the Ver... functions will cheerfully copy the alien file to your install directory and remove it from its current location. This is an endless source of confusion for users when they open your README.TXT file and it talks about a Borland product (or whatever LzOpenFile() found). The solution to this problem is simple: Ignore the documentation. Always specify a complete path to the install file.

Another documentation bug is the behavior of VerFindFile() when VFFF_ISSHAREDFILE is set. This flag allegedly causes the function to select the Windows (or Windows system) directory as the file's destination. This appears to not work under any current version of the VER.DLL library. It is safer to manually select the Windows directory when necessary. You can use the GetWindowsDirectory() and GetSystemDirectory() calls to find the names. (Win16 programs must use the GetWindowsDir() and GetSystemDir() calls instead.)

Using Multiple Disks

If your entire product fits on one disk, the install program can load from the disk and run. If you need more than one disk, however, you may have problems running the install program from the floppy. When Windows runs any program, it may not keep all of it in memory at one time. It can go back to the disk any time to load code segments, resources, or whatever. If your install program is on disk 1 and the user has disk 2 in the drive, havoc will ensue.

You can manipulate your resources and your DEF file to cause Windows to load the entire program and lock it in memory. However, this doesn't work with certain networking software (including software from Novell). The network detects that you are running a program from the floppy and keeps the file locked. When you change disks, DOS will report an invalid disk change error and not allow you to continue. This happens even if Windows no longer needs the file.

The only realistic alternative is to copy the install program to the hard disk as the first step of installation. Then the installer can run from the hard disk. If the install program can copy itself, the performance penalty is minimal since Windows will use the same in-memory copy of the program. However, the network will know that the floppy need not remain in the drive.

Problems with Compressed Hard Disks

Calculating the available disk space is always difficult. You can't simply compute the free bytes on the disk and compare that number to the amount of space your software requires because DOS stores files in clusters. If a hard disk's cluster size is 2K, for example, each file stores in multiples of 2K. A 1-byte file takes 2K; so does a 1K file. A 3900-byte file requires 4K. Therefore, you should compute space requirements for each file based on the target hard disk's cluster size and compare the number of clusters.

However, this usually isn't worth the trouble. If the user employs a disk-compression program (Stacker, for example), the numbers are meaningless. Compression programs report an estimated free space that may be way off, depending on the nature of the files you store. There is no way to know how much actual space you have until you use it.

As a compromise, I usually compute an estimated free space and compare it to the space available on the disk. If the free space is less than the estimate, I'll warn users, but allow them to continue. Although not an optimal solution, this technique works in practice.

If the user reinstalls your software, calculating free space is even more difficult. You should take into account files that you will overwrite when computing free space. With shared files, this can be difficult. Again, simply warning the user if disk space appears low is safe and easy.

Encapsulating Installation

It isn't difficult to write an installation library that does most of the hard work. You don't want a DLL for an installation program since you could have problems loading a DLL at install time. A static link library works well and allows you to produce a single install program with no external parts.

The installation library can easily incorporate all the usual Windows trappings, including a WinMain() function and the main window class. You have to supply a list of files to install, directories to create, and the information required in step 1 of the installation program. The installation library will provide support for adding program-manager groups, but you'll have to make the calls yourself. You also can make calls to set up any INI files (or the registry).

Using the Install Library

Figure 1 is an install program written with my install toolkit. The toolkit provides a window (filled with your logo) that serves as a workspace. You can provide multiple logos, and the installer will select the one

that best fits the current display. Select a logo slightly smaller than the screen size so you can leave room for the window title. For example, a logo for a 640x480 screen might be 600x400.

Your program supplies a global string (TITLE) that the installer uses as the main-window title. You can also provide an icon by using the APPICON ID in your RC file. If you use multiple disks and want the installer to copy itself to the user's hard disk, you should declare the hdcopy variable to True. If you omit this declaration, the installer will run from the floppy.

The only other required item is the install() function; see Figure 2. From here you can bring up a dialog, read configuration information, and so on. When you are ready, call cw_Install() to initiate the installation; see Figure 3. The cw_Install() function returns either SUCCESS, CANCEL, or RETRY. If the return value is SUCCESS, you are free to continue with the remainder of the installation. If it is RETRY, you should get new installation values (for example, show your dialog again) and call cw_Install() with the new parameters. When cw_Install() returns CANCEL, you should display any error or help messages you want and return.

You can also provide a cw_InstallInit() function. The toolkit executes this function before creating a window. To cancel the installation, return False from this routine. Usually you don't need this function, but it is available for special initialization.

The parameters to cw_Install() are straightforward. First, you pass the parent-window handle (usually the same one the toolkit sends you). Next, you supply the application directory (which need not exist) and the option bit mask. Each file and directory can have an option bit mask. If the mask is 0, the toolkit always installs the file. If the mask is not 0, the toolkit installs the file only when the option bit mask you pass to cw_Install() has the same bit set. For example, if you use a bit mask of 3, the installer will process files marked with 0, 1, 2, or 3.

Following the option bits, you supply cw_Install() with a pointer to an array of subdirectories (using the install_dirs structure; see Table 4) and the length of the array. If you don't need subdirectories, you can use NULL and 0 for these parameters. Next is a pointer to an array of _inst_files structures (and the length of the array). This structure contains six fields: an option bit mask, source filename, destination filename, destination directory, and flags for the VerFindFile() and VerInstallFile() flags.

The last two parameters are the estimated size (in bytes) and a Boolean that controls what happens when cw_Install() successfully completes. If you set this variable to True, cw_Install() will display a message box when the installation is successful. However, if you have more work to do (for example, setting up INI files) you may want to display your own message box at the end. To eliminate the message, set this parameter to False.

Some fields in the _inst_files structure can take special values. If the destination filename is NULL, the filename remains unchanged. The destination directory is usually "." to signify the current directory. You can also specify a subdirectory name, WINDIR for the Windows directory, or SYSDIR for the Windows system directory.

If the first flag field is -1, the installer copies the file without using the Ver... functions. This is useful for storing compressed files on the user's hard disk. Also, if the source filename is NULL, the installer uses the flag field as a disk number. It searches the install disk for a file named DISKn.ID (where n is the number in the flag field). If it can't find the file, it prompts the user to insert the disk. You can use this feature to prompt the user for multiple disks. If the disk is already present, no prompt occurs. Therefore, you will often start the list with a check for DISK1.ID.

The install toolkit offers several helper functions for use in your main routine. The cw_VersionCheck() call checks which versions of DOS and Windows are present. You can also send Program Manager DDE commands using cw_ProgManCmd(). You use these commands to set up icons in Program Manager for your application. (For more details about Program Manager's DDE interface, see the accompanying text box entitled, "Adding Icons.") To modify INI files or the registry, call the standard Windows API functions.

An Example Install Program

Listing Two is an example install program that copies a program called "CoolWorx32" and two 32-bit example editors. (CoolWorx is a C/C++ toolkit I wrote that uses the object-oriented nature of Windows programming to simplify application development; see my article "Simplifying Windows Development," Dr. Dobb's Sourcebook, March/April 1995.) The program uses two option bits: Option 1 installs the single document interface (SDI) editor, while option 2 installs the multiple document interface (MDI) editor. The installer always installs the CoolWorx support files that the editors use.

The _inst_files structure contains a list of all required files. The installer always copies files marked with option 0. Otherwise, the installer only copies files that have at least one option bit set that is also set in the selected option word.

To select options and set the install directory, this installer uses a tabbed dialog (Microsoft calls these "property sheets"), but an ordinary dialog box would serve just as well. A single call to PropertySheet() works like the ordinary DialogBox() call except that it manages multiple dialog templates. Since the tabbed dialog has two pages, the installer has two dialog templates, PG1 and PG2. Once the user enters the options and the install directory information, the installer calls cw_install(), which does all the work required to assign file locations, decompress files, and copy them to the hard disk.

If the cw_install() function returns SUCCESS, and the user selected one or both of the editor examples, the installer creates a program-manager group and adds icons for the editors. The cw_ProgManCmd() function makes this easy.

Finally, the installer opens the README.TXT file using WORDPAD.EXE, the Windows 95 replacement for NOTEPAD.EXE. Once it launches WORDPAD (using WinExec()), the installer exits. You could easily use calls like SetPrivateProfileString() or RegSetValue() to install INI files or registry entries.

Using Tabbed Dialogs

Tabbed dialogs are simple to create under Windows 95. In Figure 1 (a typical tabbed dialog), each tab represents a different dialog page. Clicking on the tab makes the specified page active.

Each page in a tabbed dialog is an individual dialog. It has its own template and can have a separate callback. Of course, you can route the callbacks for each page to the same routine, if you prefer.

The three property-sheet calls are in PRSHT.H (although Microsoft may move these to COMMCTRL.H later), but you will usually only use one--PropertySheet(). This call is analogous to the standard DialogBox() call. You supply a pointer to a PROPSHEETHEADER structure (see Table 5), which has a pointer to an array of PROPSHEETPAGE structures (Table 6). These two structures specify how the tabbed dialog behaves.

The PROPSHEETHEADER structure defines properties for the entire tabbed dialog. You need to set the dwFlags field to indicate which fields you will use; see Table 7. For example, if you want each tab to use an icon, you set the PSH_USEICON or PSH_USEICONID flag and fill in the hIcon or pszIcon field to select an icon.

If you don't set the PSH_PROPSHEETPAGE bit in the dwFlags field, you must separately create each page using CreatePropertySheetPage(). This call returns a handle that you can store in an array to use in the phpage field of the PROPSHEETHEADER structure. Usually, you simply set the PSH_PROPSHEET flag and use an array of PROPSHEETPAGE structures (in the ppsp field) instead of handles.

Each PROPSHEETPAGE structure also has a dwFlags field; see Table 8 for a list of values. You can provide a resource template name in the pszTemplate field or a dynamic-dialog template in the pResource field. If you use a dynamic-dialog template, you must set the PSP_DLGINDIRECT flag in the dwFlags field.

If you set the PSP_USETITLE flag, you can also set a title for the tab in the pszTitle field. If you don't set the flag, the dialog's caption becomes the tab title. The dialog callback is exactly like an ordinary dialog function and is set in the pfnDlgProc field. The other fields allow you to set a function to run before Windows destroys the page.

Messages

The property sheet accepts several messages (by way of macros) and can send you several WM_NOTIFY messages; see Table 9 and Table 10. You usually won't use most of these. The PSM_SETCURSEL message allows you to make a page active, and PSM_PRESSBUTTON lets you programmatically push any of the tabbed dialog buttons (not the buttons in your dialog template).

If you haven't used any of Microsoft's common controls (see "Windows 95 Common Controls," by Vinod Anantharaman, DDJ, May 1995) you may not be familiar with WM_NOTIFY. The new controls send WM_NOTIFY to alert you of noninput events. In the past, notifications came with WM_COMMAND messages (for example, the EN_CHANGED notification). WM_NOTIFY messages pass a pointer to a structure in lParam. The first part of this structure corresponds to a NMHDR structure (see Table 11). By examining the code field in this structure, you can determine the type of notification. For property sheets, these codes begin with PSN_ (see PRSHT.H). Then you cast the structure pointer to a more-specific structure. For property sheets, you don't need a special structure; just cast lParam to an LPNMHDR and examine the code field.

You can also control the state of the buttons with the PSM_CHANGED, PSM_UNCHANGED, and PSM_CANCELTOCLOSE messages. You should use these to inform the user about the state of the dialog.

You can catch notifications that tell you when Windows activates or deactivates a page (PSN_SETACTIVE and PSN_KILLACTIVE). Other notifications inform you when the user presses certain buttons; see Table 9.

There are a few tricks to using tabbed dialogs:

  • Use the WS_CHILD style for each dialog template.
  • Don't place OK and Cancel buttons in the template--the property sheet will add these.
  • Make certain each dialog template is the same size.
  • If you have a common item on multiple pages, be sure it lines up exactly in each dialog template.

Inside the Install Library

Most of the install library is straightforward. It is detailed in Listing Three; listings are available electronically, see "Availability," page 3. The WinMain() function creates a window that contains the logo bitmap and calls your install() routine. Later, you call cw_Install() to do all the messy work. When your install() routine returns, the toolkit cleans up and exits.

If you set the hdcopy flag, the installer checks its command-line arguments. If there are none, it copies itself to the temporary directory and runs the new copy with two arguments: the source directory and the name of the temporary executable. When it detects these arguments, the installer continues with normal processing. When processing completes, the installer removes itself from the temporary directory. This prevents problems with network locking.

The only special part of the install toolkit is the cw_Install() function. The other portions are straightforward Windows programming. The cw_Install() call walks through the directory array creating directories, then walks through the file list calling VerFindFile() and VerInstallFile() repeatedly. Of course, the calls only occur if the correct option bits are set.

Adding Icons

Program Manager (and Program Manager replacements) provides a DDE interface that allows install programs to manage groups and icons. The Windows 95 default shell supports this interface by adding pseudogroups and icons to the Start-button menu.

Commands are passed to Program Manager via DDE and are enclosed in square brackets. The most common commands are as follows:

  • CreateGroup(name,[path]) creates a new group with the specified name and optional group filename.
  • ShowGroup(name,cmd) is the display group. cmd can range from 1 to 8: 1 activates and displays the group window, 2 activates the group as an icon, 3 activates and maximizes the group window, 4 restores the window, 5 activates the window in place, 6 minimizes the window, 7 displays the group as an icon without activation, and 8 restores the group without activation.
  • DeleteGroup(name) deletes a group and its contents.
  • Reload([group]) reloads a group from its group file. If you specify no group, PROGMAN reloads all groups.
  • AddItem(cmd,[name],[icon_file],[icon_index],[x] [y],[start_dir],[hotkey],[minimize]) adds a new item to the current group. The parameters are: cmd, the command line; name, the item name; icon_file, the file that contains the item's icon; icon_index, the icon to use from the icon_file; x, the new item's x-coordinate (if this parameter is present, the y parameter is not optional); y, the new item's y-coordinate; start_dir, the working directory; hotkey, the item's shortcut key; and minimize, the item's run state.
  • DeleteItem(item) removes the item from the current group.
  • ReplaceItem(item) removes the item from the current group and marks its position for use by the next AddItem command.
  • ExitProgMan(save) exits the program manager. The save parameter specifies whether PROGMAN should save its current state. If PROGMAN is your default shell, this command won't work.
You can send these commands to Program Manager using the cw_ProgManCmd() function, which saves you from worrying about the details behind the DDE transmission. You'll see an example near the end of Listing Two.

--A.W.

Table 1: Fixed VERSIONINFO resource.

Field           Description

FILEVERSION     Version of this file.
PRODUCTVERSION  Version of entire product.
FILEFLAGSMASK   Contains a 1 for valid bits in the FILEFLAGS field.
FILEFLAGS       File attributes (for example, VS_FF_DEBUG).
FILEOS          Operating system (for example VOS_WINDOWS32).
FILETYPE        File type (for example, VFT_APP).
FILESUBTYPE     Type of driver, font, or VxD (if applicable).
Table 2: VerFindFile() parameters.
Parameter       Description

<I>dwFlags</I>        0 for normal file; VFFF_ISSHAREDFILE for shared files.
<I>szFileName</I>     Filename.
<I>szWinDir</I>       Windows directory.
<I>szAppDir</I>       Application's directory (destination).
<I>szCurDir</I>       <I>VerFindFile</I> places the file's current location in this variable.
<I>lpuCurDirLen</I>   Length of <I>szCurDir</I> array.
<I>szDestDir</I>      <I>VerFindFile</I> places recommended install directory in this variable.
<I>lpuDestDirLen</I>  Length of <I>szDestDir</I> array.
Table 3: VerInstallFile() parameters.
Parameter       Description

<I>dwFlags</I>        Control flags (0, VIFF_FORCEINSTALL, or VIFF_DONTDELETEOLD).
<I>szSrcFileName</I>  Source filename.
<I>szDestFileName</I> Destination name.
<I>szSrcDir</I>       Source directory.
<I>szDestDir</I>      Destination directory.
<I>szCurDir</I>       Directory where file currently resides.
<I>szTmpFile</I>      Temporary filename possibly returned by <I>VerInstallFile()</I>.
<I>lpuTmpFileLen</I>  Length of above array.
Table 4: The _inst_files structure.
Field        Description

<I>bitmask</I>     Option bits that apply to this file.
<I>srcfile</I>     Source filename.*
<I>dstfile</I>     Destination filename.*
<I>dstdir</I>      Destination directory.*
<I>flags</I>       Flags set to <I>VerFindFile()</I>.*
<I>cflags</I>      Flags set to <I>VerInstallFile()</I>.
<I>* May take special values.</I>
Table 5: PROPSHEETHEADER structure.
Field         Description

<I>dwSize</I>       Size of structure.
<I>dwFlags</I>      PSH flags (see <a href="#0109_0056">Table 6</A>).
<I>hwndParent</I>   Parent window.
<I>hInstance</I>    <I>hInstance</I> that contains resources.
<I>hIcon</I>        Icon handle (if PSH_USEHICON is set).
<I>pszIcon</I>      Icon name (if PSH_USEICONID is set).
<I>pszCaption</I>   Title to use when PSH_PROPTITLE is set.
<I>nPages</I>       Number of tabs.
<I>nStartPage</I>   Beginning tab number (if PSH_USEPSTARTPAGE is not set).
<I>pStartPage</I>   Name of beginning tab (if PSH_USEPSTARTPAGE is set).
<I>ppsp</I>         Pointer to array of property-sheet structures (if PSH_PROPSHEETPAGE is set).
<I>phpage</I>       Pointer to array of property-sheet handles (if PSH_PROPSHEETPAGE is not set).
<I>pfnCallback</I>  Global property-sheet callback.
Table 6: PROPSHEETPAGE structure.
Field         Description 

<I>dwSize</I>       Size of structure.
<I>dwFlags</I>      PSP_ flags (see <a href="#0109_0057">Table 7</A>).
<I>hInstance</I>    Instance handle for resources.
<I>pszTemplate</I>  Name of dialog template (if PSP_DLGINDIRECT is not set).
<I>pResource</I>    Pointer to resource (if PSP_DLGINDIRECT is set).
<I>hIcon</I>        Icon handle (if PSP_USEICON is set).
<I>pszIcon</I>      Icon name (if PSP_USEICONID is set).
<I>pszTitle</I>     Name to override template's title.
<I>pfnDlgProc</I>   Dialog callback.
<I>lParam</I>       32 bits of user-defined data.
<I>pfnCallback</I>  Called before destruction if PSP_USERELEASEFUNC is set.
<I>pcRefParent</I>     Pointer to reference count (used with PSP_USERREFPARENT flag).
Table 7: Flag bits for PROPSHEETHEADER.
Bit                 Description

PSH_DEFAULT         0-no bits set.
PSH_PROPTITLE       Prepend "Properties for" ahead of title.
PSH_USEHICON        Use icon handle.
PSH_USEICONID       Use icon name or ID.
PSH_PROPSHEETPAGE   Use <I>ppsp</I> field instead of <I>phpage</I> field.
PSH_MULTILINETABS   Use multiline tabs.
PSH_WIZARD          Suppress tabs and treat dialog as a wizard.
PSH_USEPSTARTPAGE   Use <I>pStartPage</I> field.
PSH_NOAPPLYNOW      Suppress the Apply Now button.
PSH_USECALLBACK     Enable global callback.
PSH_HASHELP         Support help.
Table 8: Flag bits for PROPSHEETPAGE.
Bit               Description

PSP_DEFAULT       0-no bits set.
PSP_DLGINDIRECT   Use indirect dialog resources.
PSP_USEHICON      Use icon handle.
PSP_USEICONID     Use resource ID for icon.
PSP_USETITLE      Use override title.
PSP_USEREFPARENT  Use reference-count variable.
PSP_USECALLBACK   Use release callback.
PSH_HASHELP       Support help.
Table 9: Property-sheet notifications.
Code            Description

PSN_SETACTIVE   Page receiving focus.
PSN_KILLACTIVE  Current page is losing focus.
PSN_APPLY       OK or Apply button pressed.
PSN_RESET       Cancel button pressed (too late to stop).
PSN_HASHELP     Query page to see if it supports help.
PSN_QUERYCANCEL Cancel button pressed (possible to abort).
PSN_WIZBACK     Back button pressed (wizard only).
PSN_WIZNEXT     Next button pressed (wizard only).
PSN_WIZFINISH   Finish button pressed (wizard only).
Table 10: Commonly used property-sheet messages.
Message            Pseudocall               Description

PSM_SETCURSEL      PropSheet_SetCurSel      Sets active page by
                                             handle.
PSM_SETCURSELID    PropSheet_SetCurSelByID  Sets active page by
                                             ID.
PSM_CHANGED        PropSheet_Changed        Enable Apply Now
                                             button.
PSM_RESTARTWINDOWS PropSheet_RestartWindows Ask Windows to
                                             restart when property
                                             sheet closes.
PSM_REBOOTSYSTEM   PropSheet_RebootSystem   Ask Windows to reboot
                                             when property sheet
                                             closes.
PSM_CANCELTOCLOSE  PropSheet_CancelToClose  Change "Cancel" button
                                             to "Close."
PSM_QUERYSIBLINGS  PropSheet_QuerySiblings  Forward message to all
                                             initialized pages
                                             until one returns
                                             nonzero; return the
                                             value.
PSM_UNCHANGED      PropSheet_UnChanged      Disable Apply Now
                                             button.
PSM_APPLY          PropSheet_Apply          Do the same processing
                                             as if the Apply Now
                                             button were depressed.
PSM_SETTITLE       PropSheet_SetTitle       Sets dialog title.
PSM_SETWIZBUTTONS  PropSheet_SetWizButtons  Enable specific wizard
                   or                         button (wizards
                   PropSheet_SetWizButtonsNow only); PropSheet_____LINEEND____
                                             SetWizButtonsNow()
                                             uses SendMessage()
                                             instead of
                                             PostMessage().
PSM_PRESSBUTTON    PropSheet_PressButton    Programatically press
                                             a button.
PSM_SETFINISHTEXT  PropSheet_SetFinishText  Set text on "Finish"
                                             button (wizards only).
PSM_GETTABCONTROL  PropSheet_GetTabControl  Get handle to tab
                                             control.
Table 11: Notification header structure.
Field     Description
<I>hwndFrom</I> Window handle of originating control.
<I>idFrom</I>   Window ID of originating control.
<I>code</I>     Specific notification.

Figure 1: The installer in action.

Figure 2: The install() function.

int install(HWND mainwin, HANDLE hInst, LPSTR src);

where:  mainwin=window handle for main window     
        hInst=install program's instance handle
        src=string containing the install source directory

The install program ignores the return value from <I>install()</I>.
Figure 3: The cw_Install() function.
int cw_Install(HWND w, LPSTR appdir, DWORD bitmask,
  struct install_dirs *subdir, int nrdirs,
  struct _inst_files *inst_files, int nrfiles,
  unsigned long space, BOOL mbflag);

where:  w=install window
     	appdir=destination directory
     	bitmask=option bits (see text)
     	subdir=list of subdirectories to create
     	nrdirs=number of elements in subdir
     	inst_files=list of files
     	nrfiles=number of elements in inst_files
     	space=projected size of installed components
     	mbflag=FALSE to disable success message box
     	
return values:   SUCCESS=installation complete
                 CANCEL=installation canceled
     	         RETRY=installation failed

Listing One

VS_VERSION_INFO VERSIONINFO
FILEVERSION 1, 0, 0, 0
PRODUCTVERSION 1, 0, 0, 0
FILEOS VOS_DOS_WINDOWS32
FILETYPE VFT_DLL
{
 BLOCK "StringFileInfo"
 {
  BLOCK "040904E4"
  {
   VALUE "CompanyName", 
     "Al Williams Computing\000\000"
   VALUE "FileDescription", 
  "CoolWorx Application Framework\000"
   VALUE "FileVersion", "1.00\000\000"
   VALUE "InternalName", 
     "CoolWorx\000"
   VALUE "LegalCopyright", 
     "Copyright ) 1994 by Al Williams\ 
Computing\000\000"
   VALUE "OriginalFilename", 
     "COOLWORX.DLL\000"
  }
 }
}

Listing Two

/* CoolWorx Install program */
#include <windows.h>
#include <prsht.h>     /* could change */
#include "cwinstal.h"
#include "install.h"
#include <stdlib.h>
/* You must declare the title string */
char TITLE[]="CoolWorx Install";   // title
/* The code in this source file uses this string as
   a default */
#define DEFDIR "C:\\COOLWORX"
/* Subdirectories */
struct install_dirs subdirs[]=
  {
  {0,"BIN"},
  {1,"SDI"},
  {2,"MDI"}
  };
/* Number of subdirectories */
#define NRDIRS sizeof(subdirs)/sizeof(subdirs[0])
/* File list. If srcfile is NULL install will prompt
   for disk change using flags as disk number --
   verify that diskN.id file exists on it.
   If dstdir is WINDIR, then use Windows directory
   if dstdir is SYSDIR, then use System directory
   If dstdir is ".", then use default install directory
   If flags are -1 then force straight copy
     (no decompress, etc.) */
struct _inst_files inst_files[]=
  {
     { 0,NULL,NULL,NULL,1,0 },
     { 0,"README.TXT",NULL,".",0,0 },
     { 0,"COOLWORX.DLL",NULL,WINDIR,VFFF_ISSHAREDFILE,0},
     { 0,"CBUTTON.DLL",NULL,WINDIR,VFFF_ISSHAREDFILE,0},
     { 0,"CWMISC.DLL",NULL,WINDIR,VFFF_ISSHAREDFILE,0},
     { 2,"COOLEDIT.EXE",NULL,"MDI",0,0},
     { 1,"SDIEDIT.EXE",NULL,"SDI",0,0}
  };
/* Number of files */
#define NRFILES (sizeof(inst_files)/sizeof(inst_files[0]))
char appdir[_MAX_PATH];
char opts[2];
/* 1st tab dialog proc */
BOOL pg1proc(HWND dlg,UINT cmd,WPARAM wParam,LPARAM lParam)
  {
  switch (cmd)
    {
    case WM_INITDIALOG:
      SetDlgItemText(dlg,DIR,DEFDIR);
      break;
    case WM_NOTIFY:
       {
       LPNMHDR nh=(LPNMHDR)lParam;
       switch (nh->code)
        {
/* Cancel */
        case PSN_RESET:
          EndDialog(dlg,FALSE);
          return TRUE;
/* OK */
        case PSN_APPLY:
/* Return TRUE */
          SetWindowLong(dlg,DWL_MSGRESULT,TRUE);
          GetDlgItemText(dlg,DIR,appdir,sizeof(appdir));
          EndDialog(dlg,TRUE);
          return TRUE;
        }
      break;
      }
    }
  return FALSE;
  }
/* 2nd tab dialog proc */
BOOL pg2proc(HWND dlg,UINT cmd,WPARAM wParam,LPARAM lParam)
  {
  switch (cmd)
    {
    case WM_INITDIALOG:
/* Init state */
      SendDlgItemMessage(dlg,SDI,BM_SETCHECK,
        opts[0]=='X',0);
      SendDlgItemMessage(dlg,MDI,BM_SETCHECK,
        opts[1]=='X',0);
      break;
    case WM_NOTIFY:
       {
       LPNMHDR nh=(LPNMHDR)lParam;
       switch (nh->code)
         {
          case PSN_RESET:
            EndDialog(dlg,FALSE);
            return TRUE;
/* OK */
          case PSN_APPLY:
            SetWindowLong(dlg,DWL_MSGRESULT,TRUE);
            opts[0]=SendDlgItemMessage(dlg,SDI,
              BM_GETCHECK,0,0)?'X':' ';
            opts[1]=SendDlgItemMessage(dlg,MDI,
              BM_GETCHECK,0,0)?'X':' ';
            EndDialog(dlg,TRUE);
            return TRUE;
         }
      break;
      }
    }
  return FALSE;
  }
/* Your main install routine must be named install!
  w=main window handle
  hInst=instance handle of install program
  srcdir=source directory for install (e.g., a:\) */
install(HWND w,HANDLE hInst,LPSTR srcdir)
  {
  int i;
  DWORD bitmask;   /* option bitmask */
  char tmpfile[_MAX_PATH];
/* Tabbed dialog body */
  PROPSHEETPAGE pages[2]=
    {
      {sizeof(PROPSHEETPAGE),0,0,"PG1",
        NULL,NULL,(DLGPROC)pg1proc,0,NULL,NULL},
      {sizeof(PROPSHEETPAGE),0,0,"PG2",
        NULL,NULL,(DLGPROC)pg2proc,0,NULL,NULL}
    };
/* Tabbed dialog header */
  PROPSHEETHEADER psh={sizeof(PROPSHEETHEADER),
    PSH_PROPSHEETPAGE,NULL,NULL,NULL,
    "CoolWorx32 Install",
      2,0,pages };
/* Set defaults */
  lstrcpy(appdir,DEFDIR);
  opts[0]='X';
  opts[1]='X';
  psh.hInstance=pages[0].hInstance=
    pages[1].hInstance=hInst;
  psh.hwndParent=w;
/* Come here if install returns RETRY */
retry:
  if (!PropertySheet(&psh))
     {
/* Come here if install is cancelled */
cancelinst:
     MessageBox(w,"Installation Cancelled",
       NULL,MB_OK|MB_ICONSTOP);
     return 1;
     }
  UpdateWindow(w); /* make sure window updates */
  bitmask=0;
/* decode options to bitmask */
  for (i=0;i<sizeof(opts);i++)
    if (opts[i]=='X') bitmask|=1<<i;
/* Call installer -- pass main window, app directory and options */
  switch (cw_Install(w,appdir,bitmask,subdirs,
    NRDIRS,inst_files,NRFILES,0,FALSE))
    {
    case RETRY:
      goto retry;     // show options again
    case CANCEL:
      goto cancelinst;  // forget it
    }
/* success */
/* Set up INI file, groups, etc.*/
  if (opts[0]=='X'||opts[1]=='X'
    {
    cw_ProgManCmd("[CreateGroup(CoolWorx32 Alpha,)]");
    cw_ProgManCmd("[ShowGroup(CoolWorx32 Alpha,1)]");
    if (opts[0]=='X')
      {
      cw_ProgManCmd("ReplaceItem(SDI Editor)]";
      wsprintf(tmpfile,
       "[AddItem(%s\\SDI\\SDIEDIT,SDI Editor,,,,,)]",
        appdir);
      cw_ProgManCmd(tmpfile);
      }
    if (opts[1]=='X')
      {
      cw_ProgManCmd("ReplaceItem(CoolEdit)]";
      wsprintf(tmpfile,
    "[AddItem(%s\\MDI\\COOLEDIT,CoolEdit,,,,,)]",
        appdir);
      cw_ProgManCmd(tmpfile);
      }
    WinExec("WORDPAD README.TXT",SW_SHOW);
    MessageBox(w,"Installation Complete","Notice",
      MB_OK|MB_ICONEXCLAMATION);
    }
  }

Listing Three

int WINAPI cw_Install(HWND w,LPSTR appdir,DWORD bitmask,
   struct install_dirs *subdirs,int NRDIRS,
   struct _inst_files *inst_files,int NRFILES,unsigned long space, BOOL mbflag)
  {
  int i;
  unsigned cdlen;
  unsigned inslen;
  char tmpfile[_MAX_PATH];
  char curdir[_MAX_PATH];
  char instdir[_MAX_PATH];
  char srcf[_MAX_PATH];
  unsigned tmplen;
  if (i=cw_ChdirEx(appdir))
    {
     .
     .
     .
    }
if (space)
    {
    unsigned long freesp;
#ifdef _WIN32
    DWORD secper,bps,freec,tclust;
#else
    struct diskfree_t df;
#endif
/* Compute free space */
#ifdef _WIN32
    GetDiskFreeSpace(NULL,&secper,&bps,&freec,&tclust);
    freesp=(unsigned long)secper*bps*freec;
#else
    _dos_getdiskfree(0,&df);
    freesp=
     (unsigned long)df.avail_clusters*
       df.sectors_per_cluster*df.bytes_per_sector;
#endif
    if (freesp/1024<space)
      {
      int id=
        MessageBox(w,"You may not have enough free disk space.\n"
          .
          .
          .
       }
    }
/* Set up progress bar */
  if (!prog)
    prog=cw_ProgressDlg(w,"Installing...","",
      NRDIRS+NRFILES,TRUE);
  if (prog) // position progress bar
    {
    RECT r,dr;
    int x,y,h,xw;
    GetClientRect(w,&r);
    ClientToScreen(w,(LPPOINT)&r);
    ClientToScreen(w,((LPPOINT)&r)+1);
    GetWindowRect(prog,&dr);
    x=((r.right-r.left)-(xw=dr.right-dr.left))/2;
    y=((r.bottom-r.top)-(h=dr.bottom-dr.top))/2;
    MoveWindow(prog,x+r.left,y+r.top,xw,h,TRUE);
    }
/* Need to make directory tree here */
  for (i=0;i<NRDIRS;i++)
    {
    if (subdirs[i].bitmask)
      if (!(subdirs[i].bitmask&bitmask)) continue;
    if (prog)
      {
      char ptitle[_MAX_PATH+33];
      wsprintf(ptitle,"Creating subdirectory %s",
        (LPSTR)subdirs[i].dir);
      if (cw_ProgressSet(prog,i,ptitle)) return -1;
      UpdateWindow(w);
      }
    if (access(subdirs[i].dir,0)&&mkdir(subdirs[i].dir))
      {
      MessageBox(w,"Can't create subdirectory.",
        subdirs[i].dir,MB_OK|MB_ICONSTOP);
      return -2;
      }
    }
/* Install files */
  for (i=0;i<NRFILES;i++)
    {
    UINT vrv;
    DWORD vrvi;
    char *dst;
/* Skip file if install bits don't match */
    if (inst_files[i].bitmask)
      if (!(inst_files[i].bitmask&bitmask)) continue;
    if (!inst_files[i].srcfile)
      {
      /* Special... prompt for new disk */
      static char msg[66],idfile[_MAX_PATH];
      if (srcdir[lstrlen(srcdir)-1]!='\\') lstrcat(srcdir,"\\");
      wsprintf(msg,"Please insert disk #%d",inst_files[i].flags);
      if (srcdir[lstrlen(srcdir)-1]=='\\')
        srcdir[lstrlen(srcdir)-1]='\0';
      wsprintf(idfile,"%s\\DISK%d.ID",(LPSTR)srcdir,
        inst_files[i].flags);
      while (access(idfile,0))
        {
        struct dlgboxparam pblk;
        pblk.msg=msg;
        pblk.srcdir=srcdir;
        pblk.sizsrc=sizeof(srcdir);
        if (DialogBoxParam(hInst,MAKEINTRESOURCE(DISKDLG),
          w,diskdlg,(DWORD)&pblk))
          return -1;
        UpdateWindow(w);
        if (srcdir[lstrlen(srcdir)-1]=='\\')
          srcdir[lstrlen(srcdir)-1]='\0';
        wsprintf(idfile,"%s\\DISK%d.ID",
          (LPSTR)srcdir,inst_files[i].flags);
        }
      continue;
      }
/* Get on with it */
    if (inst_files[i].dstfile)
      dst=inst_files[i].dstfile;
    else
      {
      dst=strrchr(inst_files[i].srcfile,'\\');
      if (dst) dst++; else dst=inst_files[i].srcfile;
      }
    if (prog)
      {
      char ptitle[_MAX_PATH+33];
      wsprintf(ptitle,"Installing %s",(LPSTR)dst);
      if (cw_ProgressSet(prog,i+NRDIRS,ptitle)) return -1;
      UpdateWindow(w);
      }
fretry:
    cdlen=sizeof(curdir);
    inslen=sizeof(instdir);
    if (inst_files[i].flags==0xFFFF)
      {
      /* copy unconditionally w/o decompress or checking */
      if (!copyfile(inst_files[i].dstdir,dst,
        srcdir,inst_files[i].srcfile))
        {
        int id=
          MessageBox(w,"Could not copy this file.\n"
           "You may be able to close other applications\n"
           "and then successfuly install.\n"
           "Retry?",inst_files[i].srcfile,
           MB_RETRYCANCEL|MB_ICONSTOP);
        if (id==IDRETRY) goto fretry;
        return -1;
        }
      }
    else
      {
      vrv=VerFindFile(inst_files[i].flags,dst,
        NULL,inst_files[i].dstdir?inst_files[i].dstdir:
          appdir,curdir,&cdlen,instdir,&inslen);
      if (vrv&VFF_FILEINUSE)
        {
        int id=
          MessageBox(w,"This file is in use and can't be"
          " installed.\n"
          "You may be able to close other applications\n"
          "and then successfuly install.\n"
          "Retry?",inst_files[i].srcfile,
           MB_RETRYCANCEL|MB_ICONSTOP);
        if (id==IDRETRY) goto fretry;
        return -1;
        }
      tmplen=sizeof(tmpfile);
      if (!lstrcmpi(curdir,srcdir)) *curdir='\0';
      if ((vrv&VFF_CURNEDEST)&&*curdir
        &&!(inst_files[i].flags&VFFF_ISSHAREDFILE))
          *curdir='\0';
      if (inst_files[i].dstdir)
        {
        if (inst_files[i].dstdir==(char *)1)
          getboot(instdir);  /* not supported for WIN32 */
        else if (inst_files[i].dstdir==(char *)2)
          GetWindowsDirectory(instdir,sizeof(instdir));
        else
          lstrcpy(instdir,inst_files[i].dstdir);
        }
      *tmpfile='\0';
      lstrcpy(srcf,srcdir);
      if (srcf[lstrlen(srcf)-1]!='\\') lstrcat(srcf,"\\");
      lstrcat(srcf,inst_files[i].srcfile);
      vrvi=VerInstallFile(inst_files[i].cflags,srcf,
         dst,"",
         instdir,
         curdir,tmpfile,&tmplen);
      if (vrvi&VIF_TEMPFILE)
        {
        char dfile[_MAX_PATH];
        lstrcpy(dfile,instdir);
        if (dfile[lstrlen(dfile)-1]!='\\')
          lstrcat(dfile,"\\");
        lstrcat(dfile,tmpfile);
        unlink(dfile);
        }
      if (vrvi&(VIF_WRITEPROT|VIF_FILEINUSE|
         VIF_OUTOFSPACE|VIF_ACCESSVIOLATION|
         VIF_SHARINGVIOLATION|VIF_CANNOTCREATE|
         VIF_CANNOTDELETE|VIF_CANNOTRENAME|
         VIF_OUTOFMEMORY|VIF_CANNOTREADSRC|
         VIF_CANNOTREADDST))
           {
           int m=0;
           int id;
           if (vrvi&VIF_WRITEPROT) m=1;
           if (vrvi&VIF_FILEINUSE) m=2;
           if (vrvi&VIF_OUTOFSPACE) m=3;
           if (vrvi&VIF_ACCESSVIOLATION) m=4;
           if (vrvi&VIF_SHARINGVIOLATION) m=5;
           if (vrvi&VIF_CANNOTCREATE) m=6;
           if (vrvi&VIF_CANNOTDELETE) m=6;
           if (vrvi&VIF_CANNOTRENAME) m=6;
           if (vrvi&VIF_OUTOFMEMORY) m=7;
           if (vrvi&VIF_CANNOTREADSRC) m=8;
           if (vrvi&VIF_CANNOTREADDST) m=6;
           id=MessageBox(w,vermessage[m]
             ,inst_files[i].srcfile,MB_RETRYCANCEL|MB_ICONSTOP);
           if (id==IDRETRY) goto fretry;
           return -1;
           }
       }  /* end of else */
    }
  cw_ProgressSet(prog,0xFFFF,"Copying Complete");
  if (mbflag)
    MessageBox(w,"Installation complete",
      "Success",MB_OK|MB_ICONEXCLAMATION);
  return 0;
  }

Copyright © 1995, Dr. Dobb's Journal


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.