ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Visual C++ 通过 ODBC 操作 Access MDB 数据库详解

Visual C++ 通过 ODBC 操作 Access MDB 数据库详解 简介一套基于Visual C环境、通过ODBC接口操作Access MDB数据库的通讯录示例源码适合初学Windows桌面数据库开发的C开发者。压缩包共32个文件以12个h头文件、11个cpp源文件为主附带rc资源文件、ico图标、dsp/dsw工程文件及一个示例mdb数据库整体仅42KB结构精简完整。已有381人浏览学习。源码完整演示了使用MFC的CDatabase与CRecordset连接并查询Access数据库的流程覆盖登录验证、联系人信息增改查、密码修改等常用功能模块代码注释清晰便于直接编译运行和二次扩展。通过研读该项目可掌握ODBC连接配置、SQL语句执行、记录集遍历以及数据库操作异常处理的实用方法对开发涉及Access数据交互的Windows桌面应用具有直接参考价值。1. 为什么还在用 Visual C 源码对接 Access MDB 数据库MDB 是 90 年代到 2010 年前后最常见的桌面数据库格式今天仍有大量工控、教务、医疗数据躺在 .mdb 文件里。新版 Windows 上要读出这些数据绕不开 Visual C、ODBC、Access 这条老链路。这条链路是 .NET 成熟前 Windows 桌面应用的标准方案很多存量工程至今用 VC 6.0 或 VS2008 编译源码里写死连接串换机器后先崩的往往是环境而不是业务逻辑。读者分两类一类接手老源码把 MDB 数据迁出去另一类在新机器上重编老代码遇到 DSN 看不到、记录集打不开、0xc0000005 崩溃。下面按驱动选型、连接串、取数、增删改、排错的顺序讲。2. ODBC 与 Access MDB 的驱动选型先解决 32/64 位和连接串2.1 老 Jet 驱动和 ACE 驱动怎么选访问 .mdb 文件面对两个 ODBC 驱动老驱动 Microsoft Access Driver (.mdb) 基于 Jet 4.0文件格式支持到 Access 2003新驱动 Microsoft Access Driver (.mdb, *.accdb) 基于 ACE 引擎由 AccessDatabaseEngine 安装包提供同时认 .mdb 和 .accdb。选型的关键不在格式而在进程位数Jet 4.0 驱动只有 32 位实现VC 工程平台如果是 x64这个驱动在 64 位进程里根本加载不出来ODBC 管理器里也看不到它。反过来ACE 驱动有 x86 和 x64 两个版本64 位工程必须装 AccessDatabaseEngine_x64.exe32 位工程装 x86 版。同一个 64 位系统里两个版本可以共存注册表视图不同互不干扰。老项目源码里如果写死Driver{Microsoft Access Driver (*.mdb)}工程又是 x64要么把平台改回 Win32要么把连接串改成 ACE 驱动名没有第三条路。2.2 32 位和 64 位 ODBC 管理器是两个入口odbcad32.exe 在 System32 和 SysWOW64 里各有一份分别是 64 位和 32 位的数据源管理器。很多人只打开开始菜单里的 ODBC 数据源在 64 位界面建了 DSN结果 32 位程序仍然报找不到数据源。判断机器上能用哪个驱动先看 VC 工程配置管理器的平台再看对应位数的 odbcad32。VC 工程平台打开的管理器可用的驱动名对应安装包Win32 (x86)C:\Windows\SysWOW64\odbcad32.exeMicrosoft Access Driver (.mdb) 或 (.mdb, *.accdb)AccessDatabaseEngine_x86.exex64C:\Windows\System32\odbcad32.exeMicrosoft Access Driver (*.mdb, *.accdb)AccessDatabaseEngine_x64.exe提示SysWOW64 里的 odbcad32 是 32 位路径反直觉最容易搞混64 位驱动不会出现在 32 位管理器里反之亦然。2.3 用无 DSN 连接串绕过每台机器的配置DSN 要在每台目标机器预先创建而且 32 位和 64 位各有各的 DSN 列表部署时漏一台就报错。我一般直接在源码里写无 DSN 连接串程序自己带着路径去连目标机器只要装对驱动即可。// Visual C 中的无 DSN 连接串构造Unicode 字符集 CString strConn; strConn.Format( _T(Driver{Microsoft Access Driver (*.mdb, *.accdb)};) _T(DbqC:\\data\\school.mdb;) _T(Pwd));Driver 花括号里的名字必须和系统实际注册的驱动名逐字一致多一个空格都会在打开时报未找到数据源名称。Dbq 是 MDB 文件全路径反斜杠要双写路径含中文时Unicode 字符集下直接写即可ANSI 工程里要自己做转换。Pwd 是打开数据库的密码没密码就留空结尾分号不能省。2.4 装机后用注册表确认驱动是否注册程序跑之前先在命令行确认驱动注册到位避免把时间浪费在排查环境上。:: 64 位系统的驱动视图 reg query HKLM\SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers :: 32 位系统的驱动视图 reg query HKLM\SOFTWARE\WOW6432Node\ODBC\ODBCINST.INI\ODBC Drivers输出的驱动列表里能看到 Microsoft Access Driver (*.mdb, *.accdb) 才算装好。如果装了 AccessDatabaseEngine 但列表里没有多半是装错位数或者被 Office 的 Click-to-Run 版本覆盖了注册项卸载后重装对应位数的 ACE 引擎即可。3. Visual C 源码骨架用 CDatabase 打开 MDB 并读取记录集3.1 为什么选 MFC 封装而不是裸 ODBC API裸 ODBC API 的调用链是 SQLAllocHandle、SQLSetEnvAttr、SQLDriverConnect、SQLExecDirect、SQLFetch再加上手写列绑定和类型转换一个 SELECT 就要几十行。MFC 的 CDatabase 和 CRecordset 把这套流程封装成三个对象异常统一成 CDBException。老项目的 Visual C 源码大多基于 MFC 对话框框架直接沿用封装维护成本最低。如果工程是纯 Win32 控制台、没引 MFC那就用裸 API 更轻但连接串和驱动选型的原则完全一样把下面代码里的 OpenEx 换成 SQLDriverConnect 即可。3.2 打开数据库的最小代码#include afxdb.h // CDatabase、CRecordset、CDBException void OpenMdb(CDatabase db, LPCTSTR szMdbPath) { CString strConn; strConn.Format( _T(Driver{Microsoft Access Driver (*.mdb, *.accdb)};) _T(Dbq%s;), szMdbPath); // noOdbcDialog不弹 DSN 选择框解析失败直接抛异常 db.OpenEx(strConn, CDatabase::noOdbcDialog); }OpenEx 失败时抛 CDBException 而不是返回 FALSE调用方必须包 try/catch。第二个参数 noOdbcDialog 是必须的否则驱动解析连接串失败时可能弹出系统对话框无人值守的服务器程序会卡在那里。连接串里 Dbq 指向的路径在调用前最好用 PathFileExists 确认一次把文件不存在和驱动不对两类错误分开。3.3 用 CRecordset 取数按列序号还是按列名打开记录集最省事的方式是直接传 SQL然后循环 MoveNext。取字段值用 GetFieldValue 有两种重载按列序号从 0 开始或按列名字符串。列序号在 SELECT * 时不可控我习惯在 SQL 里显式写列名可读性更好。CRecordset rs(db); rs.Open(CRecordset::forwardOnly, _T(SELECT [学号], [姓名], [成绩] FROM score)); while (!rs.IsEOF()) { CString strNo, strName; double dScore 0.0; rs.GetFieldValue((short)0, strNo); rs.GetFieldValue((short)1, strName); rs.GetFieldValue((short)2, dScore); TRACE(_T(%s %s %.1f\n), strNo, strName, dScore); rs.MoveNext(); } rs.Close();按列名版本rs.GetFieldValue(_T(成绩), dScore)在列很多时可读性更好但每次调用都要查列映射循环里性能略差。字段为 NULL 时数值类型得到 0、字符串得到空串要区分 NULL 和 0 得先用 IsFieldNull。类型不匹配会抛 CDBException比如把备注字段取成 double异常里的错误码是 SQL_ERROR。3.4 记录集类型forwardOnly、dynaset 和 snapshot 怎么挑记录集类型打开参数适用场景注意点forwardOnlyCRecordset::forwardOnly大批量只读导出只能向后不能 MovePrevdynasetCRecordset::dynaset中等数据量、要编辑要求 MDB 文件非只读snapshotCRecordset::snapshot报表、重复读取固定视图其他程序改的数据不可见3.5 更稳的字段绑定DoFieldExchange 和 RFXGetFieldValue 运行时按类型取数表结构变了就要改代码。更稳的做法是在派生 CRecordset 里用 DoFieldExchange 声明列和 C 成员的映射编译期把模型固定下来class CScoreSet : public CRecordset { public: CString m_strNo; // 学号 CString m_strName; // 姓名 double m_dScore; // 成绩 CScoreSet(CDatabase* pDB) : CRecordset(pDB) {} virtual CString GetDefaultSQL() { return _T(score); } virtual void DoFieldExchange(CFieldExchange* pFX) { pFX-SetFieldType(CFieldExchange::outputColumn); RFX_Text(pFX, _T(学号), m_strNo); RFX_Text(pFX, _T(姓名), m_strName, 64); RFX_Double(pFX, _T(成绩), m_dScore); } };RFX_Text 不传长度时默认按 255 字节缓冲绑定表里字段是备注且内容超长时要显式传长度比如RFX_Text(pFX, _T(备注), m_strMemo, 2048)。RFX 函数要和 Access 字段类型对应数字字段分 Byte、Integer、Long、Single、Double绑错类型轻则取数异常重则缓冲区越界这是后面 0xc0000005 崩溃的高发原因。派生类打开后直接用成员变量即可CScoreSet rs(db); rs.Open(CRecordset::dynaset); while (!rs.IsEOF()) { // rs.m_strNo / rs.m_strName / rs.m_dScore 已自动填充 rs.MoveNext(); }4. 增删改查落地参数化、只读陷阱和高频错误码排查4.1 ExecuteSQL 执行 INSERT/UPDATE/DELETE不查询结果的语句用CDatabase::ExecuteSQL它内部用 SQLExecDirect 一次性执行适合批量初始化和维护脚本。ExecuteSQL 不支持 MFC 参数绑定语句里出现用户输入时要自行转义单引号把换成所以涉及输入内容的写入我一般不走这条而是用 4.2 的记录集参数化。try { db.ExecuteSQL(_T(UPDATE score SET [成绩] 95 WHERE [学号] 001)); } catch (CDBException* e) { // e-m_strError 里是驱动返回的完整错误文本 e-Delete(); }列名若是 Access 保留字SQL 里必须加方括号RFX 绑定里仍写去掉括号的列名因为驱动返回的字段元数据不带括号。ExecuteSQL 执行不返回行集判断是否成功只能靠有没有抛异常。4.2 用 ? 参数和 RFX 绑定别拼 SQLMFC CRecordset 支持参数化查询在派生类里加 param 类型的 RFX 绑定配合 m_strFilter 使用。参数以?占位符出现在 WHERE 里驱动按绑定顺序填值单引号、中文都不用手工转义。class CScoreSet : public CRecordset { public: CString m_strNo; // 输出列学号 CString m_strParamNo; // 查询参数 CScoreSet(CDatabase* pDB) : CRecordset(pDB) {} virtual CString GetDefaultSQL() { return _T(score); } virtual void DoFieldExchange(CFieldExchange* pFX) { pFX-SetFieldType(CFieldExchange::outputColumn); RFX_Text(pFX, _T(学号), m_strNo, 32); pFX-SetFieldType(CFieldExchange::param); RFX_Text(pFX, _T(param1), m_strParamNo); } }; CScoreSet rs(db); rs.m_strFilter _T([学号] ?); // ? 的个数要和 param 绑定数一致 rs.m_strParamNo _T(001); rs.Open(CRecordset::dynaset); if (!rs.IsEOF()) { // 找到一条rs.m_strNo 就是它的学号 } rs.Close();参数绑定顺序由 DoFieldExchange 里 param 段落的声明顺序决定和 WHERE 中?的出现顺序一一对应。LIKE 查询也能参数化m_strFilter _T([姓名] LIKE ?)参数值传_T(张%)通配符写在参数值里。注意列名拼写错误也会报参数不足期待是 1Jet 会把无法解析的标识符当成查询参数。先拼写核对再查绑定数量。4.3 中文路径和只读文件的坑Jet 驱动解析 Dbq 路径时按系统 ANSI 代码页处理中文 Windows 上通常没问题但工程是 ANSI 字符集又改了系统区域设置时中文目录下的 MDB 可能打不开对策是把工程字符集改成 Unicode。另一个高频坑是 MDB 文件在只读目录比如被拷进 Program Files 或勾了只读属性打开记录集时报不能更新的查询或直接抛异常。先右键文件属性去掉只读再确认进程对目录有写权限。Access 是文件级锁定记录集没 Close 之前外部改文件也会失败脚本跑完记得rs.Close()。4.4 高频错误码对照表错误/退出表现最常见原因第一步处理未找到数据源名称且未指定默认驱动程序连接串 Driver 名写错或位数不匹配reg query 核对驱动注册见 2.4参数不足期待是 1WHERE 里 ? 和 param 绑定数不一致或列名拼错数 ? 个数检查 DoFieldExchange 顺序不能更新的查询记录集只读打开或 MDB 文件只读改 dynaset去只读属性退出码 0xc0000005字段类型绑错、缓冲长度不足、CDatabase 先于记录集析构先关 rs 再关 db核对 RFX 类型4.5 用 CDBException 拿到真正的原因退出码只能说明进程崩了驱动为什么拒绝 SQL 还得靠异常对象。CDBException 的 m_strError 是 ODBC 驱动返回的完整错误文本m_nRetCode 是底层 SQLxxx 调用的返回码-1 即 SQL_ERROR。调试期把这两项和 m_strStateNativeOrigin 一起打到日志很多问题一眼定位。catch (CDBException* e) { CString strLog; strLog.Format(_T(ret%d, err%s\n), e-m_nRetCode, (LPCTSTR)e-m_strError); OutputDebugString(strLog); e-Delete(); }5. 收尾技巧连接复用、ODBC 跟踪和 0xc0000005 的取证习惯5.1 一个 CDatabase 多处用别每条语句都 OpenJet/ACE 引擎启动和文件锁初始化比想象中慢对话框里每条 SQL 都重新 OpenEx 会明显卡顿。我一般把 CDatabase 作为窗口类的成员变量初始化时打开一次程序退出前再 Close。多个线程共享同一个 CDatabase 会随机崩溃必须每个线程单独一个连接。UPDATE 一直挂起时先看有没有 CRecordset 没 Close——Access 是文件级锁未关闭的记录集可能把整库锁住。5.2 用 ODBC 跟踪确认 SQL 到底发没发出去数据没写进去和SQL 压根没执行是两回事。开启 ODBC 跟踪后驱动把每条 SQLPrepare、SQLExecute 和参数值写入日志reg add HKLM\SOFTWARE\ODBC\ODBC.INI\ODBC /v Trace /t REG_SZ /d 1 /f reg add HKLM\SOFTWARE\ODBC\ODBC.INI\ODBC /v TraceFile /t REG_SZ /d C:\odbc_trace.log /f复现问题后把 Trace 改回 0 并删除日志避免持续写盘。日志里能看到执行到哪一步出错比单步调试更快。5.3 0xc0000005 先看栈再查 RFX 绑定进程以 0xc0000005十进制 -1073741819退出时用 Visual Studio 的本地 Windows 调试器跑一遍直接看调用栈。栈停在 CRecordset::MoveNext 或 RFX 内部优先核对 DoFieldExchange 里字段类型和 Access 表结构数字字段的 Byte/Integer/Long/Single/Double 宽度不同绑错会越界。另一个常见现场是 CRecordset 成员比 CDatabase 后析构访问了已关闭的连接对象。先确认目标机装了匹配编译工具集的 Visual C Redistributable缺运行库的进程会在入口直接退出栈里没有你的任何代码和 ODBC 崩溃是两类问题。Debug 版打开_CrtSetDbgFlag的检测配合 ODBC 跟踪通常能把崩溃缩到某一次具体 SQLExecute 上。本文还有配套的精品资源点击获取
返回列表