INF: Using DB-Library with C++
ID: Q96433
|
The information in this article applies to:
-
Microsoft SQL Server Programmer's Toolkit, version 4.2
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