An Inside Look at Developing Applications Using the New Features of Visual C++ 6.0
|Porting your Visual C++ 5.0 workspace to Visual C++ 6.0 will most likely be very easy. Basic functionality in the build system has not changed, although there are some nice additions to the object model. You should see improved throughput, especially with debug builds of large apps.|
This article assumes you're familiar with C++, Win32 .|
Code for this article: Oct98InsideLook.exe (325KB)
Joe Massoni is the Microsoft Technical Support lead for Visual C++ 6.0. He has worked in software development for ten years; the last six have been in Microsoft Technical Support. Joe can be reached at email@example.com.|
Microsoft® Technical Support lead for Visual C++® 6.0, I spend a lot of time talking to our customers. When it comes to revisions, our customers want a painless upgrade, faster and easier application development, and no surprises. Visual C++ 6.0 delivers on all three. The new features are so seamlessly integrated that sometimes people don't even realize they are using them. Let's go over some of the
more exciting new features in
Visual C++ 6.0. I won't attempt to provide a complete list.
I'll start with a quick overview of the things you'll see when you first start up Visual C++ 6.0. Your first impression may be that the IDE looks pretty much the same as the previous version, and you'd be right. It's hosted in the MSDEV shell that was introduced in Visual C++ 4.0. Now try loading up a project. Porting your Visual C++ 5.0 workspace to Visual C++ 6.0 will most likely be very easy. Basic functionality in the build system has not changed, although there are some nice additions to the object model.
When you fire up the compiler and rebuild your application, you might get some new warnings. Most likely the compiler will catch things you may have neglected such as an incorrect or missing assignment, or an unreferenced variable. The compiler team has worked very hard to improve the warning messages. In addition, you should see improved throughput, especially with debug builds of
To continue exploring the new features, add some new code to your application. By default, Visual C++ 6.0 will add some new switches to your project settings and workspace that enable the Automatic Statement Completion and Edit and Continue features. If you're adding a function call to a subroutine, the Automatic Statement Completion (ASC) feature will present a prompt like that shown in Figure 1.
| Figure 1 Automatic Statement Completion|
You'll encounter the Edit and Continue feature while debugging your application. When you press F10 to step through your application, the compiler can often rebuild it without skipping a beat, so you can keep debugging. You won't have to shut down the application, do the build, restart the debugger, and find where you left off. The reason the rebuild is so fast is because the memory image is modified rather than the files on the disk.
You may discover is that it is possible to load your Visual C++ 6.0 project into Visual C++ 5.0, but this is not recommended or supported. The new compiler switches will not be recognized in Visual C++ 5.0 and will generate warnings. There are also a number of new build features that are not supported by Visual C++ 5.0 (like support for IDL files).
New Compiler Switches and Warnings
When you first port your Visual C++ 5.0-based application to Visual C++ 6.0, you'll probably want to study the converted DSP files (the internal build files) to make sure you understand the changes. First, you may notice that the dependency information is missing for external dependencies. This is because the format of the .IDB file, which contains the dependency information, has changed. The Rebuild All command will reestablish your dependencies.
There are three interesting compiler switches, two of which are new (/ZI and /GZ); the other (/FD) you may have missed in Visual C++ 5.0. The /FD option, originally introduced in Visual C++ 5.0, is for incremental build information and puts dependency information (include files) in the .IDB file.
The /ZI switch replaces the /Zi switch for the debug configurations in your ported project, and generates a debug program database for Edit and Continue. If for some reason you don't want to use Edit and Continue, disable it from the Tools menu rather than in your compiler switches so you can turn it on again easily. The /ZI switch is a combination of /Zi (debug information), /Gy (enable function-level linking), /GF (string pooling), and some other /ZI-specific magic that makes it all work. The /Od switch (disable optimizations) is required for /ZI.
String pooling, which optimizes your string literals by putting them in read-only memory, usually does not cause problems. But if you are modifying string literals in your code, your application will throw an exception at runtime.
char *mystr = "Hello There!";
_strlwr(mystr); // An exception will be thrown
// in this call.
|This code will work if you turn off string pooling, but it's not considered a good programming technique. If you need to modify your strings, you should allocate them in the heap.
There is another potential downside to using the /ZI option: OBJ files are approximately 35 percent larger. I suspect that there is a build speed hit as well, but the Edit and Continue feature speeds up the build cycle sufficiently so that you'll probably find the tradeoff worth it.
The new /GZ switch enables runtime debug checks to catch bugs such as uninitialized pointers, that are usually only exposed in release builds. This switch is on by default in newly created AppWizard applications, but does not show up when you port an application.
The /GZ option complements other warnings such as C4700 (local variable name used without having been initialized) and C4701 (local variable name may have been used without having been initialized) by setting uninitialized variables to 0xCCCCCCCC. So if you've forgotten to initialize a pointer, an exception will be thrown when your application tries to access memory located at this address. Of course, this is a perfectly fine value for an integer, which is where the C4700 warning comes in.
Besides catching uninitialized pointers, /GZ will check your stack upon returns from function calls, and at the end of functions that throw an exception if the stack is out of whack. Figure 2 is a small sample that illustrates most of
/GZ's capabilities. The lines that begin with > represent the /GZ checks. If MyFn is called through a normal function call, there is no need to insert the stack-checking lines shown in the section that starts with "Line 18."
Have you ever gotten frustrated trying to disable warnings, just to discover that they get turned on somewhere else? Or maybe the warning was off and you didn't know it. The pragma warning (push) and (pop) will help control this.
# PROP AllowPerConfigurationDependencies 0
msdev crc.dsw /MAKE "WinCRC - Win32 Debug" /REBUILD /OUT wincrc.log
This command line generates a log (see the /OUT wincrc.log argument), so if you have a large project that builds overnight you'll get a log of the errors. Also, the build is batchedall the CPP files are passed to the compiler at once. This is faster than invoking the compiler on a per-file basis, common for a MAK file project.
You can use environment variables to adapt to environments that are a little different. There is an MSDEV command-line switch (/USEENV) that directs Visual C++ to use the current environment settings for SOURCE, PATH, LIB, and INCLUDE rather than the settings in the IDE's Directories dialog box. This switch was designed specifically for command-line builds, and is not recommended for use when running the IDE as an editor.
If you run the Visual C++ environment with this switch, the IDE will continue to use the environment settings, with or without the /USEENV switch. To turn this off, you must delete the registry key HKEY_CURRENT_USER\Software\Microsoft\Devstudio\6.0\Build System\Use Environment Paths. To use different paths when running the Visual C++ environment, you should use the /I switch that is described in Knowledge Base article Q127200, "HOWTO: Use Other Registry Keys with Visual C++ 2.0 and Above."
New MFC projects that build context-sensitive help use custom build rules that give the IDE more control over the build process than provided by a batch file. Visual C++ 6.0 does create a MakeHelp.bat file in case you need it. You'll notice that the online documentation for Visual C++ 6.0 and Visual Studio® uses the HTMLHelp viewer in MSDN. If you want to write help files for HTMLHelp rather than WinHelp (which is the default for MFC-based projects), refer to the online help topic titled "Help Topics (HTML Help): Context-Sensitive Help for Your Programs."
The What's New documentation for Visual C++ 6.0 states that: "Compile-time throughput in projects with large precompiled header files (.PCH) is faster. Compiler throughput on debug projects is as much as 30% faster, and on non-debug projects, as much as 15% faster." I tested this claim with a good-sized MFC application I'd developed with Visual C++ 5.0.
I installed Visual C++ 5.0 and Visual C++ 6.0 Beta 2 on the same machine. Since the directory structures and registry entries are different, this is relatively safe. The main problems are that the MFC DLL for Visual C++ 6.0 is named MFC42.DLL, and Visual C++ 5.0 cannot read Visual C++ 6.0-generated debug information. I copied my application into two different directories, one for Visual C++ 5.0 and one for Visual C++ 6.0. Then I launched the IDE (MSDEV.EXE) with the /Y3 command-line switch. This undocumented switch lists build times in the output window. I ran the tests with no network connection and no other applications running.
The test application consists of 217 source files comprising approximately 3MB, 200 header files comprising about 1MB, and 45 RES\ files. I did several debug builds, throwing out the time on the first one due to caching issues. After each build I cleaned the output directory, which forced a build of all files. The total build times were:
Visual C++ 5.0 3:52 ± 2 sec|
Visual C++ 6.0 3:51 ± 1 sec
|Obviously, this was not what I expected. Then I checked the settings and realized that I was using /YX (automatic use of precompiled headers), something that I've noticed users do quite frequently. This switch is not recommended since it incurs obvious overhead. To avoid this, I changed the project settings to use /Yc (create precompiled headers) and /Yu (use precompiled headers). The build times were now:
Visual C++ 5.0 3:30 ± 1 sec|
Visual C++ 6.0 2:59 ± 1 sec
|This showed a 14.6 percent improvement and demonstrates the importance of /Yc and /Yu.
You may notice that your release builds are larger when ported from Visual C++ 5.0 to Visual C++ 6.0. You may not notice much difference, however, if you're comparing with Visual C++ 4.x builds because of the size improvements made in Visual C++ 5.0.
The release builds are larger because of the default linker switch /OPT:WIN98, which implements 4KB sections in the executable for more efficient Windows® 98 load times. Version 5 of the linker always used 512-byte sections. More details of this switch are documented in the readme file. This is something that can catch you off guard if you don't know it's there. The readme file discussion on this topic contains an error, however. It states that there is an automatic use of this switch based on the application's size. This is incorrect; the /OPT:WIN98 switch is always enabled unless you disable it.
Figure 3 lists the release build sizes of two applications, the second much larger than the first. The first build is by Visual C++ 5.0, followed by Visual C++ 6.0 builds with and without the WIN98 option. Notice that the release build size differences are larger for the smaller application. In general, you should see minor differences in the size of large applications, whereas small applications will display greater variations. The trade-off is improved load times on Windows 98. The point of the option is to reduce the need for using the Windows 98 swap file (Win386.swp). If, in the past, your application had to use the swap file upon loading, then you may find that the /OPT:WIN98 switch will decrease swap file usage and possibly eliminate it. Load time improvement can be difficult to measure. Many factors can influence load times, including the amount of memory, the hard disk access speed, and the other applications that
Another linker enhancement lets you create code to manage delayed loading of DLLs automatically. This can be very useful when your application links to DLLs, but doesn't always need them. The code in Figure 4 demonstrates this feature. Note that unloading DLLs is strictly optional. The example is a workspace with two projects. The first, called MyClient, is a console application. The second, called MyDll, is a Win32® DLL that exports one function. The function simply returns the string length of what is passed to it. (The code for the DLL is not shown.)
To build this example, add the library delayimp.lib and the import library for your DLL (in this case MyDll.lib)
to your Object/library modules list. Then add the option
/DELAYLOAD:MyDLL.dll to your linker switches. If you want to unload, then include the option /DELAY:UNLOAD.
One way to be sure that you're unloading the DLL is to check the debugger's module list. If you have trouble unloading, it may be because the string you passed is not the same case as what the EXE is looking for. You can check the string case using dumpbin:
Dumpbin /imports MyClient.exe | more
The output includes a section that contains the following delay load imports:
47774C Address of HMODULE
47B35C Import Address Table
47B250 Import Name Table
47B580 Bound Import Name Table
47B68C Unload Import Name Table
0 time date stamp
|So the correct case for the argument to __FUnloadDelayLoadedDLL is MyDll.dll.
Without a doubt, the IntelliSense® features are the biggest visible change to the IDE. The IntelliSense feature set includes Automatic Statement Completion, Code Comments, Dynamic Parsing, and a list of globally available objects.
I referred to ASC at the beginning of this article. I like it because if I behave myself and comment my code, not only will IntelliSense give me a list of my member functions when I type the member selection operator (. or ->), but the code comments will tell me what the function does so I
don't have to go somewhere else to look it up. It works like a self-generating, dynamic online help system. ASC also shows you all the parameters in your function and highlights the one you're working on. It won't tell you what each parameter does, but if you can't remember you can press F1 for help.
| Figure 5 Code Comments|
Code Comments show up when you're looking at a list of objects. They'll even appear if you haven't saved your edits. In the example shown in Figure 5, I wanted to instantiate my DirData class. I typed dir<Ctrl+Shift> and was presented with a list of globally available objects. When I highlighted DirData, the editor displayed the Code Comments to the right. Code Comments supports most commenting styles.
There are some limitations to ASC. Macros are not resolved and declarations nested in conditionally compiled code may produce unexpected results, but most code constructs are handled. The limitations will be documented in a forthcoming Knowledge Base article. The ASC parser uses stores to optimize information display speed. The stores are located in the Microsoft Visual Studio\Common\MSDEV98\bin directory, and have an NCB file extension. You don't need to use NCB stores to display information on your codethe parser will use the header files that are included in your project to present that information. It also uses sysincl.dat to hide definitions if necessary. This release does not include a tool that will allow third-party library vendors to create stores, but that might be included in future releases.
| Figure 6 Dynamic Parsing|
To complement ASC, Visual C++ 6.0 also offers Dynamic Parsing. In Figure 6, I've just finished adding "CAboutStuff();" to my CAboutDlg class declaration in the source editor on the right. As you can see, the IDE has picked up this addition and added it to the ClassView on the left as well as the WizardBar on the top. But wait! If you hold the mouse cursor over any identifier, including Win32 API functions, you will get a tooltip that tells you the type of the identifier. Figure 7 shows the tooltip you get when placing the cursor over UpdateWindow.
| Figure 7 Function Tooltip|
New Object Model Methods
Visual C++ 5.0 included a number of COM interfaces for various functional areas in MSDEV's shell. The most interesting ones dealt with the project because it can be mani-pulated from custom AppWizards as well as add-ins and macros. The original implementation was primarily a
framework. Visual C++ 6.0 fills in that framework and makes the project/build system much more programmable. The new ObjModel methods are shown in Figure 8. In addition to these new methods, several of the existing methods were updated.
There are still some known limitations. For example, you can't enumerate the files in a project and you can't create a new workspace. The IDE team is considering these features for future versions of Visual C++.
Debugger Edit and Continue
The debugger's new Edit and Continue feature, mentioned earlier, lets you make changes to your program while debugging and rebuild it on the fly. I find it convenient to select the "Debug commands invoke Edit and Continue" checkbox in the debugging options. This way, a Step (F10) or Go (F5) command will seamlessly rebuild on the fly. Alternatively you can apply the code changes (Alt+F10) on demand.
When I'm debugging a program, I may see something that needs updatingmaybe I forgot to call a class member, or I passed the wrong value in a function parameter, or I made a mistake in a function that was defined in a header file. With Edit and Continue, I can just pop into the header file, fix the calculation, press F10, and within a couple of seconds I'm stepping along.
As I mentioned earlier, the reason it's so fast is that only the memory image of the application is changed; there's no overhead for hitting the hard drive. You can modify up to 50 files, and as long as you don't modify your global objects or change a class signature, Edit and Continue will probably succeed. Limitations are documented in the online help. If you exceeded the limitations of Edit and Continue, there will be errors in Build's output window and you'll get a dialog box stating that files are out of date.
One of the engineers suggested adding a debugger feature that would allow you to jump over a function call. While it's easy enough to RIP (Reset the Instruction Pointer) past the function, what if you're in a loop? You wouldn't want to RIP each time through the loopwhat a pain. With Edit and Continue you can achieve this effect by commenting out the function call. The program will rebuild almost instantaneously, and this approach is safer than editing your memory window and replacing your function call instructions with NOPs.
Other Debugger Features
The debugger team added some nice features to make it easier to see what's going on. Developers using ActiveX® controls and COM will notice new tooltips for GUIDS, HRESULTs, and variants. Figure 9 shows a Visual C++ 6.0 debugger's Auto window containing BSTR information from the VCTERM sample. The tooltips show this information as well. A number of new formatting symbols (see Figure 10) help you see the value of a variable in your watch window.
| Figure 9 Debugger Auto Window|
There are also two new pseudoregisters: ERR and TIB. ERR picks up the last error code for the current thread, which can be extremely helpful. TIB displays the address of the Thread Information Block (also known as the Thread Environment Block). Just knowing the address often isn't enough, so I've found it useful to include Matt Pietrek's TIB.H file, which defines the TIB structure (see "Under the Hood," Microsoft Systems Journal, May 1996).
You'll need a pointer to the TIB in order to spy on it. For example, you could add the following code to your app:
Now when you need to know what's in the TIB, you can assign the address to pTIB and display the TIB structure in a watch window as shown in Figure 11.
| Figure 11 TIB Struct|
Another useful feature added to the debugger is the module list. During the Visual C++ beta I ran into a problem where MSDEV crashed upon exit. It looked like MSDEV had a nasty bug, so I attached the debugger from one instance of MSDEV to another and took a look at the Debug.Modules dialog (see Figure 12). It was interesting to see the other modules that were a part of MSDEV's process. Any one of these could be the culprit. In this case the error message pointed to KERNEL32.DLL. I doubted that the bug was in KERNEL32, so I needed to see what was on the stack at the point of the crash.
| Figure 12 Module List|
In the debugger, I went to the offending line of code by using Ctrl+G to jump to 0x77FO5149. This showed:
77F05140 mov ecx,dword ptr [esp+4]
77F05144 mov eax,1
77F05149 lock xadd dword ptr [ecx],eax
77F0514D inc eax
77F0514E ret 4
77F05151 mov eax,eax
Seeing that ECX was getting set to zero, I set a conditional breakpoint to break when ECX == 0. When the error reproduced, I took a look at the call stack and found that the problem was HHCTRL.OCX (see Figure 13).
| Figure 13 Call Stack|
While most of the techniques I used are not new, having the address ranges of the modules in MSDEV helped me decide the course of action I needed to take to find the problem. While in this particular illustration the module list was not essential, it was informative and provided a more complete view of the running application.
There are several new AppWizards that create shell projects: utility projects, extended stored procedure projects, and the cluster Resource Type Wizards. There are also some enhancements to previously existing AppWizards such as Win32 Static Library AppWizard.
Utility projects are empty when you create them, and are generally for projects that don't need a link step in the build process. This was a bit puzzling to me at first. A utility project is often used as a master project for subprojects, and may contain nothing but custom build rules. A utility project can export a makefile. You could use the utility project to create a common type library from multiple controls. Or you can also run custom tools like yaac from it.
An extended stored procedure is a function exported from a Win32 DLL that is registered with SQL Server. A single DLL may contain several extended stored procedures. Typically you prefix your function name with xp_ to indicate that it's an extended stored procedure. The wizard just facilitates some functionality that you could implement in Visual C++ 5.0, so I won't spend a lot of time discussing it.
A cluster appears to be a single server, but in reality consists of two linked machines. For example, machine A and machine B may share a SCSI cable and a private network link. Both systems service client requests. If machine A fails, machine B will pick up the work that was being done by machine A, so clients experience minimal down time. When machine A is restored, the work may once again be picked up by machine A. Resources can be disk drives or an IP address that the server needs to use.
The Cluster Resource Type Wizard creates two related DLL projects in the same workspace: The Resource Type and the Cluster Administrator Extension. The Resource Type is responsible for knowing how to speak with the resource. It translates for the Cluster Server operations on the resource.
The Cluster Administrator Extension allows the Cluster Administrator to be extended so users can set and view resource parameters through the Cluster Administrator. These property pages are
created and managed by the extension DLL. The DLLs created by the project must be run on a Microsoft Cluster Server cluster.
You can also create other kinds of administrator extensions. You can extend the context menus of Cluster Administrator, but you can't use the Cluster Resource Type Wizard to create these other kinds of extensions.
The interfaces and APIs I've discussed here are documented in the Platform SDK in the Microsoft Cluster Server section of "Windows Base Services." For more information on Microsoft Cluster Server 1.0, including white papers and marketing information, see http://www.microsoft.com/ntserverenterprise/guide/ntse_clustering.asp.
Win32 Static Library
The Win32 Static Library AppWizard isn't new, but there are rather nice changes to this wizard that are not immediately apparent. For example, Win32 static libraries that use MFC can use the ClassWizard. The steps for doing this are not straightforward, so I'll explain.
When you first start the Win32 Static Library AppWizard, you'll get the Step 1 of 1 dialog box. Select both checkboxes for "Pre-Compiled headers" and "MFC support" and finish up the wizard. At this point, the New Class option and dialog box will only let you derive from Generic Class.
Now go to File | New | Files and add a resource script to your project. ClassWizard requires that an RC file be a part of your project. Don't add anything to it, however; this is only to enable the ClassWizard. Also, it really doesn't make much sense to have a resource file in your static library. If you need local resources you should be using a DLL.
Select View and then ClassWizard. You'll get the following error message:
The ClassWizard database "D:\projects\mylib.clw" does not exist.
Would you like to build it from your source files?
||Time (in seconds)
||Visual C++ 5.0
||Visual C++ 6.0
|First run (not cached)
|Subsequent runs (cached)
|| 1 sec
Of course, you won't see results this dramatic in a real-world application. In fact, you may not notice any benefit if your application just creates a window and does few allocations. If you are allocating a lot of memory over and over again, you should see some benefit.
I did not measure performance using low memory conditions that would force hard drive (swap file) access, like users would experience if they were running several applications at once. So I don't know what effect this would have on performance.
One reason for the increased performance involves including control structures along with the allocated memory. Previous to Visual C++ 6.0, the control structures were located in tables outside of the heap. Data overwrites occasionally existed without apparent mishaps. The new heap manager is less tolerant of memory errors in your application. Since the heap is such a focal point in any application, it is important to make sure your application works correctly. To diagnose heap errors you need to enable the debug heap by calling _CrtSetDbgFlag. For more information, please see the online documentation titled "Using the Debug Heap" and the Knowledge Base article at http://support.microsoft.com/?kbid=190536.
MFC and ATL
The new MFC adds wrappers for Microsoft Internet Explorer 4.0 common controls such as the Extended Combo Box, the Date Time Picker, the IP Address control, the Month Calendar control, and the Rebar control. A new DHTML View class makes it easy to display HTML pages in your application. The new COleDBRecordView class provides OLEDB consumer and provider support, and MFC now provides Active Document containment. Greater granularity allows for smaller code overhead. For example, you can now choose not to use the document/view architecture if you don't need those classes. Finally, there is now a wizard for adding ATL support to your MFC projects.
There are also numerous new ATL features. There are wizards and C++ template classes for OLEDB consumer and provider support. Composite controls let you easily combine ActiveX controls and other Windows-based controls. There are HTML controls and new lite controls. Lite controls are versions of composite controls and HTML controls that only support interfaces for running on Internet Explorer 4.0 or Visual Basic®-based applications. Because the lite controls are targeted to these two containers, they are up to 30 percent smaller than their non-lite counterparts. Also new are the Microsoft Management Control snap-ins and generic ActiveX control hosting.
So far I've focused on the core Visual C++ tools, but here is a brief summary of some of the new enterprise features. Visual C++ 6.0 (and Visual Studio 6.0) will be the release vehicle for Microsoft Data Access Components (MDAC) 2.0. This will be the first time that the data access SDKs will ship at the same time as the developer tools. There is a new Visual Component Manager, enhanced Oracle support from the database tools, and the ADO Data control. For more information on these features, please consult the online documentation.
There are several new add-in samples for Visual C++ 6.0 (see Figure 14). These are good demonstrations of what you can do with MSDEV's object model with add-ins, and they are useful too!
By now I'm sure that this article isn't your first exposure to the newest release of Visual C++. I've tried to bring you the tangible, albeit seemingly hodgepodge, details that will yield the quickest and biggest bang for your development buck. I've explored relevant issues that you'll encounter when porting your app from earlier versions of Visual C++. Details of new compiler switches, linker enhancements, and new MSDEV capabilities were revealed, along with the cool features of ASC and Edit and Continue.
This is an exciting edition built from your feedback. From my direct experience with the Visual C++ team, I've hopefully succeeded in bringing to your attention these need-to-know development facts.
From the October 1998 issue of Microsoft Systems Journal.
Get it at your local newsstand, or better yet, subscribe.