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


MAR96: C PROGRAMMING

In his article "Visual Programming in 3-D" (DDJ, December 1995), Marc Najork writes about "Cube," his tiny, visual programming environment. If you missed that article, I urge you to find a copy and read it. Cube is a 3-D visual programming model and environment, albeit a simple one. Pay close attention to its message because Cube foreshadows what programming will be like early in the 21st century. Something resembling Cube--something more complex and more comprehensive--will be a major computer programming model.

In the 50-odd years that digital computer programming has been a significant human endeavor, the activity has seen numerous shifts in its approach, from hard-wired plug boards and patch panels to object-oriented and quasi-visual programming models. Each shift attempted to raise the programmer's level of abstraction farther from the hardware and closer to the problem being solved; each one was determined to provide an expression of the solution that more closely suggested the problem. Some failed; some endured. Yet through all that change, one thing stayed the same; programmers remained essentially stuck with a single medium of expression, one characterized by flat, two-dimensional, textual representations of objects and algorithms. Source code, we call it. Early on, when the von Neumann stored-program model was recognized as the obvious way to make computers solve problems, programmers began almost immediately to search for better and easier ways to apply that model. Programming thus evolved from cryptic, textual, machine-language source code, written and read top-down and serially to cryptic, textual, object-oriented, high-level source code, written and read top-down and serially.

Indeed there have been and are other ways to represent programs--flow charts, structure charts, HIPO charts, schema diagrams, CRC cards, Booch diagrams, and the like. But none of these expressions could be automatically translated into executable code. They were meant instead to assist us with our objective, which is the writing of source-code expressed as structurally indented rows of digits, letters, and symbols written on a flat plane. This pervasive source-code programming model reflects thousands of years of convention in the communication of ideas; the format is prescribed and constrained by both the flat medium--cave walls, papyrus, paper, video screen--and the message--procedural, serial thought, one thing at a time, expressed and regarded in a start-to-finish, top-to-bottom fashion.

Even the engineers on Star Trek TNG write their programs on flat panels.

But the flat model just won't do for much longer. As the computer gains power and the network becomes the operating system, concurrency and parallelism will take a more essential role in software. The computer can no longer be treated as if its attention span resembles that of the conscious human mind, doing one thing at a time, in a prescribed order, interrupting one thread only to process another. Artificial intelligence never realized its full potential, and one reason is that we never provided the technology with the essential component that enables real intelligence: subconscious thought and process.

Once we free ourselves from that notion--that computer programs must be written and read like prose and equations and must resemble our own limited conscious ability to think serially--we can see the third dimension in software. And what will free us from these mental bonds? The hardware will. The exponential growth of computing power on the desk of self-directing individuals with vision will not only enable but will compel research in these areas. It has already begun. Someday, we will be able to deal with software as we deal with the universe, moving about in three dimensions and interacting with its objects, some above us, some below us, some behind, some in front, all doing different things, doing them simultaneously, some related to others, some not.

Predicting the future in print invites ridicule; the prediction remains a matter of permanent record regardless of its accuracy. I'll take that chance and be in some good company. In 10 years--15, tops--you will develop software from a virtual reality environment. You will meander in three dimensions throughout your program, rummaging among and examining graphical components to reveal and modify their behavior. Data conduits will connect components to pass arguments and results. Source, transform, and sink. This network of components and connections--the program--will look more like a molecular structure than like the video printouts of today. You will be able to view the implementation, behavior, and interface of a component at any of several levels of abstraction, depending on your mission and your interest. You will explode components into their assemblies and subassemblies to see what makes them tick. You will collapse your view of assemblies into component views and treat them as trusted boxes once you see that they work properly. At the highest level of abstraction, your program will be collapsed into one component with its input and output pipes connected to its external devices and data files.

The work to realize this fantasy is already underway.

Will such 3-D visual programming environments eliminate the need for programmers to understand source code as we know it today? Not likely. C++ and Visual Basic have not eliminated assembly language. Or machine language, for that matter. In the future, when all else fails, you will look at an occasional memory dump, stack pointer, and interrupt vector, just as you do today. At levels of abstraction close to the bottom of a component you will be permitted to view the conventional source code that the 3-D visual development environment generates to implement the component. The source code will be of a language that can be interpreted and compiled. You will try at every step to avoid going that low for a looksee. Many of your younger colleagues will not know how. The hidden language could look very much like C++.

Quincy 96, Chapter 2

