Multiple 16-bit USRDLL Clients Cannot Share MFC ODBC Objects

ID: Q137307

1.5x WINDOWS kbinterop kbcode

The information in this article applies to:

SUMMARY

You cannot share CDatabase or CRecordset objects among multiple clients of a 16-bit _USRDLL DLL.

MORE INFORMATION

The CDatabase and CRecordset classes (and classes derived from them) use ODBC to gain access to data sources. Internal to ODBC, memory is allocated to hold state information on behalf of each client calling into ODBC. This memory is allocated task-specific (that is, not using GMEM_SHARE). This means that only the client that made the call that initially caused the internal memory to be allocated can reliably use that object.

One strategy for dealing with this situation is to have each _USRDLL DLL client call an initialization function in the DLL that returns a unique number that identifies the client. Then, in subsequent calls to the DLL, have the client pass this number back to identify which client is making a call.

On the DLL side, create separate database/recordset objects for each DLL client. Use the passed identifier to know which database/recordset to operate on.

The following "Sample Code" section shows the code for a simple _USRDLL DLL that allows for multiple clients and allocates a CDatabase object for each one. It provides a function that allows the client to retrieve the driver name for the CDatabase object created on its behalf. The client can pass in a data source name or NULL if it wants the user to select a registered ODBC data source. The following functions are provided by the DLL:

  Init() - Initializes the client's database object and returns an
           identifier to be used in subsequent calls.

  DriverName() - returns the database's driver name in a caller-supplied
                 buffer.

  Term() - Tells the DLL that the client will not be using its services
           any more.

A typical sequence of calls would be:

  char buffer[20];
  int  nID = Init(myHwnd,NULL);
  DriverName(nID,buffer,20);
  MessageBox(buffer,"Driver Name");
  Term(nID);

The DLL maintains an array of CDatabase objects and passes the index of the element of the array as the return value of the Init() function. The caller then passes this array index as a parameter in any further calls to the DLL. The DLL uses this index to determine which database object should be used to process the call.

Sample Code

/* Compile options needed: /ALw /D "_USRDLL" /GD
*/ 

//----------------------------------------------------------------------
// mydll.h - header file

#ifdef __cplusplus extern "C" { #endif

int  FAR PASCAL _export Init(HWND hwnd,char* szDSN);
void FAR PASCAL _export DriverName(int nID,char* szName,int cbName);
void FAR PASCAL _export Term(int nID);

#ifdef __cplusplus } #endif

//----------------------------------------------------------------------
// mydll.cpp - implementation file

#include <afxwin.h>
#include <afxext.h>
#include <afxcoll.h>
#include <afxdb.h>

// data members
// 
CPtrArray* pDbArray = NULL;
int        nClients = 0;

// exported functions
// 
extern "C" int FAR PASCAL _export Init(HWND hwnd,char* szDSN) {
  int nReturn = -1;
  AfxLockTempMaps();
  TRY
  {
    // make sure m_pMainWnd is valid for CDatabase::Open()
    AfxGetApp()->m_pMainWnd = CWnd::FromHandle(hwnd);
    CDatabase*  pDb = new CDatabase;
    if (pDb->Open(szDSN))
    {
      // if database array is not allocated, do so
      if (pDbArray == NULL)
        pDbArray = new CPtrArray;

      // first try to reuse existing elements
      BOOL  bFound = FALSE;
      for (int i = 0; i <= pDbArray->GetUpperBound(); i++)
      {
        CDatabase* ptr = (CDatabase*)pDbArray->GetAt(i);
        if (!ptr)
        {
          pDbArray->SetAt(i,(void*)pDb);
          nReturn = i;
          bFound = TRUE;
          break;
        }
      }

      // if you can't reuse existing elements, add one
      if (!bFound)
        nReturn = pDbArray->Add((void*)pDb);

      nClients++;
    }
  }
  CATCH(CException,e)
  {
    // handle exceptions here
  }
  END_CATCH

  // free the temporary CWnd object
  AfxUnlockTempMaps();
  return nReturn;
}

extern "C" void FAR PASCAL _export DriverName(int nID,char* szName,

  int cbName)
{
  TRY
  {
    short  cbReturned;

    // get the database for this client
    CDatabase*  pDb = (CDatabase*)pDbArray->GetAt(nID));

    // get the driver name for the database
    ::SQLGetInfo(pDb->m_hdbc,SQL_DRIVER_NAME,
    szName,cbName,&cbReturned);
  }
  CATCH(CException,e)
  {
    // handle exceptions here
  }
  END_CATCH
}

extern "C" void FAR PASCAL _export Term(int nID) {

  TRY
  {
    // get this client's database, close and delete it
    CDatabase*  pDb = (CDatabase*)pDbArray->GetAt(nID));
    pDb->Close();
    delete pDb;

    // set the array element to NULL
    pDbArray->SetAt(nID,NULL);

    // if no more clients, empty and delete the array
    if (!(--nClients))
    {
      pDbArray->RemoveAll();
      delete pDbArray;
      pDbArray = 0;
    }
  }
  CATCH(CException,e)
  {
    // handle exceptions here
  }
  END_CATCH
}

// CWinApp-derived class
// 
class CMyDLL : public CWinApp { public:
  virtual BOOL InitInstance();
  virtual int ExitInstance();

  CMyDLL(const char* pszAppName)
    : CWinApp(pszAppName) { }
};

BOOL CMyDLL::InitInstance() {

  return TRUE;
}

int CMyDLL::ExitInstance()
{
  return CWinApp::ExitInstance();
}

CMyDLL NEAR myDLL("mydll.dll");

// end source code

REFERENCES

For more information, please see the following article in the Microsoft Knowledge Base:

   ARTICLE-ID: Q110475
   TITLE     : INF: Sharing ODBC Handles Among Several Applications

Additional reference words: kbinf 1.50 1.51 1.52 1.52a 1.52b 2.50 2.51 2.52 2.52a 2.52b KBCategory: kbinterop kbcode KBSubcategory: MfcDatabase Keywords : kb16bitonly

Last Reviewed: July 23, 1997