Visual C++ Tips

08 Mar 2004 09:12

Crypto API
Debugger Output Formatting
SetFilePointer Bug with Large Files
std::min and std::max
Linker Errors
64-Bit
Enable Exception Handling
Threads
Passing Smart Pointers As Parameters
Projects
Strings
Unicode
Release With Symbols

Design Patterns

Looking for a Way to Automate Your Visual Studio Builds?

Try the Programmers' Canvas Toolkit

Debugger Output Formatting

  • Read the page Symbols for Watch Variables for some enlightening information about the debugger's Variables and Watch windows
  • For example, to view the contents of a BSTR, enter the name of the variable followed by ,su in the watch window:
    bstrVariableName,su

General

  • Initialize pointers to NULL
  • Use #pragma once in header files to make sure they are included only once
  • Prefer instantiating objects on the stack instead of using new
  • Ensure that you clean up memory, etc. by using exception handlers
  • MFC should be avoided when possible.  Use STL for containers.  Use CComBStr or STL for string manipulation instead of CString.
  • In order to be compatible with the documentation pop-ups in DevStudio, C++ members should be documented in the header file using // comments.  There shouldn't be any blank lines between the comment and the declaration.
  • // The foo class is not for dummies
    class foo
    {
      // This is the size of the foobar
      int size;
    
      // This is a bar method
      void bar();
    }

Crypto API

See here

Command Line Parser

See here

SetFilePointer Bug with Large Files

SetFilePointer doesn't work on large files when FILE_END is used.  A "large" file is one that is larger than 2^32 bytes which is roughly 4 gigabytes.  Instead, you must call GetFileSize and then call SetFilePointer with the file's size and FILE_BEGIN.

std::min and std::max

Microsoft's #defines for min and max conflict with the functions from the standard C++ library.  Since #defines and macros can not be corralled into namespaces (a serious deficiency in C++ namespaces), std::min and std::max can only be used in Windows-specific applications if you #define NOMINMAX in stdafx.h

Linker Errors

warning LNK4098: defaultlib "MSVCRTD" conflicts with use of other libs; use /NODEFAULTLIB:library

mfcs42.lib(dllmodul.obj) : warning LNK4006: _DllMain@12 already defined in MSVCRT.lib(dllmain.obj); second definition ignored

  • MFC libraries must be linked before CRT libraries
  • Open the Project Settings dialog box by clicking Settings on the Build menu.
  • In the Settings For view, select (highlight) the project configuration that's getting the link errors.
  • Click the Link tab.
  • Select INPUT in the Category combo box.
  • In the Libraries to Ignore edit box, insert the library names (for example, msvctrt.lib msvcrt.lib)  NOTE: The linker command line is equivalent to /NOD:<library name>
  • In the Object/library Modules edit box, insert the library names. You must ensure that these are listed in order and as the first two libraries in the line (for example, mfcs42.lib MSVCRT.lib).

LIBCMT.lib (crt0.obj) : error LNK2001: unresolved external symbol _main

If you get this error when building the release build, click on the C/C++ tab in Project->Settings and select Preprocessor from the Category drop-down box.  Remove the ATL_MIN_CRT flag in the Preprocessor definitions box.  By default, ATL release builds use a tiny version of the C runtime library (CRT).  However, this tiny CRT does not include certain functions which your ATL project may use.  Therefore, you will get unresolved symbols.  By removing this flag, you will tell the linker to link in full-size version of the CRT.   However, this only increases your DLL by ~30KB.

64-Bit

  • Code should port to 64 bits easily
  • Don't convert pointers to integers and vice-versa (e.g., user data in GUI objects)
  • Use the new types as described in Microsoft's recommendations

Enable Exception Handling

Exceptions should not be thrown out of COM methods - the way to guarantee this is to have a try/catch block around methods that might receive exceptions.

Turn on exception handling via Project Settings/C++/C++ Language/Enable Exception Handling.

If you get the following warning:

warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify -GX

Your code will GPF (or worse) if an exception is ever thrown.

Exception handling results in robust code.  Put clean up logic in destructors.

For instance, it's better for code to use exception handling to deal with errors that occur deep down on the stack rather than relying on code checking the return value and returning E_FAIL through multiple layers of internal calls -- instead, the code just throws an HRESULT

