DUzun's Web
Programare, proiecte personale, divertisment

DUzun it's ME
 
  
arr_d File Browser

The Visual C++ Environment

This topic provides coding guidelines when using C++. It also has useful information when debugging projects in Visual Studio.

Naming conventions
    Type Names
    Function Names
    Argument names
    True and false

Class Design
    Class Layout
    Public data
    Class size
    Inline methods
    Comments
    Construction
    Initialization versus assignment in constructors
    Assignment operators
    Casting
    const Methods
    Using the const modifier
    Type definitions and constants

Syntactic guidelines
    Indentation
    Implementation organization
    Avoid macros
    Comments
    White space
    Operators
    Operator precedence
    Nested if Statements
    Function declarations
    Global scope
    Brackets
    Variable declaration
    Bit-Fields
    Nested Headers
    Switch Statements
    Use references
    Initialization
    Null initialization
    Exceptions
    Avoid Global Data
    Avoid macros

Using C++ with MFC and Win32
    Standard C++ Data Types
    Use ASSERT and VERIFY
    Use WIN32_ASSERT
    Character strings
    Application Settings
    Windows and MFC Function Calls

Localization Requirements
    Use String Resources
    Support Unicode

Smart Types

Direct-To-COM

Active Template Library

Useful C++ Tips
    A Better Callback Model

Debugging Tips in Developer Studio
    Backing up after failure
    Unicode string display
    Variable value display
    Undocking windows
    Conditional breakpoints
    Preloading DLLs
    Changing display formats
    MFC Class Autoexpand
    Keyboard Shortcuts
    Navigating through online Help topics

Naming conventions

Name variables and constants using the following format (this is an abridged Hungarian notation):

[<scope>_]<type><name>
Prefix Variable scope
mInstance class members
cStatic class member (including constants)
gGlobal static variable
<empty>local variable or struct or public class member
<type>
Prefix Data Type
bBoolean
bybyte or unsigned char
cx/cyshort used as size
dwDWORD, double word or unsigned long
fnFunction
hHandle
iint (integer)
lLong
pa pointer
sString
szASCIIZ null-terminated string
wWORD unsigned int
x,yshort used as coordinates

<name> describes how the variable is used, or what it contains.The <scope> and <type> portions should always be lowercase, and the <name> should use mixed case:

Variable Name Description
m_hWnda handle to a HWND
ipEnvelopea smart pointer to a COM interface
m_pUnkOutera pointer to an object
c_isLoadeda static class member
g_pWindowLista global pointer to an object

Type Names

All type names (class, struct, enum, and typedef) begin with an uppercase letter and use mixed case for the rest of the name:

    class Foo : public CObject { . . .};
    struct Bar { . . .};
    enum ShapeType { . . . };
    typedef int* FooInt;

Typedefs for function pointers (callbacks) append Proc to the end of their names.

    typedef void (*FooProgressProc)(int step);

Enumeration values all begin with a lowercase string that identifies the project; in the case of ArcObjects this is esri, and each string occurs on separate lines:

    typedef enum esriQuuxness
    {
      esriQLow,
      esriQMedium,
      esriQHigh
    } esriQuuxness;

Function Names

Name functions using the following conventions:

For simple accessor and mutator functions, use Get<Property> and Set<Property>:

    int GetSize();
    void SetSize(int size);

If the client is providing storage for the result, use Query<Property>:

    void QuerySize(int& size);

For state functions, use Set<State>, and Is<State> or Can<State>:

    bool IsFileDirty();
    void SetFileDirty(bool dirty);
    bool CanConnect();

Where the semantics of an operation are obvious from the types of the arguments, leave type names out of the function names.

Instead of

    AddDatabase(Database& db);

consider using

    Add(Database& db);

Instead of

    ConvertFoo2Bar(Foo* foo, Bar* bar);

consider using

    Convert(Foo* foo, Bar* bar)

If a client relinquishes ownership of some data to an object, use Give<Property>. If an object relinquishes ownership of some data to a client, use Take<Property>:

    void GiveGraphic(Graphic* graphic);
    Graphic* TakeGraphic(int itemNum);

Use function overloading when a particular operation works with differ-ent argument types:

    void Append(const CString& text);
    void Append(int number);

Argument names

Use descriptive argument names in function declarations. The argument name should clearly indicate what purpose the argument serves:

    bool Send(int messageID, const char* address, const char* message);

True and false

There are at least three different sets of keywords for indicating the truth value of an expression. C++ has a built in data-type bool, with keywords true and false. Win32 defines TRUE and FALSE macros. VB compatible COM programming requires the use of the Automation type VARIANT_BOOL, with macros VARIANT_TRUE and VARIANT_FALSE.

These keyword macros have one thing in common. True evaluates to a nonzero value, and False evaluates to zero. VARIANT_TRUE is defined as -1, which means that the correct macros and keywords must be used when comparing variables.

Class Design

Conforming to a class design standard leads to easy-to-use and maintainable C++ class implementations.

Class Layout

