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

Web Development

Your Own Netscape Plug-in Installer


Web Development: Your Own Netscape...

Mark is principal developer at HeadsOff Software and can be contacted at [email protected].


PlugInstall, the application I present in this article, lets you install plug-ins for Netscape Navigator. The main reason I wrote PlugInstall (rather than using a commercial installer) was file size--I needed the smallest possible package, since the delivery medium was to be the Internet. Also, anything but the simplest installation routine required knowledge of the commercial installer's scripting language. I felt that I'd already had to learn more proprietary scripting languages than I'd ever wanted to. Writing more than about five lines of logic is always going to be easier in a familiar programming language (in this case C/C++). More specifically, the program

  • Searches the registry for Navigator's Program directory.
  • Gets the user's okay to go ahead with a disk scan.
  • Scans specified disks in a separate thread, checking for other unregistered or deregistered copies of Navigator, while allowing users to bypass this stage at any point.
  • Presents all the Navigator program locations, allowing users to choose which are to be plug-in destinations.
  • Accepts user instructions to abort the process or to proceed with the installation.
  • Installs the files and updates the Registry with user approval.
Although I've targeted this tool at plug-in installation, you can easily modify it into a general Windows 95-friendly application or extension installer/uninstaller. The parts of the program that are Netscape-specific are obvious. Since Netscape's installer does not provide all the information necessary for other software to get complete information on its components, a couple of possible conditions must be taken into account. In time, when installation packages conform more closely to Windows 95 Registry guidelines, the process will become simpler.

Compared to the simple text-line .INI settings in 16-bit Windows, installation routines for Windows 95 require handling the system Registry. The Windows 95 Registry is extremely powerful and flexible, allowing any application or module to "know" what is going on in the system, and what applications or modules it can communicate with. For all its power, however, the Registry is a fairly finicky API, not what I'd consider the fun part of application development.

Uninstallation is accomplished by updating a particular section of the Registry, and enabling the program to accept a command-line argument from the uninstall control panel to run the uninstall routine.

The program is written in C, although it needs C++ compilation because it uses an external utility class and some local variable declarations. There are no special project-compilation requirements, except for the need to link with Version.lib. Although the supplied project files are Visual C++ 4.1, there are no 4.1-specific requirements, and it was actually a port from a working Visual C 2.0 project.

How PlugInstall Works

How PlugInstall scans the disk is central to how the program works. The search is important in this case, as any updates to Netscape (installing new or alternative versions, for example) overwrite the Registry entries, leaving the old application orphaned. They are still usable, but the only sure way of finding them is dredging the user's disks.

I first considered a directory-locator dialog box for this (as is done by many other plug-in installers), but decided this would defeat my purpose--to make it easy for novice Navigator users to get the new plug-in working, and to alert them to any other deregistered copies of Netscape in the system. If users aren't aware of other copies, there's a good chance the plug-in will go into the officially registered Navigator's Program\Plugins directory, instead of a more frequently used copy. Since each copy of Navigator has its own private plug-ins folder, users could be left wondering why the newly installed plug-in doesn't work, when it's probably residing within another Navigator's plug-ins folder.

