多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

老代码新看:VC6.0 ODBC连接Access数据库实战解析

老代码新看:VC6.0 ODBC连接Access数据库实战解析 简介这是一份面向VC 6.0开发者的ODBC入门与实战示例基于MFC封装类演示如何通过ODBC数据源访问Access数据库。资源包含完整的学生信息管理系统源码涉及DSN配置、CDatabase连接管理、CRecordset记录集操作以及增删改查CRUD等关键环节适合刚接触数据库编程的C学习者对照练习。压缩包内共278个文件以h头文件、cpp源文件、obj编译中间件、sbr浏览器信息文件为主辅以bmp位图、ico图标和两个mdb数据库文件整体大小约4.81MB。目录结构清晰便于按工程模块阅读源码和调试运行。该资源已有227人学习下载。通过本例可掌握ODBC基本概念、VC6.0中MFC数据库类的使用方式以及利用JET驱动连接mdb数据库的完整流程为后续开发基于C的数据库应用打下扎实基础。1. 翻出 VC6.0 的 ODBC 实例这份老代码到底能教会你什么很多人看到 ODBC、Access、VC6.0 这三个关键词凑在一起第一反应是“这是上个世纪的古董”。但真把数据库连接踩过一遍的人会告诉你VC6.0 年代的 ODBC 例子反而是把原理讲得最透的一批素材。当时某厂商的 ODBC 标准还处于“驱动管理器 驱动 应用”三件套的教科书形态没有 Entity Framework、没有 ORM连接数据库就是老老实实配 DSN、写 SQL、绑记录集。这份 ODBC.rar 里装的某学生信息管理实例用的是 MFC 的 CDatabase 与 CRecordset 走 ODBC 访问 Access 的 mdb 文件正好覆盖了从配置数据源到增删查改的完整链路。适合两类人一类是接手老系统、需要维护十年前 C 代码的从业者另一类是正在做课程设计、想找一个“能跑通、能看懂”的数据库 Demo 的学生。新手照着它能弄明白 ODBC 连接 Access 的每一步熟手则能借它回忆旧工程的文件组织方式顺便排查那些仍然隐藏在 Windows 系统里的 32 位 ODBC 兼容问题。2. 工程结构拆解从 DATABASEView.cpp 到 MsFlexGrid 控件这套代码是怎么组织的2.1 压缩包里的文件各是什么角色拿到压缩包先别急着编译里面十来个文件一眼扫过去能读懂的只有 DATABASEView.cpp、msflexgrid.cpp、msdgridctrl.cpp 这几个。DATABASEView.cpp 是 MFC 文档视图结构里的视图类实现负责界面交互和数据展示在单文档程序里它就是整个应用的“前台”msflexgrid.cpp 与 msdgridctrl.cpp 是对 MSFlexGrid 网格控件的封装或使用代码这个控件在当年是 VC 里最常用的表格展示方案用来把数据库查出来的记录一行行填进去。图标资源 icon.bmp、工具栏 Toolbar.bmp、背景图 bkimg256blue.bmp 都是界面素材其中 256 色背景图是那个年代非常典型的 UI 风格——用 GDI 直接贴背景再叠加控件现在看着粗糙当年是主流做法。DATABASE.aps 和 DATABASE.bsc 这两个文件最容易让新手困惑。aps 是 MFC 的资源编译缓存文件由 IDE 自动生成删了会自动重建不需要手动维护bsc 是浏览信息数据库给“跳转到定义”这类功能用的属于辅助性质。真正的业务逻辑集中在视图类和文档类文件里后续讲解以 DATABASEView.cpp 为锚点展开。2.2 界面与数据库的配合方式从压缩包里的文件组合可以推断这个程序不是纯对话框应用而是“文档视图框架 网格控件展示”的混搭结构。常见做法是程序初始化时先建立 ODBC 连接把连接句柄存在文档或视图的成员变量里然后执行查询拿到记录集后分页或一次性填充进 MSFlexGrid。MSFlexGrid 的填充逻辑是这套代码里最直观的“取数 - 填表”循环任何一个做过表格控件的人都能一眼看懂。源码里出现的 GetRecordsetCount、GetRecordsetValue 这类封装方法本质上是把 CRecordset 的 MoveNext 循环和列字段读取包了一层。我一般会建议初学者直接看视图类的 OnInitialUpdate 或 OnInitDialog看它第一步干了什么第二个动作又是什么整个程序的数据流就清楚了。2.3 工程里“看不见”的 ODBC 依赖编译这个工程不会自动帮你配数据源。ODBC 访问 Access 需要三层的配合应用代码、ODBC 驱动管理器、Access 数据库驱动JET 引擎。VC6.0 时代多数机器默认装了 JET 4.0所以代码里只要写“DSNStudentInfo”就能连上——前提是控制面板里确实存在名为 StudentInfo 的系统数据源。这个依赖关系不在压缩包里但却是程序能不能跑起来的关键。后面第 3 章会完整讲解数据源的配置链路。3. ODBC 数据源配置实战从控制面板 DSN 到 CDatabase 连接串的完整链路3.1 环境准备VC6.0 与现代 Windows 的微妙关系先说环境。VC6.0 是 1998 年发布的 IDE在 Windows 10/11 上安装会碰到兼容性提示常见处理是在安装程序上右键选择“兼容性疑难解答”或者直接装在虚拟机里。装上之后还要注意VC6.0 自带的头文件和库是 32 位的程序编译出来也默认是 32 位这与系统位数无关只与编译目标平台有关。Access 数据库方面当年配套的是 Access 2000/2003mdb 文件格式也是 JET 4.0。如果你手里只有新版 Access 或只有新版运行库需要单独处理驱动兼容问题这个放到第 5 章避坑部分展开。先把最稳定的组合列出来Windows 10 32/64位 VC6.0 Access 2003 格式 mdb 系统自带的 JET 4.0 驱动这套组合最接近当年代码的运行环境。3.2 手工配置一个系统 DSN 的完整步骤打开控制面板进入“管理工具”找到“ODBC 数据源(32位)”。注意在 64 位系统上控制面板默认显示的通常是 64 位 ODBC 管理器名称可能是“ODBC 数据源(64位)”而 VC6 编译出的 32 位程序只能看到 32 位数据源。正确入口是运行 C:\Windows\SysWOW64\odbcad32.exe这个细节能省掉后续大量“找不到 DSN”的排查时间。进入 ODBC 数据源管理器后按下面步骤操作切到“系统 DSN”页签点“添加”。驱动列表里选择“Microsoft Access Driver (*.mdb)”。在“ODBC Microsoft Access 安装”界面填数据源名例如 StudentInfo。点“选择”定位到数据库文件 student.mdb确认路径无误。点击“高级”可以设置登录用户名和密码本例通常留空。测试连接看到“连接成功”提示后确定保存。这套操作完成后一个名为 StudentInfo 的系统 DSN 就生效了。DSN 本身不包含数据库文件内容只包含“驱动类型 mdb 文件路径 可选认证信息”。3.3 代码侧连接字符串与 DSN 一一对应数据源配好后代码里的 CDatabase 连接就有了依托。常见写法是CDatabase db; CString strDSN _T(DSNStudentInfo;); BOOL bOK db.OpenEx(strDSN, CDatabase::noOdbcDialog); if (!bOK) { AfxMessageBox(_T(数据库连接失败请检查ODBC数据源)); return; }这里 OpenEx 的第一个参数就是 ODBC 连接字符串DSN 后面填的是控制面板里设置的数据源名两者必须拼写完全一致。第二个参数 noOdbcDialog 表示不弹出 ODBC 连接对话框如果数据源不存在程序会直接报错而不是弹窗询问。若希望出错时给用户一个选择驱动的窗口可以改成 0但不建议——弹窗对终端用户来说是困惑而不是帮助。连接成功之后CRecordset 通过 CDatabase 指针执行查询CRecordset rs(db); CString strSQL _T(SELECT * FROM student); rs.Open(CRecordset::forwardOnly, strSQL);CRecordset::Open 的第二参数是 SQL 语句第一参数指定游标类型forwardOnly 适合逐个扫描snapshot 支持随机定位与 Requery 刷新dynaset 适合看到其他会话的改动。对课程设计场景来说snapshot 是最稳妥的选择既支持增删改后又立即重新查询也不会出现并发锁问题。3.4 文件路径与数据库文件的放置经验典型翻车点出现在相对路径上。程序编译出来的 Debug 或 Release 目录和数据库文件的存放位置如果不一致连接时就会报“找不到文件”。比较稳的做法有两种一是把 student.mdb 放在与 exe 相同目录代码里用 GetModuleFileName 取程序路径再拼数据库文件名二是把 mdb 放在固定磁盘路径比如 C:\Data\student.mdbDSN 里写绝对路径。第二种做法简单但移植性差换一台机器就得重新配 DSN。我习惯把 mdb 文件复制到工程目录下的 data 子目录代码里用相对路径组装发布时整个 data 文件夹跟着走。这样换机器时只要保持目录结构不变DSN 里写相对路径指向当前目录即可。不过 ODBC DSN 本身对相对路径支持并不好稳妥起见还是把 mdb 放到固定位置或者干脆在程序启动时用代码检查数据库文件是否存在。4. 核心代码剖析CRecordset 学生信息 CRUD 是怎么跑通的4.1 CDatabase 与 CRecordset 的分工逻辑MFC 把 ODBC API 封装成了两个核心类CDatabase 管连接生命周期CRecordset 管记录集操作。CDatabase 可以多次打开、关闭、重连一个连接可以同时供多个 CRecordset 使用但同一时刻每个 CRecordset 只能绑定一个连接。打开连接之后CRecordset 的构造和 Open 是最关键的两步// 数据库对象与记录集对象 CDatabase m_db; CRecordset m_rs; // 打开连接 m_db.OpenEx(_T(DSNStudentInfo;), CDatabase::noOdbcDialog); // 打开记录集 m_rs.m_pDatabase m_db; CString strSQL _T(SELECT id, name, score FROM student ORDER BY id); m_rs.Open(CRecordset::snapshot, strSQL);m_pDatabase 是 CRecordset 的公有成员指向要使用的 CDatabase 对象。也可以用另一种更简洁的写法构造 CRecordset 时不传参数然后调用 m_rs.Open(NULL, strSQL)这时 MFC 自动创建一个临时连接但临时连接在记录集关闭时会一并释放管理上不如显式持有 CDatabase 清晰。工程代码里一般走显式持有这条路径因为程序还需要在多个视图和函数之间共享同一个连接。打开后判断记录集状态// 空表判定 if (m_rs.IsEOF()) { AfxMessageBox(_T(没有数据)); return; } // 逐条读取 while (!m_rs.IsEOF()) { CString strID, strName; m_rs.GetFieldValue(_T(id), strID); m_rs.GetFieldValue(_T(name), strName); // 填充到界面 m_rs.MoveNext(); }GetFieldValue 第一个参数是字段名第二个参数是接收字符串的缓冲无论底层字段是整型还是文本型都能转成 CString 方便显示。这里有一个容易被忽略的细节IsEOF 必须在 Open 之后立即判断空记录集一旦执行 MoveNext 就会越界。4.2 增删改AddNew、Update 与 Delete 的配合写入数据的标准流程是 AddNew 填值再 Update。示例代码是最常见的“表单录入 - 写库 - 刷新网格”三段式// 新增记录 m_rs.AddNew(); m_rs.SetFieldValue(_T(id), m_strID); m_rs.SetFieldValue(_T(name), m_strName); m_rs.SetFieldValue(_T(score), m_strScore); m_rs.Update();AddNew 把记录集切换为“插入模式”SetFieldValue 给字段赋值Update 提交到数据库。注意 Update 之后记录集里并没有立刻出现新记录必须 Requery 重新查询才能看到。这就是为什么很多初学者点完“保存”按钮后网格里看不到新数据——不是没写进去是没重新取。删除更直接if (!m_rs.IsEOF() !m_rs.IsBOF()) { m_rs.Delete(); m_rs.Requery(); }Delete 删除的是当前记录删除后游标位置失效必须 Requery 恢复有效状态。如果不 Requery 而直接调用 MoveNext会抛出“没有当前记录”的异常。4.3 修改与事务边界修改操作用 Edit 进入编辑模式m_rs.Edit(); m_rs.SetFieldValue(_T(score), m_strNewScore); m_rs.Update();Edit 和 AddNew 的区别在于前者针对当前已有行后者插入新行。执行 Update 后同样需要 Requery 让界面同步。当批量操作多个记录时用事务包裹能避免半截失败m_db.BeginTrans(); try { // 多个 AddNew/Update/Delete m_db.CommitTrans(); } catch (CDBException* e) { m_db.Rollback(); e-Delete(); }BeginTrans 之后所有写操作在提交前都是可回滚的。事务能显著降低数据错乱概率但要注意 JET 引擎下的事务也有锁竞争长事务容易把表锁住设计上尽量每个事务只包几条语句不要把一个很长的循环塞进去。4.4 MSFlexGrid 填充函数的复用技巧网格填充是这套代码里最典型的重复劳动。设计上可以抽一个独立函数出来每次增删改后统一调用void RefreshGrid(CRecordset rs, CMSFlexGrid grid) { grid.SetRows(2); // 保留一行表头 grid.SetCols(4); // 四列 grid.SetTextMatrix(0, 0, _T(学号)); grid.SetTextMatrix(0, 1, _T(姓名)); grid.SetTextMatrix(0, 2, _T(成绩)); int row 1; rs.Requery(); // 回到最新数据 while (!rs.IsEOF()) { CString val; rs.GetFieldValue(_T(id), val); grid.SetTextMatrix(row, 0, val); rs.GetFieldValue(_T(name), val); grid.SetTextMatrix(row, 1, val); rs.GetFieldValue(_T(score), val); grid.SetTextMatrix(row, 2, val); rs.MoveNext(); grid.SetRows(row 2); // 动态扩充行数 row; } }SetTextMatrix 是 MSFlexGrid 最常用的填值接口参数依次是行号、列号、文本。这里每加一行先扩一行最后保留 2 行空余避免网格行数不够时数组越界。刷新前必须先 Requery 而不是 MoveFirst因为新增记录后 MoveFirst 只能回到旧的记录集开头。5. 避坑记录VC6.0 ODBC Access 最常见的五个翻车现场5.1 64 位系统上找不到刚建的 DSN现象在控制面板里创建了数据源程序运行却报“找不到数据源名称”。原因64 位系统同时存在 32 位和 64 位两套 ODBC 管理器。控制面板默认打开 64 位的而 VC6 编译出的程序是 32 位进程只能访问 32 位 DSN。两边的注册表位置完全不同数据源并不互通。解决打开 32 位 ODBC 管理器路径是 C:\Windows\SysWOW64\odbcad32.exe。在“系统 DSN”里重新添加一次 Access 驱动完成后回到程序重试。5.2 mdb 文件名或路径带中文导致连接失败现象代码和 DSN 都没问题但 Open 时总报“找不到文件或无法打开”。原因JET 驱动在某些版本下对非 ASCII 路径处理有瑕疵路径里含“学生”“数据”之类的中文目录名时容易出玄学问题。解决把数据库文件名和所在目录全部改成英文比如改成 StudentData 目录下的 student.mdb再重新配置 DSN。工程里的其他素材文件名不用改只有数据库路径敏感。5.3 MSFlexGrid 控件报“未注册”或插入对象失败现象程序编译通过运行到创建网格时报错或者在资源编辑器里直接看不到控件。原因MSFlexGrid 依赖 msflxgrd.ocx 这个 ActiveX 控件部分精简版系统或旧控件版本未注册。解决在命令行里手动注册regsvr32 C:\Windows\SysWOW64\msflxgrd.ocx如果系统里没有这个文件需要从可用的 VC6 运行库或控件包里复制一份放到对应目录再注册。注册成功后资源编辑器里的“插入 ActiveX 控件”列表就能找到 MSFlexGrid。5.4 空表时程序直接崩溃现象数据库表中没有任何记录打开程序或点击刷新程序提示越界或直接退出。原因代码里执行 rs.MoveNext() 前没有判断 IsEOF。空表的当前游标已经在 EOF 位置MoveNext 直接触发非法操作。解决所有遍历循环统一加 IsEOF 判断。最稳的写法while (!rs.IsEOF()) { // 读数据 rs.MoveNext(); }打开记录集后第一件事先判断 IsEOF()为空就给出提示并返回不给后续逻辑留雷。5.5 Access 驱动版本不匹配导致无法连接 mdb现象在新版 Office 环境里ODBC 驱动列表里找不到 Microsoft Access Driver或者连接时报“找不到可用的驱动”。原因新版 Office 默认安装的是 Access 引擎 2016老 JET 4.0 驱动不再系统预装。这个压缩包是建立在 JET 驱动可用前提下的。解决先装一个可用的 Access 数据库引擎驱动安装时选择“全部安装”装完检查 ODBC 驱动列表里是否出现相应条目。如果项目必须用老 mdb 格式装完驱动后按第 3 章的步骤重新配置 DSN如果项目可以接受改连接方式也可以把数据库另存为新格式再用新驱动连接这属于换方案的路径改动较大建议保持老格式。6. 进阶验证把静态 DSN 示例改造成带查询条件的学生检索工具掌握了基础的 CRUD 之后可以给这个例子加一个小功能验证自己是否真的摸到了 ODBC 的边界。我给这个练习起名叫“按学号查询”在现有程序里加一个编辑框和一个“查询”按钮输入部分学号或姓名关键字点击后列表只显示匹配的记录。实现思路不改动整体架构只把查询 SQL 从静态串改成动态拼装CString strSQL; strSQL.Format(_T(SELECT id, name, score FROM student WHERE name LIKE %%%s%% ORDER BY id), strKeyword);写这行代码有三个要注意的点。第一LIKE 运算符在 JET 引擎里支持 % 通配符不是 *第二Format 里的 %% 是转义后的 %传给 SQL 执行的字符串才是 LIKE %关键字%第三字段名和表名要跟 mdb 里的一致拼错任何一个都会在 Open 时报字段不存在。动态拼装时还要注意防注入别直接拼用户输入的内容进字符串。随后在按钮点击事件里做一次闭环验证m_rs.Close(); m_rs.m_strFilter.Empty(); m_rs.m_strSort.Empty(); if (!m_rs.Open(CRecordset::snapshot, strSQL)) { AfxMessageBox(_T(查询失败)); return; } RefreshGrid(m_rs, m_grid);这套代码验证的是记录集能不能在同一个连接上反复开关。第一次查询成功后在编辑框输入新的关键字再次点击查询程序必须能正常关闭旧记录集再打开新记录集不报“记录集已打开”。能跑通这一步说明你理解了连接与记录集的各自生命周期而不是只会照着例子点按钮。还应该验证一下刷新行为新增一条记录点“查询”按钮网格里立刻出现新记录。这确认了 Requery 承担的取数职责不是在界面层面假刷新。我在维护老工程时养成了一个习惯每改一处数据库相关代码就同时检查“打开、查询、增改删、重新查询”四步是否全部走通少一步后面都可能出问题。这篇拆解里的配置链路和避坑记录我希望你合上文章后能自己动手一遍遇到报错也别慌对照第 5 章的踩坑清单逐条查。希望帮到你。本文还有配套的精品资源点击获取
返回列表