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

C/C++

C Programming


August 1996: C Programming

Hermits, Shortcuts, and Uninstall

There's good news. The "Computer Careers" column of the May 6, 1996 ComputerWorld, by Lynn Haber, is titled, "Damn, they're hot!" and it's about you. The column reports on a nationwide shortage of C++ programmers. Demand runs high, and someone with two to five years experience programming C++ can command $70,000 on either coast and $65,000 in the central part of the country-according to the report. That should be good news.

There's bad news, too. Many of the applicants for the choice C++ positions are C programmers who have learned only enough about the C++ syntax to get through an interview. They inadvertently misrepresent themselves as top guns, and the interviewers are not technically qualified to qualify the applicants. Mind, I'm not opposed to those folks getting jobs, but hiring them as senior developers on a C++ project is a mistake. Managers must know how to determine where in the project a newly hired programmer fits best. It's in your best interest to help your boss properly assign qualified people. You are going to be working side-by-side with those new programmers. A ringer could affect your productivity and bring down the whole shebang.

I've sat in on some employment interviews for senior C++ positions recently. If you ask an applicant if he or she knows C++ inside-out, the answer is always yes. They will discuss projects they've worked on and programs they've written. Interviewers are usually impressed. The applicants usually aren't lying. They either aren't aware of what they don't know, or they count on their ability to learn fast. But many of them reveal only a superficial knowledge of C++ when faced with what ought to be some elementary questions. As for learning fast, it is my considered opinion that it takes at least a year of both programming and reading all the current literature for a seasoned C programmer to become moderately fluent in C++, and it takes longer to become an expert. I don't think anyone ever knows the language wall-to-wall.

Here are a few questions that I use to indicate whether someone understands some fundamental differences between C and C++:

  • How do you link a C++ program to C functions?
  • Explain the scope-resolution operator.
  • What are the differences between a C struct and a C++ struct? A C++ class?
  • How many ways are there to initialize an int with a constant?
  • How does throwing and catching exceptions differ from using setjmp and longjmp?

This line of questioning is meant to get a feel for the person's overall understanding. If an applicant claims to have done extensive class design, here are some questions that reveal how well they understand that technology:

  • What is your reaction to the line of code delete this;?
  • What is a default constructor?
  • What is a conversion constructor?
  • What is the difference between a C++ struct and a C++ class?
  • When should you use multiple inheritance?
  • What is a virtual destructor?
  • Explain the ISA and HASA class relationships. How would you implement each in a class design?
  • When is a template a better solution than a base class?

Suppose an applicant claims to have kept up with the language advances proposed by the ANSI committee. Ask these questions to see how in touch they really are:

  • What is a mutable member?
  • What is an explicit constructor?
  • What is the Standard Template Library?
  • Describe run-time type information.
  • What problem does the namespace feature solve?
  • Are there any new intrinsic (built-in) data types?

The purpose of all this is to determine an applicant's skill level. I usually mix the questions up so that it's not obvious where I'm heading. I ask the questions verbally in a relaxed, conversational manner. You have to be careful these days with anything resembling a written test, particularly on a government project. For some reason, the Labor Department doesn't want you disqualifying anyone based on their inability to demonstrate that they can do the job. I'll discuss what I think are reasonable answers to these questions next month. I'd like to hear your ideas on this subject.

Herman's Hermits

I've been using this column to chronicle the development of a C/C++ training CD-ROM, which is being marketed by DDJ and which I take pleasure in plugging shamelessly at every turn. I have told you about the development of Quincy 96, an IDE front end to the GNU C/C++ compilers; Herman, a text viewer for the text of the books that provided the CD-ROM's content; and the Setup program. This month, I'll finish the Setup program, discuss the Uninstall program, and thereby bring to an end this project and the endless Pournellean plugs that accompany its every discussion.

