John has been programming computers on a variety of platforms for 12 years. He is currently a senior design engineer at Compton's NewMedia in Carlsbad, California, where he works on multimedia CD-ROM titles.
The message from Microsoft is clear: If you are a Windows developer, you should be writing 32-bit applications, developed on 32-bit platforms. The proof is Microsoft's Visual C++ 2.0, a fully 32-bit hosted environment that requires either Windows NT 3.5 or Windows 95 (Chicago) to run. Significantly, Microsoft no longer will release new 16-bit-hosted compiler environments, nor does this environment target 16-bit code. For 16-bit developers Visual C++ 1.5 is still included in the box. The version I tested, however, supports creating only 32-bit applications targeted at NT, Windows 95, and Win32s. Microsoft promises future support for NT on MIPS, Alpha, and PowerPC as well as Macintosh on 680x0 and PowerPC.
Although the system requirements for the environment are a bit steep by today's standards, I found developing code under Windows NT to be a surprising pleasure. NT's responsiveness and robustness are more important assets than I had realized. Preemptive multitasking and threads allow you to work in multiple windows without any of the hiccups and hesitations we have grown used to under Windows 3.1. NT's crash resistance allows uninterrupted debugging sessions. Visual C++ 2.0 (VC++ 2.0) employs multithreading internally, which allows you to compile in the background (with some limitations) and still do other things such as edit files or resources in the foreground.
To put VC++ to the test, I created an OLE server called "GIFSERV," which lets you embed a CompuServe GIF image file into an OLE 2.0 compatible document.
GIFSERV is a Single Document Interface (SDI) application, meaning that it will only open one GIF image at a time. It supports a File/Open command which allows opening and displaying an existing GIF image. For simplicity, GIFSERV does not support image modifications.
In setting up VC++, I opted for a full install, except for the "Books Online" feature, which is of considerable size and is best left on the CD-Rom. Overall, the installation consumed approximately 84 Mbytes of hard-disk space. In addition to the compiler environment, several icons representing various utilities are installed in Program Manager: Windiff, a file differencer originally shipped with the SDK; Pview, a process viewer allowing viewing statistics on any running process and its threads; Spy++, an improved version of Spy for capturing Windows messages; and Tracer, a kind of global filter that enables tracing of all kinds of system events.
VC++ 2.0 also includes the Microsoft Foundation Classes 3.0 (MFC 3.0), which adds several higher-level features such as enhanced toolbars, miniframe windows, and tabbed dialogs (property sheets). Win32 enhancements include support for multithreading, Unicode, shared 32-bit DLLs, and new 32-bit APIs. The library also supports C++ 3.0 language enhancements, including templates and exceptions. Templates are used in six collection classes. According to Microsoft, true C++ exception handling is used widely throughout the class library, although I wasn't able to verify this.
The first thing you'll notice about the VC++ 2.0 environment is a plethora of UI enhancements. Microsoft has made liberal use of the new Windows 95 controls, notably property sheets (tabbed dialogs) and dockable toolbar windows, implemented with a new, chiseled, three-dimensional look. The environment supports drag-and-drop for a number of features and has added right-mouse-button-activated pop-up menus pretty much everywhere.
Property sheets go a long way toward reducing user-interface clutter in a consistent way. A very effective use of property sheets, for example, is in the Output window. This is a general-purpose, scrolling-text output window that displays output from compilations, file searches, debug traces, and profilers. Each type of output is kept in a separate property sheet within this one window. You can access any of them by selecting among the tabs along the bottom.
Dockable windows are dual-personality beasts. They are normal, overlapped windows: not MDI child windows, but floating, overlapped windows. When dragged into contact with one of the four sides of the application window, however, they're "docked"--attached to the margin of the application where they were dropped. A double-click around the perimeter of a docked toolbar restores it to its overlapped personality.
My first impression of dockable toolbars was that they are gimmicky. After some use, however, I came to like their flexibility. For quick reference, they are ideal for popping up temporarily in the overlapped mode, then closing right away. This allows you to leave your existing windows in place without any fussing. For long-term reference, dockable toolbars are best docked because they're kept visible. Arranging existing windows (by tiling, for example) will automatically account for the docked toolbars position. I found docked mode great for the watch window.
Drag-and-drop is an ergonomic, intuitive advantage when used appropriately. For instance, customizing a toolbar is straightforward. Choosing the Tools/Customize option from the main menu introduces a pop-up dialog box with a palette of toolbar buttons to choose from. These are cascaded by category in property sheets--another plus. Building your custom toolbar involves dragging buttons between this dialog box and the toolbar you are customizing. Done. Any of the floating/dockable toolbars can be customized at the same time, and each responds immediately by accepting and displaying in its new configuration. Moving buttons on a toolbar is a simple matter of dragging them to their new location.
ToolTips, another Windows 95 feature, provides small fly-out hints when the cursor passes freely over toolbar buttons, much like the more familiar status bar hints that appear at the bottom of most applications. For some reason, Microsoft implemented ToolTips with a short delay, which requires pausing the mouse momentarily before they appear. I found this irritating, especially since the delay seems to be unpredictable.
Editing resources in VC++ is straightforward. Each resource can be launched into the appropriate editor window by double-clicking. The menu editor is easy to use, relying on drag-and-drop semantics, allowing true visual editing of menu layouts.
My approach to the OLE server project was to use AppWizard to generate a skeleton application as a starting point for development. Once generated, the application cannot be further modified through the AppWizard. Therefore, it is a good idea to get these selections right the first time. My selections for GIFSERV included Single Document Interface, no ODBC support, and OLE 2.0 full-server capability with OLE 2.0 Automation. The wizard allows customization of window styles, dialog as main window, toolbar, status bar, built-in printing and print-preview support, context-sensitive help, support for Most-Recently-Used (MRU) file list on the File menu, default filenames and file types, OLE registry names, and names of all classes and generated files. Listings One and Two are SRVRDOC.H and SRVRDOC.CPP, respectively. SRVRDOC implements the CGIFServerDoc class, a CDocument-derived class representing GIF documents. Except for minor additions, these two files were completely generated by AppWizard.
AppWizard creates all source files, adding them automatically to the new project file. It didn't miss a trick, even including a README.TXT file which explained the function of each of the generated files. The Wizard also generates a thorough set of resources, including several bitmaps (for the toolbars, icons, menus, and accelerator tables--normal and OLE in-place), a version-info resource, a dialog for a custom About Box, and a number of string tables. This saves repetitive work, especially when it comes to nonessential details such as the version and string-table resources, which always seem to slip through the cracks when done by hand.
Project windows are graphically oriented, displaying a tree-like structure for compilation-dependent nodes. VC++ does a thorough job with resources, tracking the compilation dependencies created in the resource (.RC) file. This includes files #included by the .RC file as well--a weakness in other environments. Typically, bitmap and icon resources are kept in separate files. Other resources are written directly into the resource file. Double-clicking on any of the resources from the dependency tree automatically launches you into the editor window for that resource.
The project created by AppWizard compiled fine, and I was able to run GIFSERV immediately. Although the code to read a GIF image was yet to be added, running the application one time caused my new OLE class to register itself in the registry database as the "Gifser Document" class (Gifserv was truncated due to a standard character limit on OLE class names--this can be changed). This allowed the Gifser class to appear automatically in other OLE 2.0 container applications in the Edit/Insert Object dialog box.
Compilation speed is a two-edged sword in VC++. Microsoft has included an incremental linker with VC++ to speed up linking of small code changes. I found incremental links to be fast. This addition is worth the overhead. Apparently, supporting incremental linking, plus VC++'s compiled browser database, makes the full-build time slow to a crawl. For example, compiling and incrementally linking a single .CPP file took 17 seconds, while a full rebuild of the GIFSERV project took 1 minute, 40 seconds.
Thanks to multithreading, it is possible to continue working in the foreground during builds, with one caveat: The background compilation thread stalls whenever the foreground thread blocks in a modal dialog box. This appears to be an architectural issue with Windows NT that impacts productivity. Whenever I performed searches, search/replace, file open, and so on, the background compilation would freeze until the operation had finished. Be careful not to leave an open dialog when you head out for that coffee break--your compilation will be sitting there staring at you when you come back.
The next step was to modify the AppWizard-generated classes to open and display GIF images. I took the simple approach of borrowing an existing class, XGIFPicture, that implements the GIF decoding. The complete GIFPICT.CPP code is provided electronically; see "Availability" on page 3. The class definition, however, is provided in Listing Four, page 113. The class lets you create an object that represents an in-memory bitmap image. The constructor takes a CFile object that is assumed to represent a GIF file on disk. After construction, the object can be asked to draw itself into a DC. The class supports width(), height(), and draw(CDC &, const RECT &) functions.
In anticipation of future expansion to other image formats, the XGIFPicture class is derived from an abstract base class, XPictureBase; see Listing Three for the class definition. The width(), height(), and draw(CDC &, const RECT &) functions are all declared pure virtual in XPictureBase. For this reason, the CDocument-derived class that handles the data, CGIFServerDoc, deals with a pointer to an XPictureBase object, pPicture. All of the imaging code is isolated in the XPictureBase-derived object. CGIFServerDoc only has to worry about instantiating the picture, freeing it, and passing along the appropriate WM_PAINT notifications by calling its draw() routine.
To correctly free the XPictureBase object in a Single Document Interface environment, the MFC documentation says to override the CDocument::DeleteContents() in my derived version of CDocument. I used ClassWizard to do this. ClassWizard automates adding your own versions of inherited virtual functions. It presents a list of inherited functions to choose from. Once I chose DeleteContents(), ClassWizard added all its declarations automatically.
I also needed to add a new, noninherited function, CGIFServerDoc::draw(CDC &, const Rect &). ClassWizard is only equipped to add limited kinds of functions to a class; in particular, inherited virtual functions. Therefore I could not coerce ClassWizard into adding this new function for me; I had to add it by hand. Nor would it create a new class not derived from an existing MFC class. This might have been a minor oversight, since with MFC, everything should theoretically be derived from CObject. However, ClassWizard does not include CObject in its selection of classes to derive from. Unfortunately, it only offers limited automation.
A capable browser has been packaged with VC++. Microsoft precompiles not only the class-structure information, but also full cross-reference lists for all symbols into the browser and function-call graph information. This was one of my favorite features. First, you can right-mouse click any symbol in the editor and jump to either its source definition or its references anywhere in your code. This functionality is reproduced in the browser window as well, along with additional browsing capability. In the browser window, you can access these lists of references in full, organized by symbol, class or file, and jump directly to those source files. The class-inheritance structure is shown in the browser, with all the class members. As you highlight each member, all of its references appear in an adjacent split window. There are your standard options for filtering the class- member lists. Simple function-call graphs are also available which show who calls a certain function or who is called from a certain function.
Having this browser capability made deciphering the AppWizard-generated classes much easier on several occasions. I was immediately able to jump to Foundation Class header files where symbols were defined to have a closer look. However, I am puzzled as to why Microsoft didn't make the browser accessible from the right-mouse button within the editor.
Two new debugging features in VC++ promise to be significant advances over conventional debug environments: Just-in-Time debugging and OLE RPC (Remote Procedure Call) debugging. Just-in-Time debugging lets you configure an application so that if a fatal exception occurs, the debugger will automatically launch. This occurs whether or not you have the VC++ environment running or even have debug information compiled into the app. When such an exception occurs, you are presented with a dialog box explaining the exception, giving the code address, and asking if you want to debug it. If selected, up pops an instance of VC++, showing you exactly where in the code the exception has occurred. You are locked out of normal project-building operations while in Just-in-Time Debugging; otherwise, this is a very cool feature and should make obsolete the standard post-mortem-type logging tools such as Dr. Watson.
I had more problems with OLE RPC debugging. RPC debugging allows you to debug two communicating applications simultaneously. This will be particularly valuable for OLE developers. The idea is to start up a debug session of your OLE container application from one instance of the VC++ environment. As you single step into a call that invokes an RPC operation in the other application, a second instance of VC++ comes up automatically, with you in the driver's seat, debugging the second (server) application. Control will revert back to the container when the RPC operation is finished.
For example, I was using DRAWCLI, the sample container application that comes with VC++, to test GIFSERV. It was having trouble activating GIFSERV.EXE properly when I double-clicked on a GIFSERV object. By setting a breakpoint in the OnButtonLDblClk() routine of DRAWCLI, then single stepping, I was able to traverse the process boundary over to GIFSERV somewhere deep in the bowels of MFC on a DoVerb() call. Another instance of VC++ popped up. I stepped through that. It then reverted back to the calling instance of VC++ just fine. I was impressed. However, it was impossible to repeat that cycle a second time. The debugger often disabled all the single-step buttons, or I found myself in a disassembly window stepping through kernel assembly code, never to launch the second instance of VC++. Also, it appears that you must single-step to get the transition to occur. When I ran the app, no process switch occurred. At the time of this writing, I am still immersed in OLE documentation trying to resolve this problem. This just goes to show you that App-Wizards and MFC encapsulation can take you only so far down the OLE path. At some point, you will have to roll up those shirt sleeves and grunt it out if you are going to do OLE.
The debugger offers other nice features. Drag-and-drop is supported for viewing variables in watch and memory windows, for viewing code in the disassembly window, or for viewing memory by dragging a register value to the memory window. The debugger makes no distinction between application source code and MFC class-library code. You can trace into the source code of any MFC function. VC++ handles the details of locating source files smoothly.
Another nice touch is the ability of the QuickWatch window to detect the actual object type when all you have is a pointer to an object of the base-class type. For instance, it was a simple matter to view all the member values for the XGIFPicture object stored in the CGIFServerDoc as the pPicture pointer, even though pPicture is a pointer to the base class XPictureBase. See Listing One for the pPicture declaration. Highlighting the pPicture variable first, then launching the QuickWatch window, produced a display showing all the XPictureBase class members, plus an extra "ghost" member of type XGIFPicture*, which you could double-click to expand into all of its member values for the current object. Surprisingly this feature does not require that your classes be derived from MFC's CObject class. It seems to use C++ 3.0's run-time type checking. I would like to see this feature in the regular Watch window as well.
Microsoft, it would seem, has put all of its wood behind the 32-bit arrow. VC++ 2.0 is an impressive product. The OLE 2.0 support, RPC debugging, and full 32-bit implementation all advance the state of the art for Windows development environments. Microsoft has also concentrated heavily on usability that clearly puts this environment on par, or ahead of, traditionally user-friendly environments like Borland C++. However, there are speed issues to be addressed. Finally, the benefit of instantly generating an OLE-compatible application is invaluable, and I would not do without it. However, when writing realistic OLE applications, the learning curve for OLE is as it has always been--tough.
/*------------------------- SRVRDOC.H ----------------------------*/
// This is the CDocument-derived class that encapsulates a picture
// object and forwards all drawing messages to it.
class CGIFServerItem;
class XPictureBase;
class CGIFServerDoc : public COleServerDoc
{
protected: // create from serialization only
CGIFServerDoc();
DECLARE_DYNCREATE(CGIFServerDoc)
// Attributes
public:
CGIFServerItem* GetEmbeddedItem()
{ return (CGIFServerItem*)COleServerDoc::GetEmbeddedItem(); }
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CGIFServerDoc)
public:
virtual BOOL OnNewDocument();
virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
virtual void DeleteContents();
protected:
virtual COleServerItem* OnGetEmbeddedItem();
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CGIFServerDoc();
virtual void Serialize(CArchive& ar);
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
//{{AFX_MSG(CGIFServerDoc)
// NOTE -the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
// Generated OLE dispatch map functions
//{{AFX_DISPATCH(CGIFServerDoc)
// NOTE -the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_DISPATCH
DECLARE_DISPATCH_MAP()
// The following lines of code were added by hand:
public:
void draw(CDC &, const RECT &);
private:
XPictureBase * pPicture;
};
/*------------------------- SRVRDOC.CPP --------------------------*/
#include "stdafx.h"
#include "gifserv.h"
#include "srvrdoc.h"
#include "srvritem.h"
#include "gifpict.hpp"
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
/////// CGIFServerDoc ////////
IMPLEMENT_DYNCREATE(CGIFServerDoc, COleServerDoc)
BEGIN_MESSAGE_MAP(CGIFServerDoc, COleServerDoc)
//{{AFX_MSG_MAP(CGIFServerDoc)
// 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
END_MESSAGE_MAP()
BEGIN_DISPATCH_MAP(CGIFServerDoc, COleServerDoc)
//{{AFX_DISPATCH_MAP(CGIFServerDoc)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
////// CGIFServerDoc construction/destruction //////
CGIFServerDoc::CGIFServerDoc()
{
// TODO: add one-time construction code here
pPicture = NULL;
EnableAutomation();
AfxOleLockApp();
}
CGIFServerDoc::~CGIFServerDoc()
{
AfxOleUnlockApp();
}
BOOL CGIFServerDoc::OnNewDocument()
{
if (!COleServerDoc::OnNewDocument())
return FALSE;
// TODO: add reinitialization code here
// (SDI documents will reuse this document)
TRACE("New Document\n");
ASSERT(pPicture == NULL);
return TRUE;
}
///// CGIFServerDoc server implementation /////
COleServerItem* CGIFServerDoc::OnGetEmbeddedItem()
{
// OnGetEmbeddedItem is called by the framework to get the COleServerItem
// that is associated with the document. It is only called when necessary.
CGIFServerItem* pItem = new CGIFServerItem(this);
ASSERT_VALID(pItem);
return pItem;
}
///// CGIFServerDoc serialization /////
void CGIFServerDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
TRACE("Storing serial document\n");
}
else
{
// TODO: add loading code here
TRACE("Loading serial document\n");
ASSERT(pPicture == NULL);
pPicture = new XGIFPicture(*ar.GetFile());
}
}
///// CGIFServerDoc diagnostics /////
#ifdef _DEBUG
void CGIFServerDoc::AssertValid() const
{
COleServerDoc::AssertValid();
}
void CGIFServerDoc::Dump(CDumpContext& dc) const
{
COleServerDoc::Dump(dc);
}
#endif //_DEBUG
///// CGIFServerDoc commands /////
BOOL CGIFServerDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
if (!COleServerDoc::OnOpenDocument(lpszPathName))
return FALSE;
// TODO: Add your specialized creation code here
TRACE("Open Document\n");
return TRUE;
}
// This function was added automatically by ClassWizard.
void CGIFServerDoc::DeleteContents()
{
// TODO: Add your specialized code here or call the base class
TRACE("Delete Document Contents\n");
// These two lines were added manually to free the picture object
delete pPicture;
pPicture = NULL;
COleServerDoc::DeleteContents();
}
// This function was added by hand. ClassWizard could not do it. It basically
// passes the WM_PAINT message along to the picture object contained herein.
void CGIFServerDoc::draw(CDC & dc, const RECT & r2)
{
if (pPicture != NULL)
pPicture->draw(dc, r2);
}
/*------------------------- PICTURE.HPP --------------------------*/
// Abstract base class for all types of picture objects.
#ifndef __PICTURE_HPP
#define __PICTURE_HPP
#include <windows.h>
#include "error.hpp"
class XPictureBase
{
protected:
XeStatus mnStatus;
public:
XPictureBase();
virtual ~XPictureBase();
// Non-virtual member functions
XeStatus status() const;
BOOL isOK() const;
// Pure virtual member functions
virtual short height() const = 0;
virtual short width() const = 0;
virtual XeStatus draw(CDC & destDC, const RECT &destRect) = 0;
};
#endif // __PICTURE_HPP
/*------------------------- GIFPICT.HPP --------------------------*/
// Class XGIFPicture, creates picture objects derived from GIF
// image files on disk.
#ifndef __GIFPICT_HPP
#define __GIFPICT_HPP
#include "picture.hpp"
class XGIFPicture : public XPictureBase
{
public:
XGIFPicture(CFile & file);
virtual ~XGIFPicture();
// Inherited pure virtuals implemented in this class
virtual short height() const;
virtual short width() const;
virtual XeStatus draw(CDC & destDC, const RECT &destRect);
private:
// Private member variables
long mlImageSize; // Size of the image in bytes
unsigned short munBitsPerPixel; // Usually 1, 4 or 8
unsigned short munHeight; // Size of image in pixels
unsigned short munWidth; // ""
BITMAPINFO * mpsDibHeader; // DIB Header and palette
unsigned char * mpPictureData; // Ptr to the DIB pixel data
// Private member functions
XeStatus loadImage(unsigned char * rawImage);
XeStatus ParseGIFHeader( unsigned char * pGIF,
unsigned char * & newPtr);
};
#endif //__GIFPICT_HPP
Copyright © 1995, Dr. Dobb's Journal