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


OCT92: C PROGRAMMING

This file contains the following executables: DFLT14.ARC D14TX.ARC

As I write this, the Democratic National Convention is on TV in the background trying to grab my attention. It's old news by the time you read this. Bill and Hillary and Al and Tipper are riding the donkey, and in November we'll know where it carried them. For now, we are watching the world a little closer and occasionally seeing how our technology changes it.

Ollie North was amazed to learn that even though he deleted sensitive e-mail messages between himself and Admiral Poindexter, PROFS made archive copies of the deleted messages, and those copies later helped prove he was less than forthcoming with the facts.

Before he withdrew, the media were probing Ross Perot's policies and positions and questioning how informed he was on the issues. Perot built EDS into the giant it is and became a billionaire. EDS is a leader in our industry. When Perot was asked if his plans for an electronic national town meeting would use technology similar to France's Minitel, he said he'd never heard of it. That's how informed he is about his own business.

President Bush can't program his VCR and was impressed by the scanner at a market check-out counter.

Dan Quayle's spell checker flunked the beta test.

Do you ever get the feeling that you're a little bit brighter than the people in charge?

D-Flat Wrapup

This month concludes the series on D-Flat, the DOS text-mode CUA user-interface library. Next month I'll begin D-Flat++, the rewrite in C++. DF++ will not take the year-and-a-half that D-Flat did. I'm using DF++ to examine the issues that surround a C++ rewrite and to compare the two languages when applied to the same solution. Therefore, this column's DF++ coverage will include the source code that supports those discussions and will not try to publish the whole enchilada. It will, however, be available for download from CompuServe and M&T Online and under the DDJ "careware" program, explained further on.

When I started D-Flat, there were only a few DOS text-mode options for implementing the CUA interface. There are more now, including some new surprises in the months to come. As Windows devours the PC operating-environment marketplace, I wonder about the future of DOS text-mode applications. Virtually every major PC application now has a Windows version, which is stealing all the attention from its DOS ancestor. OS/2 is growling from the sidelines, threatening to grab its share. There are other GUIs: X-Window, Motif, NeXT, GeoWorks, OpenLook, GEM, AppleDOS, AmigaDOS. If you could identify the lowest common denominator shared by all the GUIs, you could write a C++ class library for each that would allow an application to compile for any GUI platform without changes to the source code. The application programs wouldn't be very interesting to the user because they wouldn't use the slickest, unique qualities of any particular operating environment, but they would be more or less portable.

To finish the D-Flat project, I will discuss the File Open and Save As dialog boxes, the application window's status bar, and the text compression of the D-Flat help database.

File Open Dialog Boxes

When operating an application, users specify existing filenames to open and names of new files to create. D-Flat includes two dialog boxes for these purposes: File Open and Save As dialog boxes. These dialog boxes allow the user to select a file from any drive or subdirectory in the system. The format includes a one-line edit box where the user types the file specification, a text display that shows the current drive and subdirectory, and list boxes to select existing files and change the drive and subdirectory.

Listing One, page 160, is direct.c, code that supports the open and save dialog boxes for drive and directory processing. The CreatePath function converts an ambiguous file specification into one where the path parts are unambiguous and the filename and extension are represented at least by wild cards. If the specification has no drive or subdirectory, the function adds the currently logged-on drive and subdirectory to the path. If there is a drive but no path, the function adds the path for that drive. If the specification has no filename or extension, the function adds the * wild card for them.

The DlgDirList function builds a list box with entries for all the disk drives and subdirectories below the current one on the current drive. It also builds a text-control display that shows the current drive and directory. The two dialog boxes use this function to display where the user is in the file system and give the choices for changing the drive and directory.

Listing Two, page 161, is fileopen.c, the program that implements the File Open and Save As dialog boxes. The application calls either the OpenFileDialogBox or SaveAsDialogBox function, depending on which dialog box it wants. The former function accepts a wild-card parameter that specifies the starting path and filename to search. if you passed the value "C:\DOS\*.EXE", the dialog box would begin with the \DOS subdirectory on the C: drive and would display all the *.EXE files in the filename list box. Both functions accept a character-pointer parameter with an address to write the file specification selected by the user.

The program begins by displaying the caller's filename specification in a one-line edit box, the current drive and directory in a text control, a list of filenames that match the file specification in a list box, and the available drives and subdirectories in another list box. Users can type in a new file specification or select one from the list box. Users can also select a different drive or subdirectory, and the other displays on the dialog box modify themselves to reflect the new path. When users choose the OK command, the current path and filename are copied into the caller's string, and the function returns.

The Status Bar