Furthermore, a directory-locator dialog box is hardly the easiest way to do a manual disk search. The automated search routine is general purpose, but could easily be optimized for specific tasks (you could, for example, eliminate a few system locations on the boot disk as search targets, although there's nothing in most install routines to prevent novice users from putting their application in the system directory along with their OCX collection). In other applications of PlugInstall, if you knew that the host application had a more-thorough registry entry, the broad disk scan should not be necessary.

The other significant aspects of PlugInstall include

  • Updating the Registry with information required by the Windows 95 control panel's software install/remove option.
  • Updating the Registry with information required by the plug-in to locate its perpetual cache.
  • Installing the plug-in to the locations specified by the user.
  • Employing VerInstallFile() to carry out basic version checking.

I use a string-table resource for most of the variable parameters, enabling a simple and fast resource recompile and link to generate alternate versions. With minimal effort, the string arrays indicating Netscape specifics, as well as Netscape's directory-locator key set, could be made general purpose by being sourced from the string table or an external ASCII file. These components are easy to identify. Listing One, for instance, includes containers for registry-search parameters and entries, which are loaded by using the resource-load routines in Listing Two.

These are initialized within the program body, and probably remain reasonably consistent through each plug-in version; see Listing Three.

I've listed the first field as "preset," and the rest are assumed to be installer specified. PlugInstall takes care of accommodating existing entries and creating new keys where required.

Listing Four is the key set specified by Netscape to identify the Netscape.exe program location, while Listing Five is the uninstall entry location specified by Microsoft. Next, the registry needs a couple of bits of information on how to examine the keys; see Listing Six.

Although it works fine, Listing Six is not strictly correct procedure with respect to the initialization of n, which is declared in the initialization of a for(;;) loop but referenced again outside the body of the loop.

RegQueryValue and RegSetValue are the primary methods for accessing the Registry and getting/setting information relevant to the plug-in. For the recursive disk scan, a separate thread is spawned simply to watch for a cancellation by the user (it would be a bit unresponsive if it was sharing processing time on the same thread with a recursive function); see Listing Seven.

While one thread does the scanning, the other handles dialog response. This is certainly much easier than having to rig up your own time-sharing calls. Listing Eight is the actual function for getting the dialog going.

These threads don't need to chatter a lot, so a couple of Boolean variables do the trick: CONTINUE and CANCEL_COMMAND. While the button offers immediate response to the user, the CANCEL_COMMAND flag is checked at the head of the recursive function and once more within a work loop, as in Listing Nine. This is not an optimized search routine, and you probably have your own, more efficient routine you can substitute.

The routines for the actual installation come from Version.lib; see Listing Ten.

The String List Manager

The other handy utility included in this package is a string list manager for interfacing data with a Windows multiselection listbox. The StringList class is initialized in the last stage of the install procedure, after the disk scan. Users can select multiple destinations for the plug-in. These selections are fed into the StringList and passed to the install routine; see Listing Eleven.

One of the most frequently used functions in PlugInstall is MakeKey() (Listing Twelve), which is worth discussing since it is compact and illustrates basic Registry calls.

Traversing Registry key trees is obviously made much simpler by packaging a few general-purpose functions for getting and sending information.

Uninstalling

For space efficiency, the same program should be able to respond to a call to perform an uninstall operation. After making the Registry entry for the uninstall program name, path, and command-line options, there is enough information available for the Windows 95 Control Panel to call the program--when the user selects it in the "remove programs" dialog box--and pass it a command-line argument that puts it into uninstall mode; see Listing Thirteen.

Much of the setup code is shared between the install and uninstall routines, so the uninstall switch is checked once all the relevant information has been obtained.

As far as the final cleanup goes, there are many possible approaches, but for our purposes, the safest is to remove any installed software and registry entries, then notify the user that they are free to remove the named directory (along with its contents). A useful routine to add here would be a readme-file generator. If the user doesn't get around to deleting the folder immediately, their memory could be refreshed with the contents of a contextual readme file created and dropped into PlugInstall's folder just before PlugInstall quits. This readme file would have information on the purpose of the folder's contents (which, by then, may be swollen with cached data), the last operation performed (in this case, an uninstall operation), and the options available from here, including a reinstall or a final trashing of the folder.

There's nothing particularly complex about the install/uninstall procedures or the necessary Registry accesses; in fact, it's probably one of the simplest routines you'll ever write. When your mind is on the main job of delivering reliable software, negotiating with a necessarily pedantic Registry is a job that most of us would rather knock over with as little fuss as possible. Hopefully, the routines described here (along with complete project makefile and source code in Visual C++ 4.1 format (available electronically) will help you get on with the main job.

One small modification, necessary for some versions of Netscape, sits at the end of the install location Registration query; see Listing Fourteen. This simply appends the subpath "Program" (tagged as programID) if it is not already there, since, in some cases, the path only points as far as "\Navigator\", whereas Netscape.exe actually sits one level deeper. To make the installer more general, specific check routines like this could be packaged into your own polymorphic handlers within a drop-in .DLL or .COM object. The .COM format is perfect for this, as it has built-in support for polymorphism (enabling you to specify interface groups and retrieve them with QueryInterface()), so the code in the main installer can remain static.

For my purposes, I call the final compiled program "Setup.exe" for obvious reasons. For space considerations, I used WinZip SE to generate the self-extracting file and specified Setup.exe as the routine to execute after decompression. Admittedly, the current production version of WinZip SE is a little dated, with no support for long filenames (which is a shame, since you'd probably specify "\Program Files\Your Company\Your Product\" as the default extraction location), but it is certainly compact, adding a minimal amount to the original ZIP file.

Conclusion

PlugInstall is not a substitute for comprehensive commercial packages like InstallShield, which are more appropriate for CD-based productions. But with current Internet-based delivery, it harks back a little to the early days of PC programming when every kilobyte counted.

Listing One

char    version[REG_STRING], module[REG_STRING], 
        company[REG_STRING], program[REG_STRING],
        InstallSubdir[REG_STRING], 
        programID[REG_STRING], SearchFile[REG_STRING};

Listing Two

LoadString(AppInstance, IDS_MODULE, module, REG_STRING);
LoadString(AppInstance, IDS_COMPANY, company, REG_STRING);
LoadString(AppInstance, IDS_PROGRAM, program, REG_STRING);
LoadString(AppInstance, IDS_VERSION, version, REG_STRING);
LoadString(AppInstance, IDS_INSTALLSUBDIR, InstallSubdir, REG_STRING);
LoadString(AppInstance, IDS_PROGRAMID, programID, REG_STRING);
LoadString(AppInstance, IDS_SEARCHFILE, SearchFile, REG_STRING);

Listing Three

char    *pathsReg[] =
{   "SOFTWARE", // preset
    company,    // your company
    program,    // your product
    "Paths"     // your private registry info
};
char    *regEntry[] = 
{   "SOFTWARE", 
    company,
    program,
    "Cache"     // more private registry info
};


Listing Four

char    *sections[] = 
{   "Software", "Netscape", "Netscape Navigator", "Main", "Install Directory" 
};

Listing Five

char    *uninstall[] =
{   "SOFTWARE", "Microsoft", "Windows", "CurrentVersion", "Uninstall"
};

Listing Six

hkey        = HKEY_CURRENT_USER;
samDesired  = KEY_ENUMERATE_SUB_KEYS;
phkResult   = 0;

for (int n = 0; n < sectionElements - 1; n++)
{
    res = RegOpenKeyEx( hkey, sections[n], 0, samDesired,  &phkResult );
    if (phkResult)
        hkey = phkResult;
    else
        break;
}
res = RegQueryValueEx(  hkey, sections[n], NULL, &type, buffer, &bufferSize );

Listing Seven

hThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)DialogThread, 
                                                 (void*) &ds, 0, &dwThreadId);

