Special Edition Using Visual C++ 5 - Chapter 28

## Chapter 28

## Future Explorations

There are a number of topics that have not been covered elsewhere in this book, but that are well known to experienced Visual C++ programmers. They are best explored once you have experience with Developer Studio, MFC, and C++ programming. This chapter has just enough to show you how interesting these topics are, and to encourage you to explore them yourself in the months and years to come.

- Creating console applications

  A console application is not a Window program. It runs a character-based, non-GUI interface.

- 32-bit Dlls

  Follow the example program to see how easy writing and using 32-bit DLLs can be.

- Sending messages and commands

  An integral part of an event driven operating system like Windows is its Messaging capability. This section shows how to extend that capability with messages of your own devising.

- International issues and the Unicode standard

  Unicode is a 2 byte character set standard enabling programs to work with international languages whose alphabet requires more than 8-bits of storage. MFC makes support for Unicode easy, and this section shows how.

Creating Console Applications

A console application looks very much like a DOS application, though it runs in a resizable window. It has a strictly character-based interface with cursor keys rather than mouse movement. You use the Console API and character based I/O functions like printf() and scanf() to interact with the user.

Creating a Console Executable

A console application is still executed from the DOS command line or by choosing Start, Run and typing the full name of the application. Console applications are probably still among the easiest programs to create and this version of the compiler supports them directly.

Let's walk together through the few steps necessary to create a basic console application and then we'll explore some beneficial uses of creating these kinds of applications. The first console application we'll create is a spin on the classic 'Hello, World!' that Kernighan and Ritchie (the creators of C++'s ancestor C) created in the 1970s.

Open the Microsoft Developer Studio and follow these steps to create a console application:

1. In the Microsoft Developer Studio, select File, New.