The D-Flat application window can have a status bar on the window's bottom border. Listing Three, page 162, is statbar.c, code that implements the window-processing module for the STATUS-BAR window class. The class captures the clock for the window and displays the time for each CLOCKTICK event. When the application wants to use the status bar to display a one-line text message, it sends the ADDSTATUS message to the application window, which uses the SETTEXT message to put the text into the window. The status bar's PAINT message displays any text value the window contains.

Compressing Help

About two years ago, I published in this column the code that implements the Huffman compression and decompression algorithms. I decided to adapt those algorithms to compress the D-Flat help-text database for two reasons. First, a compressed help file makes for a smaller distribution of your application. Second, a compressed text file is protected from potential changes made by a curious user.

Listings Four, Five, and Six, page 162, are htree.h, htree.c, and huffc.c, code that compresses the help text. htree.h defines the structure of the Huffman tree as it is built and its representation in the compressed file. Huffman compression consists of reducing characters in the text to bit strings. The more frequent characters are represented by shorter bit strings.

To build a Huffman tree, first read the text file and count the characters. At the bottom of the tree is a spread of 256 nodes, each representing an eight-bit value. At first, these nodes are all the tree has, and each one records how many times the character it represents occurs in the text. The first node represents the value 0, the second node represents 1, and so on. None of these nodes has children or a parent. Next, pass through the nodes and find the two that represent the characters having the least number of occurrences in the text. For those two nodes, build a new node that is their parent and that has as its frequency count the total of the two. That node will point to its two child nodes. Repeat this scan, always by-passing nodes that have grown parents, and include any new parent nodes that have been added until there is only one node left that has no parent. That node will be the root node of the tree.

After the tree is built, the program can write it to the compressed file. Not all of the tree is needed, but the decompression algorithm needs enough of it to decompress the text. You need the number of bytes in the text, the number of parent nodes in the tree, and the offset to the root node. Then you need only the child-node pointers for each of the nodes that are parents--the nodes above the original 256.

To compress the text, the program rereads the file a byte at a time. For each byte, the program traces a path from the root node down the right or left children until it gets to the node that represents the character. For each right path, the program writes a 0 bit. For each left path, the program writes a 1 bit. The trick involves the program finding its way down the correct path. To do that, the function begins at the base node that represents the character and recursively calls itself until it gets to the root.

Listing Seven, page 163, is decomp.c, which contains the decompression code for the help database. Decompression occurs twice. When the program loads the help database, it reads the text and builds a table of help texts, specifying the text identifier and its byte/bit offset into the compressed file. Later, when the user asks for help, the program seeks the compressed text and decompresses it for display.

The decompression algorithm is the inverse of the compression algorithm. As it reads bits from the compressed stream, the program navigates from the root node to the base, taking the left child path for a 1 bit and the right child path for a 0 bit. When the path delivers a node number that is less than 256, that number is the decompressed text character.

How to Get D-Flat Now

The D-Flat source code is on CompuServe in Library 0 of the DDJ Forum and on M&T Online. If you cannot use either online service, send a formatted 360K or 720K diskette and an addressed, stamped diskette mailer to me in care of Dr. Dobb's Journal, 411 Borel Ave., San Mateo, CA 94402. I'll send you the latest version of D-Flat. The software is free, but if you'd care to, stuff a dollar bill in the mailer for the Brevard County Food Bank. They help the homeless and hungry. We call it DDJ's program of careware. If you want to discuss D-Flat with me, use CompuServe. My ID is 71101,1262, and I monitor the DDJ Forum daily.

What's Wrong with C++?

Its critics call it a "flawed language." Bjarne Stroustrup responds by saying, "It works." To identify C++'s shortcomings, you must approach it with a certain point of view. An OOP devotee and purist will observe that C++ permits you to use the classic procedural approach--that it is a C superset with object-oriented extensions, but one that does not enforce rigid object-oriented design and programming. The procedural structured programming gang will bash C++'s abilities to use the same function name for more than one purpose, to overload operators to do nonintuitive things, and to invoke an unseen barrage of hidden anonymous objects. The adherents to some--any--other language will grouse that C++ is still C with all its attendant faults: The identifiers are case-sensitive; the syntax is cryptic; everything is an expression; you can stomp all over memory with pointers; and you can plough outside the bounds of an array. Cobol, dBase, and Basic programmers will complain that the priesthood is only getting more elite, elusive, and exclusive. The C++ programmer will maintain that there is nothing at all to criticize.

Obviously, those people are not qualified to critique C++. Who is? The C programmer, that's who. You and me. But we need an open mind. We need to watch for the thorns as we embrace the rose. In the months to come, I will discuss some of the problems I perceive with C++. Not that I expect to influence the language design or standardization. C++ is pretty much what it is going to be, and its flaws are vastly overshadowed by its benefits. But, by understanding its problems, we will know how to deal with them when we write code. And by airing them, perhaps we can in some small way influence the next programming-language designer.