Organize class definitions in the following manner:

    class MyClass : public CObject, private MyPrivateClass
    {
      // The public description of the class goes here. It describes what
      // the class represents (from a client's perspective), and highlights
      // which methods are the most important. Optionally, it shows examples
      // of how to use the class.
 
    public:
      // Nested class and struct definitions.
      // Enumerations, typedefs and constants.
 
     MyClass()          {}
      virtual ~MyClass() {}
 
      // Public operations.
 
      // Public accessor/mutator functions.
 
    protected:
      // Protected description of the class goes here. This documentation
      // usually consists of instructions to potential subclassers on how to
      // subclass the class correctly.
 
      // Nested class and struct definitions.
      // Enumerations, typedefs and constants.
      // Protected data members.
 
      // Subclass-accessible operations. These are usually virtual.
 
    private:
      // Nested class definitions.
      // Enumerations, typedefs, and constants.
      // Private data members.
 
      // Private operations.
    };

Organizing classes this way helps clients of the class, since it groups public operations and functions at the beginning. All the proprietary implementation details occur at the end of the class definition, since clients do not need to know about them.

Public data

Do not make data members public unless the class is intended to be a semi-intelligent structure. One of the major benefits to using objects is the ability to encapsulate and hide implementation details from clients.

Class size

Keep classes small to decrease their complexity and increase their reusability. If you cannot summarize what the class does in a paragraph or less, chances are it is too complex and should be broken up into multiple classes.

Inline methods

Use inline methods only for empty implementations or for those containing only a few statements. Do not add a semicolon after the function body, but do add spaces to offset the brackets when there are statements in the function body. When several methods are inlined, line up the function bodies on the same column.

    M Class() : m_count(0) {}
    void SetCount(int count) { m_count = count; }

Comments

To increase class legibility, add comments after the method or data member. If the comment fits to the right, place it there; otherwise, add it afterwards as an indented comment.

    public:
      void SetCount(int count);
      //
      // Sets the count property of the object. Use this method only
      // when you are resetting the object.
 
      int GetCount(); // Gets the current count.
 
    private:
      int     m_count; // The current count.
      bool    m_inited;
       //
       // This property indicates whether or not the object is
       // currently being inited.

Construction

Be sure to provide a copy constructor and overload for operator if the default structure-wise copy will result in an invalid object. Alternatively, consider hiding both by making them private.

    MyClass(const MyClass& rhs);
    MyClass& operator=(const MyClass& rhs);

Initialization versus assignment in constructors

When the constructor is invoked for an instance of a class, the follow-ing operations occur in the following order (storage is allocated for the entire instance):

  • The constructor is invoked for the base class (in superclass to subclass order for all classes in the hierarchy).
  • The constructors for any data members that are class variables are invoked in the order of their declaration in the interface specification.
  • Execution of code defined within the constructor body occurs.

To avoid redundant operators, the following approach to constructor definition is suggested:

  • Explicitly specify the call to the base class in the initialized list, even if the default constructor is intended. This makes it more likely that errors can be detected during walkthroughs.
  • Always initialize data members that are class variables using the initialization list, and initialize them in the order in which they are declared in the interface specification. This avoids unnecessary calls to default constructors, and prevents unexpected side effects due to order of initialization.
  • Initialize any primitive data types and pointers in either the initializer list or the body of the constructor.

Assignment operators

The assignment operators (=) should be explicitly defined for all classes. The automatic member-wise copy provided by the compiler is adequate only for shallow copy situations. Even if it works for initial development, it is likely to be inadequate when maintenance is performed. The following precautions should always be taken:

  • The assignment operator should always return a reference to itself. The return type will be Type& and the return value will be *this.
  • The "other" assignment operators ( +=, -=, *=, and so on) should conform to the same behavior as the primary assignment operator.
  • Always check for self-assignment. The following example format should always be used for simple assignment operator.
    Type& Type::operator = (const Type& rhs)
    {
      if (this != &rhs)
      {
        ... code to perform cop  goes here ...
      }
      return *this;
    }
  • Assign base variables by invoking the base assignment operator. The following example indicates appropriate behavior:
    DerivedType& DerivedType::operator = (const DerivedType& rhs)
    {
      if (this != &rhs)
      {
        BaseType::operator = (rhs);
        ... code to perform cop  goes here ...
      }
      return *this;
    }

Casting

In general, all casts should now use one of the following explicit castings.

    static_cast<>()
    const_caset<>()
    dynamic_cast<>()
    reinterpret_cast<>()

The new style casts are preferred because they are more explicit and more visible.

const Methods

Make methods const when they do not change the object in any way.

    int GetCount() const { return m_count; }

If a method is conceptually const from the client's viewpoint, but internally the implementation needs to adjust some private data member, make the function const but cast away its const-like quality in the implementation.

    int GetCount() const;
 
    int MyClass::GetCount() const
    {
      MyClass*  self = const_cast<MyClass*> this;  // Cast away const-ness
 
      if (self->m_countLoaded)
        self->LoadCount();
 
      return m_count;
    }

The keyword mutable can explicitly exempt data elements from const-like quality.

Using the const modifier

