Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]-1

.pdf
Скачиваний:
108
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Debugging Techniques

}

void FunB()

{

Trace::Indent(); Trace::WriteLine(L”Starting FunB”); if(sw->TraceWarning)

Debug::WriteLine(L”FunB warning...”); FunC();

Trace::WriteLine(L”Ending FunB”); Trace::Unindent();

}

void FunC()

{

Trace::Indent(); Trace::WriteLine(L”Starting FunC”); if(sw->TraceError)

Debug::WriteLine(L”FunC error...”); Debug::Assert(value < 4); Trace::WriteLine(L”Ending FunC”); Trace::Unindent();

}

private: int value;

static TraceSwitch^ sw =

gcnew TraceSwitch(L”Trace Switch”, L”Controls trace output”);

};

int main(array<System::String ^> ^args)

{

// Direct output to the command line

TextWriterTraceListener^ listener = gcnew TextWriterTraceListener( Console::Out); Debug::Listeners->Add(listener);

Debug::IndentSize = 2;

// Set the indent size

array<TraceLevel>^ levels = { TraceLevel::Off,

TraceLevel::Error,

TraceLevel::Warning ,TraceLevel::Verbose};

TraceTest^ obj = gcnew TraceTest(0);

 

 

Console::WriteLine(L”Starting trace and debug test...”);

for each(TraceLevel level in levels)

 

 

{

 

 

obj->Level = level;

// Set level for messages

Console::WriteLine(L”\nTrace level is {0}”, obj->Level); obj->FunA();

}

return 0;

}

This example results in an assertion dialog being displayed during execution. You can then choose to continue or abort execution or retry the program in debug mode by selecting the appropriate button in the dialog.

609

Chapter 10

Depending on what you do when the program asserts, this example produces the following output:

Starting trace and debug test...

Trace level is Off

Starting FunA

Starting FunB

Starting FunC

Ending FunC

Ending FunB

Ending FunA

Trace level is Error

Starting FunA

Starting FunB

Starting FunC

FunC error...

Ending FunC

Ending FunB

Ending FunA

Trace level is Warning

Starting FunA

Starting FunB

FunB warning...

Starting FunC

FunC error...

Ending FunC

Ending FunB

Ending FunA

Trace level is Verbose

Starting FunA

FunA working...

Starting FunB

FunB warning...

Starting FunC

FunC error...

Fail:

Ending FunC

Ending FunB

Ending FunA

Press any key to continue . . .

How It Works

The TraceTest class defines three instance functions FunA(), FunB(), and FunC(). Each function contains a call to the Trace::Indent() function to increase indentation for debug output and Debug::WriteLine() function calls to track when the function is entered and exited. The Trace::Unindent() function is called immediately before exiting each function to restore the indent level to what it was when the function was called.

610

Debugging Techniques

The TraceTest class defines a private TraceSwitch member, sw, which controls what level of debug output is displayed. Each of the three member functions also calls the Debug::WriteLine() function to issue a debug message depending on the level set in the sw member of the class.

The FunA() function increments the value member of the class object each time it is called and the FunC() function asserts if value exceeds 3.

In main() you create a TextWriterTraceListener object that directs trace and debug output to the command line:

TextWriterTraceListener^ listener = gcnew TextWriterTraceListener( Console::Out);

You then add the listener object to the collection of listeners in the Debug class:

Debug::Listeners->Add(listener);

This causes debug and trace output to be directed to Console::Out, the standard output stream.

You create an array of TraceLevel objects that represent various control levels for debug and trace output:

array<TraceLevel>^ levels = { TraceLevel::Off, TraceLevel::Error, TraceLevel::Warning ,TraceLevel::Verbose};

After creating the TraceLevel object you set the trace levels for it in a for each loop:

for each(TraceLevel level in levels)

 

{

 

obj->Level = level;

// Set level for messages

Console::WriteLine(L”\nTrace level is {0}”, obj->Level); obj->FunA();

}

The level is set through the Level property for obj. This sets the Level property in the TraceSwitch member, sw, that is used in the instances functions to control output.

From the output you can see that the indent you set affects output from the static WriteLine() function in both the Debug and Trace classes. You can also see how the level that you set in the TraceSwitch class member affects the output. When the value member of the TraceTest class object, obj, reaches 4, the FunC() function asserts. You can rerun the example and try out the effects of the three buttons on the assertion dialog.

Summar y