There will be a tendency, particularly among compiler vendors, to respond to such problems by explaining why the language must be that way. The compiler would be difficult to write; the compiled code would be inefficient; correcting the problem would disable some other feature or generate a new problem. That's all fine. Although I am interested in such answers, learning them is not my objective. What follows are C++ stumbling blocks, large and small. We need to know what they are so we can step over or around them. The following are just a few.

The size of operator is not polymorphic. If you apply it to an object from within its base class, the operator delivers the size of the base class, not the object. Now this isn't a big deal. It's just something that surprises you if you didn't know about it. It also illustrates how defensive C++ programmers can be about their language. Even programmers who didn't realize that sizeof does that are quick to improvise all manner of extemporaneous reasons why it simply must work that way. I don't care why it works that way. I don't even care that it works that way. I just want to know about it, preferably before I try to use it.

Base classes cannot construct new copies of objects that are of classes derived from themselves. If an abstract base class is a disk-based container class that holds objects of the derived class, the base class cannot construct an empty object to read the next object into. It doesn't know the size of the object. The derived class has to cooperate in its own persistence strategy. The experts will tell you that templates are the answer, but...

Every use of a template compiles a new copy of the template code. If you use the same container template for ints, longs, and complex structures, you will get three copies of the code. All the code. Before you rush to write me a letter about deriving the template from a general-purpose base class that uses void pointers, remember two things. First, a purpose of templates is to do away with inheritance solutions that hide types behind void pointers. Second, in a class that really needs to be a template, most of the member functions will be sensitive to the format of the template parameter.

One of the tenets of object-oriented design is that the class consists of two parts--the interface and the implementation. The class user--the program that creates an object of that class--sees the interface. The class itself sees the implementation. The user does not need to see the implementation and does not need to know its details. By hiding the implementation details, the class protects the integrity of the object-oriented design.

A C++ programmer sees it all. There is no way to separate the details of the interface from those of the implementation because the class definition describes them both. The class member functions can be hidden in an object-module library, but the data members are hanging out there for all the world to see. The compiler needs to see them to know how to construct the object. The public and private keywords protect the programmer from inadvertently diddling with the implementation, but nothing stops you from moving the public keyword in your own copy of the class to suit your own needs. Benign protection at best.

A Book by Any Name...

Two years ago the first edition of my book Teach Yourself C++ came out, and I mentioned it in this column. I cautioned readers that Herb Shildt would no doubt soon have a book with the same title. Herb writes books with the Teach Yourself title too, but for a different publisher. A laughing Herb called me soon after the magazine hit the stands. He really enjoyed being needled in my column, he said, but not to worry. Herb wasn't doing a Teach Yourself C++; I would have full claim to that one. Well, Herb's Teach Yourself C++ just came out--a mere two years later. I repeat what I said then. Don't be fooled by imitations. Mine has a yellow cover; Herb's is purple, and I just got the old Purple Herbie.



_C PROGRAMMING COLUMN_
by Al Stevens


[LISTING ONE]
<a name="023a_000c">

/* ---------- direct.c --------- */
#include "dflat.h"

static char path[MAXPATH];
static char drive[MAXDRIVE] = " :";
static char dir[MAXDIR];
static char name[MAXFILE];
static char ext[MAXEXT];

/* --- Create unambiguous path from file spec, filling in the drive and
directory if incomplete. Optionally change to new drive and subdirectory --- */
void CreatePath(char *path,char *fspec,int InclName,int Change)
{
    int cm = 0;
    unsigned currdrive;
    char currdir[64];
    char *cp;

    if (!Change)    {
        /* ---- save the current drive and subdirectory ---- */
        currdrive = getdisk();
        getcwd(currdir, sizeof currdir);
        memmove(currdir, currdir+2, strlen(currdir+1));
        cp = currdir+strlen(currdir)-1;
        if (*cp == '\\')
            *cp = '\0';
    }
    *drive = *dir = *name = *ext = '\0';
    fnsplit(fspec, drive, dir, name, ext);
    if (!InclName)
        *name = *ext = '\0';
    *drive = toupper(*drive);
    if (*ext)
        cm |= EXTENSION;
    if (InclName && *name)
        cm |= FILENAME;
    if (*dir)
        cm |= DIRECTORY;
    if (*drive)
        cm |= DRIVE;
    if (cm & DRIVE)
        setdisk(*drive - 'A');
    else     {
        *drive = getdisk();
        *drive += 'A';
    }
    if (cm & DIRECTORY)    {
        cp = dir+strlen(dir)-1;
        if (*cp == '\\')
            *cp = '\0';
        chdir(dir);
    }
    getcwd(dir, sizeof dir);
    memmove(dir, dir+2, strlen(dir+1));
    if (InclName)    {
        if (!(cm & FILENAME))
            strcpy(name, "*");
        if (!(cm & EXTENSION) && strchr(fspec, '.') != NULL)
            strcpy(ext, ".*");
    }
    else
        *name = *ext = '\0';
    if (dir[strlen(dir)-1] != '\\')
        strcat(dir, "\\");
    memset(path, 0, sizeof path);
    fnmerge(path, drive, dir, name, ext);
    if (!Change)    {
        setdisk(currdrive);
        chdir(currdir);
    }
}
static int dircmp(const void *c1, const void *c2)
{
    return stricmp(*(char **)c1, *(char **)c2);
}
BOOL DlgDirList(WINDOW wnd, char *fspec,
                enum commands nameid, enum commands pathid, unsigned attrib)
{
    int ax, i = 0, criterr = 1;
    struct ffblk ff;
    CTLWINDOW *ct = FindCommand(wnd->extension,nameid,LISTBOX);
    WINDOW lwnd;
    char **dirlist = NULL;