First, however, I want to do some follow-up to an issue that I raised during the discussion of Herman. I ran afoul of the Win32/MFC API when trying to implement a rich-text format (RTF) viewer that also displays graphics in the documents. That discussion, the program, and my speculations about MFC and OWL generated some interesting mail from readers. It seems that a lot of you spend altogether too much time away from polite society, squinting at your screens, hammering at your keyboards, neglecting friends and family, and dealing with programming issues such as those raised by Herman. You are therefore and heretofore dubbed "the Hermits," and here are some of your delightful letters.

This first note comes from Ed Diener in response to my concern that all my MFC wrangling could be done with a few lines of OWL code:

Actually, with OWL you don't have to write a single line of code. When you start the Borland 5.0 IDE, the TMindProbe class is automatically activated, which sets a periodic timer that picks up your brain waves and, if at any time you are thinking about the Windows 95 Rich Text Edit Control, it automatically writes the functionality into whatever you are currently working on. This is an undocumented feature of BC++ 5.0. Of course, true-blue OWL programmers will tell you that, given the state of the Borland documentation which has been unanimously voted worst programming tool documentation for the 10th straight year, all features of BC++ 5.0 are undocumented but take my word for it, this one really is.

Not even Mike Swaine could come up with better satire. Ed takes my preference for MFC over OWL very much to heart. He goes on to say:

At work as a consultant on a BC++ 16-bit Windows project, all the programmers want to go to Visual C++ and MFC for the 32-bit version. I'm the only Borland fanatic left. When I ask is it because Borland wastefully supports the C++ Standard Library while Microsoft cleverly doesn't, they shake their heads no. When I ask is it because MFC neatly ropes you into the Doc/View architecture while Borland doesn't, thereby obviously giving you enough rope with which to hang yourself with if you so desire, they again shake their heads no. When I ask if it's because OWL encapsulates the entire confusing Window's API while MFC selectively encapsulates only the most important portions, they again say no. When I ask if it's because Borland confuses you by using in OWL the obviously arcane C++ features of virtual base classes and multiple inheritance while Microsoft has righteously avoided this mess with their much cleaner and simpler architecture, they say no once more. What is it I cry, beg, plead, cajole, etc. It's this Ed, they say. The DDJ C++ column writer, Al Stevens, has declared MFC the best and the standard, and that's reason enough for us.

What finer endorsement could I ask? John B. Williston sends his reminiscences about learning and doing Windows programming:

When I learned C, the standard C library seemed quite functional; it was a rare thing indeed if I ran into some undocumented problem with Microsoft's implementation. Thus, when I got my first Windows/386 SDK, I assumed the same would be true. I quickly discovered just how wrong I was.

Windows is supposed to be a black box; unfortunately, I found that my value as a Windows developer was not in my knowledge of the API but rather in my knowledge of all the hidden gotchas. I became the local guru and quickly found myself answering all kinds of esoteric questions.

When I got my copy of MSC7, I took an immediate liking to MFC. It was much cleaner in so many ways than the schizophrenic C/C++ mixture I was coding. Soon after Visual C++ was released, however, MFC went the way of the Windows API: It did a great deal for you, but changing its default behavior was not for the faint of heart.

So, here I sit today armed with VC++ and MFC 4.1; my IDE has SourceSafe, Visual Test, and MSDN a mere mouse click away. To this day, I still end up hacking my way through the MFC underbrush every time I want to depart in the slightest way from its positively draconian feature list. I keep asking myself if this really is progress.

Probably not, but it's interesting and keeps us working. J.D. Robinson reported experiences similar to those that I discussed and came up with a less complex solution than mine:

The CRichEdit fiasco is something I just went through, writing the mail program I'm using right now. Not that this is any better a solution than yours, but it seemed more straightforward to me at the time.

CRichEditDoc wouldn't work at all for me, mainly because I, too, had a splitter window that needed it [to be] a generic CDocument class. But noticing that the only thing CRichEditView really complained about was an assertion on GetDocument(), I simply replaced the call with CView::GetDocument().