Debugging is a big topic and Visual C++ 2005 provides many debugging facilities beyond what I have discussed here. If you are comfortable with what I have covered in this chapter, you should have little trouble expanding your knowledge of the debug capabilities through the Visual C++ 2005 documentation. Searching on “debugging” should generate a rich list of further information.

611

Chapter 10

The essential points introduced in this chapter were:

You can use the assert() library function that is declared in the <cassert> header to check logical conditions in your native C++ program that should always be true.

The preprocessor symbol, _NDEBUG, is automatically defined in the debug version of a native C++program. It is not defined in the release version.

You can add your own debugging code by enclosing it between an #ifdef/#endif pair of directives testing for _NDEBUG. Your debug code is then included only in the debug version of the program.

The crtdbg.h headers supplies declarations for functions to provide debugging of free store operations.

By setting the _crtDbgFlag appropriately, you can enable automatic checking of your program for memory leaks.

To direct output messages from the free store debugging functions, you call the

_CrtSetReportMode() and _CrtSetReportFile() functions.

Debugging operations using breakpoints and trace points in C++/CLI programs are exactly the same as in native C++ programs.

The Debug and Assert classes defined in the System::Diagnostics namespace provide functions for tracing execution and generating debugging output in CLR programs.

The static Assert() function in the Debug and Trace classes provides an assertion capability in CLR programs.

With the basics of debugging added to your knowledge of C++, you are ready for the big one: Windows programming!

612

11

Windows Programming

Concepts

In this chapter, you take a look at the basic ideas that are involved in every Windows program in C++. You’ll first develop a very simple example that uses the Windows operating system API directly. This will enable you to understand how a Windows application works behinds the scenes, which will be useful to you when you are developing applications using the more sophisticated facilities provided by Visual C++ 2005. You then see what you get when you create a Windows program using the Microsoft Foundation Classes, better known as the MFC. Finally, you’ll create a basic program using Windows Forms that will execute with the CLR, so by the end of the chapter you’ll have an idea of what each of the three approaches to developing a Windows application involves.

By the end of this chapter, you will have learned about:

The basic structure of a window

The Windows API is and how it is used

Windows messages and how you deal with them

The notation that is commonly used in Windows programs

The basic structure of a Windows program

How you create an elementary program using the Windows API and how it works

Microsoft Foundation Classes

The basic elements of an MFC-based program

Windows Forms

The basic elements of a Windows Forms application

Chapter 11

Windows Programming Basics

With Visual C++ 2005 you have three basic ways of creating an interactive Windows application:

Using the Windows API. This is the fundamental interface that the Windows operating system provides for communications between itself and the applications that are executing under its control.

Using the Microsoft Foundation Classes, better known as the MFC. This is a set of C++ classes that encapsulate the Windows API.

Using Windows Forms. This is a forms-based development mechanism for creating applications that executes with the CLR.

In a way these three approaches form a progression from the most programming intensive to the least programming intensive. With the Windows API you are writing code throughout — all the elements that make up the GUI for your application must be created programmatically. With MFC applications there’s some help with GUI build in that you can assemble controls on a dialog form graphically and just program the interactions with the user; however, you still are involved in a lot of coding. With a Windows Forms application you can build the complete GUI including the primary application window by assembling the controls that the user interacts with graphically. You just place the controls wherever you want them in a form window, and the code to create them is generated automatically. Using Windows Forms is by far the fastest and easiest mechanism for generating an application because the amount of code that you have to write is greatly reduced compared to the other two possibilities. The code for a Windows Forms application also gains all the benefits of executing with the CLR.

Using the MFC involves more programming effort than Windows Forms, but you have more control of how the GUI is created and you end up with a program that will execute natively on your PC. Because using the Windows API directly is the most laborious method for developing an application I won’t go into this in detail. However, you will put together a basic Windows API application so you’ll have an opportunity to understand the principles of the mechanism that all Windows applications use to work with the operating system under the covers. You’ll explore the fundamentals involved in all three possibilities for Windows application development in this chapter and investigate using MFC and Windows Forms in more detail later in the book. Of course, it also is possible to develop applications in C++ that do not require the Windows operating system, and games programs often take this approach when the ultimate in graphics performance is required. Although this is itself an interesting topic, it would require a whole book to do it justice so I won’t pursue this topic further.

Before you get into the examples in this chapter, I’ll review the terminology that is used to describe an application window. You have already created a Windows program in Chapter 1 without writing a single line of code yourself and I’ll use the window generated by this to illustrate the various elements that go to make up a window.

Elements of a Window

