Titlebanner
Home
  About
  Blog
  News
  Contact

CV
  download

Code
  C++ AVL Tree
  C++ HashMap

Articles
  VC6 STL
  VC7 STL
  VC6 debug tips

Links


python powered

xemacs

asd


Home » Articles » C++ standard library tips for VC6

C++ standard library tips for VC6

Disclaimer: A lot of things here are only valid for VC6. Obviously, a lot of people chose to stick with VC6 for Win32-only development, therefore this document still holds some value for a lot of people.

Nowadays (20060505), I use VC7 for all STL related things, since VC6 is officially deprecated and doesn't compile with the latest Platform SDK.

STLPort or native Dinkumware?

A lot of people prefer to use STLPort instead of the native STL provided with VC6. At present (2005-06-26), STLPort 4.6.3 is not "considered a good STL release". I had numerous issues, mostly related to STLPort iostreams. Anyway it was no real showstopper (see the next paragraph for one). Right now I use STLPort 5.0 from CVS because I need new-style (new? The standard is called C++98 ;-) iostreams for the socket++ library.

Also, std::string is really fast with STLPort. If you also happen to use stringstreams, STLPort really shines.

Multithreaded problems

The real main problem with native VC6 STL appears when using the STL with multi-threaded programming. For normal usage it actually works out quite nice. But the following code will kill VC6 within 10 seconds on any machine I encountered - please note that the string is not allocated on the heap or shared between threads.

/**
 * @brief appending to string on the "stack" from multiple threads
 */
unsigned long __stdcall test(void *dummy_p) {
    for (;;) {
        std::string strTest;
        for (int i=0;i<10;i++) {
            strTest += "abcdef";

        }
    }
    return 0;
}

/**
 * @brief Main tester
 */
void main(void) {
    int i;
    DWORD thread_id;

    for (i=0;i<100;i++)
        CreateThread(NULL,0,test,(void *)i,0,&thread_id);
}

I am really glad somebody found this out on the usenet, see: GoogleCrash . Basically you really do not want this kind of thing to happen, and therefore if you are doing multithreaded programming and using the STL with VC6, you need a fix.

Of course, this is not really helpful. Regarding STLPort again, I must say that dinkumware provide a fixed library beginning at USD 135. After going through some "pain" in the STLport forums I think this is an offer worth considering. But then again I personally am totally annoyed of the fact that such a bug as above can happen and therefore I am a bit reluctant to pay for a fix in a standard libary bug what should be fixed anyway.

Portable CString

If you miss some CString functionality, try the class CStdString , available here which is a CString-alike wrapper above std::string. Here are some other things you might find useful.

Compiler warning #4786

try setting #pragma warning(disable:4786) as early as possibly, if you use precompiled headers, put it in there before including the standard library headers.

CString::ToUpper() / ToLower() equivalent

#include <string>
#include <algorithm>
#include <cctype>
#include <cstdio>

// convert string to uppercase or lowercase
std::string sTesting;
std::transform( sTesting.begin(),
            sTesting.end(),
            sTesting.begin(),
            (int(*)(int))::tolower );

C-Library ::sprintf() equivalent

This one allocates memory and uses std::vector and dynamic allocation. This is not for high-performance usage. Anyway:

#include <string>
#include <vector>
#include <list>
#include <cstdarg>

inline std::string vstringprintf(const char* psFormat, va_list args)
{
  std::vector<char> rgCharBuf(500);
  int iRet = -1;
  while (iRet == -1)
  {
      iRet = _vsnprintf( &rgCharBuf[0],
                    rgCharBuf.size() - 1,
                    psFormat,
                    args );
      if (iRet == -1)
           rgCharBuf.resize( rgCharBuf.size() * 2 );
   }
   rgCharBuf[rgCharBuf.size() - 1] = '\0';
   return &rgCharBuf[0];
}

inline std::string stdprintf(char const *psFormat, ...)
{
    va_list args;
    va_start(args, psFormat);
    std::string sRet = vstringprintf(psFormat, args);
    va_end(args);
    return sRet;
}

// use it like this
{
    std::string sName = stdprintf( "The name is: %s, the age ist %d",
                             sName, lAge );
}

CString::TrimLeft/Right() equivalent

With no further interruption from my side:

#include <string>
#include <iostream>

using namespace std;

// ------------------------------------------------------------------
// --- Trim a std::string of trailing whitespaces (l&r) ---
// ------------------------------------------------------------------
static string TrimLeftStr( const string& sInStr, const string& sInChars )
{
    const string::size_type iPos = sInStr.find_first_not_of( sInChars );
    if ( iPos == std::string::npos )
        return sInStr ;
    return sInStr.substr( iPos );
}