The const modifier is used in variable declaration to indicate that the variable cannot be modified after initialization. If the variable is declared with program, file, or function scope, it must be initialized when it is declared. When a pointer variable is declared, there are five possible options, as shown below.

Statement Meaning
Foo* pFooBoth the pointer and the referenced data may be modified
const Foo* pFooThe pointer may be modified, but not the referenced data
const Foo& pFooThe referenced data may not be modified
Foo* const pFooThe referenced data may be modified, but not the pointer
const Foo* const pFooNeither the pointer or referenced data may be modified

const options for pointer variables

When using reference variables, the reference may never be modified. The const modifier only refers to the referenced data.

When const is used as a keyword following a class-member function, it indicates that the member function will not modify any class member variables. The const keyword must be used in both the interface definition and the implementation.

Type definitions and constants

If a constant or a type definition (class, struct, enum, or typedef) conceptually belongs to another class (that is, its only use is within the interface or implementation of another class), place it within the public, protected, or private scope of that class.

    class Foo : public CObject
    {
    public:
      struct Bar
      {
        int   width;
        int   height;
      };
 
      typedef int ProgressLevel;
 
    protected:
       typedef enum esriProgress
      {
        esriPIdle,
        esriPRunning,
        esriPCompleted
      } esriProgress;
    };

Syntactic guidelines

The following syntactic guidelines make code more readable; they help maintainability and group development.

Indentation

Use tabs for indentation, and set the tab size equal to two spaces. Do not replace tabs with spaces.

Implementation organization

Organize .cpp files as follows:

    // Include precompiled header.
    // Other includes.
 
    // Macro definitions.
 
    // Global data.
    // Static class members.
 
    // Constructor(s).
    // Destructor.
 
    // Public operations.
    //   These should occur in the same order as the class definition.
 
    // Protected operations.
    //   These should occur in the same order as the class definition.
 
    // Private operations.
    //   These should occur in the same order as the class definition.

Avoid macros

Where possible, use const definitions instead of macros.

Instead of

    #define MAX_COUNT    10

use

    const int    g_maxCount    = 10;

Instead of

    #define DEFAULT_USER TEXT("Moe")

use

    const TCHAR* g_defaultUser = TEXT("Moe");

Comments

Use C++-style comments rather than C comments, unless you are writing a file that needs to be compiled by the C compiler.

    // This is a C++ comment and should be used in all C++ code.
    /* This is a C comment and should onl  be used in C code. */

White space

Arguments should be separated by a comma and single space. Spaces should not occur between the function name and the initial parenthesis, or between the parentheses and the arguments.

    result = MyFunction(count, name, &context);

Separate functions with at least one blank line.

    void MyClass::MyFunction1()
    {
    }
 
    void MyClass::MyFunction2()
    {
    }

Operators

Surround all operators with a space to the left and right.

    size += sizeof(address);
    i = j / 10 - 25;

Do not use extra spaces with these operators: !, #, ->, ., ++, and ?.

    if (!fileIsDirty) return;
    #define DEBUG_ME
    AfxGetApp()->ParseCommandLine(cmdInfo);
    theConnection.Close();
    if (i++ > 10 && j? < 100)

Operator precedence

Where operator precedence is not immediately obvious, use parentheses to indicate order of execution.

    result = (i - (10 - count)) / 42;

Nested if Statements

Avoid deeply nested blocks of if statements. They are difficult to read and debug.

    if (i < 10)
    {
      if (i != 5)
      {
        if (j == 42)
        {
          MyFunc(i, j);
        }
      }
    }

Instead, use algorithmically equivalent else-if blocks that check the reverse conditions and are not deeply nested:

    if (i <=  0)
    {
    }
    else if (i == 5)
    {
    }
    else if (j == 42)
    {
      MyFunc(i, j);
    }

Function declarations

Whenever possible, place function declarations on a single line:

   bool ConnectToDatabase(const char* machineName, const char* databaseName);

If the declaration is too long, break it up into multiple indented lines, with all argument names positioned in the same column:

    bool Connection::ConnectToDatabase(
           const char* machineName,
           const char* databaseName,
           const char* userName,
           const char* password,
           unsigned long timeout,
           int& connectionID);

When calling functions, try to place all arguments on a single line. If this is not possible, break them up into multiple lines, with each line indented one tab stop in from the leftmost character of the function name:

   bool connectionResult = myConnection.ConnectToDatabase(machine, database,
                             user, password, timeout,
                             connectionID);

Global scope

Use :: to indicate global scope.

    result = ::AfxMessageBox(errMsg, MB_OK, 0);

Brackets

Brackets should occupy an entire line by themselves.

    for (int i = 0; i < 10; i++)
    {
    }
 
    if (i <= 10)
    {
    }
    else
    {
    }

Variable declaration

Where possible, declare variables where they are used, rather than grouping them together at the beginning of a function.

    void MyFunc()
    {
      . . .
      CString database;
      theDB.Quer DatabaseName(database);
      . . .
    }

Where possible, declare loop variables in the first line of a for statement.

    for (int i = 0; i <  0; i++)
    {
      . . .
    }

Avoid declaring multiple local variables on a single line.

    int connCount, connSuccess, passwordHandle, securityAttributes;