Threads

  • Non-MFC projects should call _beginthread or _beginthreadex, and call _endthreadex upon completion.  Failure to call _endthreadex causes the HANDLE returned by _beginthread to be invalid.
  • MFC projects should call AfxBeginThread
  • Worker threads should always end on their own.  Never stop a thread by calling the various "end thread" methods - they can leak memory and leave mutexes permanently locked (e.g., those for the C runtime).
  • See NT Synchronization Primitives
  • Every thread should have an outer try-catch block to avoid GPFs in the release build
    MFC Version:
    STDAPI ThreadFunction(LPVOID)
    {
        try
        {
          ...
        }
        catch(CException *pE) // MFC catcher
        {
          // Avoid memory leak
          pE->Delete();
        }
        catch(...)
        {
        }
        return 0;
    }
    Non-MFC version:
    STDAPI ThreadFunction(LPVOID)
    {
        try
        {
          ...
        }
        catch(...)
        {
        }
        return 0;
    }

Passing Smart Pointers As Parameters

For the following hypothetical method parameters:

// Parameters:
// [in] CComPtr<IXMLDOMNode>& pParentNode
// [in] BSTR bsName
// [in] unsigned long* pULValue

Alternatively this method could take an IXMLDOMNode * for four reasons:

  1. Callers may not be using CComPtr. Sounds crazy but it could happen - how about if someone used CComQIPtr - you have to create a temporary CComPtr just to call the method which also causes AddRef and Release to be called
  2. Conceptually there's no difference between IXMLDOMNode * and CComPtr<> because the method doesn't need to modify the smart pointer and the smart pointer doesn't provide any additional value
  3. CComPtr automatically converts itself to a pointer to the template class - IXMLDOMNode *, so existing code that uses CComPtr and calls this method would still work
  4. Since you're passing this in by reference, the code could inadvertently change the smart pointer to point to something else

To summarize, pass parameters as raw interface pointers instead of smart pointers.

Strings

See also string conversion information

Item 1: Using CComBSTR in STL containers

Use CAdapt which is in <atlbase.h>.  Failure to do so may result in memory leaks ???

std::vector< CAdapt <CComBSTR> > vect;

Item 2: Excessive string copies

Don't convert BSTRs to LPTSTRs for no reason.  Code that converts to LPTSTR just to convert it back to a BSTR wastes memory and CPU.  Make sure you have a good reason for converting to LPTSTRstd::basic_string has some useful methods but if the wstr functions can suffice, they are much faster because they don't require a string copy. 

Use CComBSTR::Attach to attach a CComBSTR to an existing BSTR without copying it.  CComBSTR has many useful methods such as +=, so sometimes std::basic_string can be avoided.

When returning BSTRs from methods, use Detach() on CComBSTR:

*pOut = CComBSTR(L"hello there").Detach();

Item 3: Passing strings as input parameters to C++ functions and methods

There are many options, which are listed below.  I find the most efficient and flexible approach is this:

  • Pass in a LPOLESTR along with int stringLength.  In other words, callers must provide the length of the input string
  • Create an identical method whose name has BSTR appended to it.  This method takes a BSTR which internally calls SysStringLen and passes it to the method that takes LPOLESTR str, int length
  • This way you satisfy both types of callers -- those who have a string and those who have a BSTR, and you've accounted for the case in which the length of a BSTR can be determined very quickly.
  • The only problem with this is you can't use specialized BSTR and CComBSTR functions, but I typically don't need them -- if I do, I write the method using BSTR
  • Generally, I prefer creating methods that take LPOLESTR and no string length to methods that take BSTR

2) Take LPOLESTR

This is the most common usage.  This will accept a string created with L"This is a string" or CComBSTR(L"This is a string").   The advantage is some flexibility and avoiding common bugs when users pass a L"foo" string instead of a real CComBSTR.

3) Take BSTR

The advantage of taking BSTR instead of LPOLESTR is that determining the length of a BSTR is much faster.  SysStringLen looks at the four bytes in front of the pointer to determine the length which is faster than scanning the entire string until a null character is found.  Another advantage is that the parameter can be passed directly to a COM method that expects a BSTR, so an extra string copy is avoided.

The disadvantage to passing BSTRs is that callers may pass in strings as L"foo" instead of CComBSTR(L"foo"), and although the compiler won't complain, SysStringLen will return a bogus value.

I don't recommend that you provide a BSTR method without providing an identical LPOLESTR method.

4) Take CComBSTR& / _bstr_t&

Not much advantage of this over #3 - CComBSTR may have some needed methods.  It's easy to create a CComBSTR from a BSTR without copying the data -- use Attach() but don't forget to use Detach().

5) Take LPTSTR

