这是一个比较经典的技术组合,尤其是在一些遗留系统或 Windows 桌面应用开发中,虽然现在有更现代的替代方案(如 C++/CLI + A.NET 或直接使用 ODBC),但理解 C + ADO + Access 的原理依然非常有价值。

核心概念
- C 语言: 我们使用的编程语言。
- ADO (ActiveX Data Objects): 一个微软的数据库访问技术,它提供了一个高级的、易于使用的接口来访问各种数据源,ADO 是通过 COM (Component Object Model) 技术实现的,这意味着在 C 语言中我们需要使用 COM API 来调用它。
- Microsoft Access: 我们的数据库文件,通常是一个
.accdb或.mdb文件,它本身是一个文件型数据库,我们通过 OLE DB 驱动程序来访问它。
开发环境准备
在开始编码之前,你需要确保你的开发环境已经准备就绪。
编译器
你需要一个 Windows 下的 C/C++ 编译器,最常用的是:
- Visual Studio: 最简单、最推荐的选择,安装时请确保勾选“使用 C++ 的桌面开发”工作负载。
- MinGW (GCC for Windows): 如果你习惯使用 GCC,也可以配置 MinGW。
ADO 库
ADO 的库文件和头文件通常包含在 Windows SDK 或 Visual Studio 中,只要你安装了 Visual Studio 的 C++ 开发环境,就已经拥有了所需的一切。
- 头文件:
#include <adoidl.h>和#include <adoint.h> - 库文件:
msado15.lib(这是最常用的 ADO 类型库)
Access 数据库
创建一个简单的 Access 数据库文件,mydatabase.accdb,并在其中创建一个表。

示例表 Employees:
| ID | FirstName | LastName | Department |
|----|-----------|----------|------------|
| 1 | John | Doe | IT |
| 2 | Jane | Smith | HR |
| 3 | Peter | Jones | Sales |
创建 Access 数据库和 DSN (可选)
对于初学者,创建一个 DSN (Data Source Name) 可以简化连接字符串,但对于生产环境,推荐使用“无 DSN”的连接字符串,因为它更灵活,不依赖于系统配置。
方法 A: 创建 DSN (简单)
- 在 Windows 中搜索 “ODBC 数据源” 并打开它。
- 选择 “DSN 用户数据源” 或 “DSN 系统数据源”,然后点击 “添加”。
- 在驱动程序列表中,找到 Microsoft Access Driver (.mdb, .accdb) 并点击 “完成”。
- 为数据源命名,
MyAccessDB。 - 点击 “选择...” 按钮,浏览并选择你创建的
mydatabase.accdb文件。 - 点击 “确定” 保存。
方法 B: 无 DSN (推荐)
我们将在代码中直接指定驱动程序和数据库文件路径,无需配置 DSN。
编写 C 代码
我们将创建一个 C 程序,演示如何连接数据库、执行查询、读取数据、插入数据和关闭连接。