Instead, put them on separate lines, or at least group together only those that are logically related.

    int connCount, connSuccess;
    int passwordHandle, securityAttributes;

When declaring pointers, place the asterisk directly next to the type, and leave a space before the variable, argument, or function name.

Instead of

    char *myText;

use

    char* myText;

Instead of

    void *MyFunc(int *arg);

use

    void* MyFunc(int* arg);

Bit-Fields

Use bit-fields where possible to promote efficiency.

    unsigned m_flagA:1;
    unsigned m_flagB:1;
    unsigned:0; // pads to integer boundary

Nested Headers

Avoid including headers in other headers. Use forward declarations where possible.

    class Bar;
    class Foo
    {
    public:
      Bar* m_bar;
    };

Switch Statements

Construct a switch statement as follows. Note that the case and break keywords are indented one level, and the statements are all indented two levels.

    switch (code)
    {
      case firstCase:
        . . .
      break;
 
      case secondCase:
        . . .
      break;
 
      default:
        . . .
      break;
    }

When an individual case contains many statements, move them into a separate function, or enclose them with additional brackets.

    case nthCase:
    {
      . . .
    }
    break;

Always provide a default case within switch statements, even if the result is to log an error message and terminate. Always provide a break or return statement for each case path, or an explicit comment on the justification for fall-through behavior.

Use references

Use references instead of pointers unless a NULL pointer value is needed. This is because the semantics of passing a pointer in C and C++ is very ambiguous.

    void MyFunc(int* s);

The parameters of the function above could represent any of the following:

  • A single int
  • An array of int of a certain length
  • An input-only parameter
  • An output-only parameter
  • Both an input and an output parameter

By using references (and const), these ambiguities are avoided.

    void MyFunc(int& s);
    void MyFunc(const int[]& s);
    void MyFunc(const int& s);
    void MyFunc(int& s);

Initialization

Use initialization syntax to initialize all data members to their default values, unless the initialization is conditional. Do not leave any members uninitialized. Place each data member on its own separate line.

    MyClass()
      : m_count(0),
        m_name(0)
    {
    }

Null initialization

Use 0 instead of NULL. In C++, the value 0 can be used to initialize any numeric or pointer variable.

Exceptions

A class should handle the exceptions thrown by objects that it uses, and should define and throw its own exceptions when an unrecoverable situation occurs.

Avoid Global Data

Global data is inherently dangerous in a multithreaded environment. Where possible, try to embed all data in objects. In situations where data represents a shared resource, be sure to protect access to it with a critical section.

Avoid macros

C++ provides language constructs that in many cases obviate the need for macros. The constructs are integrated into the compiler and debugger. Thus, you gain type safety and ease of debugging.

Instead of doing this:

    #define MyConstant 10

do this:

    const long s_MyConstant  = 10

Instead of doing this:

    #define MyHelper(a, b) \
    a = 1;                 \
    b = 2;                 \
    a = b + a;             \

do this:

    inline double MyHelper(double a, double b) {?}

About the only time you need macros is to adjust behavior in accordance with build settings:

    #ifdef _DEBUG
    OutputDebugString("I'm Here");
    #endif

Using C++ with MFC and Win32

Standard C++ Data Types

Use standard C++ data types (int, short, long, bool, void, and others) unless the exact size of the data is critical to the behavior of the function, as with serialization or file I/O. In these cases, use Windows data types that are explicitly signed/unsigned and have an unambiguous size.

Window Type Description
BYTEunsigned char
SHORTsigned 16-bit integer
LONGsigned 32-bit integer
WORDunsigned 16-bit integer
DWORDunsigned 32-bit integer

Window data types

Use ASSERT and VERIFY

The ASSERT and VERIFY macros are invaluable debugging aids that should be used liberally throughout your code to check for entry and exit conditions, or any other exceptional situations.

    ASSERT(pWnd);
    VERIFY(loading && userCount > 2);

Use ASSERT during the development phase to insure that clients are adhering to rules for your interfaces. An assertion failure during development indicates that the contract between caller and callee has been broken.

The VERIFY macro does not go away in release builds. Use this only to check for catastrophic failure.

ASSERT and VERIFY behave identically in debug builds. However, in release builds, ASSERT compiles into nothing whereas the arguments to VERIFY get treated as regular statements.

    ASSERT(wnd && loading);               // NOP in release build.
    VERIFY(contents->LoadContextMenu());  // LoadContextMenu happens in
                                          // release build!

Use WIN32_ASSERT

Any Win32 call that sets an error code can use WIN32_ASSERT to throw an exception that displays the result of GetLastError(). However, this macro behaves the same as VERIFY, in that the side effect remains even in a release build, so be sure that this is the behavior you want.

Character strings

Consider using CString for all string data that you track and manipulate, instead of managing your own character arrays.

If MFC is not available, consider using one of the string smart types covered later in this topic.

  • Since CString is entirely TCHAR-based, Unicode is handled transparently with no extra work on your part.
  • CString is very efficient with memory?where the same string value is passed from one CString to the next, no new storage is allocated until the second string is modified.