Not using CRichEditDoc eliminates much of the OLE capability of the Doc/View set of CRichEdit, but I don't need that. I just need, like you did, the capability to stream in and out formatted text.

I wish I'd thought of that. Thomas Kaufmann is adapting Herman for use in a commercial application. He sent a message asking permission to use Herman (permission granted, you may use any of the code I publish), adding that he had made a few minor changes to the code to make Herman look more like Microsoft InfoViewer. Thomas included modified versions of Herman's table of contents, CListView class, and some open and closed book icons to add to the display. Much appreciated, and the changes are now in the version of Herman that I use.

Not all the messages are positive. In balance, here's one from Sergey I. Yevtushenko, a reader who disapproves of the direction I've taken:

Over last 5 years I'm reading your articles in DDJ. Even more: one of the my first book in C programming was your Programming in Turbo C (sorry for possible mistake, because I'm read unofficial Russian version of this book, they was very wide used in ex-SU [the former Soviet Union] for teaching C yourself). Every time I'm was impressed with quality of your code and design of your programs and libraries. This was. But now you sell yourself to Microsoft, and forget about rest of the world of programming. I'm very sorry. You loose! at least one reader in face of mine.

That's sad, but if you think Microsoft bought me, or that I'm for sale to anyone, read what I have to say about the completeness of the Win32/MFC API and documentation in the next discussion.

Adding to the Windows 95 Start Menu