You will inevitably be familiar with most, if not all, of the principal elements of the user interface to a Windows program. However, I will go through them anyway just to be sure we have a common understanding of what the terms mean. The best way to understand what the elements of a window can be is to look at one. An annotated version of the window displayed by the example that you saw in Chapter 1 is shown in Figure 11-1.

614

 

 

 

 

 

 

 

 

 

 

Windows Programming Concepts

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Size grip

MDI parent window

 

 

 

Sizing border

MDI child window

 

 

Child window client area

Child window icon

 

Parent window client area

 

Toolbar Child window bar text

Close button

 

 

 

Menu bar

Title bar text

Maximize button

 

 

 

 

 

Title bar icon

 

Status bar

Minimize button

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Position (0,0)

increasing x

increasing y

Figure 11-1

The example actually generated two windows. The larger window with the menu and the tool bars is the main, or parent window, and the smaller window is a child window of the parent. Although the child window can be closed without closing the parent window by double-clicking the title bar icon that is in the upper-left corner of the child window, closing the parent window automatically closes the child window as well. This is because the child window is owned by, and dependent upon, the parent window. In general, a parent window may have a number of child windows, as you’ll see.

The most fundamental parts of a typical window are its border, the title bar that shows the name that you give to the window, the title bar icon that appear at the left end of the title bar, and the client area, which is the area in the center of the window not used by the title bar or borders. You can get all of these created for free in a Windows program. As you will see, all you have to do is provide some text for the title bar.

The border defines the boundary of a window and may be fixed or resizable. If the border is resizable, you can drag it to alter the size of the window. The window also may possess a size grip, which you can use to alter the size of a window while maintaining its aspect ratio — the ratio of the width to the height. When you define a window you can modify how the border behaves and appears if you want. Most windows will also have the maximize, minimize, and close buttons in the upper-right corner of the window. These allow the window to be increased to full screen size, reduced to an icon or closed.

615

Chapter 11

When you click the title bar icon with the left mouse button, it provides a standard menu for altering or closing the window called the system menu or control menu. The system menu also appears when you right-click the title bar of a window. Although it’s optional, it is a good idea always to include the title bar icon in any main windows that your program generates. Including the title bar icon provides you with a very convenient way of closing the program when things don’t work during debugging.

The client area is the part of the window where you usually want your program to write text or graphics. You address the client area for this purpose in exactly the same way as the yard that you saw in Figure 7-1 in Chapter 7. The upper-left corner of the client area has the coordinates (0, 0), with x increasing from left to right, and y increasing from top to bottom.

The menu bar is optional in a window but is probably the most common way to control an application. Each menu in the menu bar displays a drop-down list of menu items when you click it. The contents of a menu and the physical appearance of many objects that are displayed in a window, such as the icons on the toolbar that appear above, the cursor, and many others, are defined by a resource file. You will see many more resource files when we get to write some more sophisticated Windows programs.

The toolbar provides a set of icons that usually act as alternatives to the menu options that you use most often. Because they give a pictorial clue to the function provided, they can often make a program easier and faster to use.

I’ll mention a caveat about terminology that you need to be conscious of before I move on. Users tend to think of a window as the thing that appears on the screen with a border around it, and of course it is, but it is only one kind of window; however, in Windows a window is a generic term covering a whole range of entities. In fact almost any entity that is displayed is a window — for example, a dialog box is a window and each button is also a window. I will generally use terminology to refer to objects that describe what they are, buttons, dialogs, and so on, but you need to have tucked in the back of your mind that they are windows, too, because you can do things to them that you can do with a regular window — you can draw on a button for instance.

Windows Programs and the Operating System

When you write a Windows, your program is subservient to the operating system and Windows is in control. Your program must not deal directly with the hardware and all communications with the outside must pass through Windows. When you use a Windows program you are interacting primarily with Windows, which then communicates with the application program on your behalf. Your Windows program is the tail, Windows is the dog, and your program wags only when Windows tells it to.

There are a number of reasons why this is so. First and foremost, because your program is potentially always sharing the computer with other programs that may be executing at the same time, Windows has to have primary control to manage the sharing of machine resources. If one application was allowed to have primary control in a Windows environment this would inevitably make programming more complicated because of the need to provide for the possibility of other programs, and information intended for other applications could be lost. A second reason for Windows being in control is that Windows embodies a standard user interface and needs to be in charge to enforce that standard. You can only display information on the screen using the tools that Windows provides, and then only when authorized.

616

Windows Programming Concepts

Event-Driven Programs