while (!CONTINUE)
    Sleep(0);
if (!CANCEL_COMMAND)
    FindFile(&pathList, SearchFile);
CANCEL_COMMAND = CONTINUE = FALSE; // reset
dbRes = DialogBoxParam(hInstance,
            MAKEINTRESOURCE(IDD_DIALOG1), NULL,
                 (DLGPROC)IntroDialog, (LPARAM)&pathList);
if (CANCEL_COMMAND)
    return 0;
COMPLETE = TRUE;

Listing Eight

DWORD DialogThread(LPDWORD dw)
{           
    DLGSTRUCT *dlg = (DLGSTRUCT*) dw;
    int dbRes = DialogBox(  AppInstance,  MAKEINTRESOURCE(dlg->dlgRes),
                                NULL, (DLGPROC)dlg->dlgProc);
    ExitThread(0);
    CONTINUE = TRUE;
    return 0;
}

Listing Nine

void RecurseFileSearch(const char *dirPath, StringList *sl, 
                                                      const char *fileName)
{
    HANDLE      h;
    BOOL            searchResult = TRUE;
    char            *bogusFile = "*.*";
    char            local[MAX_PATH];
    WIN32_FIND_DATA     findData;
    DWORD       attr;

    // Did user cancel out in other thread?
    if (CANCEL_COMMAND)
        return;
    strcpy(local, dirPath);
    if (local[strlen(local) - 1] != '\\')
        strcat(local, "\\");
    strcat(local, bogusFile);
   searchResult = (BOOL) 
        ((h = FindFirstFile(local, &findData)) != (HANDLE)0xffffffff);
    while (searchResult)
    {
        if (CANCEL_COMMAND) // check again, as some time has elapsed
        {
            FindClose(h);
            return;
        }
        local[strlen(local) - strlen(bogusFile)] = 0;
        strcat(local, findData.cFileName);
        attr = GetFileAttributes(local);
        SetDlgItemText(WaitDlg, IDC_WAITINFO, local);
        
        if ((attr & FILE_ATTRIBUTE_DIRECTORY) && 
             (*findData.cFileName != '.'))
            RecurseFileSearch(local, sl, fileName);
        else if (CompareFile(local, fileName))
        {
            strcpy(local, dirPath); // refresh path info
            strcat(local, "\\");
            if (sl->FindIndex(local) == 0)
                sl->Insert(local);  
        }
        strcpy(local, dirPath); // refresh path info
        if (local[strlen(local) - 1] != '\\')
            strcat(local, "\\");
        strcat(local, bogusFile);
        searchResult = FindNextFile(h, &findData);
    }
    FindClose(h);
}

