数据库操作丨C++ 操作 数据库——SQLServer 篇
一、背景
C++ Window 操作SQLServer的技术无法以下几种情况:
- ODBC
- OLE DB
- 数据库自己的接口库
- ADO
- 第三方库(底层也是ODBC、OLE DB)
1.1 名词解释
ODBC (Open Database Connectivity):这是一种非常标准和通用的数据库访问接口。
OLE DB (Object Linking and Embedding, Database):这是微软提出的一个更现代的、基于 COM (Component Object Model) 的数据访问接口。
ADO (ActiveX Data Objects):ADO 是构建在 OLE DB 之上的一个更高层次的封装。
SQL Server Native Client (SQLNCLI) / Microsoft OLE DB Driver for SQL Server (MSOLEDBSQL) / Microsoft ODBC Driver for SQL Server:这些是专门为 SQL Server 优化的高性能原生驱动库。
1.2 各个技术的现状和简介
技术 | 诞生背景 | 核心特点 | 当前状态 |
ODBC | 统一不同数据库的 C API | C 风格、跨数据库、高性能 | 依然是主流,有现代驱动支持 |
OLE DB | 统一所有数据源的访问 | COM、面向对象、功能强大但复杂 | 核心技术仍在使用,但仅推荐用于 SQL Server (MSOLEDBSQL) |
ADO | 简化 OLE DB 的使用 | COM、高级封装、简单易用 | 已过时 (Legacy),不推荐用于新项目 |
SQLNCLI | 快速支持 SQL Server 新特性 | 将 ODBC/OLE DB 驱动打包 | 已弃用 (Deprecated),必须迁移 |
1.3各个技术的演进图