You have already seen, in Chapter 1, that a Windows program is event-driven, so a Windows program essentially waits around for something to happen. A significant part of the code required for a Windows application is dedicated to processing events that are caused by external actions of the user but activities that are not directly associated with your application can nonetheless require that bits of your program code are executed. For example, if the user drags the window of another application that is active alongside your program and this action uncovers part of the client area of the window devoted to your application, your application needs to redraw that part of the window.

Windows Messages

Events in a Windows application are occurrences such as the user clicking the mouse or pressing a key, or a timer reaching zero. The Windows operating system records each event in a message and places the message in a message queue for the program for which the message is intended. Thus a Windows message is simply a record of the data relating to an event and the message queue for an application is just a sequence of such messages waiting to be processed by the application. By sending a message Windows can tell your program that something needs to be done, or that some information has become available, or that an event such as a mouse click has occurred. If your program is properly organized, it will respond in the appropriate way to the message. There are many different kinds of messages and they can occur very frequently — many times per second when the mouse is being dragged, for example.

A Windows program must contain a function specifically for handling these messages. The function is often called WndProc() or WindowProc(), although it doesn’t have to have a particular name because Windows accesses the function through a pointer to a function that you supply. So the sending of a message to your program boils down to Windows calling a function that you provide, typically called WindowProc(), and passing any necessary data to your program by means of arguments to this function. Within your WindowProc() function, it is up to you to work out what the message is from the data supplied and what to do about it.

Fortunately, you don’t need to write code to process every message. You can filter out those that are of interest in your program, deal with those in whatever way you want, and pass the rest back to Windows. You pass a message back to Windows by calling a standard function provided by Windows called DefWindowProc(), which provides default message processing.

The Windows API

All of the communications between any Windows application and Windows itself uses the Windows application programming interface, otherwise known as the Windows API. This consists of literally hundreds of functions that are provided as a standard with the Windows operating system that provides the means by which an application communicates with Windows, and vice versa. The Windows API was developed in the days when C was the primary language in use, long before the advent of C++, and for this reason structures rather than classes are frequently used for passing some kinds of data between Windows and your application program.

The Windows API covers all aspects of the communications between Windows and your application. Because there is such a large number of functions in the API, using them in the raw can be very difficult — just understanding what they all are is a task in itself. This is where Visual C++ 2005 makes the life of the

617

Chapter 11

application developer very much easier. Visual C++ 2005 packages the Windows API in a way that structures the API functions in an object-oriented manner, and provides an easier way to use the interface in C++ with more default functionality. This takes the form of the Microsoft Foundation Classes, MFC. Also, for applications targeting the CLR you have a facility called Windows Forms where the code necessary to create a GUI is all created automatically. All you have to do is supply the code necessary to handle the events in the way that your application requires. You’ll be creating a Windows Forms application a little later in this chapter and you’ll explore the use of Windows Forms in more detail in Chapter 22.

Visual C++ also provides Application wizards that create basic applications of various kinds, including MFC and Windows Forms-based applications. The Application wizard can generate a complete working application that includes all of the boilerplate code necessary for a basic Windows application, leaving you just to customize this for your particular purposes. The example in Chapter 1 illustrated how much functionality Visual C++ is capable of providing without any coding effort at all on your part. I will discuss this in much more detail when we get to write some more practical examples using the MFC Application wizard.

Windows Data Types

Windows defines a significant number of data types that are used to specify functions parameter types and return types in the Windows API. These Windows-specific types also propagate through to functions that are defined in MFC. Each of these Windows types will map to some C++ type, but because the mapping between Windows types and C++ types can change, you should always use the Windows type where this applies. For example, in the past the Windows type WORD has been defined in one version of Windows as type unsigned short and in another Windows version as type unsigned int. On 16-bit machines these types are equivalent, but on 32-bit machines they are decidedly different so anyone using the C++ type rather than the Windows type could run into problems.

You can find the complete list of Windows data types in the documentation but here are a few of the most common you are likely to meet:

BOOL or BOOLEAN

A Boolean variable that can have the values TRUE or FALSE. Note that

 

this is not the same as the C++ type bool, which can have the values

 

true or false.

BYTE

An 8-bit byte.

CHAR

An 8-bit character.

DWORD

A 32-bit unsigned integer that corresponds to type unsigned long in

 

C++.

HANDLE

A handle to an object-a handle a 32-bit integer value that records the

 

location of an object in memory.

HBRUSH

A handle to a brush, a brush being used to fill an area with color.

HCURSOR

A handle to a cursor.

HDC

Handle to a device context — a device context being an object that

 

enables you to draw on a window.

HINSTANCE

Handle to an instance.

 

 

618