    CreatePath(path, fspec, TRUE, TRUE);
    if (ct != NULL)    {
        lwnd = ct->wnd;
        SendMessage(ct->wnd, CLEARTEXT, 0, 0);

        if (attrib & 0x8000)    {
            union REGS regs;
            char drname[15];
            unsigned int cd, dr;

            cd = getdisk();
            for (dr = 0; dr < 26; dr++)    {
                unsigned ndr;
                setdisk(dr);
                ndr = getdisk();
                if (ndr == dr)    {
                    /* ----- test for remapped B drive ----- */
                    if (dr == 1)    {
                        regs.x.ax = 0x440e; /* IOCTL func 14 */
                        regs.h.bl = dr+1;
                        int86(DOS, ®s, ®s);
                        if (regs.h.al != 0)
                            continue;
                    }
                    sprintf(drname, "[%c:]", dr+'A');

                    /* ---- test for network or RAM disk ---- */
                    regs.x.ax = 0x4409;     /* IOCTL func 9 */
                    regs.h.bl = dr+1;
                    int86(DOS, ®s, ®s);
                    if (!regs.x.cflag)    {
                        if (regs.x.dx & 0x1000)
                            strcat(drname, " (Network)");
                        else if (regs.x.dx == 0x0800)
                            strcat(drname, " (RAMdisk)");
                    }
                    SendMessage(lwnd,ADDTEXT,(PARAM)drname,0);
                }
            }
            setdisk(cd);
        }
        while (criterr == 1)    {
            ax = findfirst(path, &ff, attrib & 0x3f);
            criterr = TestCriticalError();
        }
        if (criterr)
            return FALSE;
        while (ax == 0)    {
            if (!((attrib & 0x4000) &&
                    (ff.ff_attrib & (attrib & 0x3f)) == 0) &&
                        strcmp(ff.ff_name, "."))    {
                char fname[15];
                sprintf(fname, (ff.ff_attrib & 0x10) ?
                                "[%s]" : "%s" , ff.ff_name);
                dirlist = DFrealloc(dirlist,
                                    sizeof(char *)*(i+1));
                dirlist[i] = DFmalloc(strlen(fname)+1);
                if (dirlist[i] != NULL)
                    strcpy(dirlist[i], fname);
                i++;
            }
            ax = findnext(&ff);
        }
        if (dirlist != NULL)    {
            int j;
            /* -- sort file/drive/directory list box data -- */
            qsort(dirlist, i, sizeof(void *), dircmp);
            /* ---- send sorted list to list box ---- */
            for (j = 0; j < i; j++)    {
                SendMessage(lwnd,ADDTEXT,(PARAM)dirlist[j],0);
                free(dirlist[j]);
            }
            free(dirlist);
        }
        SendMessage(lwnd, SHOW_WINDOW, 0, 0);
    }
    if (pathid)    {
        fnmerge(path, drive, dir, NULL, NULL);
        PutItemText(wnd, pathid, path);
    }
    return TRUE;
}





<a name="023a_000d">
<a name="023a_000e">
[LISTING TWO]
<a name="023a_000e">

/* ----------- fileopen.c ------------- */
#include "dflat.h"

static BOOL DlgFileOpen(char *, char *, DBOX *);
static int DlgFnOpen(WINDOW, MESSAGE, PARAM, PARAM);
static void InitDlgBox(WINDOW);
static void StripPath(char *);
static BOOL IncompleteFilename(char *);