Application Settings

Use the Windows registry to store and retrieve application settings. Do not use .INI files.

Windows and MFC Function Calls

Calls to all Windows and global MFC functions should use :: to indicate global scope.

Localization Requirements

When developing an application intended for use in more than one language, a number of issues must be considered that will make the localization of the software an easier process.

Use String Resources

Never place string constants in the code; instead, define them in a re-source file from which they are loaded at runtime.

    CString  errorMessage;
    errorMessage.LoadString(IDS_FILE_NOT_FOUND);

The only exceptions are debugging strings?they may reside directly in the code, since they do not affect the released product and need not be localized.

Store all string constants together in a standard module to facilitate translation to other languages.

Support Unicode

All code should be Unicode-compliant; therefore, use arrays of TCHAR (instead of char), for representing character strings. Depending on the compilation settings, TCHAR expands either into single-character strings (ANSI) or wide-character strings (Unicode).

For string literals, use the TEXT macro to force the string or character to be Unicode compliant.

    TCHAR dirSep = TEXT('\');
    CString driveName(TEXT("C:"), 2);

Instead of the standard ANSI string functions, use the generic text map-ping macros. A list of the more common string-handling functions, along with the correct macro to use, appears on the table below.

ANSI Function Unicode-compliant macro
strlen_tcslen
strcat_tcscat
strncpy_tcsncpy
strchr_tcschr
strncmp_tcsncmp
strstr_tcsstr
atoi_ttoi
atol_ttol
splitpath_tsplitpath

For a complete list of generic text-mapping macros, refer to the Visual C++ online help, in C/C++ Run-Time Library Reference, in the 'Generic Text Mappings' chapter.

Smart Types

Smart types are objects that behave like types. They are C++ class implementations that encapsulate a data type, wrapping it with operators and functions that make working with the underlying type easier and less error prone, but transparent. When these smart types encapsulate an interface pointer, they are referred to as smart pointers. Smart pointers work by working with the IUnknown interface to ensure that resource allocation and deallocation is correctly managed. They accomplish this by various functions, construct and destruct methods, and overloaded operators. There are numerous smart types available to the C++ programmer. The two main types of smart types covered here are defined by Direct-To-COM and the Active Template Library. The relevant Direct-To- COM compiler extensions for the ArcObjects developer will be covered in the Active Template Library section later in this topic.

DTC was an initiative from Microsoft to make COM C++ programming more Visual Basic like. To achieve this DTC provides a set of classes and compiler extensions that shipped initially with Visual Studio 5.

Smart types can make the task of working with COM interfaces and data types easier, since many of the API calls are moved into a class implementation; however, they must be used with caution, and never without a clear understanding of how they are interacting with the encapsulated data type.

Direct-To-COM

The smart type classes supplied with DTC are known as the Compiler COM Support Classes and consist of:

  • _com_error?this class represents an exception condition in one of the COM support classes. This object encapsulates the HRESULT and the IErrorInfo COM exception object.
  • _com_ptr_t?this class encapsulates a COM interface pointer. See below for common uses.
  • _bstr_t?this class encapsulates the BSTR data type. The functions and operators on this class are not as rich as the ATL BSTR smart type, hence this is not normally used.
  • _variant_t?this class encapsulates the VARIANT data type. The functions and operators on this class are not as rich as the ATL VARIANT smart type, hence this is not normally used.

To define a smart pointer for an interface you can use the macro _COM_SMARTPTR_TYPEDEF like this:

    _COM_SMARTPTR_TYPEDEF(IFoo, __uuidof(IFoo));

The compiler expands this as such:

    typedef _com_ptr_t<_com_IIID<IFoo, __uuidof(IFoo)> > IFooPtr;

Once declared, it is simply a matter of declaring a variable as the type of the interface and appending Ptr to the end of the interface. Below are some common uses of this smart pointer that you will see in the numerous C++ samples.

    // Get a CLSID GUID constant
    extern "C" const GUID __declspec(selectan ) CLSID_Foo = \
        {0x2f3b470c,0xb01f,0x11d3,{0x83,0x8e,0x00,0x00,0x00,0x00,0x00,0x00}};
    // Declare Smart Pointers for IFoo, IBar and IGak interfaces
 
    _COM_SMARTPTR_TYPEDEF(IFoo, __uuidof(IFoo));
    _COM_SMARTPTR_TYPEDEF(IBar, __uuidof(IBar));
    _COM_SMARTPTR_TYPEDEF(IGak, __uuidof(IGak));
 
    STDMETHODIMP SomeClass::Do ()
    {
      // Create Instance of Foo class and QI for IFoo interface
      IFooPtr       ipFoo(CLSID_Foo);
      if (ipFoo == 0) return E_NOMEMORY
 
      // Call method on IFoo to get IBar
      IBarPtr      ipBar;
      HRESULT hr = ipFoo->get_Bar(&ipBar);
      if (FAILED(hr)) return hr;
 
      // QI IBar interface for IGak interface
      IGakPtr       ipGak(ipBar);
 
      // Call method on IGak
      hr  = ipGak->DoSomething()
      if (FAILED(hr)) return hr;
 
      // Explicitly call Release()
      ipGak = 0
      ipBar = 0
 
      // Let destructor call IFoo's Release
      return S_OK;
    }

Active Template Library

ATL defines various smart types, as seen in the list below. You are free to combine both the ATL and DTC smart types in your code.

ATL smart types:

  • CComPtr?class encapsulates a COM interface pointer by wrapping the AddRef and Release methods of the IUnknown interface.
  • CComQIPtr?class encapsulates a COM interface and supports all three methods of the IUnknown interface: QueryInterface, AddRef, and Release.
  • CComBSTR?class encapsulates the BSTR data type.
  • CComVariant?class encapsulates the VARIANT data type
  • CRegKey?class provides methods for manipulating Windows registry entries.
  • CComDispatchDriver?class provides methods for getting and setting properties, and calling methods through an object's IDispatch interface.
  • CSecurityDescriptor?Class provides methods for setting up and working with the Discretionary Access Control List (DACL).

This section examines the first four smart types and their uses. The example code below written with ATL smart pointers, looks like the following:

    // Get a CLSID GUID constant
    extern "C" const GUID __declspec(selectan ) CLSID_Foo = \
 
        {0x2f3b470c,0xb01f,0x11d3,{0x83,0x8e,0x00,0x00,0x00,0x00,0x00,0x00}};
 
    STDMETHODIMP SomeClass::Do ()
    {
      // Create Instance of Foo class and QI for IFoo interface
      CComPtr<IFoo>       ipFoo;
      HRESULT hr = CoCreateInstance(CLSID_Foo, NULL, CLSCTX_INPROC_SERVER,
                                IID_IFoo, (void **)& ipFoo);
       if (FAILED(hr)) return hr
 
      // Call method on IFoo to get IBar
      CComPtr<IBar>      ipBar;
      HRESULT hr = ipFoo->get_Bar(&ipBar);
      if (FAILED(hr)) return hr;
 
      // IBar interface for IGak interface
      CComQIPtr<IGak>       ipGak(ipBar);
 
      // Call method on IGak
      hr  = ipGak->DoSomething()
      if (FAILED(hr)) return hr;
 
      // Explicitly class Release()
      ipGak = 0
      ipBar = 0
 
      // Let destructor call Foo's Release
      return S_OK;
    }

When reassigning an ATL smart pointer, a debug ASSERT is raised if the previous interface pointer is not explicitly released.

The most common smart pointer seen in the samples is the DTC type. In the examples below, which illustrate the BSTR and VARIANT data types, the DTC pointers are used. When working with CComBSTR, use the text mapping L"" to declare constant OLECHAR strings. CComVariant derives directly from the VARIANT data type, meaning that there is no overloading with its implementation, which in turn simplifies it use. It has a rich set of constructors and functions that make working with VARIANTs straightforward;+ there are even methods for reading and writing from streams. Be sure to call the Clear method before reusing the variable.

    IFeaturePtr  ipFeature(GetControllingUnknown()); // Get IFeature interface
 
    // Get IFields interface and find index of Name field
    long*         lIndex;
    IFieldsPtr    ipFields;
    HRESULT hr;
 
    hr = ipFeature->get_Fields(&ipFields);
    if (FAILED(hr)) return hr;
 
    hr = ipFields->FindField(CComBSTR(L"Name"), &lIndex);
    if (FAILED(hr)) return hr;
 
    // Get OID change its t pe to String and set Name
    // then set it back onto the feature
    CComVariant   vID;
    hr = ipFeature->get_Value(0, &vID);
    if FAILED(hr)) return hr;
 
    // Change its data t pe
    hr = vID.ChangeT pe(VT_BSTR);
    if (FAILED(hr)) return hr;
 
    hr = ipFeature->put_Value(lIndex, vID);
    if (FAILED(hr)) return hr;
    hr = ipFeature->Store();
    if (FAILED(hr)) return hr;