核心 ADO 对象:
CoInitialize: 初始化 COM 库。必须在调用任何 ADO 函数前执行。CoUninitialize: 释放 COM 库。必须在程序结束时执行。_ConnectionPtr: 代表与数据库的连接。_RecordsetPtr: 代表一个记录集(查询结果)。_CommandPtr: 代表一个要执行的命令(如 SQL 语句)。
下面是一个完整的示例代码:
// ado_access_example.c
#define WIN32_LEAN_AND_MEAN
#define INITGUID
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <olectl.h> // For OleInitialize
#include <adoidl.h> // ADO 头文件
#include <adoint.h> // ADO 头文件
// 链接 ADO 库
#pragma comment(lib, "ole32.lib") // For CoInitialize
#pragma comment(lib, "msado15.lib") // For ADO
// 定义一个用于处理错误的宏
#define CHECK_HR(hr, msg) \
if (FAILED(hr)) { \
printf("错误: %s (HRESULT: 0x%08X)\n", msg, hr); \
goto Cleanup; \
}
void main()
{
HRESULT hr = S_OK;
_RecordsetPtr pRecordset = NULL;
_ConnectionPtr pConnection = NULL;
_bstr_t strConnect; // 使用 _bstr_t 自动处理 BSTR 的内存管理
_bstr_t strQuery;
// 1. 初始化 COM 库
hr = CoInitialize(NULL);
CHECK_HR(hr, "初始化 COM 失败");
// 2. 创建 Connection 对象实例
hr = pConnection.CreateInstance(__uuidof(Connection));
CHECK_HR(hr, "创建 Connection 对象失败");
// 3. 定义连接字符串
// --- 选择一种连接方式 ---
// 方法 A: 使用 DSN (简单)
// strConnect = "DSN=MyAccessDB;";
// 方法 B: 无 DSN (推荐,更灵活)
// 将路径替换为你自己的 .accdb 文件路径
strConnect = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\path\\to\\your\\mydatabase.accdb;Persist Security Info=False;";
// 4. 打开数据库连接
printf("正在连接数据库...\n");
hr = pConnection->Open(strConnect, "", "", adConnectUnspecified);
CHECK_HR(hr, "打开数据库连接失败");
printf("连接成功!\n\n");
// 5. 执行查询并获取记录集
strQuery = "SELECT ID, FirstName, LastName, Department FROM Employees;";
printf("正在执行查询: %s\n", (char*)strQuery);
pRecordset = pConnection->Execute(strQuery, NULL, adCmdText);
if (pRecordset != NULL && pRecordset->State == adStateOpen)
{
printf("查询成功,获取到记录集,\n");
printf("--------------------------------------------------\n");
printf("%-5s %-10s %-10s %s\n", "ID", "FirstName", "LastName", "Department");
printf("--------------------------------------------------\n");
// 6. 遍历记录集
while (!pRecordset->adoEOF)
{
// 使用 Fields->GetItem(column_name)->GetValue() 获取字段值
// 使用 Fields->GetItem(column_index)->GetValue() 也可以
long id = (long)pRecordset->Fields->GetItem("ID")->GetValue();
_bstr_t firstName = pRecordset->Fields->GetItem("FirstName")->GetValue();
_bstr_t lastName = pRecordset->Fields->GetItem("LastName")->GetValue();
_bstr_t department = pRecordset->Fields->GetItem("Department")->GetValue();
printf("%-5ld %-10s %-10s %s\n", id, (char*)firstName, (char*)lastName, (char*)department);
// 移动到下一条记录
pRecordset->MoveNext();
}
printf("--------------------------------------------------\n\n");
}
else
{
printf("未能获取记录集,\n");
}
// 7. 执行插入操作
printf("正在执行插入操作...\n");
_bstr_t insertSQL = "INSERT INTO Employees (FirstName, LastName, Department) VALUES ('Alice', 'Williams', 'IT');";
pConnection->Execute(insertSQL, NULL, adCmdText);
printf("插入成功!\n\n");
// 8. 清理资源
Cleanup:
if (pRecordset != NULL)
{
if (pRecordset->State == adStateOpen)
{
pRecordset->Close();
}
pRecordset.Release();
pRecordset = NULL;
}
if (pConnection != NULL)
{
if (pConnection->State == adStateOpen)
{
pConnection->Close();
}
pConnection.Release();
pConnection = NULL;
}
// 9. 反初始化 COM 库
CoUninitialize();
printf("程序执行完毕,\n");
system("pause"); // 暂停,以便查看输出
}
编译和运行
如果你使用的是 Visual Studio:
- 创建一个新的 "Windows 桌面控制台应用" 项目(确保使用 C++ 模板,因为 C 项目无法直接链接 C++ 库,但我们可以通过一些技巧或直接创建 C++ 项目但用 C 语法来写)。
- 将上面的代码复制到
.cpp文件中(main.cpp)。 - 编译并运行,Visual Studio 会自动链接
msado15.lib。
如果你使用的是 命令行 (cl.exe):
# 假设代码文件是 ado_access_example.c cl ado_access_example.c /link msado15.lib ole32.lib
然后运行生成的 ado_access_example.exe。
重要提示: 运行此程序时,请确保:
- 你的系统安装了 Microsoft Access Database Engine,如果你的电脑上没有安装 Office,你可能需要单独下载并安装这个引擎(64 位版本或 32 位版本,需要与你的编译器和应用程序匹配),这是
Provider=Microsoft.ACE.OLEDB.12.0;能够工作的前提。 - 代码中的数据库文件路径是绝对正确的。
常见问题与注意事项
HRESULT错误: ADO 函数返回HRESULT,你需要检查它是否为S_OK或SUCCEEDED宏,如果失败,可以使用GetErrorInfo获取更详细的错误信息,但这在 C 中比较繁琐。- COM 对象的生命周期: 使用
CreateInstance创建对象后,必须使用Release()方法来释放它,这是 COM 的引用计数机制,否则会导致内存泄漏。 _bstr_t和VARIANT: ADO 内部使用BSTR(一种 COM 字符串)和VARIANT(一种通用数据类型)来传递数据。_bstr_t是 C++ 的一个智能指针类,可以极大地简化BSTR的内存管理,在纯 C 语言中,你需要手动调用SysAllocString和SysFreeString,非常麻烦,这也是为什么这个例子虽然后缀是.c,但使用了 C++ 特性,或者建议直接创建 C++ 项目的原因。- 32位 vs 64位:
- 你的编译器、ADO 库、Access 数据库引擎和应用程序必须是统一的平台(要么全是 32 位,要么全是 64 位)。
- 如果你编译的是 32 位程序,在 64 位 Windows 上运行,它会使用
WOW64子系统去访问 32 位的驱动和库,反之,64 位程序无法直接加载 32 位 DLL。 - 最常见的错误是 "Microsoft.ACE.OLEDB.12.0" 提供程序未找到,这通常是因为平台不匹配。
- SQL 注入: 如果你的 SQL 语句来自用户输入,绝对不要直接拼接字符串,应该使用参数化查询(
_CommandPtr对象的Parameters集合)来防止 SQL 注入攻击。
使用 C 语言和 ADO 操作 Access 数据库的流程可以总结为:
- 初始化 COM (
CoInitialize)。 - 创建 ADO 对象 (
_ConnectionPtr,_RecordsetPtr等)。 - 构建连接字符串。
- 打开连接 (
pConnection->Open)。 - 执行命令或查询 (
pConnection->Execute或pRecordset->Open)。 - 处理记录集 (遍历、读取字段值)。
- 关闭和释放对象 (
Close,Release)。 - 反初始化 COM (
CoUninitialize)。
尽管这个技术栈看起来有些复古,但它在理解 Windows 底层数据库访问机制方面是一个很好的实践,对于新的项目,如果你必须使用 C 语言,可以考虑更直接、更底层的 ODBC API,或者使用 C++ 来获得更现代、更安全的编程体验。