static char *OrigSpec;
static char *FileSpec;
static char *FileName;
static char *NewFileName;

static BOOL Saving;
extern DBOX FileOpen;
extern DBOX SaveAs;

/* ----  Dialog Box to select a file to open ---- */
BOOL OpenFileDialogBox(char *Fpath, char *Fname)
{
    return DlgFileOpen(Fpath, Fname, &FileOpen);
}
/* ----  Dialog Box to select a file to save as ---- */
BOOL SaveAsDialogBox(char *Fname)
{
    return DlgFileOpen(NULL, Fname, &SaveAs);
}
/* --------- generic file open ---------- */
static BOOL DlgFileOpen(char *Fpath, char *Fname, DBOX *db)
{
    BOOL rtn;
    char savedir[80];
    char OSpec[80];
    char FSpec[80];
    char FName[80];
    char NewFName[80];

    OrigSpec = OSpec;
    FileSpec = FSpec;
    FileName = FName;
    NewFileName = NewFName;

    getcwd(savedir, sizeof savedir);
    if (Fpath != NULL)    {
        strncpy(FileSpec, Fpath, 80);
        Saving = FALSE;
    }
    else    {
        *FileSpec = '\0';
        Saving = TRUE;
    }
    strcpy(FileName, FileSpec);
    strcpy(OrigSpec, FileSpec);

    if ((rtn = DialogBox(NULL, db, TRUE, DlgFnOpen)) != FALSE)
        strcpy(Fname, NewFileName);
    else
        *Fname = '\0';

    setdisk(toupper(*savedir) - 'A');
    chdir(savedir);

    return rtn;
}
static int CommandMsg(WINDOW wnd, PARAM p1, PARAM p2)
{
    switch ((int) p1)    {
        case ID_FILENAME:
            if (p2 != ENTERFOCUS)    {
                /* allow user to modify the file spec */
                GetItemText(wnd, ID_FILENAME, FileName, 65);
                if (IncompleteFilename(FileName) || Saving)    {
                    strcpy(OrigSpec, FileName);
                    StripPath(OrigSpec);
                }
                if (p2 != LEAVEFOCUS)
                    SendMessage(wnd, COMMAND, ID_OK, 0);
            }
            return TRUE;
        case ID_OK:
            if (p2 != 0)
                break;
            GetItemText(wnd, ID_FILENAME,
                    FileName, 65);
            strcpy(FileSpec, FileName);
            if (IncompleteFilename(FileName))    {
                /* no file name yet */
                InitDlgBox(wnd);
                strcpy(OrigSpec, FileSpec);
                return TRUE;
            }
            else    {
                GetItemText(wnd, ID_PATH, FileName, 65);
                strcat(FileName, FileSpec);
                strcpy(NewFileName, FileName);
            }
            break;
        case ID_FILES:
            switch ((int) p2)    {
                case ENTERFOCUS:
                case LB_SELECTION:
                    /* selected a different filename */
                    GetDlgListText(wnd, FileName, ID_FILES);
                    PutItemText(wnd, ID_FILENAME, FileName);
                    break;
                case LB_CHOOSE:
                    /* chose a file name */
                    GetDlgListText(wnd, FileName, ID_FILES);
                    SendMessage(wnd, COMMAND, ID_OK, 0);
                    break;
                default:
                    break;
            }
            return TRUE;
        case ID_DRIVE:
            switch ((int) p2)    {
                case ENTERFOCUS:
                    if (Saving)
                        *FileSpec = '\0';
                    break;
                case LEAVEFOCUS:
                    if (Saving)
                        strcpy(FileSpec, FileName);
                    break;
                case LB_SELECTION:    {
                    char dd[25];
                    /* selected different drive/dir */
                    GetDlgListText(wnd, dd, ID_DRIVE);
                    if (*(dd+2) == ':')
                        *(dd+3) = '\0';
                    else
                        *(dd+strlen(dd)-1) = '\0';
                    strcpy(FileName, dd+1);
                    if (*(dd+2) != ':' && *OrigSpec != '\\')
                        strcat(FileName, "\\");
                    strcat(FileName, OrigSpec);
                    if (*(FileName+1)!=':'&&*FileName!='.')    {
                        GetItemText(wnd,ID_PATH,FileSpec,65);
                        strcat(FileSpec, FileName);
                    }
                    else
                        strcpy(FileSpec, FileName);
                    break;
                }
                case LB_CHOOSE:
                    /* chose drive/dir */
                    if (Saving)
                        PutItemText(wnd, ID_FILENAME, "");
                    InitDlgBox(wnd);
                    return TRUE;
                default:
                    break;
            }
            PutItemText(wnd, ID_FILENAME, FileSpec);
            return TRUE;
        default:
            break;
    }
    return FALSE;
}
/* ----   Process File Open dialog box messages ---- */
static int DlgFnOpen(WINDOW wnd,MESSAGE msg,PARAM p1,PARAM p2)
{
    switch (msg)    {
        case CREATE_WINDOW:    {
            int rtn = DefaultWndProc(wnd, msg, p1, p2);
            DBOX *db = wnd->extension;
            WINDOW cwnd = ControlWindow(db, ID_FILENAME);
            SendMessage(cwnd, SETTEXTLENGTH, 64, 0);
            return rtn;
        }
        case INITIATE_DIALOG:
            InitDlgBox(wnd);
            break;
        case COMMAND:
            if (CommandMsg(wnd, p1, p2))
                return TRUE;
            break;
        default:
            break;
    }
    return DefaultWndProc(wnd, msg, p1, p2);
}
/* ----   Initialize the dialog box ---- */
static void InitDlgBox(WINDOW wnd)
{
    if (*FileSpec && !Saving)
        PutItemText(wnd, ID_FILENAME, FileSpec);
    if (DlgDirList(wnd, FileSpec, ID_FILES, ID_PATH, 0))    {
        StripPath(FileSpec);
        DlgDirList(wnd, "*.*", ID_DRIVE, 0, 0xc010);
    }
}
/* ----  Strip drive and path information from file spec ---- */
static void StripPath(char *filespec)
{
    char *cp, *cp1;
    cp = strchr(filespec, ':');
    if (cp != NULL)
        cp++;
    else
        cp = filespec;
    while (TRUE)    {
        cp1 = strchr(cp, '\\');
        if (cp1 == NULL)
            break;
        cp = cp1+1;
    }
    strcpy(filespec, cp);
}
/* ---- test for an incomplete file name ---- */
static BOOL IncompleteFilename(char *s)
{
    int lc = strlen(s)-1;
    if (strchr(s, '?') || strchr(s, '*') || !*s)
        return TRUE;
    if (*(s+lc) == ':' || *(s+lc) == '\\')
        return TRUE;
    return FALSE;
}