static string TrimRightStr( const string& sInStr, const string& sInChars )
{
    const string::size_type iPos = sInStr.find_last_not_of( sInChars );
    if ( iPos == std::string::npos )
        return sInStr ;
    return sInStr.substr( 0, iPos + 1 );
}

static string TrimAll( const string& sInStr )
{
    string sWhiteChar = " \t";
    string sOutStr    = TrimLeftStr( sInStr, sWhiteChar );
    sOutStr           = TrimRightStr( sOutStr, sWhiteChar );
    return sOutStr;
}

// testing
int main(int argc, char* argv[])
{
    string sTest    = "\t\t\t  This parrot is dead. \t";
    string sOutput  = TrimAll( sTest );
    cout << ">" + sOutput + "<";
    return 0;
}

C Wrapper for C++ Classes (one way to do it)

This is not strictly regarding the c++ standard library but sometimes still relevant and therefore I mention it. In fact I really hate doing this, but sometimes it is a requirement to have a C API.

The C++ ABI (application binary interface) is not standartized on a given platform, as you probably know. Therefore linking C++ DLLs from two different compiler vendors without sourcecode is problematic. One fundamental approach to this problem (and DLL Hell, and binary components in C++) is described in Don Box's essential COM reading called Essential COM (guess what?) . Read the first chapter and you get the whole thing (basically exporting ABCs with pure virtual functions). If the overall approach described in that chapter is not feasible for you or you just really need the plain straight C API you can create an C-API wrapper.

This is the class we would like to export from a DLL in a straightforward way.

/// a simple class
class CCarEngine
{
public:
    CCarEngine(void);
    virtual ~CCarEngine();

// --- methods ---
public:
    void    Enter();
    void    Leave();
    void    Start();
    void    Drive( const char* psDest );
};

This is the header file the C users will eventually get (all the C people will get is this header file and the DLL (import library + binary ):

#ifndef _CARENGINE_H_
#define _CARENGINE_H_

/* win32 export directives */
#ifdef CARENGINE_EXPORTS
#   define CARENGINE_API __declspec(dllexport)
#else
#   define CARENGINE_API __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C"
{
#endif


/*
 *  the glorious engine api
 */


/* known types */
struct  CCarEngine;
typedef CCarEngine*     HENG;

/* operations on engines */
CARENGINE_API HENG      CarEng_New    ( );
CARENGINE_API void      CarEng_Del    ( HENG pEng );
CARENGINE_API void      CarEng_Enter  ( HENG pEng );
CARENGINE_API void      CarEng_Leave  ( HENG pEng );
CARENGINE_API void      CarEng_Start  ( HENG pEng );
CARENGINE_API void      CarEng_Drive  ( HENG pEng, char* psDest );


#ifdef __cplusplus
}
#endif


#endif

As you can see the basic win32 C-DLL skeleton makes up for a lot of these lines here, not our actual API. The basic Idea is to forward declare the C++ class in this exported C Header File (CarEngine.h) as a struct. Now we supply the implementation, and it looks like this:

#include "CarEngine.h"            // the C API forward declares this class


// the real class
struct CCarEngine                 // note: using struct here
{
public:
    CCarEngine(void);
    virtual ~CCarEngine();

// --- methods ---
public:
    void    Enter() {}
    void    Leave() {}
    void    Start() {}
    void    Drive( const char* psDest ) {}
};

// ------------------------------------------------------------------
// --- cumbersome wrapping ---
// ------------------------------------------------------------------
CARENGINE_API HENG CarEng_New    ( )           { return new CCarEngine(); }
CARENGINE_API void CarEng_Del    ( HENG pEng ) { delete pEng; }
CARENGINE_API void CarEng_Enter  ( HENG pEng ) { pEng->Enter(); }
CARENGINE_API void CarEng_Leave  ( HENG pEng ) { pEng->Leave(); }
CARENGINE_API void CarEng_Start  ( HENG pEng ) { pEng->Start(); }
CARENGINE_API void CarEng_Drive  ( HENG pEng, char* psD) {pEng-Drive(psD);}

Yeah, this really is ackward but straightforward and simply solves the problem which I thought had gone down the drain 10 years ago in a mechanical, systematic way. The sample above needs some checking of its input values (assert or whatever) at the C-API function level, but it was skipped here for the sake of brevity. With this approach you can still design and think in C++ and export your types as ADTs ;-) to the C programming language.

More later

Good bye

top