VC下的ADO编程入门(2-2):TaoToken统一Key通道下的连接串与记录集实操
1. VC6.0 里 ADO 连接 SQL Server 到底卡在哪从 Connection 到 Recordset 的完整链路如果你正在用 VC6.0 写 MFC 程序需要从 SQL Server 里读数据大概率绕不开 ADO。ADO 全称 ActiveX Data Objects是微软早年提供的一套数据库访问组件它把底层 OLE DB 的复杂度包了一层让你用几个 COM 接口就能完成连接、查询、遍历字段。适合谁适合还在维护老 MFC 项目、做工业上位机、教学演示或者单纯想搞懂 VC6 时代数据库编程的人。但初学者最容易卡住的地方不是语法而是三件事第一Connection 对象的连接串到底怎么写Provider 选哪个、Server 写 IP 还是主机名、认证用 SQL 还是 Windows第二Recordset 打开后游标类型和锁定类型怎么选选错了要么报错要么字段读不出来第三怎么确认连接真的成功了、游标真的在第一条记录上而不是靠猜。我试过在 VC6 里直接抄网上的连接串结果Open返回失败错误码还看不出原因。后来发现是 Provider 版本和认证方式不匹配。这篇就按“能跑通第一个查询”的目标把连接串模板、TaoToken 统一 Key 通道的 endpoint 配置、以及用断点和输出窗口验证状态的具体动作讲清楚。你跟着做至少能拿到一个能返回数据的 Recordset。先明确一个概念ADO 是 COM 组件VC6 里用它需要引入类型库。通常两种方式一种是用#import指令一种是在 stdafx.h 里加#import C:\Program Files\Common Files\System\ado\msado15.dll no_namespace rename(EOF,adoEOF)。注意EOF要重命名因为 MFC 里也有 EOF 宏不重命名会冲突。这一步不做后面编译一堆错。连接串的核心是 Provider。连 SQL Server 一般用SQLOLEDB或MSOLEDBSQL。老项目里SQLOLEDB兼容性最好VC6 环境下推荐先用它。连接串里要写清楚 Server、Database、UID、PWD或者用Trusted_Connectionyes走 Windows 认证。如果你是通过统一 Key 通道访问远端数据库服务那 Server 和端口就换成通道提供的 endpoint认证信息换成通道分配的 Key。下面会给出具体模板。Recordset 打开时Open方法的参数顺序是SourceSQL 语句或表名、ActiveConnectionConnection 对象或连接串、CursorType、LockType、Options。CursorType 常用adOpenStatic或adOpenForwardOnlyLockType 常用adLockReadOnly。只读查询用adOpenStatic adLockReadOnly最稳能前后移动游标也支持RecordCount。如果你只需要单向遍历adOpenForwardOnly性能更好但RecordCount可能返回 -1。字段读取用Fields-GetItem(列名)-Value或者用序号Fields-Item[0]-Value。Value 是 VARIANT 类型VC6 里要转成_variant_t或CString。转换时注意 NULL 值直接转可能抛异常要先判断vt VT_NULL。验证连接状态最直接的办法是在Open之后检查返回值HRESULT 为 S_OK 说明成功。更直观的是在 VC6 的 Output 窗口用TRACE打印连接状态和记录数。断点打在Open下一行看m_pConn-State是否为adStateOpen。Recordset 的游标位置用AbsolutePosition看第一条是 1BOF和EOF布尔值也能判断是否为空。这一节先把整体链路和关键概念铺开下一节讲 TaoToken 统一 Key 通道的前置准备包括 endpoint 和 Key 怎么拿、怎么配到连接串里。2. TaoToken 统一 Key 通道前置准备endpoint、API Key 与模型 ID 三件套怎么配在 VC6 里连数据库传统做法是把 Server 地址、账号密码硬编码在连接串里。问题是账号密码散落在代码里不好管换环境要改代码重新编译。用 TaoToken 统一 Key 通道的思路是把认证收敛到一个 Key 上连接串里的 Server 指向通道 endpoint认证信息用通道分配的 Key。这样换数据库或换环境只改 endpoint 和 Key代码结构不动。先说要准备什么。你需要三样东西Base URL也就是 endpoint、API Key、以及你要访问的目标标识类似 Model ID 的角色在数据库场景里就是目标库或服务的标识。这三件套在 TaoToken 的 console 里都能拿到。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 进 console 页面在 API Keys 菜单里创建一个 Key。创建时给它起个名字比如vc6-ado-test方便后面区分。Key 创建后只显示一次复制下来存好。如果你用的是 Coding Plan 或模型对话场景Key 是通用的但数据库通道的 endpoint 可能不同。具体以 console 里显示的接入信息为准。API 的基础地址是 https://taotoken.net/api 注意这个地址不带 UTM 参数直接用于代码里的 Base URL。如果你在浏览器里访问可以带上 UTM 方便归因但代码里写干净的 API 地址。endpoint 的拼接方式通常是https://taotoken.net/api加上具体路径。比如模型对话是/v1/chat/completionscoding-plan 相关是/coding-planconsole 是/consoleapi-keys 管理是/api-keysClaudeCodeAnthropic 相关是/claudecode-anthropic。数据库通道的路径以 console 里“接入文档”页面显示的为准。接入文档地址是 https://taotoken.net/doc 里面有各场景的 endpoint 示例。拿到三件套后在 VC6 里怎么用传统 ADO 连接串是ProviderSQLOLEDB;Server...;Database...;UID...;PWD...。走统一 Key 通道时Server 换成通道的 host端口换成通道端口UID 和 PWD 换成 Key 相关的凭证。有些通道支持把 Key 直接放在连接串的Password字段有些需要先换 token。具体看接入文档。这里给一个通用的配置思路不针对特定数据库版本; TaoToken 统一 Key 通道配置示例放在项目配置或环境变量里 TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_API_KEYsk-你的Key TAOTOKEN_TARGETyour-database-target然后在 VC6 代码里读取这些配置拼成连接串。不要硬编码 Key至少放在单独的配置文件或注册表里。VC6 读 ini 可以用GetPrivateProfileString读环境变量用getenv。如果你用的是 Cline MCP 或 CC Switch 这类工具配置格式可能是 JSON 或 TOML。比如 Cline MCP 的配置片段{ mcpServers: { taotoken-db: { command: your-mcp-server, env: { TAOTOKEN_BASE_URL: https://taotoken.net/api, TAOTOKEN_API_KEY: sk-你的Key, TAOTOKEN_TARGET: your-database-target } } } }Codex 的 auth.json 类似{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: your-target-id }注意VC6 本身不直接读这些 JSON但你可以用同样的字段名组织自己的配置。关键是 Base URL、Key、目标标识三件套齐全缺一个都连不上。前置准备做完后下一节给可复制的连接串模板和 Recordset 打开代码。如果你还没创建 Key先去 console 的 API Keys 页面建一个再回来看代码。3. 可复制配置VC6 里 ADO 连接串模板与 Recordset 打开代码这一节直接给能复制进项目的代码。先确认你的 VC6 工程已经引入了 ADO 类型库。在 stdafx.h 里加#import C:\Program Files\Common Files\System\ado\msado15.dll no_namespace rename(EOF, adoEOF)如果路径不对找到你机器上 msado15.dll 的实际位置。编译前记得初始化 COM::CoInitialize(NULL);程序退出时::CoUninitialize();。连接串模板分两种。第一种是直连 SQL ServerCString strConn; strConn.Format( _T(ProviderSQLOLEDB;Server%s,%d;Database%s;UID%s;PWD%s;), _T(192.168.1.100), 1433, _T(TestDB), _T(sa), _T(yourpassword));第二种是走 TaoToken 统一 Key 通道Server 换成通道 endpoint 的 host 和端口认证用 KeyCString strConn; strConn.Format( _T(ProviderSQLOLEDB;Server%s,%d;Database%s;UID%s;PWD%s;), _T(taotoken.net), 1433, _T(your-database-target), _T(taotoken), _T(sk-你的Key));实际 host 和端口以接入文档为准。有些通道用 HTTP 协议那 ADO 的 Provider 可能要换成MSDASQL或自定义 Provider具体看文档。如果文档说用 ODBC 方式连接串改成DSNtaotoken-db;UIDtaotoken;PWDsk-你的Key;然后在 ODBC 数据源里配 DSN 指向通道。VC6 里用ProviderMSDASQL加 DSN。连接对象和记录集的完整代码_ConnectionPtr m_pConn; _RecordsetPtr m_pRs; try { m_pConn.CreateInstance(__uuidof(Connection)); HRESULT hr m_pConn-Open(_bstr_t(strConn), , , adConnectUnspecified); if (FAILED(hr)) { TRACE(_T(连接失败, hr0x%08X\n), hr); return; } TRACE(_T(连接成功, State%d\n), m_pConn-State); m_pRs.CreateInstance(__uuidof(Recordset)); m_pRs-Open( _bstr_t(SELECT TOP 10 * FROM YourTable), m_pConn.GetInterfacePtr(), adOpenStatic, adLockReadOnly, adCmdText); TRACE(_T(记录数%ld, BOF%d, EOF%d\n), m_pRs-RecordCount, m_pRs-BOF, m_pRs-EOF); while (!m_pRs-adoEOF) { _variant_t vtVal m_pRs-Fields-GetItem(YourColumn)-Value; if (vtVal.vt ! VT_NULL) { CString strVal (LPCTSTR)_bstr_t(vtVal); TRACE(_T(值%s\n), strVal); } m_pRs-MoveNext(); } m_pRs-Close(); m_pConn-Close(); } catch (_com_error e) { TRACE(_T(COM 错误: %s, 描述: %s\n), (LPCTSTR)e.ErrorMessage(), (LPCTSTR)e.Description()); }注意adOpenStatic和adLockReadOnly是枚举值需要包含 ADO 的类型库后才有定义。如果编译报未定义手动加#define adOpenStatic 3 #define adLockReadOnly 1 #define adCmdText 1或者用_variant_t((long)3)这种写法。RecordCount在adOpenStatic下有效adOpenForwardOnly下可能返回 -1。字段读取时Fields-GetItem的参数可以是列名或序号。列名区分大小写取决于数据库设置不确定就用序号。_variant_t转CString时如果值是数字类型_bstr_t转换可能失败要先判断vt。安全做法CString VariantToCString(const _variant_t vt) { CString str; switch (vt.vt) { case VT_NULL: case VT_EMPTY: str _T(); break; case VT_BSTR: str (LPCTSTR)_bstr_t(vt); break; case VT_I2: str.Format(_T(%d), vt.iVal); break; case VT_I4: str.Format(_T(%ld), vt.lVal); break; case VT_R8: str.Format(_T(%f), vt.dblVal); break; default: str (LPCTSTR)_bstr_t(vt); break; } return str; }这段代码可以直接放进你的工具类。连接串里的adConnectUnspecified是 -1表示异步关闭一般用这个就行。配置部分就这些。下一节讲怎么用断点和 Output 窗口验证连接状态和游标位置确保你真的连上了、真的读到数据了。4. 验证请求与成功结果用断点、TRACE 输出和游标位置确认跑通代码写完了不代表跑通了。VC6 的调试手段有限但断点加 Output 窗口足够验证。这一节给具体动作。第一步在m_pConn-Open这一行按 F9 下断点。F5 启动调试。程序停在这里时把鼠标移到strConn上看连接串内容对不对。特别检查 Server、Database、UID、PWD 四个字段。如果 Key 里有特殊字符确认没有被转义。第二步按 F10 单步执行过Open。看hr的值。如果FAILED(hr)为真Output 窗口会打印连接失败, hr0x...。常见 hr 值0x80004005一般是认证失败或 Server 不可达0x80040E4D是登录失败0x80040E21是连接串格式错误。对照这些值排查。第三步连接成功后看m_pConn-State。adStateOpen的值是 1。Output 窗口会打印连接成功, State1。如果 State 是 0说明连接被关闭了检查是不是Open之后立刻Close了。第四步在m_pRs-Open之后下断点看RecordCount、BOF、EOF。如果RecordCount是 0 且EOF为真说明查询没返回数据。检查 SQL 语句里的表名和列名是否存在或者 WHERE 条件是否过滤掉了所有行。如果RecordCount是 -1说明游标类型不支持计数换成adOpenStatic。第五步在while循环里下断点看m_pRs-AbsolutePosition。第一条记录时是 1每MoveNext一次加 1。如果AbsolutePosition返回 -1说明游标不支持定位还是游标类型问题。BOF和EOF在循环中的变化也能看出游标是否正常移动。第六步看 Output 窗口打印的字段值。如果值是乱码检查数据库列的字符集和 VC6 工程的字符集设置。VC6 默认是 MBCS如果数据库是 Unicode需要转换。用_bstr_t转换时_bstr_t内部是宽字符转CString在 MBCS 工程下会走WideCharToMultiByte一般没问题。如果乱码试试CString strVal (LPCTSTR)_bstr_t(vtVal);改成CString strVal; strVal vtVal.bstrVal;看是否改善。成功的结果长这样Output 窗口依次打印连接成功、记录数、每行的字段值。比如连接成功, State1 记录数10, BOF0, EOF0 值张三 值李四 ...如果只打印了连接成功没有记录数说明m_pRs-Open抛异常了被 catch 捕获。看 catch 里的e.Description()通常会说明原因比如“对象名 YourTable 无效”。还有一个验证点m_pConn-Errors-Count。连接失败时Errors 集合里会有详细错误信息。加一段if (m_pConn-Errors-Count 0) { for (long i 0; i m_pConn-Errors-Count; i) { _com_error err(m_pConn-Errors-Item[i]-Number); TRACE(_T(错误 %d: %s\n), i, (LPCTSTR)err.Description()); } }这段放在Open失败之后能打印出比 hr 更具体的描述。验证通过后你就有了一个能跑通的 ADO 查询。下一节讲常见报错和排查方法包括 401、local proxy failed、reading choices、OAuth 这些你可能遇到的错误。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth 报错怎么解这一节对照真实报错给排查路径。这些错误不一定都出现在 VC6 里但走统一 Key 通道时可能遇到。401 Unauthorized。这是认证失败。在 ADO 场景里表现为Open返回0x80040E4D或0x80004005Errors 集合里提示“登录失败”。排查顺序第一Key 是否复制完整有没有多余空格第二Key 是否过期或被删除去 console 的 API Keys 页面确认状态第三连接串里的 UID 和 PWD 字段是否和通道要求的一致有些通道要求 UID 固定为taotokenPWD 放 Key第四如果通道要求 Bearer Token 而不是连接串认证那 ADO 直连可能不支持需要走 ODBC 或中间层。接入文档里会写明认证方式。local proxy failed。这个错误通常出现在通过本地代理访问通道时。VC6 程序如果配了系统代理ADO 的 HTTP 请求可能走代理失败。排查第一检查 IE 的代理设置VC6 的 WinINet 会读这个把代理关掉或配成通道要求的地址第二如果通道需要代理确认代理地址和端口正确第三防火墙是否拦截了出站连接临时关掉防火墙测试。注意这里说的代理是网络配置层面的不是让你去搞什么特殊网络工具就是普通的 HTTP 代理设置。reading choices 报错。这个错误一般出现在解析响应时通道返回的 JSON 结构和你预期的不一致。比如你按模型对话的格式去解析数据库通道的响应字段对不上。排查第一用模型对话页面或 curl 先测通道是否正常返回第二看接入文档里数据库通道的响应格式确认字段名第三VC6 里如果自己解析 JSON检查解析代码是否处理了错误分支。如果你用的是现成的 MCP server看它的日志输出。OAuth 相关报错。如果通道用 OAuth 认证而你在连接串里直接放 Key会报 token 无效。排查第一确认通道的认证方式是 API Key 还是 OAuth第二如果是 OAuth需要先走授权流程拿 access token再把 token 放进连接串第三token 有过期时间过期后要刷新。VC6 里做 OAuth 比较麻烦建议用 API Key 方式。如果文档说支持 API Key就优先用 Key。连接串格式错误。报错0x80040E21。排查第一每个字段用分号分隔最后也要有分号第二值里如果有分号或等号要用引号包起来第三Provider 名称拼写正确SQLOLEDB不是SQLOLEDB.1有些环境要加 .1第四Server 字段如果是 IP 加端口格式是Server192.168.1.100,1433逗号不是冒号。Recordset 打开失败。报错0x80040E14一般是 SQL 语法错误。排查第一把 SQL 语句复制到查询分析器里单独执行第二检查表名和列名是否有保留字有的话用方括号包起来第三SELECT *在有些视图上会失败改成具体列名。字段读取返回空。不是报错但结果不对。排查第一列名大小写SQL Server 默认不区分但有些排序规则区分第二GetItem的序号从 0 开始还是 1 开始ADO 里Fields-Item[0]是第一列第三NULL 值判断vt VT_NULL时不要转字符串。CC Switch / Cline MCP / Codex auth.json 配置错误。如果这三件套里缺了 Base URL、Key、Model ID 任何一个都会连不上。检查 JSON 格式是否正确字段名是否和文档一致。Cline MCP 的 env 里三个变量都要有。Codex 的 auth.json 里base_url不要带尾部斜杠api_key不要带Bearer前缀。排查完这些大部分连接问题都能定位。如果还不行去接入文档页面看最新的配置示例或者用模型对话页面发一条测试消息确认 Key 本身有效。6. 从能连到能用把 ADO 查询封装成可复用函数与后续接入建议跑通第一个查询后下一步是把代码封装成可复用的函数避免每次写一堆重复的 try-catch。建议封装一个CAdoHelper类提供Open、Query、Execute、Close方法。Query返回_RecordsetPtr调用方自己遍历。Execute用于 INSERT/UPDATE/DELETE返回影响行数。封装时注意几点第一COM 初始化放在应用启动时做一次不要每次查询都CoInitialize第二连接对象可以复用不要每次查询都新建连接连接池在 ADO 里是自动的但频繁 Open/Close 有开销第三异常处理统一在 helper 里做把_com_error转成错误码和描述返回给调用方第四Key 从配置文件读不要硬编码。如果你后续要做更复杂的场景比如批量写入、事务、存储过程调用ADO 都支持。事务用m_pConn-BeginTrans()、CommitTrans()、RollbackTrans()。存储过程用m_pConn-Execute(EXEC ProcName ?, ?, ...)或者_CommandPtr。这些在接入文档里也有示例。对于长期编码和 Agent 场景可以考虑用 Coding Plan把数据库访问封装成工具函数让 Agent 调用。Coding Plan 的接入方式和模型对话类似endpoint 是 https://taotoken.net/api 加/coding-plan路径。如果你要管理多个 Key 或查看用量去 console 页面。API Keys 管理在 https://taotoken.net/api-keys 。最后给一个实用技巧在 VC6 里调试 ADO 时把TRACE输出重定向到文件方便事后分析。在InitInstance里加#ifdef _DEBUG freopen(ado_debug.log, w, stdout); #endif然后TRACE的内容会写到文件里。注意TRACE默认输出到 Output 窗口重定向后两者都有。调试完记得关掉。还有一个坑VC6 的_variant_t在某些 SP 版本下有内存泄漏频繁循环读取字段时注意。如果发现内存持续增长把_variant_t改成variant_t或手动VariantClear。实测下来用_variant_t配合Fields-GetItem在循环里一般没问题但如果循环几十万次建议用GetRows一次性取回数组再遍历。到这里从连接串配置到 Recordset 遍历的完整链路就走通了。你可以把代码复制进工程改一下连接串里的 endpoint 和 Key编译运行看 Output 窗口的输出。如果遇到报错回到第 5 节对照排查。需要创建 Key 或查看接入文档去 console 和 doc 页面。