<a name="023a_000f">
<a name="023a_0010">
[LISTING THREE]
<a name="023a_0010">

/* ---------------- statbar.c -------------- */
#include "dflat.h"

int StatusBarProc(WINDOW wnd, MESSAGE msg, PARAM p1, PARAM p2)
{
    char *statusbar;
    switch (msg)    {
        case CREATE_WINDOW:
        case MOVE:
            SendMessage(wnd, CAPTURE_CLOCK, 0, 0);
            break;
        case KEYBOARD:
            if ((int)p1 == CTRL_F4)
                return TRUE;
            break;
        case PAINT:
            if (!isVisible(wnd))
                break;
            statusbar = DFcalloc(1, WindowWidth(wnd)+1);
            memset(statusbar, ' ', WindowWidth(wnd));
            *(statusbar+WindowWidth(wnd)) = '\0';
            strncpy(statusbar+1, "F1=Help", 7);
            if (wnd->text)    {
                int len = min(strlen(wnd->text),
                    WindowWidth(wnd)-17);
                if (len > 0)    {
                    int off=(WindowWidth(wnd)-len)/2;
                    strncpy(statusbar+off, wnd->text, len);
                }
            }
            if (wnd->TimePosted)
                *(statusbar+WindowWidth(wnd)-8) = '\0';
            SetStandardColor(wnd);
            PutWindowLine(wnd, statusbar, 0, 0);
            free(statusbar);
            return TRUE;
        case BORDER:
            return TRUE;
        case CLOCKTICK:
            SetStandardColor(wnd);
            PutWindowLine(wnd,(char *)p1,WindowWidth(wnd)-8,0);
            wnd->TimePosted = TRUE;
            return TRUE;
        case CLOSE_WINDOW:
            SendMessage(NULL, RELEASE_CLOCK, 0, 0);
            break;
        default:
            break;
    }
    return BaseWndProc(STATUSBAR, wnd, msg, p1, p2);
}






<a name="023a_0011">
<a name="023a_0012">
[LISTING FOUR]
<a name="023a_0012">

/* ------------------- htree.h -------------------- */
#ifndef HTREE_H
#define HTREE_H

typedef unsigned int BYTECOUNTER;

/* ---- Huffman tree structure for building ---- */
struct htree    {
    BYTECOUNTER cnt;        /* character frequency         */
    int parent;             /* offset to parent node       */
    int right;              /* offset to right child node  */
    int left;               /* offset to left child node   */
};
/* ---- Huffman tree structure in compressed file ---- */
struct htr    {
    int right;              /* offset to right child node  */
    int left;               /* offset to left child node   */
};
extern struct htr *HelpTree;
void buildtree(void);
FILE *OpenHelpFile(void);
void HelpFilePosition(long *, int *);
void *GetHelpLine(char *);
void SeekHelpLine(long, int);