Return with us now to those thrilling days of yesteryear--er, today. Last month, I launched the Quincy 96 project, a Windows 95 integrated development environment for the GNU C and C++ compiler systems. I said then that there was no debugger yet but that I was working on one. This month, the debugger is partially completed, and I'll tell you about my experience so far with that part of the project. I'm using this project as a deep learning experience with Visual C++ 4.0 and the Microsoft Foundation Classes (MFC). Last month, I began a list of design patterns about that platform, and I'll continue that discussion, too.

The Quincy 96 Debugger

Quincy 96 is an MDI editor that launches the GNU compiler to compile and link C and C++ programs. There's nothing unique about that. You can get some really good Windows programmer's editors that do the same thing. RimStar from RimStar Technology ([email protected] or 603-778-2500) and Visual SlickEdit from MicroEdge (http://www.slickedit.com or 800-934-3348) are two examples. Both packages are Windows-hosted MDI programmer's editors with C-like macro languages, and both allow you to launch the compiler of your choice from within the editor. I like both of them. I'd be hard-pressed to choose a favorite. (This project involved a lot of research. If it seems like I'm plugging a lot of products and books in this column, it's because those resources made the project possible.)

I'm developing Quincy 96 as a training tool, one that I can distribute royalty-free on a CD-ROM. It is launched from within an interactive tutorial that specifies what program to load, which source file and line number to display, what breakpoints and watch variables to set, and so on. These requirements imply OLE automation, which I will address in a future column after I've figured it out, and an integrated debugger, a feature that programmer's editors do not usually include.

Writing a Source-Level Debugger

There are three things to learn if you want to roll your own debugger. First, how does a debugger work to interact with the program being debugged? Second, where does the executable program file store the necessary symbolic debugging information? Third, what is the format of that information? These three questions are answered by delving into the mysteries of three complex architectures: the Win32 SDK's debug API, the Win32 portable executable file format, and the GNU symbolic table (stab) system for encoding debug information.

Inside the Debugger

You can debug a program at the assembly-language level without any debugging information. The MS-DOS DEBUG program does that, allowing breakpoints and single-stepping, all of which implies that the hardware must be cooperating. It is. Back when my brother Fred and I were building embedded systems with 4-MHz Z-80s, I used a homebrew debugger that plugged interrupt op codes into the instruction stream to generate breakpoints.

Nothing has changed. That's how you debug a program on a Pentium. The x86 architecture includes software interrupts. The 1-byte op code 0xCC is the INT 03 instruction, reserved for debuggers. You can put the INT 03 op code in place of the program's instruction op code where the break is to occur and replace the original op code at the time of the interrupt. In the 386 and later, you can set a register flag that tells the processor to generate an unintrusive INT 01 instruction for every machine instruction executed. That device supports single stepping. The INT 01 and 03 information and everything else you want to know about interrupts comes from Ralf Brown and Jim Kyle's invaluable PC Interrupts, Second Edition (Addison-Wesley, 1994).

Now, let's raise ourselves up a notch or two in the levels of abstraction. The Quincy 96 debugger is a Windows 95 32-bit GUI program. At the time I was planning this project, I had no idea how to connect such a program to interrupt vectors, but I figured it would be hairy and was mulling it over when I happened by the Nu-Mega Technologies (http://www.numega.com) booth at the Software Development '95 East conference. Frank Grossman, the MFWIC ("main fellow who's in charge") at Nu-Mega, was demonstrating BoundsChecker for Windows. (Another plug: You should not write another Windows program without having this tool. It's like air conditioning; once you've had it, you can't do without it.) Frank used one of his own programs for a demo. I spotted a call to a WaitForDebugEvent function in the source code and asked him what it was. As it turns out, the Win32 SDK includes functions that allow one program to launch another program and debug it. You can forget about how the interrupts and interrupt vectors get managed. The SDK's debug API takes care of all that.

A debugger program launches a program to be debugged by calling the CreateProcess function, specifying in an argument that the program is to be debugged. Then the debugger program enters a loop to run the program. At the top of the loop, the debugger calls WaitForDebugEvent. Each time WaitForDebugEvent returns, it sets indicators that tell about the event that suspended the program being debugged. This is where the debugger traps breakpoints and single-step exceptions. WaitForDebugEvent fills in an event structure that contains the address that was interrupted, the event that caused the interrupt, and so on. The debugger calls GetThreadContext to get the running context of the debugged program, including the contents of the registers. The debugger can, as the result of programmer interaction, modify these values and the contents of the debugged program's memory.

The debugger sets breakpoints by saving the op code at the instruction to be intercepted and putting the INT 03 op code in its place. When the breakpoint occurs, the debugger replaces the original op code in the program's instruction memory, and decrements the interrupted program counter in the saved context so that execution resumes at the instruction that was broken.

To single step a program, the debugger sets a bit in the context's flags register that tells the processor to generate an INT 01 for every instruction cycle. When that interrupt occurs, the debugger checks to see if the interrupted address is at a new source-code line number. If not, the debugger continues execution. Otherwise, the debugger displays the new line in the IDE and waits for the programmer to take an action that resumes the program.

While the debugged program is suspended, the debugger interacts with the programmer and provides full access to the debugged program's context and memory. This access permits the programmer to examine and modify variables, which requires that the debugger know something about the program's source code, symbols, and memory organization. More about that later.

To resume the debugged program, the debugger resets the program's context by calling SetThreadContext and calls ContinueDebugEvent. Then, the debugger returns to the top of the loop to call WaitForDebugEvent again.

Listing One is a fragment that demonstrates this debugging procedure. Take note. The code that executes as a result of the EXCEPTION_BREAKPOINT and EXCEPTION_ SINGLE_STEP exceptions does not itself call the ResumeBrokenProgram and ResumeSteppingProgram functions. That logic would have the program getting deeper and deeper into its own stack. Those exceptions display the current program counter's source file and line number and then return from DebugApplicationProgram, which leaves the IDE in control and the debugged program suspended. Subsequent actions by the user call ResumeBrokenProgram and ResumeSteppingProgram, which themselves resume the program being debugged.

Debugging Information

Programs can be compiled with or without debug information. If you load a program that was compiled without debug information into a source-level debugger, a typical stand-alone debugger will display the unassembled machine code of the program and then politely tell you that there is no symbolic information in the executable file. When you compile debugging information into the program, however, the executable file includes information that relates symbols to memory addresses and memory addresses to source-code files and line numbers. Given a path to the source code, the debugger can then properly display the program's source code and variable values as you step through the program's execution.

A program compiled with debugging information takes no run-time performance hit if it is not being debugged. The debugging information is stored in special sections in the executable file, and these sections are not loaded when DOS loads the program to execute outside of a debugger. There is nothing added to the executable code itself to support the debugger. Therefore, the debugger must read and interpret the debugging information from the debugged program's .EXE file before launching the program.

The Win32 Portable Executable File Format

To extract debug information from a Win32 executable file, you must understand the format of that file. I started out by knowing only that the data values were in there somewhere. After a bit of simple reverse engineering (consisting of poring over an ASCII HEX dump of a small executable file produced with the -g option from the GCC compiler) I learned that the executable file has two sections not found in other executable files. Those two sections are named ".stab" and ".stabstr." How nice that they used names that suggest their purpose. Otherwise, I would still be searching. I knew that .stab and .stabstr were so-called "sections" because I found them in what appeared to be a table of fixed-length entries that included entries for .text, .bss, .data, and .idata. I knew from previous experience that those things are sections into which compilers put different parts of a program. A quick run of Borland's TDUMP utility program against the executable verified those findings.

The header part of the executable file did not match my earlier understanding of that format, and so I dug into Andrew Schulman's Unauthorized Windows 95 (IDG Books, 1995) to learn something about the new Win32 portable executable file format. Recently, I got a copy of Matt Pietrek's Windows 95 System Programming Secrets (IDG Books, 1995) and learned a good bit more about portable executables. If Matt's book had been here a few days sooner, it could have saved me a lot of time, not only in learning about the format of executable files but in understanding the details of the SDK debug API. I highly recommend this book to anyone interested in systems programming and Windows 95. This month's "Programmer's Bookshelf," by Lou Grinzo (page 127) discusses Matt's book in more detail.

GNU Stab Information

There are several different formats for encoding debug information in an executable file. Borland's Turbo Debugger uses one format. Microsoft's CodeView uses another. The GNU C/C++ compiler, being implemented on many platforms, uses at least two different formats, maybe more, depending on which port you have. The gnu-win32 port from Cygnus is the one that Quincy 96 launches, and it uses the stab format, which is apparently an acronym meaning "symbol table," although the table contains much more than just symbol information.

The .stab section in a portable executable file is a table of fixed-length entries that represent debugging information in the stab format. The .stabstr section contains variable-length, null-terminated strings into which the .stab table entries point.

The documentation for the stab format is available in text and .inf formats on the Cygnus ftp site (ftp.cygnus.com//pub/gnu-win32). That document explains that the compiler reacts to its -g option by inserting macros into the assembly-language file. The assembler translates the macros into stab entries in the .stab and .stabstr sections of the object file. The linker combines the sections from the object files into two common sections in the executable file.

Stabs contain, in a most cryptic format, the names and characteristics of all intrinsic and user-defined types, the memory address of every symbol in external memory and on the stack, the program counter address of every function, the program counter address where every brace-surrounded statement block starts and ends, the memory address of line numbers within source-code files, and anything else that a debugger needs. The format is complex and cryptic because it is intended to support any source-code language. It is the responsibility of a debugger program to translate the stab entries into something meaningful to the debugger in the language being debugged.

There is a strange bug in the GNU compiler that has been tolerated for a long time because somebody built a workaround for it in the GNU debugger. The bug assigns zero memory addresses to external, nonstatic symbols in the macros that the compiler emits in the assembly-language file. Apparently, the GNU debugger figures out what the address should be from the symbol's characteristics, its position among the stab macros, and the sizes of the other variables that surround it. That looked like a daunting algorithm to me. Rather than spending forever getting that to work in my debugger, I inserted a filter into the compile stream. The filter reads the assembly-language file and replaces the zero in each offending macro with the name of its global variable. I wonder why the GNU hackers didn't think of that.

I cannot begin to describe the stab format to you in this column. If it interests you, read the document that Cygnus distributes. My efforts to date have produced a Windows 95 GUI source-level debugger that can set breakpoints, intercept breakpoints, single step, and examine, modify and watch variables of intrinsic types.

The Quincy 96 debugger cannot yet examine or watch pointers, arrays, or structure and class members. Doing that involves first parsing the stab entries that describe those things and then implementing an expression parsing algorithm that translates C/C++ expressions that include symbols, integer constants, arithmetic operators, address-of operators, pointer operators, structure member operators, and subscript operators into memory addresses. The old Quincy interpreter already has such a parser, and I intend to adapt it to this project. By the time this column is published, I will have everything working.

About GDB

The GNU compiler includes its own debugger named "gdb." Gdb runs from the command line and is a line-oriented source-code debugger. The source code is available, and I learned some of what I needed to know by reading the gdb code. After all, gdb does most of the low-level stuff that I need to do in the Quincy 96 debugger. I would have liked to have used some of gdb's source code in implementing my own debugger. That would have made Quincy 96 a "derivative work" in the language of the GNU license, and I want to avoid that association for reasons that I explained last month. But there is another reason not to go that route. Gdb is a portable, platform- and language-independent debugger, and its code tends to be difficult to read. There are many levels of indirection through #define macros and tables all of which are designed to support several platform-specific portability layers for the particular implementation.

Then there is the code itself. The gdb programmers use a bewildering indent-outdent style in the code that adds to the arcane quality of this program. Before anyone gets testy, let me add that my opinion is a personal observation not meant to start any religious wars. Anyone who disagrees with me has that right and should do so. There is no one right way to code C.

Gdb is an impressive piece of programming, but I was certain that I'd spend less time and learn more if I ignored gdb and went my own way. Every time I peeked into its code to try to unearth a solution to some new mystery, that certainty was reinforced. Finally, I deleted the gdb source code from my hard disk so that I'd stop wasting time that way.

Debugging Console Applications

Quincy 96 compiles, executes, and debugs C and C++ training exercises. It is not a Windows programming tool. Therefore, the programs that it launches are assumed to be console applications that use the stdio and iostream libraries for console input and output. A console program opens an MS-DOS window when it starts and closes that window when it exits. The console window is the equivalent of the screen and keyboard when you run a program from a DOS box. When you run such a program from Quincy 96 without stepping or breakpoints, the program runs to completion, and the MS-DOS window closes. You have no opportunity to view the output unless the program includes its own "Any key to continue" operation at the end. That won't do. To keep the MS-DOS window around after the program quits, Quincy 96 has to provide the console window and let the debugged program inherit it. Then, when the debugged program exits, Quincy 96 can display a message on the console or a dialog box for you to respond to before it closes the MS-DOS window.

A Windows 95 application creates and destroys a console window by calling the AllocConsole and FreeConsole functions. In between, there are three handles open for the standard input, output, and error devices, and the program can retrieve them by calling GetStdHandle. The CreateProcess function, which launches the application, includes arguments that allow the creating process to provide the standard devices to be inherited by the created process. By using this mechanism, Quincy 96 is able to retain the MS-DOS window and even write to it after the debugged program has shut down.

There are some side effects to this strategy. If you close the MS-DOS window with the mouse or system menu, that action terminates Quincy 96, too. If you had changed any files in the editor that were not saved, the closing action does not prompt you to save them. In an attempt to provide a more hospitable user interface, Quincy 96 tries to manage the position and condition of the MS-DOS window, but these actions are often ignored by Windows 95. There are ways for CreateProcess to control the console window of a program that provides its own console, but trying to manage your own AllocConsole window is unreliable. Either I'm missing something or Microsoft's developers did not give enough attention to the way that console applications work.

Displaying Tokens in the Margins of a CEditView Control

Quincy 96's IDE displays tokens in the left margin of the editor window to indicate breakpoints and the current program counter. Getting it to do that was no easy task. A CEditView window expects the edited text to be flush to the left margin. To make space for the tokens, I called the SetMargins function from the CTextView::Create function (in the editor's view class) as shown in Listing Two.

With a margin set, my next task was to get the program to write those tokens in the margin. After trying several different ways, I found that writing anything into the margin of a CEditView window involves writing text to the device context rather than to the CEdit control. Listing Three is the CTextView::WriteMarginCharacter function that I contrived to do that. I wanted to use a color bar for the program counter rather than a token, but that meant that I'd have to redisplay the line of code with different color settings. I had no problem changing colors but was never able to change the font from the default font of a CWnd object to the Courier 10 font that the editor uses. Everything I tried generated strange results, so I postponed the idea until I have more time to deal with it.

Managing Asynchronous Processes

Quincy 96 runs two kinds of programs. When debugging a program, Quincy 96 runs the program to be debugged and watches its progress through the WaitForDebugEvent function. When building a project, Quincy 96 runs the preprocessor, compiler, assembler, and linker programs in that order, starting each one only when the previous one has exited. Quincy 96 needs to sense that a program is still running and that it has stopped.

When you use CreateProcess to launch another program, that program takes off, and the launching program continues. The created process runs asynchronous to the creating program unless you are debugging and have called WaitForDebugEvent. When not debugging, you can periodically call the GetExitCodeProcess function, which returns an exit code of STILL_RUNNING while the program is still running. Quincy 96 cannot simply go into a loop waiting for that exit code because the Windows 95 event and message system would not permit any user interaction during that loop, and the user would not be able to stop the process if it was taking too long or running away.

Quincy 96 handles this matter by overriding the CWinApp::OnIdle function, which gets called by the system whenever nothing else is going on in the debugger program. That function calls GetExitCodeProcess to see if the current compiler program is still running, launching the next one as appropriate. Listing Four shows a fragment of that operation.

OnContextMenu

This discussion is about those menus that pop up when you click the right mouse button. I don't know what you're supposed to call them. I've seen them called popup menus, context menus, right-click menus, and shortcut menus. Whatever they're called, you have to use them to get Microsoft's blessing to put the Windows 95 logo on your work. Developer Studio provides such a menu for document views derived from CEditView. The popup-context-shortcut-right-click menu that they provide is a subset of the standard Edit menu. But I wanted to add one to Quincy 96's project document view, which is derived from CListView, and Developer Studio does not provide one for that.

When you follow the instructions in the VC++ online help and attach an OnContextMenu handler to a CListView-derived document view, it takes a double-click of the right mouse button to open the menu. It looks like a bug in VC++ 4.0 or MFC to me. To get the preferred behavior (single-click activation), I overrode OnContextMenu in the derived CMDIChildWnd class. Menus opened that way are somehow impervious to the global enabling and disabling of commands through OnUpdate functions in the document and view classes, so I had to repeat every test and use EnableMenuItem function calls for the context menu.

Next month I'll fill you in on how far I've gotten with Quincy 96. No doubt I'll have some more VC++ and MFC design patterns. I hope by then to have completed the debugger, and I plan to be well into the mechanisms that integrate Quincy 96's role as the development environment with the host interactive multimedia tutorial presentation.

Source Code

The source-code files for the Quincy 96 project are free. You can download them from the DDJ Forum on CompuServe and on the Internet by anonymous ftp; see "Availability," page 3. To run Quincy, you'll need the GNU Win32 executables from the Cygnus port. They can be found on ftp.cygnus.com//pub/sac.

If you cannot get to one of the online sources, send a 3.5 inch, high-density diskette and an addressed, stamped mailer to me at Dr. Dobb's Journal, 411 Borel Avenue, San Mateo, CA 94402, and I'll send you the Quincy source code (not the GNU stuff, however--it's too big). Make sure that you include a note that says which project you want. The code is free, but if you care to support my Careware charity, include a dollar for the Brevard County Food Bank.

Listing One

// ---- run an application program with the debugger
void CQuincyApp::RunApplicationProgram(CString& strCmd)
{
    // .... 
    CreateProcess(0, strCmd.GetBuffer(0), 0, 0, TRUE,
                  DEBUG_ONLY_THIS_PROCESS | DEBUG_PROCESS,
                  0, 0, &suinfo, &m_ProcessInformation );
    DebugApplicationProgram();
}
// ------ resume a program that was interrupted by a breakpoint
void CQuincyApp::ResumeBrokenProgram(BOOL bStep,
                            unsigned char cSaveInst)
{
    // ---- adjust the instruction pointer back to the 
    //      breakpoint (int 3) op code
    --m_Context.Eip;
    ResumeSteppingProgram(bStep);
}
// ------ resume a program that was interrupted by a single step
void CQuincyApp::ResumeSteppingProgram(BOOL bStepAgain)
{
    if (bStepAgain)
        m_Context.EFlags |= FLAG_TRACE_BIT;
    SetAllBreakpoints();
    SetThreadContext(m_hThread, &m_Context);
    ContinueDebugEvent(m_dwProcessId, m_dwThreadId, DBG_CONTINUE);
    DebugApplicationProgram();
}
// ----- debug an application program
void CQuincyApp::DebugApplicationProgram()
{
    while (!bDone)  {
        WaitForDebugEvent(&event, INFINITE);
        switch (event.dwDebugEventCode) 
        {
            case CREATE_PROCESS_DEBUG_EVENT:
                // startup procedures
                break;
            case EXIT_PROCESS_DEBUG_EVENT:
                // shutdown procedures
                break;
            case EXCEPTION_DEBUG_EVENT:
                // breakpoints and single-stepping
                m_Context.ContextFlags = CONTEXT_FULL;
                GetThreadContext(m_hThread, &m_Context);
                switch (event.u.Exception.ExceptionRecord.ExceptionCode)
                {
                    case EXCEPTION_BREAKPOINT:
                        // --- process breakpoints
                        break;
                    case EXCEPTION_SINGLE_STEP:
                        // --- process single steps
                        break;
                }
        }
        ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE);
    }
}

Listing Two

BOOL CTextView::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName,
                       DWORD dwStyle, const RECT& rect,
                       CWnd* pParentWnd, UINT nID,
                       CCreateContext* pContext) 
{
    BOOL rtn = CWnd::Create(lpszClassName, lpszWindowName, dwStyle,
                            rect, pParentWnd, nID, pContext);
    // ...
    CEdit& rEdit = GetEditCtrl();
    rEdit.SetMargins(13, 0);       // left margin for tokens
    return rtn;
}

Listing Three

// ----- writes a character in the editor's left margin 
void CTextView::WriteMarginCharacter(int nLine, int ch, COLORREF clr)
{
    char strch[] = "   ";
    strch[0] = ch;
    HideCaret();
    CDC* pCDC = GetDC();
    pCDC->SetTextColor(clr);
    pCDC->TextOut(0, nLine, strch, 3);
    ReleaseDC(pCDC);
    ShowCaret();
}

Listing Four

BOOL CQuincyApp::OnIdle(LONG lCount) 
{
    CWinApp::OnIdle(lCount);
    DWORD exitcode;
    if (m_CompileStatus != idle)  {
        GetExitCodeProcess(m_CompileInformation.hProcess, &exitcode);
        if (exitcode != STILL_ACTIVE)  {
            switch (m_CompileStatus)       {
                case preprocessing:
                    // finished preprocessing, launch the compiler ...
                    m_CompileStatus = compiling;
                    break;
                case compiling:
                    // finished compiling, launch the assembler ...
                    m_CompileStatus = assembling;
                    break;
                case assembling:
                    // finished assembling, launch the linker ...
                    m_CompileStatus = linking;
                    break;
                case linking:
                    // finished linking, project is built
                    m_CompileStatus = idle;
                    break;
            }
        }
    }
    return (m_CompileStatus !


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.