![]() |
|||||||||||||||||
|
|
|||||||||||||||||
|
|
|||||||||
|
Borland C++
Turbo Assembler |
Mastering Windows Programming with Borland C++4, Chapter 1
Chapter 1 Introducing Windows Programming Whether you're a seasoned veteran or just getting started in Windows programming, it's tough to master the techniques behind features that users take for granted. To be competitive, modern Windows software must take every possible advantage of multitasking, 32-bit code generation, graphics, custom and VBX controls, toolbars, status lines, and a host of other exotic components. This book can help. Borland C++ 4 (BC4) and the ObjectWindows class library 2.0 (OWL) are designed to help you create reliable 16- and 32-bit Windows applications without wasting months of your time just getting up to speed. In the coming chapters, you learn how to use BC4, and you focus on ObjectWindows techniques for performing tasks that, at the moment, might seem impossibly complex--like how to create a toolbar of icons, how to customize dialogs and controls, how to program a popup menu, how to print text and graphics, how to validate control data, and dozens of other topics. This is a how-to book with a one-track purpose: to help you save time writing applications with features that Windows users demand. To help you get started, this chapter provides an overview of the ObjectWindows library. The next chapter covers the BC4 integrated development environment (IDE), preparing you for in-depth looks at Windows programming throughout the rest of the book. In this chapter, you learn:
NOTE Please feel free to use the sample listings in this book in any way you wish. You do not need special permission to incorporate my sample programs into your own compiled code. The Complexity Problem The Straight C Approach The Application Generator Approach NOTE BC4 includes the AppExpert application generator, which suffers from the same disadvantages discussed here. AppExpert might be useful in some cases, but I suggest you learn how to program Windows applications from scratch before using AppExpert. That way, you'll have a better appreciation for the generated code, and you stand a better chance of being able to reuse that programming for your program's upgrades. See Chapter 2, "Introducing Borland C++ 4," for more information about AppExpert. The Visual Plug-and-Play Approach NOTE BC4 includes classes for interfacing with Visual Basic controls, also called VBX controls. With these classes, you can incorporate plug-and-play modules into Windows programs yet still use C++ as your development language. For more information on VBX controls, see Chapter 15, "Putting On the Finishing Touches." The Class Library Approach Two, a class library such as OWL provides an application framework on which you build programs. Through inheritance, you derive new classes based on those that BC4 provides to which you add the capabilities you need. With C++, you reuse existing code, which saves development time, minimizes debugging, and improves your application's reliability. OWL isn't the only Windows class library on the market. Some products such as the Microsoft Foundation Class Library provide wrapper classes that serve as stand-ins for Windows functions. Wrapper-class libraries make it possible to use C++ rather than straight C for Windows programming, but they offer little or no savings in development and debugging time. OWL has wrapper classes, and it also provides a framework that helps direct, mold, and shape application design. Other products offer single-source compilation for different operating systems. You can purchase a library, for example, with identical classes that target the Macintosh and Windows operating systems. Unfortunately, you pay a high price in lost performance for this level of compatibility. Functions in multiplatform libraries must be translated into low-level subroutine calls for Macintosh and Windows, which wastes time. OWL makes direct calls to the Windows API (application programming interface). Many such calls are compiled as inline statements--not, in other words, as nested subroutines. Because of BC4's extensive use of inline code, using the library adds practically no appreciable runtime overhead to the final application. Many expert programmers claim that because a class library helps organize programs more efficiently, the final program usually performs better than straight code written from scratch. That's a difficult claim to prove, but those who are familiar with OWL generally confirm that using a class library does not necessarily degrade runtime performance, which you can expect with interpreted languages such as Visual Basic. NOTE OWL 2.0 is written to conform to the latest ANSI C++ standard, raising the possibility that future editions of OWL will be available for other operating systems. So far, OWL is available only for Windows, but it stands to reason that Borland didn't insist on OWL's ANSI C++ compatibility for no reason at all. Say Hello to ObjectWindows 2.0
NOTE There are two ways to compile and run HOWL.CPP. Using the BC4 integrated environment, switch to the HOWL directory, open the HOWL.IDE project, and press Ctrl+F9. Or, from a DOS prompt, change to the HOWL directory and type make, then use the File Manager to select HOWL.EXE. Compile and run all of this book's sample programs in the same way. Remember, to compile programs in the IDE, you must open their .IDE project files. If you have trouble compiling programs, see Appendix A, " compilation Instructions," for help. Listing 1.1. HOWL.CPP. #include <owl\applicat.h>
int
OwlMain(int argc, char* argv[]) {
TApplication app("Hello World from OWL 2.0!");
return app.Run();
}
Though simple-minded, HOWL.CPP demonstrates four key elements found in every Windows program:
Unused Arguments For your own functions, when you receive an unused argument warning, you probably should delete the unused parameter or other variable. When using supplied functions such as OwlMain, though, the arguments are passed to the functions whether the code uses the parameters or not. In that case, you have two options: ignore the warning or disable it by inserting this line above OwlMain: #pragma argsusedThe #pragma argsused directive disables the compiler's warning about unused arguments, but only for the next function. You can also disable the warning by surrounding selected function parameters with comment brackets: int OwlMain(int /*argc*/, char* /*argv*/[ ] )That's usually better because it makes you account for the use of each parameter. The #pragma option affects all unused parameters and variables in the function. Sample programs in this book use both techniques. A Shorter OwlMain It's possible to shorten OwlMain in HOWL.CPP to one statement, a technique you may see in other published OWL programs (most notably, for example, BC4's example OWL applications). OwlMain can create and execute a TApplication object, and call its Run member function, with this lone statement: return TApplication("Hello World from OWL 2.0!").Run ( ) ;To
try the alternate code, replace OwlMain's two statements with that one. It
constructs a temporary TApplication object on the stack, calls its Run member
function, and returns that function's result. The object is automatically
destroyed when OwlMain ends.
There's no advantage of the single-line method over that shown in Listing 1.1--I prefer the longer method of constructing objects and then using them. The single-line method is tricky, and it provides no practical benefits. Sample programs in this book use the longer, and more readable, technique shown in HOWL.CPP. Module-Definition Files Windows programs are rarely composed of a single file such as HOWL.CPP. Even this simple example, for instance, uses a module-definition file, which specifies linker options that affect the type of module created, and that configure some module settings. Listing 1.2, HOWL.DEF, lists the sample program's module-definition file. Listing 1.2. HOWL.DEF. EXETYPE WINDOWS CODE PRELOAD MOVEABLE DISCARDABLE DATA PRELOAD MOVEABLE MULTIPLE HEAPSIZE 4096 STACKSIZE 8192 BC4 does not require a module-definition file to create Windows applications and libraries. If you don't specify a .DEF file, the linker uses a default file, a fact the linker also warns you about. You also can select linker settings by using IDE commands. NOTE BC4's default module-definition file, DEFAULT.DEF, is located in the C:\BC4\LIB directory. Even so, module-definition files have many possible settings--too many to explain fully in this introduction. Some options differ depending on whether you are creating a 16- or a 32-bit program, and many options won't make any sense until you learn about the associated programming, so I'll skip a complete overview here. For more information about module-definition files, look them up in the BC4 User's Guide. Following, however, are some notes about options you may want to use now. The first line of a module-definition file for 16-bit applications (32-bit programs don't need this) is always: EXETYPE WINDOWSThere is only one type of executable code file for 16-bit Windows, so there aren't any other options in the EXETYPE statement. The next line in the file is usually a CODE statement, which has the following syntax: CODE [FIXED|MOVEABLE] [DISCARDABLE|NONDISCARDABLE] [PRELOAD|LOADONCALL] The brackets indicate items that are optional. (All the ones shown here are optional, but of course, if you didn't include any settings, there'd be no reason to have a CODE statement.) The vertical bar means "or." So, you can specify a CODE segment to be either FIXED or MOVEABLE, but not both. The CODE statements options are:
NOTE The 32-bit linker, TLINK32, recognizes only the PRELOAD and LOADONCALL code segment settings. It also recognizes two others:
DATA [NONE|SINGLE|MULTIPLE] [READONLY|READWRITE] [PRELOAD|LOADONCALL] [SHARED|NONSHARED]
NOTE A description statement inserts text into a code file, usually to embed a copyright notice. You may insert a DESCRIPTION statement such as the following in a module-definition file. DESCRIPTION `Copyright (c) 1994 by Your Name'HEAPSIZE and STACKSIZE statements set the size of the local heap and the program's stack. Only an executable code file has a stack--a DLL shares the host application's stack. A STUB statement inserts a DOS program into the Windows .EXE code file. It is never used for a library. If you don't specify a STUB program, BC4 automatically inserts WINSTUB.EXE into the finished .EXE file. The default file is located in C:\BC4\BIN. From a DOS prompt, switch to that directory, and type WINSTUB to execute the program. On screen, you see the familiar message: This program must be run under Microsoft Windows.Actually, in this case, that's a lie because you just executed WINSTUB from DOS! Seriously, however, the stub is there just to do something if a user attempts to run a Windows program from DOS, which, of course, is a no-no. If you don't like the default message, write your own DOS .EXE file, and specify it in the module-definition file's STUB statement: STUB mystub.exe The stub is supposed to display a message and quit, but some programmers use this trick to create .EXE code files that can run the same program under DOS and Windows. Each part of the dual-purpose code file is still a separate program with its own copies of any library functions and so on. Other module-definition options include a NAME statement for 32-bit applications, plus EXPORTS and IMPORTS statements for dynamic link libraries, and SEGMENTS for defining multiple segment options. With BC4, and especially when using the integrated development environment, there are easier methods to create 32-bit programs and libraries, so you may never need these settings. The ObjectWindows Class Library
Header Files OWL header files are stored in the OWL subdirectory, located in the default INCLUDE path where standard headers such as STDIO.H and STDLIB.H are found, usually C:\BC4\INCLUDE. Specify this directory with a command-line -I option, or by using BC4's Options|Project... command and selecting Directories from the Topics list. Installing BC4 automatically sets the default directories for the integrated development environment and command line tools. Unless you rename your BC4 directory (never a wise move), you should be able to compile all of this book's sample programs without having to change default directory settings. To reduce the number of individual paths the compiler searches, rather than add OWL's directory to the list of default paths, specify the OWL path name in the #include directive: #include <owl\gadget.h>Most OWL source files have numerous such directives for each class (or class category) the program uses. It's also possible to include all OWL classes with a single directive: #include <owl\owlall.h>The OWLALL.H file is the Superman of OWL headers. It makes every class available, but it also takes a long time to compile. Including OWLALL.H might save compilation time for large applications that use precompiled headers (binary representations of header files stored in .CSM compiled symbol files). Small applications and demonstrations, however, such as those in this book, should include individual headers as needed. You might also include a subset of commonly OWL used classes with the following directive, which also works well with precompiled headers: #include <owl\owlcore.h>To include a standard header, because those files are stored in the default path, just specify the header name prefaced with no path: #include <stdlib.h>To use a container class, include a header from the CLASSLIB subdirectory. This, for example, makes array containers available to the program: #include <classlib\arrays.h>The ANSI C++ string class, fully supported by BC4 and OWL, is part of the standard C++ library. To use this class, include its header, CSTRING.H, from the default include path: #include <cstring.h>The standard C null-terminated string library, containing functions such as strlen and strcpy, is still available. The string class is compatible with these time tested subroutines, and you may use the class and standard string functions in the same program. Include the standard string functions with the directive #include <string.h>Don't confuse CSTRING.H and STRING.H. The former (CSTRING.H) declares the ANSI C++ string class. The latter (STRING.H) declares null-terminated (also called C-style) string functions that address character data with char* pointers. You may use either or both of these headers and string types. NOTE
Encapsulation and Inheritance Encapsulation TApplication app("Window Title");The
app object encapsulates data and functions for a Windows application. To obtain
a data member from the object, you normally call a member function that returns
the value you want. For example, if you need the application's instance handle
(an integer assigned by Windows that identifies the running application), call
the GetInstance member function:
HINSTANCE hInstance = app.GetInstance();The app object doesn't contain the GetInstance function--a common misconception. Rather, the function is defined for all objects of the class. One way to make the concept clearer is to think of GetInstance as a command or a message that is applied to the object. You are in effect saying to the app object, "do your GetInstance thing, whatever that may be." You might similarly command another object of the same class. You can also construct a dynamic instance of a class with the C++ new operator: TApplication* papp = new TApplication("Window Title");If
that fails, the statement throws an exception that is caught by an exception
handler. In the recent past, all such statements would be followed with a test
of papp's value. If papp is null, there wasn't enough memory available for new
to construct the requested object, and the program would call an Error
function. Because of ANSI C++ exceptions, however, it is pointless to test
whether papp is null. With exceptions, the statement either succeeds or an
error handler takes over. The next statement can therefore safely assume that
the preceding memory allocation was successful.
Programming with exceptions takes care, and if you are new to the subject, you'll find it difficult at first. Chapter 3 explains more about exception handling in C++. For now, just be aware that critical errors are handled by this new technique. Call class member functions for a dynamic object by using a pointer dereference operator (->): papp->SetName("New Application Name");By
the way, for 16-bit programming, you might need to preface literal strings with
LPSTR, casting the string's address to a far pointer:
papp->SetName((LPSTR)"New Application Name");In 32-bit applications, such casts are not needed because all pointers are far by definition. There are no near (segment and offset) pointers in a 32-bit program. Encapsulation's main benefit is the elimination of logical errors caused by passing the wrong data to a function. A typical example is a misused window handle. In conventional Windows programs, you obtain a window handle and pass it to an API function. If hWindow represents the window's handle, you might use programming such as: RECT r; GetClientRect(hWindow, &r);Calling the Windows function GetClientRect fills a RECT structure r with the client area's dimensions--the space inside the window's menu, title bar, and borders. Even simple function calls like that, however, can cause a world of trouble if the program passes the wrong or an uninitialized handle argument. OWL virtually eliminates such errors because the window object encapsulates the handle. The preceding code is better written as: TRect r; winObject.GetClientRect(&r);Rather than RECT, the sample uses the TRect class. Instead of calling the API GetClientRect function, the statement calls the member function of that name for winObject. There is no need to pass the window handle to the function because the handle is encapsulated inside that object. Many functions such as GetClientRect that accept address-arguments such as &r (the address of r) are overloaded to return a value or reference of that type. The preceding sample code is even safer when written like this: TRect r; r = winObject.GetClientRect ( );Or, inside another TFrameWindow (or derived class) function, you can simply call member functions directly: TRect r = GetClientRect ( );You may still call API functions directly, but to prevent name conflicts, you might have to preface statements with a scope resolution operator: RECT r; ::GetClientRect(hWindow, &r);The :: operator tells the compiler to look in the global scope for GetClientRect. (All API functions are in the global scope.) Whenever possible, this book's sample programs call encapsulated member functions. All direct calls to API functions are prefaced with a scope resolution operator even when unnecessary so you can tell what kind of function is being used in each case. Inheritance class TDerived: public TFrameWindow {
public:
TMyClass(TWindow* parent, const char* title);
// ...
};The
new class, TDerived, inherits the data and functions from TFrameWindow, known
as the base class, or sometimes, the ancestor class. TDerived must define a
constructor to initialize objects of the class. The constructor is typically
implemented something like this:
TDerived::TDerived(TWindow* parent, const char* title)
: TFrameWindow(parent, title),
TWindow(parent, title)
{
// ...
}
The first line declares the constructor's implementation. The second and third lines call the base class constructors. TFrameWindow is virtually derived from TWindow so that, in cases where a derived class might inherit TWindow from many different paths, only one TWindow object exists in the derived result. This fact means you must call TDerived's immediate base class constructor (TFrameWindow) and also the virtual base class (TWindow) NOTE Some classes are abstract; that is, they declare pure virtual member functions that end with = 0. You cannot create objects of an abstract class, which serves as a kind of schematic for designing other classes. You must instead derive a new class using the abstract class as a base and provide finished versions of all pure virtual member functions. The TSlider class, for example, is an abstract class. To use it, you must derive a new class from TSlider and provide implementations for each pure virtual member function. A derived class usually overrides inherited virtual member functions. For example, all classes derived from TWindow or TFrameWindow inherit a member function declared as virtual BOOL CanClose();The program calls CanClose to determine whether it is safe to close a window. Normally, CanClose returns true. To replace the existing function with a new model that returns false--if a file is still open, for instance--derive a class from TFrameWindow and override the virtual function (shown in bold here): 2class TDerived: public TFrameWindow {
BOOL fileIsOpen; // True if file is open
public:
TMyClass(TWindow* parent, const char* title);
virtual BOOL CanClose();
// ...
};Implement
the replacement CanClose function to return false if the fileIsOpen flag
indicates a file is open, in which case the window must not be permitted to
close (assume this flag is set by other member functions in the class):
BOOL
TDerived::CanClose()
{
if fileIsOpen {
// display error message
return FALSE;
} else
return TRUE;
}Users
are now forced to save their work before they can close the window. Best of
all, you programmed that feature without having to consider the many possible
ways in which a window might be closed. You can be confident that the class
always calls CanClose to determine if it is safe to close the window, so
there's no need to track down all events that might cause that to happen. You
simply override functions and trust classes to perform as expected.
Looking Ahead |
|
|
|
|||||
| Made in Borland® Copyright© 1994-2002 Borland Software Corporation. All rights reserved. Report Piracy, Legal Notices, Privacy Policy Last Modified Tuesday, 05-Feb-2002 10:07:05 EST |
|||||