#endif





<a name="023a_0013">
<a name="023a_0014">
[LISTING FIVE]
<a name="023a_0014">

/* ------------------- htree.c -------------------- */

#include "dflat.h"
#include "htree.h"

struct htree *ht;
int root;
int treect;

/* ------ build a Huffman tree from a frequency array ------ */
void buildtree(void)
{
    int i;
    treect = 256;
    /* ---- preset node pointers to -1 ---- */
    for (i = 0; i < treect; i++)    {
        ht[i].parent = -1;
        ht[i].right  = -1;
        ht[i].left   = -1;
    }
    /* ---- build the huffman tree ----- */
    while (1)   {
        int h1 = -1, h2 = -1;
        /* ---- find the two lowest frequencies ---- */
        for (i = 0; i < treect; i++)   {
            if (i != h1) {
                struct htree *htt = ht+i;
                /* --- find a node without a parent --- */
                if (htt->cnt > 0 && htt->parent == -1)   {
                    /* ---- h1 & h2 -> lowest nodes ---- */
                    if (h1 == -1 || htt->cnt < ht[h1].cnt) {
                        if (h2 == -1 || ht[h1].cnt < ht[h2].cnt)
                            h2 = h1;
                        h1 = i;
                    }
                    else if (h2 == -1 || htt->cnt < ht[h2].cnt)
                        h2 = i;
                }
            }
        }
        /* --- if only h1 -> a node, that's the root --- */
        if (h2 == -1) {
            root = h1;
            break;
        }
        /* --- combine two nodes and add one --- */
        ht[h1].parent = treect;
        ht[h2].parent = treect;
        ht = realloc(ht, (treect+1) * sizeof(struct htree));
        if (ht == NULL)
            break;
        /* --- the new node's frequency is the sum of the two
            nodes with the lowest frequencies --- */
        ht[treect].cnt = ht[h1].cnt + ht[h2].cnt;
        /* - the new node points to the two that it combines */
        ht[treect].right = h1;
        ht[treect].left = h2;
        /* --- the new node has no parent (yet) --- */
        ht[treect].parent = -1;
        treect++;
    }
}





<a name="023a_0015">
<a name="023a_0016">
[LISTING SIX]
<a name="023a_0016">

/* ------------------- huffc.c -------------------- */
#include "dflat.h"
#include "htree.h"

extern struct htree *ht;
extern int root;
extern int treect;
static int lastchar = '\n';

static void compress(FILE *, int, int);
static void outbit(FILE *fo, int bit);

static int fgetcx(FILE *fi)
{
    int c;
    /* ------- bypass comments ------- */
    if ((c = fgetc(fi)) == ';' && lastchar == '\n')
        do    {
            while (c != '\n' && c != EOF)
                c = fgetc(fi);
        } while (c == ';');
    lastchar = c;
    return c;
}
void main(int argc, char *argv[])
{
    FILE *fi, *fo;
    int c;
    BYTECOUNTER bytectr = 0;

    if (argc < 3)   {
        printf("\nusage: huffc infile outfile");
        exit(1);
    }
    if ((fi = fopen(argv[1], "rb")) == NULL)    {
        printf("\nCannot open %s", argv[1]);
        exit(1);
    }
    if ((fo = fopen(argv[2], "wb")) == NULL)    {
        printf("\nCannot open %s", argv[2]);
        fclose(fi);
        exit(1);
    }
    ht = calloc(256, sizeof(struct htree));

    /* - read the input file and count character frequency - */
    while ((c = fgetcx(fi)) != EOF)   {
        c &= 255;
        ht[c].cnt++;
        bytectr++;
    }
    /* ---- build the huffman tree ---- */
    buildtree();
    /* --- write the byte count to the output file --- */
    fwrite(&bytectr, sizeof bytectr, 1, fo);
    /* --- write the tree count to the output file --- */
    fwrite(&treect, sizeof treect, 1, fo);
    /* --- write the root offset to the output file --- */
    fwrite(&root, sizeof root, 1, fo);
    /* -- write the tree to the output file -- */
    for (c = 256; c < treect; c++)   {
        int lf = ht[c].left;
        int rt = ht[c].right;
        fwrite(&lf, sizeof lf, 1, fo);
        fwrite(&rt, sizeof rt, 1, fo);
    }
    /* ------ compress the file ------ */
    fseek(fi, 0L, 0);
    while ((c = fgetcx(fi)) != EOF)
        compress(fo, (c & 255), 0);
    outbit(fo, -1);
    fclose(fi);
    fclose(fo);
    free(ht);
    exit(0);
}
/* ---- compress a character value into a bit stream ---- */
static void compress(FILE *fo, int h, int child)
{
    if (ht[h].parent != -1)
        compress(fo, ht[h].parent, h);
    if (child)  {
        if (child == ht[h].right)
            outbit(fo, 0);
        else if (child == ht[h].left)
            outbit(fo, 1);
    }
}
static char out8;
static int ct8;
/* -- collect and write bits to the compressed output file -- */
static void outbit(FILE *fo, int bit)
{
    if (ct8 == 8 || bit == -1)  {
        while (ct8 < 8)    {
            out8 <<= 1;
            ct8++;
        }
        fputc(out8, fo);
        ct8 = 0;
    }
    out8 = (out8 << 1) | bit;
    ct8++;
}





