INF: Using DB-Library with C++

ID: Q96433


The information in this article applies to:


SUMMARY

This article contains an overview of issues concerning the encapsulation of DB-Library (DB-Lib) calls within a C++ class. Information is also presented on general issues you may encounter when you implement DB-Lib within a C++ program.


MORE INFORMATION

Encapsulation of DB-Library Within a C++ Class

Encapsulation of DB-Library functionality within a C++ class hierarchy can yield significant benefits for a software developer. Robust, extensible code for accessing SQL Server can be created using C++'s features of inheritance, access control, type checking and polymorphism. However, several pitfalls exist when building C++ code around DB-Library calls. This article will attempt to outline and address several potential hurdles that may be encountered when attempting to implement DB-Library within a C++ program.

DB-Library functions are fully compatible with Microsoft C++ and the MFC libraries. Under C++, DB-Library functions can be either called using a C style structured approach, or they can be encapsulated within a class hierarchy. Microsoft currently does not make available a class library specifically designed for DB-Library. It is important to remember that DB- Library functions are "C" functions. Thus, DB-Library functions must be declared within an extern "C" block as shown below:

extern "C"
{
#define DBMSWIN
#include <sqlfront.h>
#include <sqldb.h>
} 

A programmer attempting to make extensive use of the MFC classes may encounter difficulties when attempting to use CStrings as parameters to DB- Library functions. DB-Library functions are not currently "const correct", thus the CString::operator LPCSTR() function cannot be used to pass CStrings to DB-Library functions . For example, the dbcmd() function ( declared as RETCODE dbcmd( DBPROCESS *, LPSTR ) under Windows ) does not modify the contents of the string pointed to by the 2nd parameter, but the 2nd parameter is not declared const. In order to use a CString as the 2nd parameter, it is necessary to either make use of the CString::GetBuffer() / CString::ReleaseBuffer() functions or the double cast: (LPSTR)(LPCSTR)CString.

Initialization of DB-Library, particularly in the Windows environment, requires special handling. Two approaches are available when implementing a DB-Library aware class hierarchy. The first, and simplest, is to call dbinit() and install DB-Library error and message handler functions within the initialization code of the application, before any other DB-Library calls are made. If within the Windows environment, dbwinexit() should be called at program termination. This approach is similar to traditional "C" DB-Library programming techniques and has the advantage of allowing the safe calling of DB-Library functions from any point within the main body of code.

The second approach is to make all DB-Library calls through a class hierarchy based on a DB-Library initialization object class. This base class maintains a static member variable which is incremented in the constructor and decremented in the destructor. If the object increments the use count from 0 to 1, it initializes DB-Library with dbinit() and calls dbmsghandle() and dberrhandle() with pointers to exported static member functions. If the object decrements the use count from 0 to 1, it calls dbwinexit() if under Windows. This approach allows transparent management of DB-Library initialization, and offers advantages in implementing object oriented message and error handling.

Encapsulation of error and message handling functions within a C++ class can also be tricky. Under all platforms, these functions must be declared as "static cdecl" member functions. This must be done to prevent the C++ compiler from generating code to handle the implied "this" pointer argument which is passed to non-static member functions. Under Windows, the error and message handlers should be declared as "static int FAR cdecl __export". The object responsible for DB-Library initialization should then be modified to contain two static member variables of type FARPROC. Upon initialization of DB-Library, the object now sets these pointers to the error and message handling functions by calls to MakeProcInstance(), and frees them using FreeProcInstance() when freeing up DB-Library resources.

To obtain DB-Library version information, the base class responsible for calling dbinit() should contain a static member variable to hold the version string returned by dbinit(). Since dbinit() cannot safely be called twice in succession without freeing DB-Library resources with a call to dbwinexit(), it is necessary to obtain this value at DB-Library initialization time and to store the value in a static member.

An implementation of DB-Library initialization within a base class is shown below:

CString  Cdbproc::m_VersionString;
int      Cdbproc::m_nUseCount = 0;
FARPROC  Cdbproc::lpMsgHandlerSG = NULL;
FARPROC  Cdbproc::lpErrHandlerSG = NULL;

Cdbproc::Cdbproc( void )
{
    if( !m_nUseCount )
    {
        m_VersionString = (LPSTR) dbinit( );

        lpMsgHandlerSG = MakeProcInstance((FARPROC)Cdbproc::msg_handler,
                                           AfxGetInstanceHandle() );

        lpErrHandlerSG = MakeProcInstance((FARPROC)Cdbproc::err_handler,
                                          AfxGetInstanceHandle() );
        dbmsghandle( lpMsgHandlerSG );
        dberrhandle( lpErrHandlerSG );
    }
    m_nUseCount ++;
}

Cdbproc::~Cdbproc( void )
{
    m_nUseCount --;

    if( !m_nUseCount )
    {
        FreeProcInstance( lpMsgHandlerSG );
        FreeProcInstance( lpErrHandlerSG );
        dbwinexit( );
    }
} 