When working with CComBSTR and CComVariant, the Detach() function returns the underlying data type and should be used when passing a pointer as an [out] parameter of a method. The use of the Detach method is shown below.

    void GetName(BSTR* name)
    {
      CComBSTR bsName(L"FooBar");
      *name = bsName.Detach();
    }

Useful C++ Tips

These C++ tips are included here as tips for better development, and should not be seen as a set of rules.

A Better Callback Model

Instead of passing function pointers and opaque context data to implement callbacks, consider defining an abstract notification class that encapsulates the various notification events that can be fired. Clients can then subclass and instantiate this class to register for notification.

To see how this might work, consider the following example. It shows how to implement a traditional callback mechanism between an object (Bar) and a client (Foo).

    class Foo : public CObject
    {
    public:
      Foo(Bar& bar) { bar.m_client = this; bar.m_clientProc = BarStub; }
 
      int Bar(char* string)  { printf("%s", string); }
 
      static int BarStub(void* client, char* string)
                       { ((Foo*)client) ->Bar(string); }
    };
    class Bar
    {
    public:
      t pedef int (*BarProc)(void* client, char* string);
      void*     m_client;
      BarProc   m_clientProc;
      void InvokeCallback()
        { if (m_clientProc) (*m_clientProc)(m_client, string); }
    };

