方法一:修改注册表
设计思路
一般情况下,当用户在控制面板中配置好 ODBC数据源后, Windows系统便在注册表中加入了一些子键来存储用户的配置结果。当应用程序需要用到数据源时, Windows便会通知底层接口查阅注册表中该数据源的配置。如果用户删除了某个 ODBC数据源,那么也会在注册表中有所反应。如果配置的数据源是用户数据源, Windows系统便会修改注册表的 HKEY_CURRENT_USER\SOFTWARE\ODBC\ODBC.INI子键;如果配置的数据源是系统数据源, Windows系统便会修改注册表的 HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBC.INI主键。因此,我们可以在应用程序中使用 Windows API中的注册表编辑函数来完成 Windows所做的工作,这样就可以达到动态加载数据源的目的。
具体实现
对于不同类型的数据源,注册表的修改也各有不同,但基本上都要修改两个地方。一个是在 ODBC.INI子键下建立一个与数据源描述名同名的子键,并在该子键下建立与数据源配置相关的项;另一个是在 \ODBC.INI\ODBC Data Sources子键下建立一个新项以便告诉驱动程序管理器 ODBC数据源的类型。下面以配置一个 Microsoft Access数据源为例给出实现此功能的函数的代码。
#include <Windows.h>
#include <atlstr.h>
/*
此函数可以动态创建Microsoft Access数据库的系统DSN。如果要创建用户DSN只需要把HKEY_LOCAL_MACHINE改为HKEY_CURRENT_USER
如果之前已经存在了一个相同名称的DSN,则删除之前的再创建
strSourceName是要创建的数据源名,也就是odbc中的DSN名称
strSourceDb是数据库存放路径(如D:\Access.mdb)
strDescription是数据源的描述字符串(仅仅是个字符串对数据源的描述)
cstrUid 访问数据源的uid
cstrPwd访问数据源的pwd
返回TRUE为创建DSN成功
*/
BOOL CreateAccessDSN(CString cstrSourceName,CString cstrSourceDb, CString cstrDescription,CString cstrUid,CString cstrPwd)
{
//存放打开的注册表键
HKEY hKey;
DWORD dw;
//存放注册表 API函数执行的返回值
LONG lReturn;
//存放要打开的子键
CString cstrSubKey;
//检测是否安装了 MS Access ODBC driver:odbcjt32.dll
//获得 Windows系统目录
TCHAR sysDir[MAX_PATH];
TCHAR drvName[]=_T("\\odbcjt32.dll");
::GetSystemDirectory (sysDir,MAX_PATH);
lstrcat(sysDir,drvName);
/*CFileFind findFile;
if(!findFile.FindFile (sysDir))
{
AfxMessageBox("您的计算机系统中没有安装 MS ssAcce的 ODBC驱动程序 odbcjt32.dll,您将无法加载该类数据源。" ,MB_OK|MB_ICONSTOP);
return false;
} */
WIN32_FIND_DATA FindFileData;
HANDLE hFind = FindFirstFile(sysDir, &FindFileData);
if(hFind == INVALID_HANDLE_VALUE)
{
return false;
}
cstrSubKey=_T("SOFTWARE\\ODBC\\ODBC.INI\\")+cstrSourceName;
//先判断要创建的ODBC数据源是否存在
lReturn=::RegOpenKeyEx(HKEY_LOCAL_MACHINE,(LPCTSTR)cstrSubKey,0,KEY_READ,&hKey);
if (lReturn==ERROR_SUCCESS)//打开成功说明已经存在
{
//删除它和他的所有子键
BOOL bRet=RegDelnode(hKey,(LPTSTR)cstrSubKey.GetString());
if (!bRet)//删除失败
return FALSE;
}
//创建 ODBC数据源在注册表中的子键
lReturn=::RegCreateKeyEx(HKEY_LOCAL_MACHINE,(LPCTSTR)cstrSubKey,0,NULL,REG_OPTION_NON_VOLATILE,KEY_WRITE,NULL,&hKey,&dw);
if(lReturn != ERROR_SUCCESS)
return false;
//设置数据源的各项参数
CString cstrDbq=cstrSourceDb;
CString cstrDriver=sysDir;
DWORD dwDriverId=25;
CString cstrFil=_T("MS Access") ;
CString cstrPWD=cstrPwd;
DWORD dwSafeTransactions=0;
CString cstrUID=cstrUid;
::RegSetValueEx (hKey,_T("DBQ") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR) cstrDbq),cstrDbq .GetLength ()) ;
::RegSetValueEx (hKey,_T("Description") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)cstrDescription),cstrDescription.GetLength());
::RegSetValueEx (hKey,_T("Driver"),0L,REG_SZ,(CONST BYTE*)((LPCTSTR)cstrDriver),cstrDriver .GetLength ());
::RegSetValueEx (hKey,_T("DriverId") ,0L,REG_DWORD,(CONST BYTE*)(&dwDriverId),sizeof(dw));
::RegSetValueEx (hKey,_T("FIL") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR) cstrFil),cstrFil .GetLength ());
::RegSetValueEx (hKey,_T("PWD") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)cstrPWD),cstrPWD.GetLength ()) ;
::RegSetValueEx (hKey,_T("SafeTransactions") ,0L,REG_DWORD,(CONST BYTE*)(&dwSafeTransactions),sizeof(dw));
::RegSetValueEx (hKey,_T("UID") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)cstrUID),cstrUID .GetLength ());
::RegCloseKey(hKey);
//创建 ODBC数据源的 Jet子键
cstrSubKey+=_T("\\Engines\\Jet") ;
lReturn=::RegCreateKeyEx (HKEY_LOCAL_MACHINE ,(LPCTSTR)cstrSubKey,0,NULL,REG_OPTION_NON_VOLATILE,KEY_WRITE,NULL,&hKey,&dw);
if(lReturn != ERROR_SUCCESS)
return false;
//设置该子键下的各项参数
CString strImplict=_T(" ") ;
CString strUserCommit=_T("Yes") ;
DWORD dwPageTimeout=5;
DWORD dwThreads=3;
DWORD dwMaxBufferSize=2048;
::RegSetValueEx (hKey,_T("ImplictCommitSync") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)strImplict),strImplict.GetLength ()+1);
::RegSetValueEx (hKey,_T("MaxBufferSize") ,0L,REG_DWORD,(CONST BYTE*)(&dwMaxBufferSize),sizeof(dw));
::RegSetValueEx (hKey,_T("PageTimeout") ,0L,REG_DWORD,(CONST BYTE*)(&dwPageTimeout),sizeof(dw));
::RegSetValueEx (hKey,_T("Threads") ,0L,REG_DWORD,(CONST BYTE*)(&dwThreads),sizeof(dw));
::RegSetValueEx (hKey,_T("UserCommitSync") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)strUserCommit),strUserCommit.GetLength ());
::RegCloseKey (hKey);
//设置 ODBC数据库引擎名称
lReturn=::RegOpenKeyEx (HKEY_LOCAL_MACHINE ,_T("SOFTWARE\\ODBC\\ODBC.INI\\ODBC Data Sources") ,0L,KEY_WRITE,&hKey);
if(lReturn !=ERROR_SUCCESS)
return false;
CString strDbType=_T("Microsoft Access Driver (*.mdb)");
::RegSetValueEx (hKey,cstrSourceName,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)strDbType),strDbType.GetLength ());
return true;
}
由于在动态加载中,一般只会改变数据库文件、数据源说明以及数据源描述,故上述函数可以实现应用中的大部分要求。如果应用中还需要作更多的改变,那么也可以通过改变函数参数的方式加以实现。对于需要动态加载多种类型数据源的情况,可以用具有不同参数的重载函数去实现。
方法二:利用 DLL
设计思路
创建ODBC数据源可以调用Windows系统子目录下的动态链接库ODBCCP32.DLL中的函数SQLConfigDataSource(),该函数可以动态地增加、修改和删除数据源。SQLConfigDataSource()函数的原型如下:
BOOL SQLConfigDataSource(HWND hwndParent,WORD fRequest, LPCSTR lpszDriver, LPCSTR lpszAttributes);
hwndParent参数是父窗口句柄。如果该值为 NULL,将不会显示与父窗口有关的对话框。 如果参数 IpszAttributes提供的信息不够完善,在创建过程中就会出现对话框要求用户提供相应信息。
fRequest参数可以设置为下面的数值之一:
ODBC_ADD_DSN:增加一个新的用户数据源;
ODBC_CONFIG_DSN:修改(配置)一个已经存在的用户数据源;
ODBC_REMOVE_DSN:删除一个已经存在的用户数据源;
ODBC_ADD_SYS_DSN:增加一个新的系统数据源;
ODBC_CONFIG_SYS_DSN:修改 (配置 )一个已经存在的系统数据源;
ODBC_REMOVE_SYS_DSN:删除一个已经存在的系统数据源。
ODBC_REMOVE_DEFAULT_DSN:删除省缺的数据源说明部分。
lpszDriver参数用于传递数据库引擎的名字,等同于方法一中 strDbType变量。再比如要加载的是Excel数据库,那么数据库引擎名称就为Microsoft Excel Driver(*.xls)
lpszAttributes参数为一连串的"KeyName=value"字符串,每两个KeyName值之间用"\0"字符隔开(或者\0隔开即可)。如 DSN=Personnel Data\0UID=Smith\0DATABASE=Personnel。KeyName主要是新数据源缺省的驱动程序注册说明,其中最主要的关键字是"DSN"(新数据源的名称)和"DBQ"(数据源的地址),其余关键字则根据不同的数据源有不同要求。关于该参数的详细设置请参阅 MSDN中 SQLConfigDataSource()函数的帮助文档和各种 ODBC驱动程序文档。 http://msdn.microsoft.com/zh-cn/library/ms130822.aspx
具体实现
由于 VC的缺省库文件中不包含 SQLConfigDataSource()函数,因此使用该函数之前需要包涵头文件#include <odbcinst.h>,在工程的 Settings属性对话框 Link属性页的 Object/library modules编辑框中增加 odbc32.lib(或者用#pragma comment(lib, "ODBCCP32.lib")方式链接),同时保证系统目录 system32下有文件 odbccp32.dll。
仍以 Microsoft Access为例,设置数据源名为 demo,数据源描述为 "示例数据源 ",那么在需要动态加载数据源的地方加入下列代码即可:
::SQLConfigDataSource (NULL,ODBC_ADD_SYS_DSN," Microsoft Access Driver (*.mdb)"," DSN=demo\0Descirption=示例数据库 " );
或者
SQLConfigDataSource(NULL,ODBC_ADD_DSN,"Microsoft Access Driver (*.mdb)",
"DSN=Personnel\0"
"DBQ=C:\\My Documents\\dq.mdb\0"
"Description=ODBC数据源\0"
"DataDirectory=C:\\My Documents\0"
"\0");
小结
上述两种方法都可以实现动态加载各种类型的 ODBC数据源,并且在 Windows95/98/NT/2000环境下调试通过。方法一在实现时需要较多的代码,方法二所需代码虽少,但需要额外文件的支持,而且随着数据源配置的灵活性的增加,为了形成 lpszAttributes字符串,其代码长度也会相应增加。由于从控制面板配置数据源使得程序员可以获得更加直观的理解,所以对于注册表中各项值以及相应项名称的获得除了可以查阅相关驱动程序的文档外,程序员也可以在编程前先通过控制面板配置 ODBC数据源,然后根据注册表中相应部分的内容进行编程。
//
#include "stdafx.h"
#include <Windows.h>
#include <odbcinst.h>
#pragma comment(lib, "ODBCCP32.lib")
#include <atlstr.h>
#include <windows.h>
#include <stdio.h>
#include <strsafe.h>
//*************************************************************
//
// RegDelnodeRecurse()
//
// Purpose: Deletes a registry key and all its subkeys / values.
//
// Parameters: hKeyRoot - Root key
// lpSubKey - SubKey to delete
//
// Return: TRUE if successful.
// FALSE if an error occurs.
//
//*************************************************************
BOOL RegDelnodeRecurse (HKEY hKeyRoot, LPTSTR lpSubKey)
{
LPTSTR lpEnd;
LONG lResult;
DWORD dwSize;
TCHAR szName[MAX_PATH];
HKEY hKey;
FILETIME ftWrite;
// First, see if we can delete the key without having
// to recurse.
lResult = RegDeleteKey(hKeyRoot, lpSubKey);
if (lResult == ERROR_SUCCESS)
return TRUE;
lResult = RegOpenKeyEx (hKeyRoot, lpSubKey, 0, KEY_READ, &hKey);
if (lResult != ERROR_SUCCESS)
{
if (lResult == ERROR_FILE_NOT_FOUND) {
printf("Key not found.\n");
return TRUE;
}
else {
printf("Error opening key.\n");
return FALSE;
}
}
// Check for an ending slash and add one if it is missing.
lpEnd = lpSubKey + lstrlen(lpSubKey);
if (*(lpEnd - 1) != TEXT('\\'))
{
*lpEnd = TEXT('\\');
lpEnd++;
*lpEnd = TEXT('\0');
}
// Enumerate the keys
dwSize = MAX_PATH;
lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, NULL,
NULL, NULL, &ftWrite);
if (lResult == ERROR_SUCCESS)
{
do {
StringCchCopy (lpEnd, MAX_PATH*2, szName);
if (!RegDelnodeRecurse(hKeyRoot, lpSubKey)) {
break;
}
dwSize = MAX_PATH;
lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, NULL,
NULL, NULL, &ftWrite);
} while (lResult == ERROR_SUCCESS);
}
lpEnd--;
*lpEnd = TEXT('\0');
RegCloseKey (hKey);
// Try again to delete the key.
lResult = RegDeleteKey(hKeyRoot, lpSubKey);
if (lResult == ERROR_SUCCESS)
return TRUE;
return FALSE;
}
//*************************************************************
//
// RegDelnode()
//
// Purpose: Deletes a registry key and all its subkeys / values.
//
// Parameters: hKeyRoot - Root key
// lpSubKey - SubKey to delete
//
// Return: TRUE if successful.
// FALSE if an error occurs.
//
//*************************************************************
BOOL RegDelnode (HKEY hKeyRoot, LPTSTR lpSubKey)
{
TCHAR szDelKey[MAX_PATH*2];
StringCchCopy (szDelKey, MAX_PATH*2, lpSubKey);
return RegDelnodeRecurse(hKeyRoot, szDelKey);
}
/*
此函数可以动态创建Microsoft Access数据库的系统DSN。如果要创建用户DSN只需要把HKEY_LOCAL_MACHINE改为HKEY_CURRENT_USER
如果之前已经存在了一个相同名称的DSN,则删除之前的再创建
strSourceName是要创建的数据源名,也就是odbc中的DSN名称
strSourceDb是数据库存放路径(如D:\Access.mdb)
strDescription是数据源的描述字符串(仅仅是个字符串对数据源的描述)
cstrUid 访问数据源的uid
cstrPwd访问数据源的pwd
返回TRUE为创建DSN成功
*/
BOOL CreateAccessDSN(CString cstrSourceName,CString cstrSourceDb, CString cstrDescription,CString cstrUid,CString cstrPwd)
{
//存放打开的注册表键
HKEY hKey;
DWORD dw;
//存放注册表 API函数执行的返回值
LONG lReturn;
//存放要打开的子键
CString cstrSubKey;
//检测是否安装了 MS Access ODBC driver:odbcjt32.dll
//获得 Windows系统目录
TCHAR sysDir[MAX_PATH];
TCHAR drvName[]=_T("\\odbcjt32.dll");
::GetSystemDirectory (sysDir,MAX_PATH);
lstrcat(sysDir,drvName);
/*CFileFind findFile;
if(!findFile.FindFile (sysDir))
{
AfxMessageBox("您的计算机系统中没有安装 MS ssAcce的 ODBC驱动程序 odbcjt32.dll,您将无法加载该类数据源。" ,MB_OK|MB_ICONSTOP);
return false;
} */
WIN32_FIND_DATA FindFileData;
HANDLE hFind = FindFirstFile(sysDir, &FindFileData);
if(hFind == INVALID_HANDLE_VALUE)
{
return false;
}
cstrSubKey=_T("SOFTWARE\\ODBC\\ODBC.INI\\")+cstrSourceName;
//先判断要创建的ODBC数据源是否存在
lReturn=::RegOpenKeyEx(HKEY_LOCAL_MACHINE,(LPCTSTR)cstrSubKey,0,KEY_READ,&hKey);
if (lReturn==ERROR_SUCCESS)//打开成功说明已经存在
{
//删除它和他的所有子键
BOOL bRet=RegDelnode(hKey,(LPTSTR)cstrSubKey.GetString());
if (!bRet)//删除失败
return FALSE;
}
//创建 ODBC数据源在注册表中的子键
lReturn=::RegCreateKeyEx(HKEY_LOCAL_MACHINE,(LPCTSTR)cstrSubKey,0,NULL,REG_OPTION_NON_VOLATILE,KEY_WRITE,NULL,&hKey,&dw);
if(lReturn != ERROR_SUCCESS)
return false;
//设置数据源的各项参数
CString cstrDbq=cstrSourceDb;
CString cstrDriver=sysDir;
DWORD dwDriverId=25;
CString cstrFil=_T("MS Access") ;
CString cstrPWD=cstrPwd;
DWORD dwSafeTransactions=0;
CString cstrUID=cstrUid;
::RegSetValueEx (hKey,_T("DBQ") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR) cstrDbq),cstrDbq .GetLength ()) ;
::RegSetValueEx (hKey,_T("Description") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)cstrDescription),cstrDescription.GetLength());
::RegSetValueEx (hKey,_T("Driver"),0L,REG_SZ,(CONST BYTE*)((LPCTSTR)cstrDriver),cstrDriver .GetLength ());
::RegSetValueEx (hKey,_T("DriverId") ,0L,REG_DWORD,(CONST BYTE*)(&dwDriverId),sizeof(dw));
::RegSetValueEx (hKey,_T("FIL") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR) cstrFil),cstrFil .GetLength ());
::RegSetValueEx (hKey,_T("PWD") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)cstrPWD),cstrPWD.GetLength ()) ;
::RegSetValueEx (hKey,_T("SafeTransactions") ,0L,REG_DWORD,(CONST BYTE*)(&dwSafeTransactions),sizeof(dw));
::RegSetValueEx (hKey,_T("UID") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)cstrUID),cstrUID .GetLength ());
::RegCloseKey(hKey);
//创建 ODBC数据源的 Jet子键
cstrSubKey+=_T("\\Engines\\Jet") ;
lReturn=::RegCreateKeyEx (HKEY_LOCAL_MACHINE ,(LPCTSTR)cstrSubKey,0,NULL,REG_OPTION_NON_VOLATILE,KEY_WRITE,NULL,&hKey,&dw);
if(lReturn != ERROR_SUCCESS)
return false;
//设置该子键下的各项参数
CString strImplict=_T(" ") ;
CString strUserCommit=_T("Yes") ;
DWORD dwPageTimeout=5;
DWORD dwThreads=3;
DWORD dwMaxBufferSize=2048;
::RegSetValueEx (hKey,_T("ImplictCommitSync") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)strImplict),strImplict.GetLength ()+1);
::RegSetValueEx (hKey,_T("MaxBufferSize") ,0L,REG_DWORD,(CONST BYTE*)(&dwMaxBufferSize),sizeof(dw));
::RegSetValueEx (hKey,_T("PageTimeout") ,0L,REG_DWORD,(CONST BYTE*)(&dwPageTimeout),sizeof(dw));
::RegSetValueEx (hKey,_T("Threads") ,0L,REG_DWORD,(CONST BYTE*)(&dwThreads),sizeof(dw));
::RegSetValueEx (hKey,_T("UserCommitSync") ,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)strUserCommit),strUserCommit.GetLength ());
::RegCloseKey (hKey);
//设置 ODBC数据库引擎名称
lReturn=::RegOpenKeyEx (HKEY_LOCAL_MACHINE ,_T("SOFTWARE\\ODBC\\ODBC.INI\\ODBC Data Sources") ,0L,KEY_WRITE,&hKey);
if(lReturn !=ERROR_SUCCESS)
return false;
CString strDbType=_T("Microsoft Access Driver (*.mdb)");
::RegSetValueEx (hKey,cstrSourceName,0L,REG_SZ,(CONST BYTE*)((LPCTSTR)strDbType),strDbType.GetLength ());
return true;
}
/*
检测一个系统DSN是否存在
pszDSN DSN数据源名称
pszDBName 数据库文件名(包括完整路径)
存在返回真,其他返回假
*/
BOOL IsExistAccessDSN(const char* pszDSN,const char *pszDBName)
{
HKEY hkey = {0};
char szReg[MAXBYTE] = {0};
strcpy(szReg,"Software\\ODBC\\ODBC.INI\\");
strcat(szReg, pszDSN);
//检测系统ODBC数据源是否存在。
LONG lRet = RegOpenKeyA(HKEY_LOCAL_MACHINE, szReg, &hkey);
if(lRet == ERROR_SUCCESS)
{
DWORD dwcbData=0;
DWORD dwType=REG_SZ;
//先查询键值的长度
lRet= RegQueryValueEx(hkey, (LPTSTR)"DBQ", NULL, &dwType,NULL, &dwcbData);
if (lRet==ERROR_SUCCESS)
{
char *pszData=new char[dwcbData+1];
RegQueryValueExA(hkey,"DBQ", NULL,&dwType,(LPBYTE)pszData, &dwcbData);
if(strcmp((const char*)pszData, pszDBName) == 0)
{
delete [] pszData;
pszData=NULL;
RegCloseKey(hkey);
hkey=NULL;
return TRUE;
}
if (pszData)
{
delete []pszData;
pszData=NULL;
}
}
}
if(hkey != NULL)
{
RegCloseKey(hkey);
hkey=NULL;
}
return FALSE;
}
/*
删除一个系统DSN
pszDSN DSN名称
note:
#include <odbcinst.h>
#pragma comment(lib, "ODBCCP32.lib")
*/
BOOL DelAccessDSN(const char* pszDSN)
{
char szAttr[512]="DSN=";
strcat(szAttr,pszDSN);
return SQLConfigDataSource(NULL,ODBC_REMOVE_SYS_DSN,"Microsoft Access Driver (*.mdb)\0",szAttr);
}
int _tmain(int argc, _TCHAR* argv[])
{
//动态创建SQLite3 DSN数据源
bool bRet=::SQLConfigDataSource(NULL,ODBC_ADD_SYS_DSN,"SQLite3 ODBC Driver",
"DSN=SQLite3 Test\0Database=C:\\Users\\HAO\\Desktop\\test.db\0Description=SQLite3 ODBC数据源\0");
if (bRet)
{
printf("创建SQLite3 Test DSN成功\n");
}
bRet=::SQLConfigDataSource(NULL,ODBC_ADD_SYS_DSN,"SQLite3 ODBC Driver",
"DSN=Test2\0"
"Database=C:\\Users\\HAO\\Desktop\\events2.db\0"
"Description=ODBC数据源\0"
"DataDirectory=C:\\Users\\HAO\\Desktop\0"
"Timeout=1000\0"
"\0");
if (bRet)
{
printf("创建Test2 DSN成功\n");
}
//创建一个Access的DSN数据源
bRet=CreateAccessDSN("AccessTest","D:\\TestAccess.mdb","这是一个DEMO,创建一个Access的DSN","这是登录ID","这是登录密码");
//数据源相同时删除以前的然后创建
bRet=CreateAccessDSN("AccessTest","D:\\TestAccess2.mdb","覆盖掉之前的","UID","PWD");
bRet=IsExistAccessDSN("AccessTest","D:\\TestAccess2.mdb");
bRet=DelAccessDSN("AccessTest");
system("PAUSE");
return 0;
}