在使用 VC、 VB、 Delphi等高级语言编写数据库应用程序时,往往需要用户自己在控制面板中配置 ODBC数据源。对于一般用户而言,配置 ODBC数据源可能是一件比较困难的工作。 而且,在实际应用中,用户往往要求在同一个应用程序中访问不同的数据源,因此采用一般的加载方法就有了无法克服的缺陷。为能在程序中完成这一工作,方便应用程序的使用,本文以 VC为开发环境介绍两种在应用程序中动态加载 ODBC系统数据源的方法。
方法一:修改注册表
设计思路
一般情况下,当用户在控制面板中配置好 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数据源为例给出实现此功能的函数的代码。
/* strSourceName是要创建的数据源名, strSourceDb是数据库存放路径, strDescription是数据源的描述字符串。* /
BOOL CLoadOdbcDlg:: LoadDbSource(CString strSourceName,CString strSourceDb, CString strDescription)
{
//存放打开的注册表键
HKEY hKey;
DWORD dw;
//存放注册表 API函数执行的返回值
LONG lReturn;
//存放要打开的子键
CString strSubKey;
//检测是否安装了 MS Access ODBC driver:odbcjt32.dll
//获得 Windows系统目录
char sysDir[MAX_PATH];
char drvName[]=\" file://odbcjt32.dll/\" ;
::GetSystemDirectory (sysDir,MAX_PATH);
strcat(sysDir,drvName);
CFileFind findFile;
if(!findFile.FindFile (sysDir))
{
AfxMessageBox(\"您的计算机系统中没有安装 MS Access的 ODBC驱动程序 odbcjt32.dll,您将无法加载该类数据源。 \" ,MB_OK|MB_ICONSTOP);
return false;
}
strSubKey=\" SOFTWARE\\\\ODBC\\\\ODBC.INI\\\\\"+ strSourceName;
//创建 ODBC数据源在注册表中的子键
lReturn=::RegCreateKeyEx(HKEY_LOCAL_
MACHINE,(LPCTSTR)strSubKey,0,NULL,REG_OPTION
_NON_VOLATILE,KEY_WRITE,NULL,& hKey,& dw);
if(lReturn != ERROR_SUCCESS)
return false;
//设置数据源的各项参数
CString strDbq=strSourceDb; [Page]
CString strDriver=sysDir;
DWORD dwDriverId=25;
CString strFil=\" MS Access\" ;
CString strPwd=strSourceName;
DWORD dwSafeTransactions=0;
CString strUid=strSourceName;
::RegSetValueEx (hKey,\" DBQ\" ,0L,REG_SZ,
(CONST BYTE* )((LPCTSTR) strDbq),strDbq .GetLength ()) ; ::RegSetValueEx (hKey,\" Description\" ,0L,REG_SZ,(CONST BYTE* )((LPCTSTR)strDescription),strDescription.GetLength());
::RegSetValueEx (hKey,\" Driver\" ,0L,REG_SZ,(CONST BYTE* )((LPCTSTR)strDriver),strDriver .GetLength ());
::RegSetValueEx (hKey,\" DriverId\" ,0L,REG_DWORD,(CONST BYTE* )(& dwDriverId),sizeof(dw));
::RegSetValueEx (hKey,\" FIL\" ,0L,REG_SZ,
(CONST BYTE* )((LPCTSTR) strFil),strFil .GetLength ());
::RegSetValueEx (hKey,\" PWD\" ,0L,REG_SZ,
(CONST BYTE* )((LPCTSTR)strPwd),strPwd.GetLength ()) ; ::RegSetValueEx (hKey,\" SafeTransactions\" ,0L,
REG_DWORD,(CONST BYTE* )(& dwSafeTransactions),sizeof(dw));
::RegSetValueEx (hKey,\" UID\" ,0L,REG_SZ,
(CONST BYTE* )((LPCTSTR)strUid),strUid .GetLength ()); ::RegCloseKey(hKey);
//创建 ODBC数据源的 Jet子键
strSubKey+ =\" file://Engines//Jet\" ;
lReturn=::RegCreateKeyEx (HKEY_LOCAL_MACHINE ,(LPCTSTR)strSubKey,0,NULL,REG_OPTION_NON_
VOLATILE,KEY_WRITE,NULL,& hKey,& dw);
if(lReturn != ERROR_SUCCESS)
return false;
//设置该子键下的各项参数
CString strImplict=\" \" ;
CString strUserCommit=\" Yes\" ;
DWORD dwPageTimeout=5;
DWORD dwThreads=3;
DWORD dwMaxBufferSize=2048;
::RegSetValueEx (hKey,\" ImplictCommitSync\" ,0L,REG_SZ,(CONST BYTE* )((LPCTSTR)strImplict),strImplict.GetLength ()+ 1);
::RegSetValueEx (hKey,\" MaxBufferSize\" ,0L,REG_DWORD,(CONST BYTE* )(& dwMaxBufferSize),sizeof(dw));
::RegSetValueEx (hKey,\" PageTimeout\" ,0L,REG_DWORD,(CONST BYTE* )(& dwPageTimeout),sizeof(dw));
::RegSetValueEx (hKey,\" Threads\" ,0L,REG_DWORD,(CONST BYTE* )(& dwThreads),sizeof(dw));
::RegSetValueEx (hKey,\" UserCommitSync\" ,0L,REG_SZ,(CONST BYTE* )((LPCTSTR)strUserCommit),strUserCommit.GetLength ());
::RegCloseKey (hKey);
//设置 ODBC数据库引擎名称
lReturn=::RegOpenKeyEx (HKEY_LOCAL_MACHINE, \" SOFTWARE\\\\ODBC\\\\ODBC.INI\\\\ODBC Data Sources\" ,0L,KEY_WRITE,& hKey);
if(lReturn !=ERROR_SUCCESS)
return false;
CString strDbType=\" Microsoft Access Driver (* .mdb)\" ; ::RegSetValueEx (hKey,strSourceName,0L,REG_SZ,(CONST BYTE* )((LCTSTR)strDbType),strDbType.GetLength ());
return true;
}
由于在动态加载中,一般只会改变数据库文件、数据源说明以及数据源描述,故上述函数可以实现应用中的大部分要求。如果应用中还需要作更多的改变,那么也可以通过改变函数参数的方式加以实现。对于需要动态加载多种类型数据源的情况,可以用具有不同参数的重载函数去实现。 [Page]
方法二:利用 DLL
设计思路
Windows系统子目录中的动态链接库 Odbcinst.dll提供了一个可以动态地增加、修改和删除数据源的函数 SQLConfigDataSource()。该函数的原型如下:
BOOL SQLConfigDataSource(HWND hwndParent,WORD fRequest, LPCSTR lpszDriver, LPCSTR lpszAttributes);
hwndParent参数是父窗口句柄。如果该值为 NULL,将不会显示与父窗口有关的对话框。
fRequest参数可以设置为下面的数值之一:
ODBC_ADD_DSN:增加一个新的用户数据源;
ODBC_CONFIG_DSN:修改(配置)一个已经存在的用户数据源;
ODBC_REMOVE_DSN:删除一个已经存在的用户数据源;
ODBC_ADD_SYS_DSN:增加一个新的系统数据源;
ODBC_CONFIG_SYS_DSN:修改 (配置 )一个已经存在的系统数据源;
ODBC_REMOVE_SYS_DSN:删除一个已经存在的系统数据源。
lpszDriver参数用于传递数据库引擎的名字,等同于方法一中 strDbType变量。
lpszAttirbutes参数是关键字的值,即一连串的 \" keyname=value\"字符串,每两个字符串之间用 \" \\\"隔开,如 DSN=Personnel Data\\0UID=Smith\\0DATABASE=Personnel。关于该参数的详细设置请参阅 MSDN中 SQLConfigDataSource()函数的帮助文档和各种 ODBC驱动程序文档。
具体实现
由于 VC的缺省库文件中不包含 SQLConfigDataSource()函数,因此使用该函数之前需要将 odbcinst.h文件包含在工程的头文件中,在工程的 Settings属性对话框 Link属性页的 Object/library modules编辑框中增加 odbc32.lib,同时保证系统目录 system32下有文件 odbccp32.dll。
仍以 Microsoft Access为例,设置数据源名为 demo,数据源描述为 \"示例数据源 \",那么在需要动态加载数据源的地方加入下列代码即可:
::SQLConfigDataSource (NULL,ODBC_ADD_SYS_DSN,\" Microsoft Access Driver (* .mdb)\",\" DSN=demo\\0Descirption=示例数据库 \" );
小结
上述两种方法都可以实现动态加载各种类型的 ODBC数据源,并且在 Windows95/98/NT/2000环境下调试通过。方法一在实现时需要较多的代码,方法二所需代码虽少,但需要额外文件的支持,而且随着数据源配置的灵活性的增加,为了形成 lpszAttributes字符串,其代码长度也会相应增加。由于从控制面板配置数据源使得程序员可以获得更加直观的理解,所以对于注册表中各项值以及相应项名称的获得除了可以查阅相关驱动程序的文档外,程序员也可以在编程前先通过控制面板配置 ODBC数据源,然后根据注册表中相应部分的内容进行编程。