1.4 官方建议
- 对于新的原生 C++ 项目,应使用现代的 Microsoft ODBC Driver for SQL Server 或 Microsoft OLE DB Driver for SQL Server (MSOLEDBSQL)。
- 对于维护现有的、已经在使用 ADO 的老项目,可以继续使用,但应意识到其功能限制。
二、OLE DB和ODBC 操作数据库
2.1 对比两个数据库操作方法
特性 | ODBC | OLE DB |
设计目标 | SQL 可移植性(跨关系型数据库) | 统一数据访问(跨所有数据源) |
API 风格 | C 语言,过程式,基于句柄 (Handle) | C++/COM,面向对象,基于接口 (Interface) |
数据源范围 | 仅限关系型数据库(或任何提供 ODBC 驱动的数据源) | 通用:关系型数据库、文件系统、Excel、邮件等 |
核心抽象 | 连接 (Connection)、语句 (Statement) | 消费者 (Consumer)、提供程序 (Provider)、会话 (Session)、行集 (Rowset) |
平台 | 跨平台(Windows, Linux, macOS) | 主要面向Windows(因其深度依赖 COM) |
复杂性 | 相对简单,学习曲线平缓 | 非常复杂,需要理解 COM 编程模型 |
性能 | 非常高。API 轻量,开销小,接近底层。 | 可能非常高。原生提供程序 (如 MSOLEDBSQL) 性能极佳。但通过桥接 (如访问 ODBC) 会有额外开销。 |
当前现状 | 非常活跃。行业标准,现代驱动持续更新。 | 基本是遗留技术,除了一个重要例外:用于 SQL Server 的原生驱动 (MSOLEDBSQL)依然被支持和推荐。 |
备注:
1、在 Windows 上为 SQL Server 的操作上,如果有极致的性能要求,建议选择 OLE DB,其余情况选择的ODBC就可以使用
2.2 ODBC 操作数据
2.2.1 环境配置
ODBC 驱动下载
下载 ODBC Driver for SQL Server - ODBC Driver for SQL Server | Microsoft Learn
ODBC 版本选择问题:注意!!!具体的最好查看一下官方的介绍,数据库连接的加密协议很重要!!!
2.2.2 怎么使用
ODBC 编写流程:
- 分配环境句柄: 调用SQLAllocHandle创建一个环境句柄 (SQLHENV)。这是所有操作的顶层容器。
- 设置环境属性: 调用SQLSetEnvAttr设置ODBC版本,这是必需的步骤。
- 分配连接句柄: 调用SQLAllocHandle创建一个连接句柄 (SQLHDBC)。
- 连接数据库: 调用SQLDriverConnect,传入一个连接字符串来建立连接。这比OLE DB的属性集设置要简单得多。
- 分配语句句柄: 调用SQLAllocHandle创建一个语句句柄 (SQLHSTMT)。所有SQL执行都与此句柄关联。
- 定义数据绑定 (列绑定): 这是ODBC性能的关键。
- 定义一个与OLE DB版本相同的C++ struct。
- (批量模式)**告诉ODBC我们将一次处理一批数据,通过SQLSetStmtAttr设置SQL_ATTR_ROW_ARRAY_SIZE。
- (批量模式)**通过SQLSetStmtAttr设置SQL_ATTR_ROW_BIND_TYPE为我们struct的大小。
- 为结果集的每一列调用SQLBindCol,将列直接绑定到struct数组的第一个元素的相应成员上。ODBC会自动处理后续元素的地址偏移。
- 执行SQL: 调用SQLExecDirect传入SQL语句。
- 抓取并处理数据: 调用SQLFetchScroll来获取下一批数据。由于我们设置了行数组绑定,这一步会自动将整批数据填充到我们的C++ struct数组中。
- 手动释放所有句柄: 与分配顺序相反,依次为语句、连接、环境句柄调用SQLFreeHandle。
#include <iostream>
#include <windows.h>
#include <sql.h> // ODBC 核心头文件
#include <sqlext.h> // ODBC 扩展头文件
#include <vector>
#include <string>
#include <stdexcept>
#include <iomanip>// 宏,用于检查ODBC函数调用的返回值。如果失败,则打印诊断信息并抛出异常。
void HandleDiagnosticRecord(SQLHANDLE hHandle, SQLSMALLINT hType, RETCODE RetCode);
#define CHECK_ODBC(ret, hHandle, hType) { if (!SQL_SUCCEEDED(ret)) { HandleDiagnosticRecord(hHandle, hType, ret); throw std::runtime_error("ODBC call failed. Check console for details."); } }// 1. 定义一个与OLE DB版本完全相同的C++结构体来接收数据
struct ProductDataODBC {SQLINTEGER productID;SQLWCHAR name[51]; // nvarchar(50) -> WCHAR[51]double listPrice; // money -> double// ODBC绑定也需要为每个可变长度或可为NULL的字段提供一个长度/状态指示器SQLLEN name_len_or_ind;SQLLEN listPrice_len_or_ind;
};void RunOdbcDemo() {std::wcout << L"\n\n--- Starting ODBC DEMO ---\n" << std::endl;// --- ODBC 句柄:所有ODBC操作都围绕这些句柄进行 ---SQLHENV hEnv = NULL;SQLHDBC hDbc = NULL;SQLHSTMT hStmt = NULL;RETCODE ret; // 用于接收ODBC函数调用的返回码try {// --- 流程 1 & 2: 分配并设置环境句柄 ---ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv); CHECK_ODBC(ret, hEnv, SQL_HANDLE_ENV);// 必须设置ODBC版本为3.xret = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0); CHECK_ODBC(ret, hEnv, SQL_HANDLE_ENV);std::wcout << L"1. ODBC Environment Handle created and configured." << std::endl;// --- 流程 3 & 4: 分配连接句柄并连接到数据库 ---ret = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc); CHECK_ODBC(ret, hDbc, SQL_HANDLE_DBC);// 使用连接字符串,这比OLE DB的属性集更直观SQLWCHAR connStrIn[] = L"DRIVER={ODBC Driver 17 for SQL Server};SERVER=YourServerName;DATABASE=AdventureWorks2019;UID=YourUsername;PWD=YourPassword;"; // !!! 替换为你的信息ret = SQLDriverConnectW(hDbc, NULL, connStrIn, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);CHECK_ODBC(ret, hDbc, SQL_HANDLE_DBC);std::wcout << L"2. Connection successful via ODBC Driver." << std::endl;// --- 流程 5: 分配语句句柄 ---ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);std::wcout << L"3. Statement Handle created." << std::endl;// --- 流程 6: 数据绑定 (在执行前进行批量绑定) ---// 这是ODBC高性能的关键:将结果集列直接绑定到一个C++结构体数组const int BATCH_SIZE = 10; // 我们要一次性获取10行std::vector<ProductDataODBC> productBatch(BATCH_SIZE);// 告诉ODBC我们将使用行优先的绑定,并一次处理BATCH_SIZE行ret = SQLSetStmtAttr(hStmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)sizeof(ProductDataODBC), 0); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);ret = SQLSetStmtAttr(hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)BATCH_SIZE, 0); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);// 告诉ODBC将获取到的行数存入这个变量SQLULEN numRowsFetched = 0;ret = SQLSetStmtAttr(hStmt, SQL_ATTR_ROWS_FETCHED_PTR, &numRowsFetched, 0); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);// 将结果集列绑定到结构体数组的第一个元素的对应成员// ODBC会自动处理后续元素的内存偏移ret = SQLBindCol(hStmt, 1, SQL_C_LONG, &productBatch[0].productID, sizeof(SQLINTEGER), &productBatch[0].name_len_or_ind); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);ret = SQLBindCol(hStmt, 2, SQL_C_WCHAR, &productBatch[0].name, sizeof(productBatch[0].name), &productBatch[0].name_len_or_ind); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);ret = SQLBindCol(hStmt, 3, SQL_C_DOUBLE, &productBatch[0].listPrice, sizeof(double), &productBatch[0].listPrice_len_or_ind); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);std::wcout << L"4. Column binding for batch fetching completed." << std::endl;// --- 流程 7: 执行SQL ---SQLWCHAR* wcsSQL = (SQLWCHAR*)L"SELECT TOP 10 ProductID, Name, ListPrice FROM Production.Product ORDER BY ProductID";ret = SQLExecDirectW(hStmt, wcsSQL, SQL_NTS); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);std::wcout << L"5. SQL command executed." << std::endl;// --- 流程 8: 批量抓取数据 ---// 关键的一步:一次调用就将所有10行数据填充到我们的vector中ret = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0);if (ret == SQL_NO_DATA) {std::wcout << L"No data found." << std::endl;} else {CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);std::wcout << L"6. Batch fetch successful. " << numRowsFetched << L" rows obtained in one call." << std::endl;// --- 处理已直接写入到vector中的数据 ---std::wcout << L"\n--- Fetched Data (ODBC) ---" << std::endl;for (ULONG i = 0; i < numRowsFetched; ++i) {std::wcout << L" ID: " << std::setw(4) << productBatch[i].productID<< L", Name: " << std::left << std::setw(30) << (productBatch[i].name_len_or_ind == SQL_NULL_DATA ? L"NULL" : productBatch[i].name) << std::right<< L", Price: " << std::fixed << std::setprecision(2) << (productBatch[i].listPrice_len_or_ind == SQL_NULL_DATA ? -1.0 : productBatch[i].listPrice)<< std::endl;}}}catch (const std::exception& ex) {std::cerr << "An exception occurred in ODBC demo: " << ex.what() << std::endl;}// --- 流程 9: 清理所有句柄 (顺序与分配相反) ---std::wcout << L"\n--- Cleaning up ODBC resources ---" << std::endl;if (hStmt) SQLFreeHandle(SQL_HANDLE_STMT, hStmt);if (hDbc) {SQLDisconnect(hDbc);SQLFreeHandle(SQL_HANDLE_DBC, hDbc);}if (hEnv) SQLFreeHandle(SQL_HANDLE_ENV, hEnv);std::wcout << L"All ODBC resources released." << std::endl;
}// ODBC错误处理函数的实现
void HandleDiagnosticRecord(SQLHANDLE hHandle, SQLSMALLINT hType, RETCODE RetCode) {SQLSMALLINT i = 0;SQLINTEGER native;SQLWCHAR state[7];SQLWCHAR text[256];SQLSMALLINT len;while (SQLGetDiagRec(hType, hHandle, ++i, state, &native, text, sizeof(text)/sizeof(SQLWCHAR), &len) == SQL_SUCCESS) {std::wcerr << L"[ODBC Error] STATE: " << state << L", NATIVE: " << native << L", MSG: " << text << std::endl;}
}// 主函数入口,可以用来依次调用OLE DB和ODBC的DEMO
int main() {// 您可以将之前的OLE DB代码封装成一个 RunOledbDemo() 函数// RunOledbDemo(); RunOdbcDemo();return 0;
}
2.2.3 完整代码
#include "OdbcWrapper.h"
//==============================================================================
// OdbcWrapper Class Implementation
//==============================================================================OdbcWrapper::OdbcWrapper() :m_hEnv(SQL_NULL_HANDLE),m_hDbc(SQL_NULL_HANDLE),m_isConnected(false)
{// 构造函数中分配环境句柄并设置ODBC版本if (SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_hEnv))) {if (!SQL_SUCCEEDED(SQLSetEnvAttr(m_hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0))) {SetError(SQL_HANDLE_ENV, m_hEnv, L"Failed to set ODBC version.");SafeFreeHandle(m_hEnv, SQL_HANDLE_ENV);}}else {m_lastError = L"Failed to allocate environment handle.";}
}OdbcWrapper::~OdbcWrapper() {Disconnect();SafeFreeHandle(m_hEnv, SQL_HANDLE_ENV); // 确保环境句柄在最后被释放
}void OdbcWrapper::Disconnect() {if (m_hDbc != SQL_NULL_HANDLE) {if (m_isConnected) {SQLDisconnect(m_hDbc);}SafeFreeHandle(m_hDbc, SQL_HANDLE_DBC);}m_isConnected = false;
}bool OdbcWrapper::Connect(const std::wstring& connectionString) {if (m_hEnv == SQL_NULL_HANDLE) return false;Disconnect(); // 先断开旧连接m_lastError.clear();SQLRETURN ret;// 分配连接句柄ret = SQLAllocHandle(SQL_HANDLE_DBC, m_hEnv, &m_hDbc);if (!SQL_SUCCEEDED(ret)) {SetError(SQL_HANDLE_ENV, m_hEnv, L"Failed to allocate connection handle.");return false;}// 使用连接字符串进行连接std::vector<wchar_t> connStrBuf(connectionString.begin(), connectionString.end());connStrBuf.push_back(L'\0');ret = SQLDriverConnect(m_hDbc, NULL, connStrBuf.data(), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);if (!SQL_SUCCEEDED(ret)) {SetError(SQL_HANDLE_DBC, m_hDbc, L"SQLDriverConnect failed.");Disconnect(); // 失败后清理return false;}m_isConnected = true;return true;
}bool OdbcWrapper::ExecuteQuery(const std::wstring& sql, std::vector<std::map<std::wstring, std::wstring>>& results) {results.clear();m_lastError.clear();if (!m_isConnected) {m_lastError = L"Not connected to a database.";return false;}SQLHSTMT hStmt = SQL_NULL_HANDLE; // 语句句柄是局部的SQLRETURN ret;// --- 步骤 1: 分配语句句柄 ---ret = SQLAllocHandle(SQL_HANDLE_STMT, m_hDbc, &hStmt);if (!SQL_SUCCEEDED(ret)) {SetError(SQL_HANDLE_DBC, m_hDbc, L"Failed to allocate statement handle.");return false;}// --- 步骤 2: 执行 SQL ---// SQLExecDirect可以直接执行SQL语句ret = SQLExecDirect(hStmt, (SQLWCHAR*)sql.c_str(), SQL_NTS);if (!SQL_SUCCEEDED(ret)) {SetError(SQL_HANDLE_STMT, hStmt, L"SQLExecDirect failed.");SafeFreeHandle(hStmt, SQL_HANDLE_STMT);return false;}// --- 步骤 3: 获取列信息 ---SQLSMALLINT nCols = 0;ret = SQLNumResultCols(hStmt, &nCols);if (!SQL_SUCCEEDED(ret) || nCols <= 0) {// 这不是一个错误,可能是一个没有返回结果的 DML 语句SafeFreeHandle(hStmt, SQL_HANDLE_STMT);return true;}std::vector<std::wstring> colNames(nCols);std::vector<std::vector<wchar_t>> colData(nCols);std::vector<SQLLEN> colLen(nCols);for (SQLSMALLINT i = 0; i < nCols; ++i) {wchar_t colNameBuf[256];SQLSMALLINT nameLen;SQLSMALLINT dataType;SQLULEN colSize;SQLSMALLINT decimalDigits;SQLSMALLINT nullable;SQLDescribeCol(hStmt, i + 1, colNameBuf, sizeof(colNameBuf) / sizeof(wchar_t),&nameLen, &dataType, &colSize, &decimalDigits, &nullable);colNames[i] = colNameBuf;// 为数据绑定分配缓冲区,加1用于空终止符colData[i].resize(4097); // 分配一个足够大的缓冲区 (例如4KB)SQLBindCol(hStmt, i + 1, SQL_C_WCHAR, colData[i].data(), colData[i].size() * sizeof(wchar_t), &colLen[i]);}// --- 步骤 4: 循环获取行数据 ---while (SQL_SUCCEEDED(SQLFetch(hStmt))) {std::map<std::wstring, std::wstring> row;for (SQLSMALLINT i = 0; i < nCols; ++i) {if (colLen[i] == SQL_NULL_DATA) {row[colNames[i]] = L"(NULL)";}else {row[colNames[i]] = colData[i].data();}}results.push_back(row);}// --- 步骤 5: 清理 ---SafeFreeHandle(hStmt, SQL_HANDLE_STMT);return true;
}std::wstring OdbcWrapper::GetLastError() const {return m_lastError;
}void OdbcWrapper::SetError(SQLSMALLINT handleType, SQLHANDLE handle, const std::wstring& message) {wchar_t sqlState[6];SQLINTEGER nativeError;wchar_t msg[SQL_MAX_MESSAGE_LENGTH];SQLSMALLINT msgLen;std::wstring errorDetails;SQLSMALLINT i = 1;while (SQL_SUCCEEDED(SQLGetDiagRec(handleType, handle, i, sqlState, &nativeError, msg, sizeof(msg) / sizeof(wchar_t), &msgLen))) {wchar_t buffer[2048];swprintf_s(buffer, L"\n - SQLSTATE: %s, Native Error: %d, Message: %s", sqlState, nativeError, msg);errorDetails += buffer;i++;}m_lastError = message + errorDetails;
}
2.3 OLE DB操作数据
2.3.1 环境配置
OLE DB驱动下载:
下载适用于 SQL Server 的 Microsoft OLE DB 驱动程序 - OLE DB Driver for SQL Server | Microsoft Learn
OLE DB 版本选择问题:注意!!!具体的最好查看一下官方的介绍,数据库连接的加密协议很重要!!!