Listing Ten

BOOL InstallFile(char *szSrcDir, char *szDestDir, char *szSrcFileName)
{
    char        szTmpFile[MAX_PATH];
    UINT        uTmpFileLen;
    int         res;

    CreateDirectory(szDestDir, NULL);
    uTmpFileLen = (unsigned)MAX_PATH;   // <-size of szTmpFile

    res = VerInstallFile(   0, szSrcFileName, szSrcFileName, szSrcDir,
                szDestDir, "", szTmpFile, &uTmpFileLen );
    if (res== 0)
    {
        DeleteFile(szTmpFile);
        return TRUE;
    }
    else if (res & VIF_SRCOLD)
        MessageBox(GetFocus(),  "The module you are trying to install "
                    "is older than that already installed.", 
                    "Bailing out...", 
                    MB_OK | MB_ICONHAND);
   else if (res & VIF_FILEINUSE)
        MessageBox(GetFocus(),  "The old file is still in use. "
                                        "Quit Netscape first.", 
                    "Bailing out...", 
                    MB_OK | MB_ICONHAND);
    . . .

Listing Eleven

BOOL CALLBACK IntroDialog( HWND  hwndDlg, UINT  uMsg, WPARAM  wParam,
                                                LPARAM  lParam )
{
    static      StringList *pl;
    char        pathStr[MAX_PATH];
    int     nBuffer[512];
    int     nSelItemsInBuffer, nSelItems, i;

    MainWin = hwndDlg;
    switch (uMsg)
    {
         case WM_INITDIALOG:
         pl = (StringList *)lParam;
         pl->FillListBox(GetDlgItem(hwndDlg, IDC_LIST1));
         SetDlgItemText(hwndDlg, IDC_REGISTEREDPATH, PathBuffer);
         SendMessage(GetDlgItem(hwndDlg, IDC_LIST1), LB_SETSEL, TRUE, 
                (LPARAM) 0);
           CenterWindow(hwndDlg, GetDesktopWindow());
               return TRUE;
        ...

Listing Twelve

BOOL MakeKey( HKEY hkey, char *regEntry[], int sectionElements,
         char *newKey[], int newElements, char *item, char *val)
{
    LPBYTE  bytePtr;
    DWORD   createStatus;
    char    *parentKey;
    int     n, res;

    HKEY    phkResult   = 0;
    REGSAM  samDesired  = KEY_ALL_ACCESS;

    for (n = 0; n < sectionElements; n++) 
    {
        res = RegOpenKeyEx( hkey, regEntry[n], 0, samDesired, &phkResult );
        if (phkResult)
            hkey = phkResult;
        else
            return FALSE;
    }
    if (res != ERROR_SUCCESS)
        return FALSE;
    n--;
    parentKey = regEntry[n];
    for (n = 0; n < newElements; n++)

   {
        hkey = phkResult;
        res = RegCreateKeyEx(   hkey, newKey[n], 0, parentKey,
                    REG_OPTION_NON_VOLATILE, samDesired,
                    NULL, &phkResult, &createStatus);
        if (phkResult)
        {
            hkey = phkResult;
            parentKey = newKey[n];
        }
        else
            return FALSE;
    }
    if (res != ERROR_SUCCESS)
        return FALSE;

    bytePtr = (unsigned char*) val;
    res = RegSetValueEx(hkey, item, 0, REG_SZ, bytePtr, strlen(val) + 1);
    if  (res != ERROR_SUCCESS)
        return FALSE;
    else
        return TRUE;
}

Listing Thirteen

    // the uninstall command line switch
    if (strstr(lpszCmdLine, "-u"))
        REMOVE = TRUE;

Listing Fourteen

buffPtr = (char*)buffer;  // contains retrieved path to "Navigator" directory
                          // conversions are to avoid compile warnings when 
                          // moving between signed and unsigned char pointers
if (res == ERROR_SUCCESS)
    AppendDirectory(buffPtr, programID);
else
    *buffer = 0;


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.