You'd think that a hot-shot GUI API like Win32 would include functions for all routine operations. Take, for example, the business of adding items to the Windows 95 Start menu. That procedure is commonly in the province of a Setup program, and most applications use one of the setup program managers to build a Setup program from a script. Consequently, programmers don't have to worry about details such as how things get on the Start menu. When I decided to write my own setup program (discussed in last month's column), I ran smack dab into that concern. The Setup program I published last month did not add things to the Start menu, because I did not yet know how.

Adding to the Start menu's Programs submenu is fairly simple. The whole thing is done with subdirectories. The WINDOWS\START MENU\PROGRAMS subdirectory is where all the Start\Programs items occur. To add submenus, you just add subdirectories. The Win32 API includes the _mkdir function for that purpose. The hard part is what goes into those subdirectories. You'd think that you could put executable files there, but Windows 95 ignores them when it displays the menus that cascade off the Start menu. The Start menu needs so-called "shortcut" files, which are links to the real executables. You will be surprised, no doubt, to learn that the procedure for building those links is not that well-documented. You can find a little bit of information in the documentation if you know exactly what you are looking for, but most people would not know where to start looking. When you do find the right reference, the information is incomplete and inaccurate. Perhaps Microsoft figured that everyone would use InstallShield or the Setup SDK. Even so, there are other times when programmers need to build shortcuts. Shortcuts can link to documents and other objects in addition to executable files. We need an easier way to build them.

Windows 3.1 programmers have a different problem. Windows 3.1 does not support the link concept, so you can't build shortcuts to documents. Setup programs need to add groups and items to the Program Manager, which involves a cryptic and convoluted DDE interface to Program Manager. There should be an API function that encapsulates that procedure, too. I haven't found one, but, working mostly with Windows 95, I haven't searched all that diligently for a Windows 3.1 solution.

Programmer's Guide to Microsoft Windows 95

I looked everywhere for help with the Windows 95 Start menu. Fortunately, being a famous and revered columnist, I am on the review copy mailing lists of most trade publishers, so my library of Windows 95 programming books (and every other kind of obscure programming book) is extensive. I finally found a reference to the Start menu in a book titled Programmer's Guide to Microsoft Windows 95, published in 1995 by Microsoft Press. The book has no author credits other than to say that due to the courtesy of some authors, the book contains articles previously published in other Microsoft publications. I'm sure that Microsoft paid them all a little extra taste for their courteous contribution to this book. I'm sure.

One of the book's articles, titled "Installing Applications," says, "To add an icon to the Start menu, your installation program should create a link to your application's executable file and place the link in the directory named \WINDOWS\STARTMEN\PROGRAMS... An installation can create a link by using the IShellLink interface."

There is an error in this statement, one that cost me some time I might add, thank you very much to whomever wrote the article. Apparently, early Windows 95 betas named that subdirectory STARTMEN, because my Windows 95 installation, which saw first light as a Chicago beta, has such a subdirectory. The shipping version of Windows 95 changed the name to "Start menu." I fooled around in STARTMEN for a while before it hit me what was wrong. This book must predate that decision.

Nonetheless, that clue got me pointed in the right direction. The IShellLink interface is the secret. Another of the book's articles, titled "Shell Links," discusses the IShellLink interface and provides an example program. The source code has errors and is missing some stuff, such as some necessary #include statements, but between the example, Visual C++'s on-line help, and the error messages that the example produced when compiled, I was able to work out a procedure that worked.

The Missing .Lnk

Microsoft's Win32 and MFC documentation has an annoying deficiency. It seldom tells you what header files you need to include when you use one of the API functions. The documentation has another equally annoying deficiency. It seldom (if ever) tells you which library files you need to include in the link when you use one of the API functions.

I had to use Borland's grep utility against the VC++ header files to find references to the functions and globals that the example program uses. Then, as those header files generated their own errors for lack of other header files, I used grep iteratively until I had the list of header files needed to get a clean compile of a program that uses IShellLink.

Linking the program that used IShellLink resulted in numerous unresolved references. I tried using LIB/LIST on the .Lib files to see which functions are in which libraries, but, for most of the files, that procedure lists only the names of the DLLs that support the library's functions. Odd, indeed. My only recourse was to generate a list of all the library files, link the program, and then selectively remove items from the list and relink until I had pared the list down to only the required files.

My late friend Jim Weir would look into the eye of someone who had done something questionable, pretend not to know who was responsible, and say, "What incredibly stupid moron came up with such an obviously idiotic idea?" I'd like to put Jim eye-to-eye with whomever at Redmond decided that Win32 and MFC documentation do not require header and library file references.

If you want to provide a useful service to MFC programmers, build a program that interactively reports the header and library files that a particular Win32 API call uses. Integrate that program into the Visual C++ editor. Don't count on building an empire from the sales, though. If you sell a lot of copies, the feature will surely be included in a future version of Visual C++.

The MakeShortCut and AddToStartMenu Functions

Listing One contains two functions that I hereby submit ought to have been in the Win32 API all along. The MakeShortCut function makes a shortcut with the name specified in the second parameter. The shortcut is to the object specified in the third parameter and is placed in the subdirectory specified in the first parameter. The AddToStartMenu function adds an entry to the Start menu by making the appropriate subdirectory and calling MakeShortCut to build and copy the shortcut into the subdirectory. The first parameter names the submenu to be added to the Start/Programs menu. If the submenu already exists, the existing one is used. The second parameter contains a name for the program to be displayed in the menu selection. The third parameter is a path to the executable program file.

Listing One shows the header files that you need to include in order to use IShellLink. To link a program that uses these functions, you must add UUID.LIB and OLE32.LIB to the Object/library modules list on the Link page of the Project Settings in Developer Studio. Depending on your original project build options, either library might already be included. My Setup program uses none of the optional features, so these additions are necessary.

Uninstall

Having written a custom Setup program, I move next to the problem of uninstalling an application's installation. The problems faced by an uninstall program are simpler than those faced by Setup. You have to delete the application's files, delete any .INI files that the application added to the \WINDOWS subdirectory, and remove the application's entries on the Start menu. My application makes no changes to the registry and adds no DLLs to the WINDOWS\SYSTEM directory, so those issues do not apply.

I wrote a simple dialog-based MFC program that has a Yes and a No button, which are the ID_OK and ID_CANCEL buttons with Yes/No captions. If you click Yes, the program uninstalls the application.

The only problem to be solved is determining where Setup installed the application. To that purpose, I modified Setup to add a DDJTUTOR.INI file with an entry that specifies the installation subdirectory. The Uninstall program reads that parameter. A side problem would occur if someone deleted or tampered with that file. The program published does not try to deal with that. A more bullet-proof program would at least ensure that the subdirectory contains the files that the application expects to have deleted.

Listings Two and Three are Uninstall.h and Uninstall.cpp, two unremarkable source-code files that implement and launch the application. Listings Four and Five are UninstallDlg.h and UninstallDlg.cpp, which implement the uninstallation by overriding the OnOK function from the base CDialog class. The foundation of uninstallation is found in the Remove member function. The Remove function deletes all the files in a subdirectory by scanning for them and calling the Delete member function to delete each file. If a file is a subdirectory, the Remove function calls itself instead.

The dialog consists of the Yes and No buttons, a CProgressCtrl control to report the progress of the uninstallation, and a static text control to report the name of the file being deleted.

Uninstall cannot, unfortunately, delete its own .exe file or remove the subdirectory in which the Uninstall.exe file resides. The Uninstall program posts a message to that effect and advises the user to remove those things manually. If anyone knows how an executing program can unlock its own .exe file long enough to delete itself, I'd like to know about it. I'll call it the "Kevorkian algorithm."

Listing One




#include "stdafx.h" // built by VC++ Developer Stdio for your app

#include <WINNETWK.H>
#include <SHLOBJ.H>
#include <WINNLS.H>
#include <direct.h>

void MakeShortCut(LPCSTR lpszPathLink, LPCSTR lpszDesc,
                              LPCSTR lpszPathObj)
{
    CoInitialize(0);
    HRESULT hres;
    IShellLink *psl;
    hres = CoCreateInstance(CLSID_ShellLink, 0,
                 CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&psl);
    if (SUCCEEDED(hres))    {
        IPersistFile* ppf;
        psl->SetPath(lpszPathObj);
        psl->SetDescription(lpszDesc);
        hres = psl->QueryInterface(IID_IPersistFile, (void**) &ppf);
        if (SUCCEEDED(hres))    {
            WORD wsz[MAX_PATH];
            int nMbwc = MultiByteToWideChar(CP_ACP, 0,
                           lpszPathLink, -1, wsz, MAX_PATH);
            hres = ppf->Save(wsz, TRUE);
            ppf->Release();
        }
        psl->Release();
    }
    CoUninitialize();
}
void AddToStartMenu(LPCSTR lpszProgram, LPCSTR lpszDesc,
                            LPCSTR lpszPathObj)
{
    char szPathLink[MAX_PATH];
    GetWindowsDirectory(szPathLink, MAX_PATH);
    strcat(szPathLink, "\\Start Menu\\Programs");
    // --- just in case there is no Programs submenu
    _mkdir(szPathLink);
    strcat(szPathLink, "\\");
    // --- create Start Menu\Programs subdirectory for the shortcut
    strcat(szPathLink, lpszProgram);
    _mkdir(szPathLink);
    // --- create the shortcut path
    strcat(szPathLink, "\\");
    strcat(szPathLink, lpszDesc);
    strcat(szPathLink, ".lnk");
    MakeShortCut(szPathLink, lpszDesc, lpszPathObj);
}

Listing Two




// ---- Uninstall.h
#ifndef UNINSTALL_H
#define UNINSTALL_H
#include "resource.h"
class CUninstallApp : public CWinApp
{

public:
    CUninstallApp() { }
    BOOL InitInstance();
};
#endif

Listing Three




// --- Uninstall.cpp
#include "stdafx.h"
#include "Uninstall.h"
#include "UninstallDlg.h"
CUninstallApp theApp;
BOOL CUninstallApp::InitInstance()
{
#ifdef _AFXDLL
    Enable3dControls();
#else
    Enable3dControlsStatic();
#endif
    CUninstallDlg dlg;
    m_pMainWnd = &dlg;
    dlg.DoModal();
    return FALSE;
}

Listing Four



// --- UninstallDlg.h
#ifndef UNINSTALLDLG_H
#define UNINSTALLDLG_H

class CUninstallDlg : public CDialog
{
    CProgressCtrl m_ctlProgress;
    CString m_strDoing;
    void Remove(const CString& strPath);
    void Delete(const CString& strFile);
    void DoDataExchange(CDataExchange* pDX);
    void OnOK();
public:
    CUninstallDlg(CWnd* pParent = NULL);
};
#endif

Listing Five




// --- UninstallDlg.cpp
#include "stdafx.h"
#include <direct.h>
#include <io.h>
#include <sys\stat.h>
#include "Uninstall.h"
#include "UninstallDlg.h"

CUninstallDlg::CUninstallDlg(CWnd* pParent):
                    CDialog(IDD_UNINSTALL_DIALOG, pParent)
{

    m_strDoing = "";
}

void CUninstallDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_PROGRESS, m_ctlProgress);
    DDX_Text(pDX, IDC_DOING, m_strDoing);
}
void CUninstallDlg::OnOK()
{
    // ---- set up the progress indicator
    int bQuincyInstalled = GetPrivateProfileInt("Setup", "Quincy", 0,
                                                    "DDJTutor.ini");
    // --- Quincy installed, range different than Quincy not installed
    m_ctlProgress.SetRange(0, bQuincyInstalled ? 300 : 20);
    m_ctlProgress.SetStep(1);

    char path[MAX_PATH];    // where the tutorial was installed
    if (GetPrivateProfileString("Setup", "Path", "", path,
                    MAX_PATH, "DDJTutor.ini") != 0) {
        // ---- remove the DDJTUTOR subdirectory
        //      and all its files and subdirectories
        Remove(path);
        char msg[MAX_PATH + 200];
        sprintf(msg,
            "Uninstall is complete. "
            "%s\\Programs\\Bin\\Uninstall.exe still exists. "
       "You may remove that file and those subdirectories manually.",
            path);
        // ---- remove the Shortcuts from the start menu
        GetWindowsDirectory(path, MAX_PATH);
        CString strWindir(path);
        Remove(strWindir + "\\Start Menu\\Programs\\DDJ Tutorial");
        // ---- remove the .ini files from the windows subdirectory
        Delete(strWindir + "\\ddjtutor.ini");
        Delete(strWindir + "\\herman.ini");
        Delete(strWindir + "\\quincy.ini");
        m_strDoing = "";
        UpdateData(FALSE);
        // --- report the completion
        AfxMessageBox(msg);
    }
    CDialog::OnOK();
}
// ---- delete a subdirectory and all its files
void CUninstallDlg::Remove(const CString& strPath)
{
    m_strDoing = "Deleting " + strPath;
    UpdateData(FALSE);
    // ------ scan for files to delete
    struct _finddata_t fileinfo;
    CString strSub = strPath + "\\*.*";
    long hFile = _findfirst(strSub.GetBuffer(0), &fileinfo);
    while (hFile != -1) {

        // ---- build file specification
        CString strFile(strPath);
        strFile += "\\";
        strFile += fileinfo.name;
        if (fileinfo.attrib & _A_SUBDIR)    {
            // --- this is a subdirectory
            if (*fileinfo.name != '.')
                Remove(strFile);
        }
        else
            // --- this is a file
            Delete(strFile);
        if (_findnext(hFile, &fileinfo) == -1)
            hFile = -1;
    }
    _rmdir(strPath);
}
// ------ delete a file
void CUninstallDlg::Delete(const CString& strFile)
{
    // ---- post the file name to the dialog
    m_strDoing = strFile;
    UpdateData(FALSE);
    // ---- step the progress indicator
    m_ctlProgress.StepIt();
    // ---- just in case someone write-protected the file
    _chmod(strFile, _S_IWRITE);
    // ---- delete the file
    remove(strFile);

}


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.