- COM 环境: 初始化 (CoInitialize)。
- 创建数据源对象: 使用CoCreateInstance创建驱动实例 (e.g., CLSID_MSOLEDBSQL),获取IDBInitialize接口。
- 设置连接属性: 创建一个DBPROPSET数组,用DBPROP结构体填充服务器、数据库、用户名、密码等信息。
- 连接: 调用IDBInitialize::Initialize建立连接。
- 创建会话与命令: 从连接对象获取IDBCreateSession,然后创建IDBCreateCommand。
- 设置并执行SQL: 从命令对象获取ICommandText接口,设置SQL语句,然后调用ICommand::Execute,返回一个行集接口IRowset。
- 定义数据绑定 (创建访问器): 这是最复杂的部分。a. 定义一个C++ struct来映射一行数据。b. 创建一个DBBINDING结构体数组,将结果集的每一列精确地映射到C++ struct的成员变量的内存地址、类型和长度。c. 从行集对象获取IAccessor接口,调用CreateAccessor方法,用绑定信息创建一个高效的访问器句柄HACCESSOR。
- 抓取并处理数据: 循环调用IRowset::GetNextRows获取行句柄HROW,然后调用IRowset::GetData并传入访问器句柄,数据会被直接写入你之前绑定的C++ struct中。
- 手动释放所有资源: 必须为每一个获取到的COM接口指针手动调用Release()方法,并为访问器句柄调用ReleaseAccessor。
2.3.2 数据库的连接
流图