2. In the New dialog box, click the Projects tab to bring up the now familiar New Project dialog box. (If it isn't familiar, go back to Chapter 1, "Building Your First Application."

3. Name the project HelloWorld, set an appropriate folder for the project, and choose Win32 Console Application from the list on the left.

4. Click OK.

The project is created immediately: no wizard dialog boxes appear and there e are no further questions to answer. Now you need to create source and header files and add them to the project. This sample will all fit in one file. Follow these steps:

1. Select File, New from the file menu and click the File tab

2. Leave the Add To Projectbox selected; the new file will be added to the project.

3. Choose C++ Source File from the box on the left.

4. Enter HelloWorld as the filename—the extension .cpp will be added automatically.

5. The New dialog box should resemble Figure 28.1. Click OK.

Fig. 28.1

Create a C++ source file for your console application.

A blank text file is created and named for you, and added to the project, all in one step. This is a big improvement from previous versions of Visual C++, where you created a file with a name like Text1, renamed it to HelloWorld.cpp (or whatever name you wished) and then added it to the project.

Add the code in Listing 28.1 to the new file.

Listing 28.1—HelloWorld.Cpp

    #include <iostream.h>

    int main()

    {

         cout << "Hello from the console!"<< endl;

         return 0;

    }

Choose Build, Go to compile, link, and execute the program. You should see a DOS box appear that resembles Figure 28.2. The line Press any key to continue is generated by the system and gives you a chance to read your output before the DOS box disappears.

Fig. 28.2

Your application appears to be a DOS program.

Writing an Object-Oriented Console Application

The HelloWorld application is clearly C++ and would not compile in a C compiler, which does not support stream based I/O with cout, but it's not object oriented—there's not an object in it. Replace the code in HelloWorld.cpp with the lines in Listing 28.2.

Listing 28.2—HelloWorld.Cpp—With Objects

    // HelloWorld.cpp

    //

    #include <iostream.h>

    #include <afx.h>

    class Hello

    {

    private:

         CString message;

    public:

         Hello();

         void display();

    };

    Hello::Hello()

    {

         message = "Hello from the console!";

    }

    void Hello::display()

    {

         cout << message << endl;

    }

    int main()

    {

         Hello hello;

         hello.display();

         return 0;

    }

Now this is an object-oriented program, and what's more it uses CString, an MFC class. To do so it must include <afx.h>. If you build the project now, you will get linker error messages that refer to _beginthreadex and _endthreadex. By default, console applications are single-threaded, but MFC is multithreaded. By including afx.h and bringing in MFC, this application is making itself incompatible with the single-threaded default. To fix this, choose Project Settings and click the C/C++ tab. From the drop-down box at the top of the dialog box, choose Code Generation. In the drop-down list box labeled Use Runtime Library, choose Debug Multithreaded. (The completed dialog box is shown in Figure 28.3.) Click OK, and rebuild the project.

Fig. 28.3

Make your console application multithreaded so that it can use MFC.

The output of this object-oriented program is just like that of the previous program—this is just a sample. But you see that console applications can use MFC, can be built around objects, and can be quite small. They must have a main() function and it is this function that is called by the operating system when you run the application.

> Although this application is small, Visual C++ creates a lot of overhead files. The Debug directory occupies about 6.5 M, of which almost a full megabyte is HelloWorld.exe. The rest is the MFC libraries—they aren't small.

Scaffolding Discrete Algorithms

The best argument for anyone to build a DOS application these days is to scaffold small code fragments or single objects. This refers to building a temporary framework around the code you want to test. (Some developers call this a test harness.) The simplest possible framework is a console application like the one you just built.

To scaffold an object or function, you should do the following:

1. Create a new console application just for the scaffolding process.

2. Add a main() function to the .CPP file you plan to scaffold.

3. Include the header file for the object or function to be tested

4. Write code that exercises the function or object in a variety of test cases and include it in main().

Having followed those steps, you can now test the code thoroughly, focusing only on the performance characteristics and correctness of this small piece of your large project. Scaffolding holds true to the canon of software development which states: "Design in the large and program in the small."

By applying a scaffold to any algorithm you are helping to ensure the accuracy in the small. Remember there are additional benefits involved too: by placing the scaffold code directly into the module you are clearly documenting that the code has been tested, and how to use it. You make it available for further testing, debugging, or extending at a later date.

### Creating and Using a 32-bit Dynamic Link-Library

Dynamic-link libraries (DLLs) are the backbone of the Windows 95 and Windows NT operating systems. Windows 95 uses Kernel32.Dll, User32.Dll, and Gdi32.Dll to perform the vast majority of its work, and you can use them as well. The Microsoft Visual C++ On-line Books are a good source of information for API functions found in these three DLLs.

Another tool for poking around in Windows applications is the DumpBin utility in \MSDEV\BIN. This utility is a command line program that shows you the imports and exports of executable files and dynamic link libraries. The following listing is an excerpted example of the output produced when using DumpBin to examine the executable file for Spy++, one of the utilities provided with Visual C++.

Listing 28.3—Output from Dumpbin

    dumpbin -imports spyxx.exe

    Microsoft (R) COFF Binary File Dumper Version 3.00.5270

    Copyright (C) Microsoft Corp 1992-1995. All rights reserved.

    Dump of file spyxx.exe

    File Type: EXECUTABLE IMAGE

             Section contains the following Imports

                USER32.dll

                    167   LoadCursorA

                    135   GetWindowTextA

                    1DF   SetDlgItemTextW

                    153   IsChild

                     D7   GetClassLongA

                     D8   GetClassLongW

                     C5   FillRect

                    165   LoadBitmapA

                    16B   LoadIconA

                     E6   GetDC

                    1F4   SetRectEmpty

                    15B   IsRectEmpty

                    1F3   SetRect

                    141   InflateRect

                    1FE   SetTimer

                    162   KillTimer

                    1CF   SetActiveWindow

                    249   wsprintfA

                     80   DeleteMenu

                    122   GetSystemMenu

                    1A1   PeekMessageA

                    100   GetLastActivePopup

                      2   AdjustWindowRectEx

                      4   AppendMenuA

                     51   CreatePopupMenu

                     C1   EnumWindows

                     B1   EnumChildWindows

                    188   MessageBoxA

                    206   SetWindowPlacement

                      A   BringWindowToTop

                    197   OffsetRect

                    132   GetWindowPlacement

                    13A   GetWindowWord

                    15E   IsWindowUnicode

                    243   WinHelpA

                     DF   GetClipboardFormatNameA

                    12F   GetWindowDC

                    160   IsZoomed

                    1B9   ReleaseDC

                     D0   GetCapture

                     33   ClientToScreen

                    246   WindowFromPoint

                    1D8   SetCursor

                    237   UpdateWindow

                    12D   GetWindow

                    15F   IsWindowVisible

                     E8   GetDesktopWindow

                    204   SetWindowLongA

                    1B8   ReleaseCapture

                    1D0   SetCapture

                    1A8   PtInRect

                     F0   GetFocus

                    120   GetSysColor

                     CB   FrameRect

                     9B   DrawFocusRect

                     F9   GetKeyState

                    187   MessageBeep

                    22C   TranslateMessage

                     8C   DispatchMessageA

                    159   IsIconic

                      1   AdjustWindowRect

                    133   GetWindowRect

                    1BF   ScreenToClient

                     C2   EqualRect

                    148   InvalidateRect

                     DC   GetClientRect

                    123   GetSystemMetrics

                    15C   IsWindow

                     D9   GetClassNameA

                    130   GetWindowLongA

                     D3   GetClassInfoA

                    1A3   PostMessageA

                    20E   SetWindowsHookExA

                    22E   UnhookWindowsHookEx

                     7D   DefWindowProcA

                    139   GetWindowThreadProcessId

                    1AF   RegisterClipboardFormatA

                     DB   GetClassWord

                     86   DestroyWindow

                    1AB   RegisterClassA

                     52   CreateWindowExA

                     AB   EnableWindow

                    1C6   SendMessageA

                    115   GetParent

                    1E2   SetForegroundWindow

                    232   UnpackDDElParam

                    216   ShowWindow

    Summary

           10000 .data

            3000 .idata

            8000 .rdata

            8000 .reloc

            F000 .rsrc

           36000 .text

As you can see, the utility program Spy++ uses the User32.Dll extensively.

You can call the Windows DLLs in any of your programs and more importantly, you can write DLLs of your own.

Making a 32-bit DLL

There are two kinds of DLLs in Visual C++: those that use MFC and those that do not. Each kind of DLL has its own AppWizard, as you will see shortly.

If you gather three or four functions into a DLL, your DLL exports those functions for other programs to use. Quite often a DLL will also import functions from other DLLs to get its work done.

Importing and Exporting Functions

To designate a symbol as exportable, use the following syntax:

    __declspec(dllexport) data_type int var_name; // for variables

or

    __declspec(ddlexport) return_type func_name( [argument_list ] ); // for functions

Importing functions is almost identical: simply replace the keyword tokens, __declspec(dllexport) with __declspec(dllimport). Using an actual function and variable to demonstrate the syntax this time:

    __declspec(dllimport) int referenceCount;

    __declspec(dllimport) void DiskFree( lpStr Drivepath );

> Two underscores precede the keyword __declspec.

By convention Microsoft uses a header file and a preprocessor macro to make the inclusion of DLL declarations much simpler. The technique simply requires that you make a preprocessor token using a unique token—the header file name works easily, and requires very little in the way of memorization—and define a macro which will replace the token with the correct import or export statement. Thus, assuming a header file named DISKFREE.H, the preprocessor macro in the header file would be as follows:

Listing 28.4—Diskfree.h

    // DISKFREE.H - Contains a simpler function for returning the amount of free disk space.

    // Copyright (c) 1996. All Rights Reserved.

    // By Paul Kimmel. Okemos, MI USA

    #ifndef __DISKFREE_H

    #define __DISKFREE_H

    #ifndef __DISKFREE__

    #define DISKFREELIB __declspec(dllimport)

    #else

    #define DISKFREELIB __declspec(dllexport)

    #endif

    // Use the macro to control an import or export declaration.

    DISKFREELIB unsigned long DiskFree( unsigned int drive ); // (e.g. o = A:, 1 = B:, 2 = C:

    #endif

Simply by including the header file you can let the preprocessor decide whether DiskFree is being imported or exported. Now you can share the header file for the DLL developer and the DLL user, and that means less maintenance headaches.

Creating the DiskFree DLL

The DiskFree utility provides a simple way to determine the amount of free disk space for any given drive. The underlying functionality is the GetDiskFreeSpace() function found in Kernel32.Dll.

To create a non-MFC DLL, choose File, New, click the Projects tab, select Win32 DLL from the list on the left, and enter DiskFree for the project name, as shown in Figure 28.4. Click OK and the project is created with no files in it.

Fig. 28.4

Creating a non-MFC DLL project is a one-step process.

Add a C++ header file called DiskFree.h and type in the code from Listing 28.5. Add a C++ source file called DiskFree.cpp and type in the code from Listing 28.6 (or copy these files from the CD.)

Listing 28.5—DiskFree.h

    #ifndef __DISKFREE_H

    #define __DISKFREE_H

    #ifndef __DISKFREE__

    #define __DISKFREELIB__ __declspec(dllimport)

    #else

    #define __DISKFREELIB__ __declspec(dllexport)

    #endif

    // Returns the amount of free space on drive number (e.g. 0 = A:, 1= B:, 2 = c:)

    __DISKFREELIB__ unsigned long DiskFree( unsigned int drive );

    #endif

Listing 28.6—DiskFree.Cpp

    #include <afx.h>

    #include <winbase.h>     // Contains the kernel32 GetDiskFreeSpace declaration.

    #define __DISKFREE__     // Define the token before the inclusion of the library

    #include "diskfree.h"

    // Returns the amount of free space on drive number (e.g. 0 = A:, 1= B:, 2 = c:)

    __DISKFREELIB__ unsigned long DiskFree( unsigned int drive )

    {

         unsigned long bytesPerSector, sectorsPerCluster,

              freeClusters, totalClusters;

         char DrivePath[4] = { char( drive + 65 ), ':', '\\', '\0' };

         if( GetDiskFreeSpace( DrivePath, &sectorsPerCluster,

              &bytesPerSector, &freeClusters, &totalClusters ))

         {

              return sectorsPerCluster * bytesPerSector * freeClusters;

         }

         else

         {

              return 0;

         }

    }

Change the Code Generation settings to Multithreaded Debug as discussed in the Console Applications section earlier in this chapter, and build the DLL. In the next section you will see how to use 32-bit DLLs in general, and how Windows finds DLLs on your system.

The most common use of a DLL is to provide extended, reusable functionality and let Windows implicitly load the DLL. Topics that will not be discussed in this book, that you might want to explore for yourself, include:

- Dynamic versus static linking of MFC.
- Implicit versus explicit DLL loading, which requires the use of LoadLibrary and FreeLibrary.
- Multithreading DLLs.
- Sharing data across DLL boundaries.
- Calling conventions for DLLs that will be used by other languages (__stdcall, WINAPI, ...).

In this chapter you are going to use a default compile of DiskFree, using an implicit DllMain (the compiler added one), and an implicit loading of the DLL, allowing Windows to manage loading and unloading the library.

Using 32-bit DLLs

Many DLLs are loaded implicitly and their loading and unloading is managed by Windows. Libraries loaded in this fashion are searched for like executables: first the directory of the application loading the DLL is searched, followed by the current directory, the Windows\System directory, the Windows directory and finally each directory specified in the PATH.

It is a common practice to place a DLL in the Windows or Windows\System directories once the application is shipped, but in the meantime you may use the development directory of the executable for temporary storage. One thing to safeguard against is that you do not end up with multiple versions of the DLL in each of the Windows, Windows\System, or project directories.

Using a DLL

Implicitly loading and using a DLL is about as simple as using any other function. This is especially true if you created the header file as described in the "Creating the DiskFree DLL" section. When you compile your DLL, Microsoft Visual C++ creates a .LIBfile. (So, DISKFREE.DLL has a DISKFREE.LIB created by the compiler.) The library (.LIB) file is used to resolve the load address of the DLL and specify the full pathname of the dynamic link library, and the header file provides the declaration.

All you have to do is include the header in the file using the DLL functionality and add the .LIB name to the Project, Settings dialog box, on the Link tab (see Figure 28.3), in the Object/library modules edit field.

Fig. 28.5

Add your LIB file to the project settings.

To test the DiskFree DLL, create a console application called TestDiskFree and add a C++ source file called TestDiskFree.cpp. Add the code from Listing 28.7 to this file. Copy DiskFree.h to this folder and add it to the project by choosing Project, Add To Project, Files, and selecting DiskFree.h. Copy DiskFree.Dll and DiskFree.Lib to the TestDiskFree folder also. (You'll find them in DiskFree\Debug.) Change the project settings as just described, and build the project.

Listing 28.7—TestDiskFree.Cpp

    #include <afx.h>

    #include <iostream.h>

    #include "diskfree.h"

    #define CodeTrace(arg) \

         cout << #arg << endl;\

         arg

    int main()

    {

         CodeTrace( cout << DiskFree(2) << endl );

         return 0;

    }

This code brings in the DLL by including diskfree.h, and then uses it. The CodeTrace macro simply prints out a line of code before executing it. All that this application does is call the DiskFree() function to ask how much space is free on drive 2. Drive 0 is a:, drive 1 is b: and drive 2 is c:. If you build and execute the program you should see output like Figure 28.6

Fig. 28.6

Your little application calls the DLL.

According to TestDiskFree, the C: drive on the machine used for these samples has almost 400 M of free disk space. This number is correct. Now you can write real functions in a DLL, and use them or make them available to other people to use.

### Sending Messages and Commands

As discussed in Chapter 4, "Messages and Commands," messages are the heart of Windows. Everything that happens in a Windows application happens because a message showed up to make it happen. When you move your mouse and click a button, a huge number of messages are generated, including WM_MOUSEMOVE for each movement of the mouse, WM_LBUTTONDOWN when the button goes down, WM_LBUTTONCLICK when the button is released, and higher level, more abstract messages like the WM_COMMAND message with the button's resource ID as one of its parameters. You can ignore the lower-level messages if you wish and many programmers do.

What you may not know is that you can generate messages, too. There are two functions that generate messages: CWnd::SendMessage() and CWnd::PostMessage(). Each of these gets a message to an object that inherits from CWnd. An object that wants to send a message to a window using one of these functions must have a pointer to the window, and the window must be prepared to catch the message. A very common approach to this situation is to have a member variable in the sending object that stores a pointer to the window that will receive the message and another that stores the message to be sent:

    CWnd* m_messagewindow;

    UINT m_message;

Messages are represented by unsigned integers. They appear to have names only because names like WM_MOUSEMOVE are connected to integers with #define statements.

The sending class has a member function to set these member variables, typically very short:

    void Sender::SetReceiveTarget(CWnd *window, UINT message)

    {

        m_messagewindow = window;

        m_message = message;

    }

When the sending class needs to get a message to the window, it calls SendMessage:

        m_messagewindow->SendMessage(m_message, wparam, lparam);

Or PostMessage:

        m_messagewindow->PostMessage(m_message, wparam, lparam);

The difference between sending and posting a message is that SendMessage() does not return until the message has been handled by the window that received it, but PostMessage() just adds the message to the message queue and returns right away. If, for example, you build an object, pass that object's address as the lparam, and then delete the object, you should choose SendMessage(), because you can't delete the object until you are sure that the message-handling code has finished with it. If you are not passing pointers you can probably use PostMessage() and move on as soon as the message has been added to the queue.

The meaning of the wparam and lparam values depends on the message you are sending. If it is a defined system message like WM_MOUSEMOVE, you can read the online documentation to learn what the parameters are. If, as is more likely, you are sending a message that you have invented, the meaning of the parameters is entirely up to you. You are the one who is inventing this message, and writing the code to handle it when it arrives at the other window.

To invent a message, add a defining statement to the header file of the class that will catch it:

    #define WM_HELLO WM_USER + 300

WM_USER is an unsigned integer that marks the start of the range of message numbers available for user defined messages. In this release of MFC, its value is 0x4000, though you should not depend on that. User defined messages have message numbers between WM_USER and 0x7FFF.

Then add a line to the message map, in both the header and source file, outside the ClassWizard comments. The source file message map might look like this:

    BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)

         //{{AFX_MSG_MAP(CMainFrame)

              // NOTE - the ClassWizard will add and remove mapping macros here.

              //    DO NOT EDIT what you see in these blocks of generated code !

         //}}AFX_MSG_MAP

         ON_MESSAGE(WM_HELLO, OnHello)

    END_MESSAGE_MAP()

The entry added outside the //AFX_MSG_MAP comments catches the WM_HELLO message and arranges for the OnHello() function to be called. The header file message map might look like this:

    // Generated message map functions

    protected:

         //{{AFX_MSG(CMainFrame)

         afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

              // NOTE - the ClassWizard will add and remove member functions here.

              //    DO NOT EDIT what you see in these blocks of generated code!

         //}}AFX_MSG

         afx_msg LRESULT OnHello(WPARAM wParam, LPARAM lParam);

         DECLARE_MESSAGE_MAP()

Then you add an implementation of OnHello() to the source file to complete the process.

### Considering International Software Development Issues

International boundaries are shrinking at incredible rates. As access to wider serial communications widens and the preponderance of discrete resalable components continues, more and more demands for pieces built by vendors world-wide will grow. Even in-house software development will less frequently be able to ignore international markets. The rise in popularity of the Internet has expanded the reach of many developers into countries where languages other than English and character sets other than ASCII predominate. This means your applications should be able to communicate with users in languages other than English, and in characters sets other than the typical Western character set.

Microcomputers were created in the United States which explains why we have 8-bit character based operating systems. There are only 26 letters in our alphabet and ten digits, which leaves plenty of room (about 220 characters worth) for punctuation and other miscellaneous characters. But countries like Japan and China require a character set in the thousands.

Unicode is one way to tackle the character set problem. The Unicode standard was developed and is supported by a consortium of some of the biggest players in the international computing markets. Among these are Adobe, Aldus, Apple, Borland, Digital, IBM, Lotus, Microsoft, Novell, and Xerox.

Unicode uses two bytes for each character, whereas ASCII uses only one. One byte (8 bits) can represent 28 or 256 characters. Two bytes (16 bits) can represent 65,536 characters. This is enough not just for one language, but for all the character sets in general use. For example, the Japanese character set, one of the largest, needs about 5000 characters. Most require far less. The Unicode specification sets aside different ranges for different character sets and can cover almost every language on Earth in one universal code—a Unicode.

MFC has full Unicode support, with Unicode versions of almost every function. For example, consider the function CWnd::SetWindowText(). It takes a string and sets the title of the window, or the caption of a button, to that string. What kind of string it takes depends on whether you have Unicode support turned on in your application. In reality, there are two different functions to set the window text one, a Unicode version and a non-Unicode version, and in WINUSER.H, the block of code shown in Listing 28.8 changes the function name that you call to SetWindowTextA if you are not using Unicode, or SetWindowTextW if you are.

Listing 28.8—Microsoft's WINUSER.H Implementing Unicode Support

    WINUSERAPI BOOL WINAPI SetWindowTextA(HWND hWnd, LPCSTR lpString);

    WINUSERAPI BOOL WINAPI SetWindowTextW(HWND hWnd, LPCWSTR lpString);

    #ifdef UNICODE

    #define SetWindowText  SetWindowTextW

    #else

    #define SetWindowText  SetWindowTextA

    #endif // !UNICODE

The difference between these two functions is the type of the second parameter: LPCSTR for the A version and LPCWSTR for the W (Wide) version.

If you are using Unicode, whenever you pass a literal string (like "Hello") to a function, wrap it in the _T macro, like this:

    pWnd->SetWindowText(_T("Hello"));

If you can deal with the annoyance of wrapping all text strings in _T macros, just like that your application is Unicode aware. When you prepare your Greek or Japanese version of the application, life will be much simpler.

> Windows 95 was built on old Windows, so it was not built using Unicode. This means that if you use Unicode in your Windows 95 programs, you are going to suffer performance penalties because the Windows 95 kernel will have to convert Unicode strings back to ordinary strings. Windows NT was designed at Microsoft from scratch, so is completely compatible with Unicode.

> If you are developing for several platforms with C++ and using Unicode, your Win95 version may seem sluggish in comparison to the Windows NT version.

### From Here...

This chapter demonstrated the way to build a console application, with or without MFC. You also learned how to use a dynamic-link library (DLL) and how to create your own DLL, and how to send custom messages to another part of your application. Finally, you learned how to ready your applications for the international market with Unicode.

To learn about related topics see:

- Chapter 4, "Messages and Commands," for an introduction to the importance of messages and how message maps work.
- Chapter 26, "Exceptions, Templates, and the Latest Additions to C++" for other advanced programming topics.

© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.