Microsoft Visual C++ 6.0 Migration Guide

Microsoft Visual C++ Team
Microsoft Corporation

August 28, 1998

Summary: Provides a technical drill-down of the new features of Visual C++ 6.0. Familiarity with Visual C++ necessary. (11 printed pages) Discusses:

  1. Changes to the development environment

  2. Changes to the compiler

  3. Changes to the linker

  4. Changes to the debugger

The Microsoft® Visual C++® version 6.0 development system includes many new features to enhance developer productivity, improve executable performance, and ensure the highest levels of control. However, before migrating to Visual C++ 6.0, it is prudent to carefully analyze the changes made to the product and consider their impact on your code and development cycle. This guide will assist your migration to the Microsoft Visual C++ 6.0 development system and focuses specifically on the compiler, linker, and debugger with the goal of enabling you to take the fullest advantage of Visual C++ 6.0.

Note   Projects built in previous 32-bit versions of Visual C++ can be opened and saved in Visual C++ 6.0 projects automatically. For more information, see the "Porting and Upgrading" section of the Visual C++ Programmer's Guide (available in the MSDN Library that ships with Visual C++ 6.0 and online at http://msdn.microsoft.com/developer/default.htm).

Changes to the Development Environment and Editor

IntelliSense Technology

Microsoft IntelliSense® Technology (also known as IntelliSense or Statement Completion) allows easy access to useful definitions while writing C or C++ source code in the editor. Function names, parameters, and comments are displayed. When you type a character that offers an opportunity for Visual C++ to automatically complete (such as '.' or '->' on a variable name, or '(' on a function name, additional information will be presented. If you don't want this to happen automatically, you can clear any of the "Statement completion options" check boxes in the Editor tab of the Options dialog box. The following text describes the different IntelliSense options and usage scenarios.

Auto list members

When you type the name of a class or struct and type '.' or '->', Visual C++ will display an alphabetically sorted list of member variables and functions. You can select from the list of candidates with the mouse or by typing the first characters of the item you want to select.

Code comments

When displaying the list of variables/members, Visual C++ will search the header in which the class is defined for comments for that item (first next to the function definition, and then before it if there are none next to it). If comments are found, they will be displayed in a ToolTip window next to the parameter list. The standard C and C++ comment delimiters are supported.

Auto Parameter Info

When you have chosen a function, Visual C++ presents a ToolTip window with the function's parameters after you type the '(' character. As arguments are entered, the ToolTip window highlights the current parameter in boldface. Overloaded functions are supported by clicking in the ToolTip to switch between implementations.

Auto Type Info

Auto Type Info displays the type of a variable while you're editing the file. Simply place the pointer on the variable, pause, and the type of the variable will appear in a ToolTip.

Global completion

Global completion allows you to select from system application programming interface (API) functions, available C++ classes, instance variables, and local variables. Because there is no way to automatically detect when such a case exists, you must press <CTRL><SPACE> to bring up the function/class list.

Dynamic ClassView Updating

The same technology in Visual C++ 6.0 that enables IntelliSense in the editor also enables Dynamic ClassView updating. Dynamic ClassView updating is a feature that ensures the ClassView is up-to-date with class, function, and variable information immediately, without requiring a save, rebuild, or compile. As you enter a variable declaration, for example, the variable will appear in the ClassView as soon as the type name and one character of the variable name have been entered.

Quick Macro Recording

Similar to keystroke recording and playback, Quick Macro Recording is a menu shortcut to the more extensive macro recording functionality. You can record any action supported by the Developer Studio Object Model into a temporary VBScript macro with this feature, available from the Tools menu.

Changes to the Compiler

New and Improved Warnings

New compiler warnings have been added to help catch some common programming errors.

Improved unreferenced variable detection

Unreferenced variables are now detected in both default (/Od) and optimized (/Ox) builds. The compiler will also now detect cases where a local variable is initialized but not referenced:

int test()
{
   int   nValue = 15;
      // nValue not used in code...
}

Additionally, these errors are now reported at the line declaring the symbol rather than the end of the function.

Empty if statements

The compiler will now catch cases such as:

if (<cond>);      // note extra semi-colon
   <statement>;   // should execute only if <cond> is true

and issue the following warning:

4390 : empty controlled statement found; is this what was intended?

Improved verification of storage-class or type specifiers

The compiler detects a variety of situations where storage-class specifiers could be incorrectly placed. For example:

__declspec(dllexport) extern "C" void foo();   // Here

class C {
   int * virtual vfunc();         // And here
};

will now generate the following warning:

4518 : storage-class or type specifier(s) unexpected here; ignored

Incorrect or missing assignment

Cases where an expression statement has an operator with no side effect as the top of the expression are now detected:

int i;
i == func();   // user intended i = func();
      // new warning C4553: '==' : operator has no effect; 
      // did you intend '='?

or:

int i, j;
i + j;      // user intended ???
      // new warning 4552: operator has no effect, did you 
      // forget something?

Warning on possibly wrong precedence with <<

Detects a possible operator precedence error:

a = a << b + c;   // user probably intended (a << b) + c, 
         // but will get a << (b + c)

will now generate the following error:

4554 : possible operator precedence error; use parentheses to avoid ambiguity

Improved warning on redefinition

With previous versions of Visual C++, it was sometimes difficult to find the original definition that led to a redefinition warning. In Visual C++ 6.0, the compiler will now give the location of both declarations:

int i;

char i;

results in the following messages:

test.cpp(3) : error C2371: 'i' : redefinition; different basic types
test.cpp(1) : see declaration of 'i'

Improved template warnings

In previous versions of Visual C++, it was sometimes difficult to understand error messages in code that uses templates. Visual C++ 6.0 provides additional information to make this task easier. In the following example:

template <typename T> class foo {
   T aT;          // Can't specialize on void
};

template <typename T> class foo2 {
   foo<T> myFoo;       // This is OK, as long as foo<T> is OK.
};

void bar()
{
   foo2<void> bad;
}

Visual C++ 5.0 would give only a first error message, shown below. Visual C++ 6.0 adds additional information to locate the instantiations involved:

test.cpp(6) : error C2182: 'aT' : illegal use of type 'void'
While compiling class template instantiation 'foo<void>' referenced at : test.cpp(10)
While compiling class template instantiation 'foo2<void>' referenced at : test.cpp(15)

Better Inlining Control

The inline and __inline keywords are hints to try to inline the function, and the optimizer decides whether inlining should be performed based upon optimization switches (optimizing for size versus speed) and other heuristics.

The __forceinline keyword has been added to override the decision of the optimizer, and inline wherever possible. In some cases, the compiler will not inline because of command-line switches or other reasons. For example, the optimizer will not inline in the following cases:

  1. With /Ob0, which is the default for debug builds.

  2. SEH / EH mismatch in a function call (SEH calls EH or EH calls SEH) means the one called cannot be inlined.

  3. Functions with variable argument lists.

  4. Function recursion. This behavior can be changed (subject to the following restriction) with #pragma(inline_recursion, on) to enabling inlining in recursive cases.

  5. Inline depth exceeds the limit. This is done to handle recursion. The default for this value is 8, and can be controlled with #pragma(inline_depth, <n>) where n < 256 and #pragma inline_recursion(on | off).

  6. Some functions with copy constructed object passed by value when -GX/EHs/EHa is on.

  7. Functions returning an unwindable object by value when -GX/EHs/EHa is on.

  8. Functions with inline assembly when compiling without -Og/Ox/O1/O2.

If the compiler cannot inline a function with __forceinline, it will generate a level 1 warning (4714).

It is important to understand the behavior of your code and the trade-offs between code size and code speed when using this keyword and measure the results, because it could result in larger and slower code.

Note   The proper functioning of __forceinline cannot necessarily be verified from the map file, because taking the address of a function or exporting a function will result in an instance of the function being generated (though the function will be inlined where called by name).

An expression that takes the address of a function will force an instance of the function in the OBJ. If a function is called through a function pointer, the instance of the function in the OBJ will be called, although the function will be inlined (subject to the other restrictions) when called by name.

This is by design. Inlining is a compile-time operation and calling a function through a function pointer is a run-time operation. In all but the most trivial examples (for instance, when the compiler can optimize the 'address of a function/call through the pointer' back to the function) calling a function through a function pointer will not inline and is not expected to inline.

_assume Keyword

The __assume keyword is a hint to the optimizer that says the expression passed as the argument is true until the expression is killed (by interference with other variables, assignment to one of the variables, and so on). It can be used in conjuction with assert() during debug builds to ensure that the expression used with __assume is always true.

The format of the keyword is:

__assume(expr)

A typical usage of __assume is in assert macros. For example:

#ifdef DEBUG
# define ASSERT(e)    (void) ((e) ? 0 : assert(__FILE__, __LINE__))
# define ASSERTNR(e)    (void) ((e) ? 0 : assert(__FILE__, __LINE__))
#else // RELEASE build
# define ASSERT(e)    (void) (((e) ? 0 : assert(__FILE__, __LINE__)), __assume(e))
# define ASSERTNR(e)    (__assume(e))
#endif

#define UNREACHABLE   0

void foo(int *p)
{
      // Optimizer can eliminate p==NULL checks which might follow
   ASSERTNR(p!=NULL);

   switch(bar()){
      case 1:
         blah(1);
         break;

      case 2:
         blah(-1);
         break;

      default:
            // default case unreachable, so the optimizer
            // can assume bar() only returns 1 or 2, and 
            // eliminate range checks
         ASSERTNR(UNREACHABLE);
   }
}

Run-Time Error Checks

The Visual C++ 6.0 compiler can embed certain run-time checks in code to detect common bugs. Enabling these checks is done using the -GZ compiler flag. Run-time checks can only be used in debug (-Od) builds.

The following checks are added.

Auto-initialization of local variables

All local variables are initialized to a specific value (0xCC). This should help catch uses of uninitialized variables.

Function pointer call stack validation

The stack pointer (ESP) is checked to make sure it is the same before and after the call through the function pointer. This will catch cases where a function prototype has the incorrect number of parameters if the function uses the __stdcall, __fastcall, or __thiscall calling convention. __cdecl cases are not caught because the caller does both the pushes and the pops of parameters.

It will also catch cases where a __stdcall function is called through a __cdecl function pointer.

Call stack validation

The stack pointer (ESP) is checked at the end of the function to see that the stack pointer has not been changed. This can catch cases where ESP is corrupted in inline assembly or a function's calling convention is misdeclared. For example:

void foo(int i);    // misdefinition - should be void __stdcall foo(int i);

void bar() {
...
foo(4);
...
// the ESP corruption caused by foo will be caught here, as bar exits
}

In this case, the improper prototype of foo() is caught.

Placement Form of Operator Delete

The placement form of operator delete is now supported. This allows the proper delete function to be called in cases when placement new is used.

Changes to the Linker

Revised Import Library Format

The import library format has been revised to reduce the size of library files. Most files are 50-75 percent smaller.

Delay Load Imports

The linker can now create code to automatically manage delay loading of dynamic-link libraries (DLLs). This can be very useful when your application links to DLLs but doesn't always need them. Simply add:

   /DELAYLOAD:<dllname>

to the link command to enable delay loading.

Map File Can Now Show Exports

To get a listing of exports, add:

   /MAPINFO:EXPORTS 

to the linker command line.

Changes to the Debugger

Edit and Continue

Edit and Continue can dramatically increase your productivity by allowing you to make code changes and continue execution without exiting the debugger. While some changes will require a rebuild, in most cases you can merely change the code and continue debugging.

Enabling Edit and Continue

Note   Edit and Continue is enabled by default for debug projects created with Visual C++ 6.0. However, if you are importing a project from a previous version of Visual C++, you should follow these instructions.

To enable Edit and Continue, you must change the following settings:

  1. Tools menu->Options…->Debug tab->Edit and continue debugging (enabled).

  2. Project menu->Settings…->C/C++ tab->Debug info: Program database for "Edit & Continue" (selected).

  3. Project menu->Settings…->Link tab-> Category: Customize (selected)->Incremental Linking: Enabled for "Edit & Continue" (selected).

  4. A full rebuild of the project is also required.

Using Edit and Continue

The use of Edit and Continue is largely transparent to the user. Merely do a Step or Go after editing to initiate Edit and Continue. If the code edit is not supported, you will be prompted to rebuild, as usual.

Restrictions

Here is a partial list of operations Edit and Continue does not support:

  1. Edits of header files

  2. Edits of C++ class definitions

  3. Edits of function prototypes

  4. Most edits of global/static code

Variant and GUIDs Decoded for Display

VARIANT display

Variants are automatically displayed in their correct form, so integers are displayed numerically, BSTRs as strings, and so on. Also displayed is the type of the Variant, for example:

VARIANT vt;
vt.vt = VT_I2;
vt.iVal = 123

In this case, vt will be displayed in Locals, Watch and DataTips as {123 VT_I2}. VT_BYREF objects will also be displayed automatically, as will Microsoft Foundation Classes (MFC) COleVariants.

GUID and IID display

Types based on globally unique identifiers (GUIDS), including IIDs, CLSIDs and REFIIDs, are displayed by name (if found in the registry), or, failing that, in the normal GUID hexadecimal form.

Thread Information Block

This is a new pseudo-register that displays the Thread Information Block (TIB, also confusingly known as a TEB) for the current thread. Like all registers, it may be prefixed with an @ sign. Information on the content of TIBs may be found in "Under the Hood," Microsoft Systems Journal (MSJ), May 1996.

GetLastError() Value Displayed as Register

This is a new pseudo-register called ERR that displays the last error code for the current thread. It retrieves the same value as calling the GetLastError API. Using it in conjunction with the ",hr" modifier produces very useful output. Like all registers it may be prefixed with an @ sign.

MMX Register Display

You can now display MMX® registers in the Watch and QuickWatch window using the symbols MM0-MM7. MMX registers are 64-bit integer registers and will be displayed on all x86 machines, whether they support the MMX instructions or not. MMX is a registered trademark of Intel Corporation.

Improved V-Table and Function Pointer Display

Pointers to functions and v-table entries are now displayed symbolically wherever possible, including parameters, instead of just hexadecimal addresses, as in Visual C++ 5.0.

Improved Disassembler Output

The disassembler output has been improved, particularly with regard to the inclusion of symbols in the output.

Undecorated Symbols

The disassembler and callstack windows now undecorate C++ names when they did not do so before, such as when displaying system callstacks from DBG files. For example, a function in rpcrt4.dll previously displayed as the somewhat meaningless:

?MTAInvoke@@YGJPAUtagRPCOLEMESSAGE@@KPAUIRpcStubBuffer@@PAUIRpcChannelBuffer@@PAK@Z

is now displayed as:

MTAInvoke 

with a proper argument list.

New Formatting Symbols

The following table lists the new symbols for Watch variables in Visual C++ 6.0.

,hr This displays 32-bit values (HRESULTs or Win32® error codes) in a more meaningful way, such as S_OK, or E_NOTIMPL, or as a system error message such as "File not found." Unfortunately, it is not possible to set up an automatic rule in autoexp.dat. To do this for types such as HRESULT, you have to manually add the ",hr" postfix to symbol names in the Watch window.
,st This displays string values either as ANSI or Unicode, depending on the Unicode Strings setting. This is now used in the default autoexp.dat file to display MFC CStrings according to the current setting.
,mq This displays memory as quadwords to go with the existing mb, mw, and md formatting symbols.
,wm This displays numeric values decoded into the names of windows message numbers (the WM_ constants), so this code
WORD wMessage = WM_CLOSE

displays wMessage,wm as {WM_CLOSE}.

,wc This displays numeric values decoded into window class flags (the WC_ constants).

Load COFF and Exports Option

This new option allows additional debugging information to be loaded and can be found in the Debug tab in the Options dialog box. Because this option can affect debugger performance during debuggee loading, it defaults to off.

COFF Symbols

Other debuggers use COFF symbols, but Visual C++ (which uses CodeView-format debug information) does not. However, sometimes it can be useful to read COFF information when no other information is available. For example, the Microsoft Visual Basic® 5.01 development system includes an msvbvm50.dbg file that supplies debug information in COFF format, so enabling this option allows that file to be read. To see what form of debug information is in a .dbg file, use dumpbin –headers and look at the Debug Directory for COFF.

Exports

When there is no other debugging information available (for example, Microsoft Windows® 95 operating system files), this option also converts the Export table of each loaded DLL into a symbol. To see what sort of symbols this will add for a particular DLL, use dumpbin –exports.

Module List

When your application crashes during a call to a system DLL or someone else's code, how do you determine which DLL was active when the crash occurred? The new Module List in Visual C++ 6.0 displays the address, name, path, and load order for all DLLs used by your program. You can use this dialog box to track down which DLL was active when your application crashed.

Conclusion

Despite the significant changes to the editor, compiler, linker, and debugger in Visual C++ 6.0, migrating projects from previous version of Visual C++ can go smoothly. However, to take full advantage of the new features in Visual C++ 6.0, developers need to understand what the new features are, how they impact existing code, and how to use them. This document makes an attempt at providing that information, but the best education comes from installing Visual C++ 6.0, building samples, and importing code. Use this document and the MSDN Library to make sure you're developing at your best with Visual C++ 6.0.