关键函数说明:
- **CoInitialize(nullptr)**
- 初始化 COM 库,OLE DB 依赖此环境 。
- **CoCreateInstance(CLSID_MSOLEDBSQL, ...)**
- 创建 OLE DB 数据源对象(IDBInitialize),优先选择现代驱动 。
- **IDBProperties::SetProperties(rgPropSets)**
- 配置连接属性(服务器地址、数据库名、账号密码等)。
- **IDBInitialize::Initialize()**
- 触发实际数据库连接和身份验证,失败返回 0x80040E4D 需重点检查。这个错误可能是版本问题,以及 guidPropertySet属性问题
配置属性说明
属性名 | 数据类型 | 所属属性集 | 作用 | 推荐值 | 必填 |
DBPROP_INIT_DATASOURCE | VT_BSTR | DBPROPSET_DBINIT | 服务器地址(IP 或主机名) | 127.0.0.1 | 是 |
DBPROP_INIT_CATALOG | VT_BSTR | DBPROPSET_DBINIT | 目标数据库名 | master | 是 |
DBPROP_AUTH_INTEGRATED | VT_BSTR | DBPROPSET_DBINIT | Windows 身份验证(值固定为 "SSPI") | SSPI | 可选 |
DBPROP_AUTH_USERID | VT_BSTR | DBPROPSET_DBINIT | SQL 登录账号 | sa | 可选 |
DBPROP_AUTH_PASSWORD | VT_BSTR | DBPROPSET_DBINIT | SQL 登录密码 | your_password | 可选 |
SSPROP_INIT_ENCRYPT | VT_BOOL | DBPROPSET_SQLSERVERDBINIT | 启用 TLS 加密(生产环境必备) | VARIANT_TRUE | 建议启用 |
SSPROP_INIT_TRUST_SERVER_CERTIFICATE | VT_BOOL | DBPROPSET_SQLSERVERDBINIT | 信任自签名证书(测试环境适用) | VARIANT_TRUE(测试) | 可选 |
DBPROP_MULTIPLECONNECTIONS | VT_BOOL | DBPROPSET_DATASOURCE | 是否允许多连接并行操作(默认单连接阻塞) | VARIANT_FALSE | 否 |
DBPROP_CURRENTCATALOG | VT_BSTR | DBPROPSET_DATASOURCE | 获取或设置当前数据库(等效 USE database) | 动态设置 | 否 |
#ifdef OLE_DB_19hr = CoCreateInstance(CLSID_MSOLEDBSQL19, // Microsoft OLE DB Driver for SQL Servernullptr,CLSCTX_INPROC_SERVER,IID_IDBInitialize, // 直接请求IDBInitialize接口(void**)&m_pIDBInitialize);
#elsehr = CoCreateInstance(CLSID_MSOLEDBSQL, // Microsoft OLE DB Driver for SQL Servernullptr,CLSCTX_INPROC_SERVER,IID_IDBInitialize, // 直接请求IDBInitialize接口(void**)&m_pIDBInitialize);
#endif // #ifdef OLE_DB_19if (FAILED(hr)) {//异常处理}std::vector<DBPROP> rgProps;DBPROP prop;/// addPropBool addProp 的匿名函数代码.... addProp(DBPROP_INIT_DATASOURCE, server);addProp(DBPROP_INIT_CATALOG, database);if (useTrustedConnection) {addProp(DBPROP_AUTH_INTEGRATED, L"SSPI");}else {addProp(DBPROP_AUTH_USERID, user);addProp(DBPROP_AUTH_PASSWORD, password);}// 禁用加密addPropBool(SSPROP_INIT_ENCRYPT, VARIANT_FALSE);// 或者使用信任服务器证书(如果启用加密)addPropBool(SSPROP_INIT_TRUST_SERVER_CERTIFICATE, VARIANT_TRUE);// 4. 设置连接属性IDBProperties* pIDBProperties = nullptr;hr = m_pIDBInitialize->QueryInterface(IID_IDBProperties, (void**)&pIDBProperties);if (FAILED(hr)) {//异常处理}DBPROPSET rgPropSets[1] = {};rgPropSets[0].guidPropertySet = /*DBPROPSET_SQLSERVERDBINIT*/DBPROPSET_DBINIT;rgPropSets[0].cProperties = (ULONG)rgProps.size();rgPropSets[0].rgProperties = &rgProps[0];hr = pIDBProperties->SetProperties(1, rgPropSets);for (auto& p : rgProps) {VariantClear(&p.vValue);}if (FAILED(hr)) {//异常处理}hr = pIDBProperties->QueryInterface(IID_IDBInitialize, (void**)&m_pIDBInitialize);SafeRelease(pIDBProperties);if (FAILED(hr)) {//异常处理}hr = m_pIDBInitialize->Initialize();if (FAILED(hr)) {//异常处理}
2.3.3 数据库的查询