<a name="023a_0017">
<a name="023a_0018">
[LISTING SEVEN]
<a name="023a_0018">

/* ------------------- decomp.c -------------------- */
/* Decompress the application.HLP file or load the application.TXT file if
 * the .HLP file does not exist */

#include "dflat.h"
#include "htree.h"

static int in8;
static int ct8 = 8;
static FILE *fi;
static BYTECOUNTER bytectr;
static int LoadingASCII;
struct htr *HelpTree;
static int root;

/* ------- open the help database file -------- */
FILE *OpenHelpFile(void)
{
    char *cp;
    int treect, i;
    char helpname[65];
    /* -------- get the name of the help file ---------- */
    BuildFileName(helpname, ".hlp");
    if ((fi = fopen(helpname, "rb")) == NULL)    {
        /* ---- no .hlp file, look for .txt file ---- */
        if ((cp = strrchr(helpname, '.')) != NULL)    {
            strcpy(cp, ".TXT");
            fi = fopen(helpname, "rt");
        }
        if (fi == NULL)
            return NULL;
        LoadingASCII = TRUE;
    }
    if (!LoadingASCII && HelpTree == NULL)    {
        /* ----- read the byte count ------ */
        fread(&bytectr, sizeof bytectr, 1, fi);
        /* ----- read the frequency count ------ */
        fread(&treect, sizeof treect, 1, fi);
        /* ----- read the root offset ------ */
        fread(&root, sizeof root, 1, fi);
        HelpTree = DFcalloc(treect-256, sizeof(struct htr));
        /* ---- read in the tree --- */
        for (i = 0; i < treect-256; i++)    {
            fread(&HelpTree[i].left,  sizeof(int), 1, fi);
            fread(&HelpTree[i].right, sizeof(int), 1, fi);
        }
    }
    return fi;
}
/* ----- read a line of text from the help database ----- */
void *GetHelpLine(char *line)
{
    int h;
    if (LoadingASCII)    {
        void *hp;
        do
            hp = fgets(line, 160, fi);
        while (*line == ';');
        return hp;
    }
    *line = '\0';
    while (TRUE)    {
        /* ----- decompress a line from the file ------ */
        h = root;
        /* ----- walk the Huffman tree ----- */
        while (h > 255)    {
            /* --- h is a node pointer --- */
            if (ct8 == 8)   {
                /* --- read 8 bits of compressed data --- */
                if ((in8 = fgetc(fi)) == EOF)    {
                    *line = '\0';
                    return NULL;
                }
                ct8 = 0;
            }
            /* -- point to left or right node based on msb -- */
            if (in8 & 0x80)
                h = HelpTree[h-256].left;
            else
                h = HelpTree[h-256].right;
            /* --- shift the next bit in --- */
            in8 <<= 1;
            ct8++;
        }
        /* --- h < 255 = decompressed character --- */
        if (h == '\r')
            continue;    /* skip the '\r' character */
        /* --- put the character in the buffer --- */
        *line++ = h;
        /* --- if '\n', end of line --- */
        if (h == '\n')
            break;
    }
    *line = '\0';    /* null-terminate the line */
    return line;
}
/* --- compute the database file byte and bit position --- */
void HelpFilePosition(long *offset, int *bit)
{
    *offset = ftell(fi);
    if (LoadingASCII)
        *bit = 0;
    else    {
        if (ct8 < 8)
            --*offset;
        *bit = ct8;
    }
}
/* -- position the database to the specified byte and bit -- */
void SeekHelpLine(long offset, int bit)
{
    int i;
    fseek(fi, offset, 0);
    if (!LoadingASCII)    {
        ct8 = bit;
        if (ct8 < 8)    {
            in8 = fgetc(fi);
            for (i = 0; i < bit; i++)
                in8 <<= 1;
        }
    }
}













Copyright © 1992, 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.