DB-Library error and message handling on an object by object basis (i.e. routing messages automatically to the object which caused the message to be generated ) can be an extremely useful property for a class hierarchy built around DB-Library. Using the dbsetuserdata() and dbgetuserdata() functions provided in DB-Library 4.2 and virtual member functions, this can be easily accomplished. To implement automatic routing of DB-Library errors and messages, the base class should expose a function which must be called to obtain a DBPROCESS, one or more virtual functions for use in processing DB- Library errors and messages, and a static member variable of type Cdbproc *, where Cdbproc is the data type of the base class. Before calling dbopen(), the object sets the static Cdbproc * equal to the "this" pointer. The object then obtains a DBPROCESS by calling dbopen() and immediately calls dbsetuserdata() for this new DBPROCESS, specifying the "this" pointer as the second parameter. This sets the user data in the DBPROCESS obtained from the call to dbopen() to the "this" pointer. After dbsetuserdata() is called, the DB-Library aware object sets the static Cdbproc * member variable to NULL.

The error and message handler static member functions called by DB- Library are then written to extract the "this" pointer from the passed DBPROCESS and call the virtual message processing functions within the object pointed to by the "this" pointer. The error and message handlers must be written to use the static member Cdbproc * variable as the target object should dbgetuserdata() return NULL. The use of a global Cdbproc * variable is made necessary by the fact that we cannot call dbsetuserdata() before obtaining a DBPROCESS via a call to dbopen(), yet the call to dbopen() can generate messages and errors. This forces us to provide a default object to route the error or message to should we not be able to extract a "this" pointer from the DBPROCESS within the error or message handling function using dbgetuserdata(). The code fragment below shows one way in which this process might be implemented.

// initialize static member variable

DBPROCESS * Cdbproc::m_pNewCdbprocS = NULL;

BOOL Cdbproc::Connect( LOGINREC NEAR * LoginRec, LPSTR lpszServer )
{
    BOOL fRetval = 1;

        //  NOTE:  In a multithreaded app, we would use
        //  a semaphore here to allow only one app to obtain
        //  the "newcdbproc" at a time.  However, for a single
        //  threaded program this is unnecessary.

        // set static member variable to "this"

        m_pNewCdbprocS = this;

        // logon and obtain DBPROCESS.  Set DBPROCESS * member
        // variable to new DBPROCESS.

        if( ( m_dbproc = dbopen( LoginRec,  lpszServer ) ) == NULL )
        {
            fRetval = 0;
        }
        else
        {
               // connected.  Set userdata associated with this
               // dbproc to "this"

               dbsetuserdata( m_dbproc, this );
        }
        // reset static member variable to NULL

        m_pNewCdbprocS = NULL;

        // again, for a multithreaded app, we would free the
        // semaphore here

    }
    return fRetval;
}

int FAR cdecl __export Cdbproc::err_handler( DBPROCESS NEAR *dbproc,
                                             int    severity,
                                             int    dberr,
                                             int    oserr,
                                             LPSTR  dberrstr,
                                             LPSTR  oserrstr)
{
    int nRetval = INT_CANCEL;
    Cdbproc * pCdbproc = NULL;

    if ( dbproc == NULL )
    {
        MessageBox( GetActiveWindow(),
                    "DB-Library Error",
                    "NULL DBPROCESS encountered",
                    MB_OK | MB_ICONEXCLAMATION | MB_APPLMODAL );

        nRetval = INT_CANCEL;
    }
    else
    {
        //attempt to extract "this" pointer from dbproc.

        pCdbproc = ( Cdbproc * ) dbgetuserdata( dbproc );

        // if extracted pointer is NULL, send message to object
        // specified by the static member variable m_pNewCdbprocS

        pCdbproc = ( pCdbproc == NULL ) ? m_pNewCdbprocS : pCdbproc;

        // if we still don't have an object to send the error to,
        // don't send it to anyone.
        // optionally, we could implement some sort of default error
        // handling code or terminate with an ASSERT

        if( pCdbproc != NULL )
        {
            pCdbproc->m_ErrorStruct.severity = severity;
            pCdbproc->m_ErrorStruct.dberr    = dberr;
            pCdbproc->m_ErrorStruct.oserr    = oserr;
            pCdbproc->m_ErrorStruct.dberrstr = dberrstr;
            pCdbproc->m_ErrorStruct.oserrstr = oserrstr;

            // Call virtual message processing function.

            pCdbproc->ProcessError();
        }
        nRetval = INT_CANCEL;
     }
    return nRetval;
} 

One caveat to this approach is that all objects calling DB-Library must be derived from this base class. Otherwise, the message and error handlers will not call the correct function when attempting to invoke the message processing function in the object referred to by the obtained "this" pointer.

Additional query words: dblib


Keywords          : kbprg SSrvDB_Lib 
Version           : 4.2 | 4.2
Platform          : MS-DOS WINDOWS 
Issue type        : 

Last Reviewed: March 16, 1999