Multiple 16-bit USRDLL Clients Cannot Share MFC ODBC ObjectsLast reviewed: July 22, 1997Article ID: Q137307 |
1.5x
WINDOWS
kbinterop kbcode
The information in this article applies to:
SUMMARYYou cannot share CDatabase or CRecordset objects among multiple clients of a 16-bit _USRDLL DLL.
MORE INFORMATIONThe 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 REFERENCESFor 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
© 1998 Microsoft Corporation. All rights reserved. Terms of Use. |