The Bar class defines the prototype for the callback function, and has two member variables: the address of the function to invoke and the object (stored as a void*) to pass along to the callback. Furthermore, at the Foo end, an additional static stub routine (BarStub) is needed that casts the opaque pointer to a Foo object before the real Foo method (Bar) is invoked. This seems like a lot of overhead for such a simple task. And it is dangerous, since it casts the void* into a Foo*.

However, there is a better way. By taking advantage of abstract classes in C++, the relationship between Foo and Bar can be much more cleanly implemented:

    class Foo : public CObject, public BarInterface
    {
    public:
      Foo(Bar& bar)         { bar.m_client = this; }
      int Bar(char* string) { printf("%s", string); }
    };
 
    class BarInterface
    {
    public:
      virtual int Bar(void* client, char* string) = 0;
    };
 
    class Bar
    {
    public:
      BarInterface*   m_client;
 
      void InvokeCallback() { if (m_client) m_client->Bar(string); }
    };

The difference in this solution is that an abstract class, BarInterface, has been introduced. It lives alongside the Bar class, like before, and contains virtual methods that must be overridden by subclasses. These methods represent the events (callbacks) that the Bar class sends. The events are handled when a client provides a subclass that implements them. In this example, Foo derives both from CObject and from BarInterface, and implements the BarInterface method, Bar.

There are several advantages to this approach. First of all, type safety is always maintained, unlike the former example; objects are never cast to void* and then cast back to objects. Also, when a class provides multiple callbacks (which is often the case), they can all be encapsulated together in the abstract callback class. Some or all of them may be tagged with = 0, indicating that they must be overridden; this prevents clients from unwittingly implementing one callback while forgetting another which is vital for proper functioning. One can also provide default implementations for the callbacks, should a subclass choose not to implement one. (Providing defaulted functions under the traditional model is difficult and error-prone.) Lastly, by using virtual functions directly, there is no need for static stub functions.

Debugging Tips in Developer Studio

Visual C++ comes with a feature-rich debugger. These tips will help you get the most from your debugging session.

Backing up after failure

When a function call has failed and you'd like to know why (by stepping into it), you don't have to restart the application. Use the Set Next Statement command to reposition the program cursor back to the statement that failed (right-click on the statement to bring up the debugging context menu). Then, just step-in to the function.

Unicode string display

Set your debugger options to display Unicode strings (Tools -> Options? -> Debug -> Display Unicode Strings check box).

Variable value display

Pause the cursor over a variable name in the source code to see its current value. If it is a structure, click it and bring up the QuickWatch dialog box (the Eyeglasses icon, or Shift-F9), or drag and drop it into the Watch window.

Undocking windows

If the Output window (or any docked window, for that matter) seems too small to you, try undocking it to make it a real window. Just right-click it and toggle the Docking View item.

Conditional breakpoints

Use conditional breakpoints when you need to stop at a breakpoint only once some condition is reached (a for-loop reaching a particular counter value). To do so, set the breakpoint normally, then bring up the Breakpoints window (Ctrl+B or Alt+F9). Select the specific breakpoint you just set and then click the Condition button to display a dialog in which you specify the breakpoint condition.

Preloading DLLs

You can preload DLLs that you wish to debug before executing the program. This allows you to set breakpoints up front rather than wait until the DLL has been loaded during program execution. (Project -> Settings? -> Debug -> Category -> Additional DLLs.) Then, click in the list area below to add any DLLs you wish to have preloaded.

Changing display formats

You can change the display format of variables in the QuickWatch dialog box or in the Watch window using the formatting symbols in the following table.

Symbol Format Value Displays
d, isigned decimal integer0xF000F065-268373915
uunsigned decimal integer0x0065101
ounsigned octal integer0xF0650170145
x, Xhexadecimal integer615410x0000F065
l, hlong or short prefix for d, I, u, o, x, X00406042, hx0x0C22
fsigned floating-point3./2.1.500000
esigned scientific notation3./2.1.500000e+00
ge or f, whichever is shorter3./2.1.5
csingle character0x0065'e'
sstring0x0012FDE8"Hello"
suUnicode string "Hello"
hrstring0S_OK

To use a formatting symbol, type the variable name followed by a comma and the appropriate symbol. For example, if var has a value of 0x0065, and you want to see the value in character form, type var,c in the Name column on the tab of the Watch window. When you press ENTER, the character-format value appears: var,c = 'e'. Likewise, assuming that hr is a variable holding HRESULTS, view a human-readable form of the HRESULT by typing "hr,hr" in the Name column.

You can use the formatting symbols shown in the following table to format the contents of memory locations.