LPTSTR is passed when the code is not dealing with UNICODE strings, which is rare since all COM methods only deal with BSTRs.  This will cause gratuitious string copies.   It's generally preferable to use #2 or #3.

6) Take std::basic_string<>

I generally disapprove of this convention as it assumes that the caller uses std::basic_string<>, and most code doesn't use it

Item 4: String output parameters

There are many ways to do this which are listed below.  I prefer to pass a BSTR * and let the system allocate memory for me.  For really time-sensitive code you have to pass fixed-length arrays, but those cases are very rare.

1) Take LPOLESTR

This is not optimal as it requires the string length to have a predetermined maximum.   In many cases the function will need to return a variable-length string.

2) Take LPOLESTR * / LPTSTR *

Supports returning variable-length strings.

"new" allocates the string and the caller to use delete[] to delete it.  Not ideal if the caller really needs a BSTR to be returned since the LPOLESTR has to be converted into a BSTR, which involves reallocating the string so the 4-byte length can be put in front of the string.

3) Take BSTR *

This is the most common usage.  It supports returning variable-length strings.  Ideal if caller needs BSTR to be returned.

For most strings you can take a BSTR * to modify a string and avoid an extra string copy.  You can't do this with input strings passed to methods, however (what if the IDL specifies [in, out] ?).

Methods like foo(BSTR in, BSTR *out) typically result in an extra string copy when the string needs to be modified and you don't need the original value.  The alternative is to add a falg, as in foo(BSTR in, BSTR *out, bool & bOutContainsValidData); however, this results in a very difficult to use function.

A method like foo(BSTR *inAndOut) can avoid an extra string copy.  The string is both an input and an output.  The passed-in string must be Free'd by the called method.

Warning!  This will cause memory leaks unless callers are very careful.  If you pass a pointer to a BSTR that contains data, the previous data won't get free'd.  This happens even if a CComBSTR is used because operator &() does not free the string before returning the pointer to the BSTR data member (m_str).

4) Take CComBSTR& / _bstr_t &

Advantage is that CComBSTR::Empty() can be called implicitly inside the method.  Memory leaks are less possible.  Downside is that wrapper must be created by the caller.

Unicode

  • For UNICODE configurations, define UNICODE and _UNICODE
  • All code should, generally, build and work in UNICODE and non-UNICODE environments
    • This means using "tchar.h", USES_CONVERSION, and the various _tcs macros.   See also
    • Non-UNICODE code can take the performance hit required by converting to/from UNICODE.
  • All projects should have "Win32 Unicode Debug" and "Win32 Unicode Release" configurations

    For Exes and Libs...

    1. Choose Configurations from the Build menu
    2. Add "Unicode Debug" - Copy Win32 Debug
    3. Add "Unicode Release" - Copy Win32 Release
    4. Click OK
    5. Choose Settings from the Project menu
    6. Go to the General tab
    7. For "Win32 Unicode Debug" set Intermediate Files and Output Files to DebugU
    8. For "Win32 Unicode Release", set Intermediate Files and Output Files to ReleaseU
    9. Go to the C++ tab
    10. Choose "Preprocessor" from the combo-box
    11. For "Win32 Unicode Debug" and "Win32 Unicode Release", add _UNICODE and UNICODE to the list of preprocessor variables
    12. Go to the Link tab
    13. Choose "Output" from the combo-box
    14. The MIDL step seems to be missing from the newly created configurations.  Go to the project settings for the IDL file and copy the Debug settings to the two Unicode configurations.

Release With Symbols

Projects should have an additional configuration that adds debug symbols to "Release" builds.  This will be helpful for debugging release builds.
  1. Choose Configurations from the Build menu
  2. Add "Release Symbols" - Copy "Win32 Release MinSize"
  3. Add "Unicode Release Symbols" - Copy "Win32 Release Unicode MinSize"
  4. Click OK
  5. Select Settings from the Project menu
  6. Select "Multiple Configurations" in the "Settings For" dropdown
  7. Select "Win32 Release Symbols" and "Win32 Unicode Release Symbols"
  8. Click OK
  9. In Intermediate Files and Output Files, enter "ReleaseSymbols"
  10. Click the link tab
  11. Click on Generate Debug Info
  12. Click the C++ tab
  13. Select "Program Database for Edit and Continue" in the Debug Info combo box
  14. Select "Disable (Debug)" in the Optimizations combo box
  15. Select the "Win32 Unicode Release Symbols" configuration
  16. Click the General tab
  17. Set Intermediate Files and Output Files to "ReleaseUSymbols"