ADO 操作access
简介
DAO
DAO 是 Microsoft 早期推出的数据库访问技术,专门针对 Access(Jet 数据库引擎)进行了优化,对于 Access 数据库操作非常高效。
ADO
ADO(ActiveX Data Objects)是微软推出的一种基于 COM(Component Object Model)的数据库访问技术,用于在应用程序中实现对各种数据源的访问和操作。它提供了一套简单、高效的对象模型,使开发者能够轻松地连接数据库、执行查询、处理结果集等。
ADO 的主要特点:
- 通用性强:支持多种数据库,包括 Access、SQL Server、Oracle、MySQL 等,无需针对不同数据库编写大量适配代码。
- 基于 COM:作为 COM 组件,可在多种编程语言中使用,如 C++、VB、VBScript、ASP 等。
- 轻量级:相比早期的 DAO(Data Access Objects),ADO 架构更简洁,性能更优,资源占用更少。
- 灵活的对象模型:核心对象包括Connection(连接)、Command(命令)、Recordset(记录集)等,分工明确,易于理解和使用。
- 支持断开连接的记录集:可将数据读取到本地后断开与数据库的连接,在本地处理数据,适合网络环境下减少连接开销。
ADO 的核心对象:
Connection
:负责与数据库建立和管理连接,提供连接字符串配置、打开 / 关闭连接等功能。Command
:用于执行 SQL 语句或存储过程,支持参数化查询,可提高执行效率和安全性。Recordset
:表示查询返回的结果集,提供对记录的遍历、添加、修改、删除等操作,支持游标和锁定机制。Parameter
:配合Command使用,用于定义参数化查询中的参数,防止 SQL 注入。Field
:表示Recordset中的一个字段(列),用于访问字段的值和属性。
ADO 的典型使用流程:
- 初始化 COM 环境(在 C++ 中需调用CoInitialize)。
- 创建Connection对象,通过连接字符串连接到目标数据库。
- 使用Command对象执行 SQL 命令,或直接通过Recordset执行查询。
- 处理Recordset中的数据(遍历、修改等)。
- 关闭记录集和连接,释放资源。
- 反初始化 COM 环境(调用CoUninitialize)。
ADO 与其他数据库访问技术的对比:
- 与 DAO 相比:ADO 更通用,支持多种数据库,而 DAO 主要针对 Access;ADO 性能更优,适合现代应用开发。
- 与 ODBC 相比:ADO 是更高层次的封装,使用更简单,而 ODBC 是底层接口,需要更多的代码来实现相同功能。
- 与 OLE DB 相比:ADO 是 OLE DB 的封装,简化了 OLE DB 的复杂操作,更易于上手。
导入msado15.dll
#import "msado15.dll" no_namespace rename("EOF", "adoEOF")
#import
是 Visual C++ 编译器的一个预处理指令,用于导入 COM 组件(.dll 或 .tlb 类型库文件)的类型信息。- “msado15.dll” 是 ADO 组件的动态链接库文件,包含了 ADO 的类型库信息。这个文件通常位于系统目录(如 C:\Program Files\Common Files\System\ado\)。
- 执行这条指令后,编译器会自动生成两个文件
msado15.tlh
:类型库头文件,包含了 ADO 接口、类、枚举等的声明(类似头文件)。msado15.tli
:实现文件,包含了一些包装函数的实现(无需手动包含,编译器会自动处理)。
no_namespace
指示编译器导入 ADO
类型时不使用其默认命名空间。
ADO
类型库的默认命名空间是 ADODB
,如果不添加此参数,使用 ADO
组件时需要加上命名空间前缀(如 ADODB::_ConnectionPtr
)。
使用 no_namespace 后,可以直接写 _ConnectionPtr
而无需 ADODB::
前缀,简化代码。
rename(“EOF”, “adoEOF”)
用于重命名 ADO
中的 EOF
符号,避免与其他代码中的 EOF
(如 C 标准库中的文件结束符)冲突。
ADO
中 EOF
(End Of File)表示记录集的末尾(当 Recordset 遍历到最后一条记录后,EOF
为 true
)。
C 标准库(如 <stdio.h>)中也有一个 EOF
宏(通常定义为 -1),表示文件操作的结束。如果不重命名,两者会产生命名冲突,导致编译错误。
重命名后,在代码中需使用 adoEOF
来表示 ADO 记录集的末尾(如 while (!pRs->adoEOF)
)。
初始化COM环境
CoInitialize(NULL);
_ConnectionPtr
用于管理数据库连接的智能指针类,封装了 ADO 的 Connection 对象,是 ADO 操作数据库的基础。它负责建立、管理和关闭与数据库的连接,同时提供事务处理等核心功能。
内部维护了对 COM 对象的引用计数,超出作用域时会自动释放资源(无需手动调用 Release(),但显式释放更规范)。
封装了数据库连接的核心功能:连接数据库、执行 SQL 命令、事务管理等。
常用方法和属性
创建 _ConnectionPtr
实例
使用 CreateInstance()
方法初始化对象,需指定 ADO Connection
类的 GUID
:
_ConnectionPtr pConn;
// 创建实例,__uuidof(Connection) 是 ADO Connection 类的 GUID
HRESULT hr = pConn.CreateInstance(__uuidof(Connection));
if (FAILED(hr)) {// 处理创建失败
}
连接数据库:Open()
方法
pConn->Open(_bstr_t(连接字符串), _bstr_t(用户名), _bstr_t(密码), 连接选项);
连接字符串
指定数据库类型、路径 / 地址等信息(因数据库而异)。
access
"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\test.accdb;"
SQL Server
"Provider=SQLOLEDB;Server=localhost;Database=test;Uid=sa;Pwd=123;"
用户名 / 密码
数据库登录凭证
连接选项
通常使用 adModeUnknown(默认值)。
关闭连接:Close() 方法
if (pConn->State == adStateOpen) { // 检查连接是否打开pConn->Close();
}
State
属性:返回连接状态,adStateOpen
表示已连接,adStateClosed
表示已关闭。
执行 SQL 命令:Execute() 方法
直接执行 SQL 语句(适合无需返回结果集的操作,如 CREATE TABLE、INSERT、UPDATE、DELETE 等):
// 语法:_RecordsetPtr Execute(_bstr_t CommandText, VARIANT* RecordsAffected, long Options);
_variant_t RecordsAffected;
// 执行创建表操作
pConn->Execute("CREATE TABLE Users (ID INT, Name TEXT(50), Age INT)", &RecordsAffected, adCmdText);
// 执行插入操作
pConn->Execute("INSERT INTO Users VALUES (1, '张三', 25)", &RecordsAffected, adCmdText);
参数:
CommandText
:SQL 语句或存储过程名。RecordsAffected
:返回受影响的行数(可选)。Options
:命令类型,adCmdText
表示 SQL 语句,adCmdStoredProc
表示存储过程。
返回值:如果 SQL 是查询语句(SELECT
),返回 _RecordsetPtr
结果集;否则返回 NULL
。
事务管理
_ConnectionPtr 提供事务的开始、提交和回滚功能,确保多个操作的原子性:
try {pConn->BeginTrans(); // 开始事务// 执行一系列操作pConn->Execute("UPDATE Accounts SET Balance = Balance - 100 WHERE ID = 1", NULL, adCmdText);pConn->Execute("UPDATE Accounts SET Balance = Balance + 100 WHERE ID = 2", NULL, adCmdText);pConn->CommitTrans(); // 提交事务(所有操作生效)
} catch (_com_error& e) {pConn->RollbackTrans(); // 回滚事务(所有操作撤销)std::cout << "事务失败: " << (const char*)e.Description() << std::endl;
}
其他常用属性
ConnectionString
:获取或设置连接字符串。Version
:返回 ADO 版本号。DefaultDatabase
:设置或获取默认数据库。
ACCESS数据库引擎
在连接 Access 数据库时,最常用的 Jet OLEDB(Microsoft.Jet.OLEDB.4.0)和 ACE OLEDB(Microsoft.ACE.OLEDB.12.0/16.0)驱动
连接数据库
Microsoft.ACE.OLEDB.12.0(推荐,支持所有格式)
适用于 Access 2007 + 的.accdb 文件和旧版.mdb 文件,支持 32 位和 64 位程序:
_ConnectionPtr pConn;
pConn.CreateInstance(__uuidof(Connection));
// 使用ACE驱动连接.accdb
pConn->Open(_T("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Database\\db1.accdb;")_T("Jet OLEDB:System Database=Database\\workgroup.mdw;") // 工作组文件路径_T("Jet OLEDB:Database Password=dbpassword;"), // 数据库密码(若有)_T("username"), // 用户名(如 Admin)_T("userpassword"), // 用户密码adModeUnknown
);
Microsoft.Jet.OLEDB.4.0(适用于.mdb 文件)
// C++ ADODB连接示例
_ConnectionPtr pConn;
pConn->Open(_T("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Database\\db1.mdb;")_T("Jet OLEDB:System Database=Database\\workgroup.mdw;") // 工作组文件路径_T("Jet OLEDB:Database Password=dbpassword;"), // 数据库密码(若有)_T("username"), // 用户名_T("userpassword"), // 用户密码adModeUnknown
);
_CommandPtr
_CommandPtr
是 ADO(ActiveX Data Objects)中用于执行数据库命令的智能指针类,封装了 ADO
的 Command
对象。它主要用于处理 SQL 语句、存储过程等数据库命令,支持参数化查询,是 ADO 中执行复杂数据库操作的核心组件。
- 执行 SQL 语句(查询、插入、更新、删除等)或调用存储过程。
- 支持参数化查询(通过 Parameters 集合),避免 SQL 注入,提高执行效率。
- 可将命令与特定连接关联,也可动态指定连接。
- 执行后可返回结果集(通过
_RecordsetPtr
)或受影响的行数。
CreateInstance
_CommandPtr pCmd;
HRESULT hr = pCmd.CreateInstance(__uuidof(Command));
if (FAILED(hr)) {// 处理创建失败
}
关联数据库连接:ActiveConnection 属性
_CommandPtr
需与一个打开的数据库连接关联(通过 _ConnectionPtr 或连接字符串):
// 方式1:关联已打开的_ConnectionPtr
pCmd->ActiveConnection = pConn; // pConn 是已打开的连接// 方式2:直接指定连接字符串
pCmd->ActiveConnection = _bstr_t("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\test.accdb;");
设置命令内容:CommandText
与 CommandType
CommandText
:指定要执行的命令(SQL 语句、存储过程名或表名)。CommandType
:指定命令类型(提高解析效率,可选):adCmdText
:SQL 语句(默认值)。adCmdStoredProc
:存储过程。adCmdTable
:表名(等效于 SELECT * FROM 表名)。
// 示例1:执行SQL语句
pCmd->CommandText = "SELECT * FROM Users WHERE Age > ?"; // 带参数的查询
pCmd->CommandType = adCmdText;// 示例2:调用存储过程
pCmd->CommandText = "sp_InsertUser"; // 存储过程名
pCmd->CommandType = adCmdStoredProc;
参数化查询:Parameters 集合
CreateParameter():创建参数对象。
Append():将参数添加到 Parameters 集合。
SetParamValue():设置参数值(或直接通过索引 / 名称访问)。
// 1. 添加参数(假设SQL为"SELECT * FROM Users WHERE Age > ? AND Name LIKE ?")
pCmd->Parameters->Append(pCmd->CreateParameter("AgeParam", // 参数名(可选)adInteger, // 数据类型(整数)adParamInput, // 参数方向(输入)sizeof(int), // 长度(仅字符串等类型需要)18 // 默认值(可选)
));pCmd->Parameters->Append(pCmd->CreateParameter("NameParam", adVarChar, // 字符串类型adParamInput, 50, // 字符串长度"%张%" // 默认值
));// 2. 动态修改参数值(执行前)
pCmd->Parameters->GetItem("AgeParam")->Value = _variant_t(20);
// 或通过索引访问(0-based)
pCmd->Parameters->GetItem(1)->Value = _variant_t("%李%");
执行命令:Execute() 方法
// 语法:_RecordsetPtr Execute(VARIANT* RecordsAffected, VARIANT* Parameters, long Options);
_variant_t RecordsAffected; // 用于接收受影响的行数
_RecordsetPtr pRs;// 执行查询命令(返回结果集)
pRs = pCmd->Execute(&RecordsAffected, NULL, adCmdText);// 执行非查询命令(如INSERT/UPDATE/DELETE,返回NULL)
pCmd->Execute(&RecordsAffected, NULL, adCmdText);
long affected = (long)RecordsAffected; // 获取受影响的行数
释放资源:Release() 与状态检查
if (pCmd) {pCmd->Cancel(); // 取消正在执行的命令(如需)pCmd.Release(); // 释放智能指针
}
示例
#import "msado15.dll" no_namespace rename("EOF", "adoEOF")
#include <iostream>
#include <atlbase.h>int main() {CoInitialize(NULL);_ConnectionPtr pConn;_CommandPtr pCmd;_RecordsetPtr pRs;try {// 1. 连接数据库pConn.CreateInstance(__uuidof(Connection));pConn->Open("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\test.accdb;", "", "", adModeUnknown);// 2. 创建并配置Command对象pCmd.CreateInstance(__uuidof(Command));pCmd->ActiveConnection = pConn; // 关联连接pCmd->CommandText = "SELECT * FROM Users WHERE Age > ?"; // 带参数的SQLpCmd->CommandType = adCmdText;// 3. 添加参数pCmd->Parameters->Append(pCmd->CreateParameter("AgeParam", adInteger, adParamInput, sizeof(int), 20));// 4. 执行查询,获取结果集pRs = pCmd->Execute(NULL, NULL, adCmdText);// 5. 遍历结果std::cout << "年龄大于20的用户:" << std::endl;while (!pRs->adoEOF) {std::cout << "ID: " << (long)pRs->Fields->GetCollect("ID")<< ", Name: " << (const char*)_bstr_t(pRs->Fields->GetCollect("Name"))<< ", Age: " << (long)pRs->Fields->GetCollect("Age") << std::endl;pRs->MoveNext();}// 6. 执行插入命令(非查询)pCmd->CommandText = "INSERT INTO Users (Name, Age) VALUES (?, ?)";pCmd->Parameters->Clear(); // 清除原有参数pCmd->Parameters->Append(pCmd->CreateParameter("Name", adVarChar, adParamInput, 50, "赵六"));pCmd->Parameters->Append(pCmd->CreateParameter("Age", adInteger, adParamInput, sizeof(int), 35));_variant_t affected;pCmd->Execute(&affected, NULL, adCmdText);std::cout << "\n插入成功,受影响行数:" << (long)affected << std::endl;// 7. 释放资源pRs->Close();pCmd.Release();pConn->Close();} catch (_com_error& e) {std::cout << "错误: " << (const char*)e.Description() << std::endl;}CoUninitialize();return 0;
}
_RecordsetPtr
_RecordsetPtr
是 ADO(ActiveX Data Objects)中用于处理数据库查询结果集的智能指针类,封装了 ADO 的 Recordset
对象。它是 ADO 中最常用的组件之一,负责存储查询返回的数据,并提供对记录的遍历、修改、添加、删除等操作。
- 表示从数据库查询返回的结果集(类似表格数据,由行和列组成)。
- 提供游标(Cursor)功能,支持在结果集中移动(如首行、尾行、指定行)。
- 支持对记录的增删改操作,并能将修改提交到数据库。
- 可独立使用(直接执行查询)或配合
_CommandPtr
使用(执行命令后获取结果)。
CreateInstance()
_RecordsetPtr pRs;
HRESULT hr = pRs.CreateInstance(__uuidof(Recordset));
if (FAILED(hr)) {// 处理创建失败
}
open
HRESULT Open(const _variant_t& Source, // 数据源const _variant_t& ActiveConnection, // 连接对象enum CursorTypeEnum CursorType, // 游标类型enum LockTypeEnum LockType, // 锁定类型long Options // 命令执行选项
);
Source
指定要打开的记录集来源,常见类型:
- SQL 语句:如"SELECT * FROM HistoryRecord"
- 表名:如"HistoryRecord"(需配合adCmdTable选项)
- 存储过程名:如"sp_GetUserInfo"(需配合adCmdStoredProc选项)
- Command 对象:已创建的_CommandPtr对象
ActiveConnection(连接对象,可选)
指定记录集使用的数据库连接,有两种设置方式:
直接传入连接对象
_variant_t((IDispatch*)theApp.m_pConnection, TRUE)
传入连接字符串
_variant_t("Provider=SQLOLEDB;Data Source=.;Database=Test;Uid=sa;Pwd=123;")
pConn.GetInterfacePtr()
pRs->Open("SELECT * FROM Users", pConn.GetInterfacePtr(), // 已打开的_ConnectionPtradOpenDynamic, // 动态游标adLockOptimistic, // 乐观锁定adCmdText // 第一个参数是SQL语句
);
CursorType(游标类型,可选,默认adOpenForwardOnly)
决定记录集的遍历方式和功能,常用类型:
adOpenForwardOnly
:仅向前游标(默认),只能向前遍历记录,效率最高。adOpenStatic
:静态游标,创建记录的静态副本,可前后遍历,但不反映其他用户的修改。adOpenKeyset
:键集游标,可前后遍历,能反映其他用户的记录更新,但不显示新增记录。adOpenDynamic
:动态游标,实时反映所有用户对记录的增删改,功能最强但效率较低。
LockType(锁定类型,可选,默认adLockReadOnly)
控制记录集的编辑权限和锁定机制,常用类型:
adLockReadOnly
:只读(默认),不能修改记录。adLockPessimistic
:悲观锁定,编辑记录时立即锁定该记录,防止其他用户修改。adLockOptimistic
:乐观锁定,仅在调用Update时锁定记录,适合多用户环境。adLockBatchOptimistic
:批量乐观锁定,允许批量修改后统一提交(需配合UpdateBatch)。
Options(命令执行选项,可选,默认-1)
指定Source参数的类型,避免 ADODB 自动判断,提高效率并防止错误:
adCmdText
:Source是 SQL 命令文本(如"SELECT …")。adCmdTable
:Source是表名(如"HistoryRecord")。adCmdStoredProc
:Source是存储过程名。adCmdUnknown
:未知类型(ADODB 会自动判断,不推荐)。
遍历结果集
- adoEOF:布尔值,true 表示已到达结果集末尾(需配合 rename(“EOF”, “adoEOF”) 使用)。
MoveFirst()
:移动到第一条记录。MoveLast()
:移动到最后一条记录。MoveNext()
:移动到下一条记录。MovePrevious()
:移动到上一条记录。Move(n)
:移动 n 条记录(正数向前,负数向后)。
// 遍历所有记录
pRs->MoveFirst(); // 移动到首行
while (!pRs->adoEOF) {// 读取字段值(见下文“获取字段值”)_variant_t varID = pRs->Fields->GetItem("ID")->Value;_variant_t varName = pRs->Fields->GetItem("Name")->Value;std::cout << "ID: " << (long)varID << ", Name: " << (const char*)_bstr_t(varName) << std::endl;pRs->MoveNext(); // 移动到下一行
}
字段值
_RecordsetPtr::Fields
是记录集中所有字段(Field 对象)的集合。
FieldsPtr fds = rs->Fields; // 智能指针,自动释放
long n = fds->Count; // 字段个数for (long i = 0; i < n; ++i)
{FieldPtr fld = fds->GetItem(i); // 或 fds->Item[i]_bstr_t name = fld->Name; // 字段名DataTypeEnum type = fld->Type; // 数据类型long size = fld->DefinedSize; // 长度printf("%d %s %d\n", i, (char*)name, type);
}
rs->Fields->Item["Age"]->Value = _variant_t((short)30);
// 等价于:
rs->PutCollect("Age", _variant_t((short)30));
GetItem
// 通过字段名获取
_variant_t varID = pRs->Fields->GetItem("ID")->Value;
_variant_t varName = pRs->Fields->GetItem("Name")->Value;// 通过索引获取(0表示第一列)
_variant_t varAge = pRs->Fields->GetItem(2)->Value;// 转换为C++类型
long id = (long)varID; // 整数
std::string name = (const char*)_bstr_t(varName); // 字符串
int age = (long)varAge; // 整数
设置
pRs->Fields->GetItem("Name")->Value = _variant_t("新名称");
pRs->Fields->GetItem("Age")->Value = _variant_t((long)30);
GetCollect
_variant_t value = m_pRecordset->GetCollect("字段名");_variant_t vName = m_pRecordset->GetCollect("FirstName");
CString strName = (LPCTSTR)(_bstr_t)vName;
PutCollect
m_pRecordset->PutCollect("FirstName", _variant_t("John"));
比较
GetItem
是基础方法,获取字段对象后可做更多操作(如查看字段类型),但代码稍繁琐。GetCollect
和PutCollect
是快捷方法,直接操作字段值,代码更简洁,适合大多数仅需读写值的场景。
实际开发中,若仅需读写字段值,优先使用GetCollect
和PutCollect
;若需处理字段元数据,再使用GetItem
。
添加新记录:AddNew() + Update()
// 1. 开始添加新记录
pRs->AddNew();// 2. 设置字段值
pRs->Fields->GetItem("ID")->Value = _variant_t((long)3);
pRs->Fields->GetItem("Name")->Value = _variant_t("王五");
pRs->Fields->GetItem("Age")->Value = _variant_t((long)28);// 3. 提交到数据库
pRs->Update();
m_pRecordset->AddNew();
m_pRecordset->PutCollect("EmployeeID", _variant_t((long)10));
m_pRecordset->PutCollect("FirstName", _variant_t("Mary"));
m_pRecordset->PutCollect("LastName", _variant_t("Williams"));
m_pRecordset->Update();
修改现有记录:Edit() + Update()
// 1. 移动到要修改的记录
pRs->MoveFirst();// 2. 进入编辑模式
pRs->Edit();// 3. 修改字段值
pRs->Fields->GetItem("Age")->Value = _variant_t((long)29);// 4. 提交修改
pRs->Update();
批量更新
prs->CursorLocation = adUseClient; // 客户端游标
prs->Open("SELECT * FROM LogTable",conn.GetInterfacePtr(),adOpenStatic,adLockBatchOptimistic); // 批模式while (!prs->EndOfFile)
{prs->PutCollect("Status", _variant_t("Processed"));prs->MoveNext();
}
prs->UpdateBatch(adAffectAll); // 一次性提交
删除记录:Delete()
// 1. 移动到要删除的记录
pRs->MoveFirst();// 2. 删除当前记录
pRs->Delete(adAffectCurrent); // adAffectCurrent表示仅删除当前记录// 3. 移动游标(删除后需手动移动,否则可能出错)
pRs->MoveNext();
关闭结果集:Close()
if (pRs->State == adStateOpen) { // 检查是否打开pRs->Close();
}
pRs.Release(); // 释放智能指针
其他常用属性
RecordCount
:返回结果集中的记录总数(需配合OpenStatic
或adOpenKeyset
游标)。BOF
:布尔值,true 表示已定位到结果集开头之前。Fields->Count
:返回字段(列)的数量。AbsolutePosition
:获取或设置当前记录的绝对位置(从 1 开始)。
示例
#import "msado15.dll" no_namespace rename("EOF", "adoEOF")
#include <iostream>
#include <atlbase.h>int main() {CoInitialize(NULL);_ConnectionPtr pConn;_RecordsetPtr pRs;try {// 1. 连接数据库pConn.CreateInstance(__uuidof(Connection));pConn->Open("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\test.accdb;", "", "", adModeUnknown);// 2. 执行查询获取结果集pRs.CreateInstance(__uuidof(Recordset));pRs->Open("SELECT * FROM Users", pConn.GetInterfacePtr(), adOpenDynamic, adLockOptimistic, adCmdText);// 3. 遍历记录std::cout << "查询结果(共 " << pRs->RecordCount << " 条):" << std::endl;while (!pRs->adoEOF) {std::cout << "ID: " << (long)pRs->Fields->GetItem("ID")->Value<< ", Name: " << (const char*)_bstr_t(pRs->Fields->GetItem("Name")->Value)<< ", Age: " << (long)pRs->Fields->GetItem("Age")->Value << std::endl;pRs->MoveNext();}// 4. 添加新记录pRs->AddNew();pRs->Fields->GetItem("ID")->Value = _variant_t((long)3);pRs->Fields->GetItem("Name")->Value = _variant_t("王五");pRs->Fields->GetItem("Age")->Value = _variant_t((long)28);pRs->Update();std::cout << "\n添加新记录成功" << std::endl;// 5. 关闭资源pRs->Close();pConn->Close();} catch (_com_error& e) {std::cout << "错误: " << (const char*)e.Description() << std::endl;}pRs.Release();pConn.Release();CoUninitialize();return 0;
}