Symbol Format Value
ma 64 ASCII characters 0x0012ffac
.4...0...".0W&..
.....1W&.0.:W..1
...."..1.JO&.1.2
.."..1...0y....1
m 16 bytes in hex, followed by 16 ASCII characters 0x0012ffac
B3 34 CB 00 84 30 94 80
FF 22 8A 30 57 26 00 00 .4...0....".0W&..
mb 16 bytes in hex, followed by 16 ASCII characters 0x0012ffac
B3 34 CB 00 84 30 94 80
FF 22 8A 30 57 26 00 00 .4...0...".0W&..
mw 8 words 0x0012ffac
34B3 00CB 3084 8094
22FF 308A 2657 0000
md 4 double-words 0x0012ffac
00CB34B3 80943084 308A22FF 00002657
mu 2-byte characters (Unicode) 0x0012fc60
8478 77f4 ffff ffff
0000 0000 0000 0000

With the memory location formatting symbols, you can type any value or expression that evaluates to a location. To display the value of a character array as a string, precede the array name with an ampersand, &yourname. A formatting character can also follow an expression:

  • rep+1,x
  • alps[0],mb
  • xloc,g
  • count,d

To watch the value at an address or the value pointed to by a register, use the BY, WO, or DW operator:

  • BY returns the contents of the byte pointed at.
  • WO returns the contents of the word pointed at.
  • DW returns the contents of the doubleword pointed at.

Follow the operator with a variable, register, or constant. If the BY, WO, or DW operator is followed by a variable, then the environment watches the byte, word, or doubleword at the address contained in the variable.

You can also use the context operator { } to display the contents of any location.

You can apply formatting symbols to structures, arrays, pointers, and objects as unexpanded variables only. If you expand the variable, the specified formatting affects all members. You cannot apply formatting symbols to individual members.

To display a Unicode string in the Watch window or the QuickWatch dialog box, use the su format specifier. To display data bytes with Unicode characters in the Watch window or the QuickWatch dialog box, use the mu format specifier.

MFC Class Autoexpand

Microsoft Developer Studio has an autoexpand capability for Microsoft Foundation Class library classes. The string (or other information) between the braces ({ }) is automatically expanded.

Keyboard Shortcuts

There are numerous keyboard shortcuts that make working with the Visual Studio editor faster. Some of the more useful keyboard shortcuts are listed below.

The text editor uses many of the standard shortcut keys used by Windows applications like Word. Some specific source code editing short-cuts are listed below.

Shortcut Action
Alt+F8Correct indent selected code based on surrounding lines.
Ctrl+]Find the matching brace.
Ctrl+JDisplay list of members.
Ctrl+SpacebarComplete the word, once the number of letters entered allows the editor to recognize it. Use full when completing function and variable names.
TabIndents selection one tab stop to the right.
Shift+TabIndents selection one tab to the left.

Below is a table of common keyboard shortcuts used in the debugger.

Shortcut Action
F9Add or remove breakpoint from current line.
Ctrl+Shift+F9Remove all breakpoints.
Ctrl+F9Disable breakpoints.
Ctrl+Alt+ADisplay auto window and move cursor into it.
Ctrl+Alt+CDisplay call stack window and move cursor into it.
Ctrl+Alt+LDisplay locals window and move cursor into it.
Ctrl+Alt+ADisplay auto window and move cursor into it.
Shift+F5End debugging session.
F11Execute code one statement at a time, stepping into functions.
F10Execute code one statement at a time, stepping over functions.
Ctrl+Shift+F5Restart a debugging session.
Ctrl+F10Resume execution from current statement to selected statement.
F5Run the application.
Ctrl+F5Run the application without the debugger.
Ctrl+Shift+F10Set the next statement.
Ctrl+BreakStop execution.

Loading the following shortcuts can greatly increase your productivity with the Visual Studio development environment.

Shortcut Action
ESCClose a menu or dialog box, cancel an operation in progress, or place focus in the current document window.
CTRL+SHIFT+NCreate a new file.
CTRL+NCreate a new project.
CTRL+F6 or CTRL+TABCycle through the MDI child windows one window at a time.
CTRL+ALT+ADisplay the auto window and move the cursor into it.
CTRL+ALT+CDisplay the call stack window and move the cursor into it.
CTRL+ALT+TDisplay the document outline window and move the cursor into it.
CTRL+HDisplay the find window.
CTRL+FDisplay the find window. If there is no current Find criteria, put the word under your cursor in the find box.
CTRL+ALT+IDisplay the immediate window and move the cursor into it. Not available if you are in the text editor window.
CTRL+ALT+LDisplay the locals window and move the cursor into it.
CTRL+ALT+ODisplay the output window and move the cursor into it.
CTRL+ALT+JDisplay the project explorer and move the cursor into it.
CTRL+ALT+PDisplay the properties window and move the cursor into it.
CTRL+SHIFT+OOpen a file.
CTRL+OOpen a project.
CTRL+PPrint all or part of the document.
CTRL+SHIFT+SSave all of the files, project, or documents.
CTRL+SSelect all.
CTRL+ASave the current document or selected item or items.

Navigating through online Help topics

Right-click a blank area of a toolbar to display a list of all the available toolbars. The Infoviewer toolbar contains up and down arrows that allow you to cycle through help topics in the order in which they appear in the table of contents. The left and right arrows cycle through help topics in the order that you visited them.

News

arr_r Login

Flag Counter

arr_d Limba / Language