键函数说明:
- **IDBCreateSession::CreateSession(IID_IUnknown, &pSession)**
- 创建会话对象,用于后续命令操作 。
- **IDBCreateCommand::CreateCommand(IID_ICommandText, &pICommandText)**
- 生成命令对象,支持 SQL 语句执行 。
- **ICommandText::SetCommandText(DBGUID_DBSQL, query)**
- 绑定 SQL 查询文本(如 SELECT * FROM sys.databases)。
- **ICommandText::Execute(..., IID_IRowset, &pRowset)**
- 执行查询并返回结果集句柄 IRowset 。
- **IColumnsInfo::GetColumnInfo(&numColumns, &pColumnInfo)**
- 获取列元数据(名称、类型、长度),用于动态数据绑定 。
- **IAccessor::CreateAccessor(DBBINDING[...], &hAccessor)**
- 创建内存访问器,定义列数据解析规则(当前代码硬编码为 DBTYPE_STR,需优化)。
- **IRowset::GetNextRows(NULL, 0, 1, &cRowsObtained, &pRows)**
- 逐行获取数据,单次仅取 1 行(建议改为批量获取提升性能)。
- **IRowset::GetData(hRow, hAccessor, pData)**
- 按绑定规则解析行数据,需配合 MultiByteToWideChar 转换编码
// 1. 创建会话hr = m_pIDBInitialize->QueryInterface(IID_IDBCreateSession, (void**)&pIDBCreateSession);if (FAILED(hr)) {// 异常操作}// 2. 创建会话对象IUnknown* pSession = nullptr;hr = pIDBCreateSession->CreateSession(nullptr, IID_IUnknown, &pSession);if (FAILED(hr)) {// 异常操作}// 3. 创建命令对象IDBCreateCommand* pIDBCreateCommand = nullptr;hr = pSession->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand);SafeRelease(pSession);if (FAILED(hr)) {// 异常操作}// 4. 创建命令文本对象ICommandText* pICommandText = nullptr;hr = pIDBCreateCommand->CreateCommand(nullptr, IID_ICommandText, (IUnknown**)&pICommandText);SafeRelease(pIDBCreateCommand);if (FAILED(hr)) {// 异常操作}if (SUCCEEDED(hr = pICommandText->SetCommandText(DBGUID_DBSQL, sql.c_str()))) {hr = pICommandText->Execute(nullptr, IID_IRowset, nullptr, nullptr, (IUnknown**)&pIRowset);}if (FAILED(hr)) {// 异常操作}//其他代码if (SUCCEEDED(pIRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo))) {if (SUCCEEDED(pIColumnsInfo->GetColumnInfo(&nCols, &pColInfo, &pStringsBuffer))) {for (DBORDINAL i = 0; i < nCols; ++i) {colNames.push_back(pColInfo[i].pwszName ? pColInfo[i].pwszName : L"");}}}
// 以下就是获取所有数据的代码
2.3.4 完整DEMO代码
#include <iostream>
#include <windows.h>
#include <comdef.h>
#include <io.h>
#include <fcntl.h>
#include <vector>#include <oledb.h>
#include <msoledbsql.h>#define SafeRelease(p) { if(p) { (p)->Release(); (p) = nullptr; } }// 打印详细错误信息
void PrintDetailedError(const wchar_t* message, HRESULT hr) {_com_error err(hr);std::wcerr << L"--- ERROR ---" << std::endl;std::wcerr << L"Location: " << message << std::endl;std::wcerr << L"HRESULT: 0x" << std::hex << hr << L" (" << err.ErrorMessage() << L")" << std::endl;IErrorInfo* pErrorInfo = nullptr;if (SUCCEEDED(GetErrorInfo(0, &pErrorInfo)) && pErrorInfo != nullptr) {BSTR bstrDesc = nullptr;pErrorInfo->GetDescription(&bstrDesc);if (bstrDesc) {std::wcerr << L"OLE DB Description: " << bstrDesc << std::endl;SysFreeString(bstrDesc);}SafeRelease(pErrorInfo);}// 尝试获取OLE DB特定错误信息IErrorRecords* pErrorRecords = nullptr;if (SUCCEEDED(GetErrorInfo(0, IID_IErrorRecords, (IUnknown**)&pErrorRecords) && pErrorRecords) {ULONG cRecords = 0;pErrorRecords->GetRecordCount(&cRecords);for (ULONG i = 0; i < cRecords; i++) {ERRORINFO errorInfo;IErrorInfo* pRecordErrorInfo = nullptr;if (SUCCEEDED(pErrorRecords->GetBasicErrorInfo(i, &errorInfo)) {std::wcerr << L"OLE DB Error: " << errorInfo.dwMinor << L" - " << errorInfo.iid << std::endl;}if (SUCCEEDED(pErrorRecords->GetErrorInfo(i, 0, &pRecordErrorInfo)) && pRecordErrorInfo) {BSTR bstrDesc = nullptr;pRecordErrorInfo->GetDescription(&bstrDesc);if (bstrDesc) {std::wcerr << L"Error Details: " << bstrDesc << std::endl;SysFreeString(bstrDesc);}SafeRelease(pRecordErrorInfo);}}SafeRelease(pErrorRecords);}std::wcerr << L"-------------" << std::endl;
}// 执行SQL查询并显示结果
HRESULT ExecuteSQLQuery(IUnknown* pDataSource, const wchar_t* query) {HRESULT hr;// 1. 创建会话IDBCreateSession* pIDBCreateSession = nullptr;hr = pDataSource->QueryInterface(IID_IDBCreateSession, (void**)&pIDBCreateSession);if (FAILED(hr)) {PrintDetailedError(L"QueryInterface for IDBCreateSession", hr);return hr;}std::wcout << L"Step 7: IDBCreateSession obtained." << std::endl;// 2. 创建会话对象IUnknown* pSession = nullptr;hr = pIDBCreateSession->CreateSession(nullptr, IID_IUnknown, &pSession);SafeRelease(pIDBCreateSession);if (FAILED(hr)) {PrintDetailedError(L"CreateSession", hr);return hr;}std::wcout << L"Step 8: Session created." << std::endl;// 3. 创建命令对象IDBCreateCommand* pIDBCreateCommand = nullptr;hr = pSession->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand);SafeRelease(pSession);if (FAILED(hr)) {PrintDetailedError(L"QueryInterface for IDBCreateCommand", hr);return hr;}std::wcout << L"Step 9: IDBCreateCommand obtained." << std::endl;// 4. 创建命令文本对象ICommandText* pICommandText = nullptr;hr = pIDBCreateCommand->CreateCommand(nullptr, IID_ICommandText, (IUnknown**)&pICommandText);SafeRelease(pIDBCreateCommand);if (FAILED(hr)) {PrintDetailedError(L"CreateCommand", hr);return hr;}std::wcout << L"Step 10: Command object created." << std::endl;// 5. 设置SQL命令文本hr = pICommandText->SetCommandText(DBGUID_DBSQL, query);if (FAILED(hr)) {PrintDetailedError(L"SetCommandText", hr);SafeRelease(pICommandText);return hr;}std::wcout << L"Step 11: Query set: \"" << query << L"\"" << std::endl;// 6. 执行查询IRowset* pRowset = nullptr;hr = pICommandText->Execute(nullptr, IID_IRowset, nullptr, nullptr, (IUnknown**)&pRowset);SafeRelease(pICommandText);if (FAILED(hr)) {PrintDetailedError(L"Execute query", hr);return hr;}std::wcout << L"Step 12: Query executed successfully." << std::endl;// 7. 获取列信息DBCOLUMNINFO* pColumnInfo = nullptr;IColumnsInfo* pColumnsInfo = nullptr;OLECHAR* pColumnNames = nullptr;ULONG numColumns = 0;hr = pRowset->QueryInterface(IID_IColumnsInfo, (void**)&pColumnsInfo);if (SUCCEEDED(hr)) {hr = pColumnsInfo->GetColumnInfo(&numColumns, &pColumnInfo, &pColumnNames);if (FAILED(hr)) {numColumns = 0;}}// 8. 显示列名if (numColumns > 0) {std::wcout << L"\nQuery Results:\n";std::wcout << L"----------------------------------------\n";std::wcout << L"| ";for (ULONG i = 0; i < numColumns; i++) {std::wcout << pColumnInfo[i].pwszName << L" | ";}std::wcout << L"\n----------------------------------------\n";}// 9. 获取行数据HACCESSOR hAccessor = NULL;IAccessor* pAccessor = nullptr;hr = pRowset->QueryInterface(IID_IAccessor, (void**)&pAccessor);if (SUCCEEDED(hr) && numColumns > 0) {DBBINDING* pBindings = new DBBINDING[numColumns];DBLENGTH rowSize = 0;// 为每列创建绑定for (ULONG i = 0; i < numColumns; i++) {pBindings[i].iOrdinal = i + 1;pBindings[i].obValue = rowSize;pBindings[i].obLength = rowSize + sizeof(DBSTATUS);pBindings[i].obStatus = rowSize + sizeof(DBSTATUS) + sizeof(ULONG);pBindings[i].pTypeInfo = nullptr;pBindings[i].pObject = nullptr;pBindings[i].pBindExt = nullptr;pBindings[i].dwPart = DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS;pBindings[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;pBindings[i].eParamIO = DBPARAMIO_NOTPARAM;pBindings[i].cbMaxLen = 255; // 最大长度pBindings[i].dwFlags = 0;pBindings[i].wType = DBTYPE_STR;pBindings[i].bPrecision = pColumnInfo[i].bPrecision;pBindings[i].bScale = pColumnInfo[i].bScale;rowSize += sizeof(DBSTATUS) + sizeof(ULONG) + pBindings[i].cbMaxLen;rowSize = (rowSize + 7) & ~7; // 8字节对齐}// 创建访问器hr = pAccessor->CreateAccessor(DBACCESSOR_ROWDATA, numColumns, pBindings, rowSize, &hAccessor, nullptr);if (SUCCEEDED(hr)) {BYTE* pData = new BYTE[rowSize];HROW hRow = NULL;HROW* pRows = &hRow;// 获取行句柄while (SUCCEEDED(pRowset->GetNextRows(NULL, 0, 1, &cRowsObtained, &pRows)) && cRowsObtained > 0) {// 获取行数据hr = pRowset->GetData(hRow, hAccessor, pData);if (SUCCEEDED(hr)) {std::wcout << L"| ";for (ULONG i = 0; i < numColumns; i++) {DBBINDING* pBind = &pBindings[i];DBSTATUS status = *(DBSTATUS*)(pData + pBind->obStatus);if (status == DBSTATUS_S_OK) {char* pValue = (char*)(pData + pBind->obValue);ULONG len = *(ULONG*)(pData + pBind->obLength);// 转换为宽字符串输出wchar_t wcValue[256] = {0};MultiByteToWideChar(CP_ACP, 0, pValue, len, wcValue, 255);std::wcout << wcValue << L" | ";} else {std::wcout << L"NULL | ";}}std::wcout << std::endl;}pRowset->ReleaseRows(1, pRows, nullptr, nullptr, nullptr);}delete[] pData;pAccessor->ReleaseAccessor(hAccessor, nullptr);}delete[] pBindings;}// 清理资源if (pColumnInfo) CoTaskMemFree(pColumnInfo);if (pColumnNames) CoTaskMemFree(pColumnNames);SafeRelease(pColumnsInfo);SafeRelease(pAccessor);SafeRelease(pRowset);std::wcout << L"\nQuery completed." << std::endl;return S_OK;
}int main() {// 设置控制台输出为Unicode模式_setmode(_fileno(stdout), _O_U16TEXT);_setmode(_fileno(stderr), _O_U16TEXT);HRESULT hr;// 1. 初始化COM环境hr = CoInitialize(nullptr);if (FAILED(hr)) {std::wcerr << L"CoInitialize failed." << std::endl;return -1;}std::wcout << L"Step 1: CoInitialize OK." << std::endl;// 数据库连接参数const wchar_t* server = L"127.0.0.1";const wchar_t* database = L"master";const wchar_t* user = L"sa";const wchar_t* password = L"your_password";const wchar_t* query = L"SELECT name, database_id, create_date FROM sys.databases";// 2. 准备连接属性std::vector<DBPROP> rgProps;DBPROP prop;// Lambda函数:添加字符串类型的属性auto addPropString = [&](DWORD dwPropertyID, const std::wstring& value) {VariantInit(&prop.vValue);prop.dwPropertyID = dwPropertyID;prop.dwOptions = DBPROPOPTIONS_REQUIRED;prop.colid = DB_NULLID;prop.vValue.vt = VT_BSTR;prop.vValue.bstrVal = SysAllocString(value.c_str());rgProps.push_back(prop);};// Lambda函数:添加布尔类型的属性auto addPropBool = [&](DWORD dwPropertyID, VARIANT_BOOL value) {VariantInit(&prop.vValue);prop.dwPropertyID = dwPropertyID;prop.dwOptions = DBPROPOPTIONS_REQUIRED;prop.colid = DB_NULLID;prop.vValue.vt = VT_BOOL;prop.vValue.boolVal = value;rgProps.push_back(prop);};// 添加核心连接属性addPropString(DBPROP_INIT_DATASOURCE, server); // 服务器地址addPropString(DBPROP_INIT_CATALOG, database); // 数据库名称addPropString(DBPROP_AUTH_USERID, user); // 用户名addPropString(DBPROP_AUTH_PASSWORD, password); // 密码// 添加加密选项addPropBool(SSPROP_INIT_ENCRYPT, VARIANT_FALSE); // 禁用加密// 配置属性集DBPROPSET rgPropSets[1] = {};rgPropSets[0].guidPropertySet = DBPROPSET_DBINIT; // 使用标准属性集rgPropSets[0].cProperties = (ULONG)rgProps.size();rgPropSets[0].rgProperties = &rgProps[0];std::wcout << L"Step 2: Properties prepared." << std::endl;// 3. 创建OLE DB数据源对象IDBInitialize* pIDBInitialize = nullptr;hr = CoCreateInstance(CLSID_MSOLEDBSQL, // Microsoft OLE DB Driver for SQL Servernullptr,CLSCTX_INPROC_SERVER,IID_IDBInitialize, // 直接请求IDBInitialize接口(void**)&pIDBInitialize);if (FAILED(hr)) {PrintDetailedError(L"CoCreateInstance", hr);std::wcout << L"Trying legacy SQLOLEDB driver..." << std::endl;return -1;} else {std::wcout << L"Using MSOLEDBSQL driver" << std::endl;}// 4. 设置连接属性IDBProperties* pIDBProperties = nullptr;hr = pIDBInitialize->QueryInterface(IID_IDBProperties, (void**)&pIDBProperties);if (SUCCEEDED(hr)) {hr = pIDBProperties->SetProperties(1, rgPropSets);SafeRelease(pIDBProperties);}// 清理属性内存for (auto& p : rgProps) VariantClear(&p.vValue);if (FAILED(hr)) {PrintDetailedError(L"SetProperties", hr);SafeRelease(pIDBInitialize);CoUninitialize();return -1;}std::wcout << L"Step 3: SetProperties OK." << std::endl;// 5. 初始化连接std::wcout << L"Step 4: Initializing connection..." << std::endl;hr = pIDBInitialize->Initialize();if (FAILED(hr)) {PrintDetailedError(L"Initialize", hr);SafeRelease(pIDBInitialize);CoUninitialize();return -1;}std::wcout << L"Step 5: CONNECTION SUCCEEDED!" << std::endl;// 6. 执行SQL查询std::wcout << L"\nStep 6: Executing SQL query..." << std::endl;HRESULT queryResult = ExecuteSQLQuery(pIDBInitialize, query);if (FAILED(queryResult)) {std::wcerr << L"\nQuery execution failed. See error details above." << std::endl;}// 7. 清理资源if (pIDBInitialize) {pIDBInitialize->Uninitialize();SafeRelease(pIDBInitialize);}CoUninitialize();return 0;
}
2.4 测试结论
8337 行数据 ODBC 21108 ms OLE DB 20734 ms
耗时时间基本都是一个比较稳定,差异不是特别大,如果选择的优化 还是建议优先选择ODBC,OLE DB的使用更复杂度和陷阱更多。
ODBC代码 AI一搜基本都能获取到正确的代码,而OLE DB AI写的不一定正确,注意!!!,所以ODBC 写的比较简洁
三、ADO(新项目不推荐用) 历史遗留项目

就不写DEMO了,这个底层就是OLE DB和ODBC,意味着ADO可以切换不同底层,故要注意底层选择的驱动,
可以使用 UDL文件验证有哪些可以使用的OLE DB