C++ ADODB 数据库访问实战:从COM初始化到封装避坑指南
简介针对C环境下的数据库交互需求这份资源提供了一套基于ADO组件访问数据库的工程源码适用于SQL Server、Oracle、MySQL等常见关系型数据库适合有基本C语法基础、希望掌握COM数据库编程的开发者。压缩包内含ADODatabase.h和ADORecordset.h等核心头文件以及对应的实现代码系统展示了连接对象的连接串设置、命令对象的SQL执行与存储过程调用、记录集对象的结果集遍历、游标选择与锁定策略。资源共79个文件大小约38.43MB以源程序和Visual Studio工程文件为主兼顾编译生成的obj、lib、dll、pdb、日志等中间产物便于读者观察工程组织与编译流程。已有111人浏览学习。除基础操作外代码中还涉及regtlibv12注册ADODB类型库、数据库异常捕获、连接管理等实用细节既适合系统梳理C调用ADODB的技术主线也可作为日后封装数据访问层或排查数据库连接问题的参考。1. 拆开 AdoDB.rar 之前先搞清楚 regtlibv12 与 ADODB 在 C 里的真实分工很多从网上下载的数据库相关压缩包里打开后除了 cpp、h 文件之外还会躺着一个 regtlibv12 工具和一堆 ADODB 相关的封装代码。我最初也以为这是个安装脚本跑一下就能连库结果真正折腾下来才发现ADODB 是 C 通过 COM 访问数据库的一整套接口而 regtlibv12 这类工具只负责把类型库注册进系统让 #import 能顺利生成包装头文件。两者分工不同搞混了就会在一个本不该出问题的地方卡上好几天。这篇文章想解决的就是三件事搞清楚 ADODB 在 C 里的运行机制写出一份能直接复用的封装代码再把 regtlibv12、连接串、COM 初始化这些最常翻车的地方提前讲透。适合谁读手头有个现成工程要接 SQL Server、Access 或者 Excel不想引入 ORM愿意用原生 COM 方式搞定数据库读写并且已经开始对内存泄漏和多线程感到头疼的 C 开发者。2. C 与 ADODB 的第一次握手COM 初始化到连接串配置的全过程2.1 为什么在 C 里偏偏选 ADODB而不是其他数据库接口C 访问数据库的路不止一条。最底层的是 ODBC API直接写 SQLAllocHandle、SQLExecDirect代码量大而且每个数据库的驱动行为差异都要自己处理。再往上是 OLE DB性能确实好但模板代码能把人看晕。如果项目用的数据库比较单一直接上官方 SDK 也行可一旦要同时支持 SQL Server、Access、甚至 Excel 文件维护成本立刻上来了。我选择 ADODB 的核心理由是它把数据源差异收敛在了 Provider 这一个参数上。ConnectionString 里换一个 Provider代码逻辑几乎不用动这在中小型项目里性价比极高。另外 ADODB 的 Recordset 模型很接近表格思维字段通过 Fields-GetItem(列名) 直接取比裸 ODBC 按列号绑定直观得多。代价是它建立在 COM 之上BSTR、VARIANT、智能指针这些概念绕不开而且生命周期管理不当极容易泄漏。这不是劝退理由而是使用前必须具备的心理预期——把 COM 的规矩当成工程规范来对待就行。2.2 环境准备与工程配置regtlibv12 到底在解决什么问题拿到 AdoDB.rar 这类包之后先别急着解压往工程里拖。常见做法是先看里面有没有 .tlb、.dll 或者 .exe 形式的注册工具regtlibv12 就是其中一种典型代表。它做的事情和 regsvr32 类似都是把 COM 组件或者类型库的信息写进注册表只不过 regtlibv12 当年主要被用来注册一些旧工具链自带的类型库。对 ADODB 而言真正核心的类型库是系统目录下的 msado15.dll正常情况下 Windows 自带并已被注册所以你不一定需要手动跑 regtlibv12。但如果你在 #import 时报错找不到类型库或者运行时提示“未提供程序”那就需要检查一下 msado15.dll 是否在当前环境可见。我一般会这么处理regsvr32 C:\Program Files\Common Files\System\ado\msado15.dll注意这里不是必须的绝大多数系统已注册过。regtlibv12 更多是某些压缩包作者为了让目标机器“确保能跑”而附带的。你要是真想手动注册类型库执行方式类似regtlibv12.exe msado15.tlb这段命令行里regtlibv12.exe 接受一个 .tlb 文件路径把类型库信息写入注册表。它本身不负责数据库连接也不负责驱动安装它的产出是让后续 #import 指令能解析出完整的类型定义。如果你在 64 位系统上跑 32 位版本的注册工具大概率会遇到注册成功但程序仍找不到组件的情况这个问题第四章会详细展开。2.3 最小可用一段能跑的 C ADODB 查询代码先抛开封装写一个最原始的查询让你看到 ADODB 的真实调用长什么样。这个代码我在模拟项目X里用来验证环境是否通通过后再考虑封装#include windows.h #include comdef.h #include iostream #import msado15.dll no_namespace rename(EOF, adoEOF) rename(BOF, adoBOF) int main() { HRESULT hr CoInitialize(nullptr); if (FAILED(hr)) { std::cerr COM 初始化失败 std::endl; return 1; } try { _ConnectionPtr conn(ADODB.Connection); conn-ConnectionString ProviderSQLOLEDB;Data Source127.0.0.1; Initial Catalogtestdb;Integrated SecuritySSPI;; conn-Open(, , , adConnectUnspecified); _RecordsetPtr rs conn-Execute( SELECT id, name FROM users, nullptr, adCmdText); while (!rs-adoEOF) { _variant_t idVal rs-Fields-GetItem(id)-Value; _variant_t nameVal rs-Fields-GetItem(name)-Value; long id (idVal.vt VT_I4) ? idVal.lVal : 0; _bstr_t name (nameVal.vt VT_BSTR) ? nameVal.bstrVal : L; std::wcout Lid id L, name name std::endl; rs-MoveNext(); } rs-Close(); conn-Close(); } catch (const _com_error e) { std::cerr 错误码: e.Error() std::endl; std::cerr 描述: (char*)e.Description() std::endl; } CoUninitialize(); return 0; }这段代码里的关键点有三个。第一#import 后必须用 rename 把 EOF 和 BOF 改掉否则会跟标准库宏冲突这是 ADODB 入门的第一个坑。第二Fields-GetItem()-Value 返回的是一个 VARIANT 指针不要直接强转先判断 vt 类型再取对应字段能避开大量运行时崩溃。第三我用的是集成认证而不是用户名密码连接串里就没有敏感信息实际项目中如果必须用账号密码建议把密码放到环境变量或配置文件里而不是写死在源码中。连接串的 Provider 是 SQLOLEDB这是访问 SQL Server 的传统方式。如果你的目标库是 Access换成 Microsoft.ACE.OLEDB.12.0同时 Data Source 指向 .accdb 文件路径如果只是临时查 ODBC 数据源就写 ProviderMSDASQL。换 Provider 之后其余代码不用动这体现的正是 ADODB 横向兼容的价值。3. 把 ADODB 封装成 C 类从裸调用到可复用的工程模板3.1 封装 Connection 与 Recordset 的常见框架裸调用能跑通但不适合直接铺到业务代码里。原因很实际每个函数都要写 try/catch、都要重复构造连接串、都要管 Close代码很快就变成一堆复制粘贴。我一般会做一个 CAdoHelper 类把连接、查询、执行、关闭收口成几个方法业务层只关心 SQL 和返回结果。class CAdoHelper { public: CAdoHelper() default; ~CAdoHelper() { Close(); } bool Connect(const std::string connStr) { try { m_conn.CreateInstance(ADODB.Connection); m_conn-ConnectionString _bstr_t(connStr.c_str()); m_conn-Open(, , , adConnectUnspecified); return true; } catch (const _com_error e) { m_lastError e.Description().copy(); return false; } } _RecordsetPtr Query(const std::string sql) { if (!IsConnected()) return nullptr; return m_conn-Execute(_bstr_t(sql.c_str()), nullptr, adCmdText); } bool ExecuteSQL(const std::string sql) { try { if (!IsConnected()) return false; m_conn-Execute(_bstr_t(sql.c_str()), nullptr, adCmdText); return true; } catch (const _com_error e) { m_lastError e.Description().copy(); return false; } } bool IsConnected() const { return m_conn ! nullptr m_conn-State adStateOpen; } void Close() { if (m_conn ! nullptr m_conn-State ! adStateClosed) { m_conn-Close(); } } std::string GetLastError() const { return m_lastError; } private: _ConnectionPtr m_conn; std::string m_lastError; };之所以用 _ConnectionPtr 而不是裸 IConnectionPtr是因为 ADODB 的智能指针重载了析构函数在对象销毁时会自动调用 Release减少一个泄漏点。Connect 方法里 CreateInstance 指定 ProgID ADODB.Connection这是 ADODB 的标准创建方式。我这里没有在 Connect 内部做 CoInitialize 封装的考虑留到 4.3 节细说因为 COM 初始化属于线程级操作放类里反而容易忽略线程切换问题。使用这个类时只在程序入口初始化一次 COM随后可以反复 Connect、Query、Close。注意 Query 返回的 _RecordsetPtr 如果不再用了要主动调用 Close否则记录集对象会一直持有连接引用导致连接无法真正释放。3.2 参数化查询与 VARIANT 转换BSTR 与 _variant_t 的边界把用户输入直接拼进 SQL 是安全大忌ADODB 场景也不例外。ADODB 的参数化要借助 _Command 对象把参数一个个 Append 进去再执行。很多人在这里被 CreateParameter 的参数类型绕晕我给出一个可直接抄的 UPDATE 示例bool UpdateUserName(CAdoHelper db, long userId, const std::wstring name) { _ConnectionPtr conn nullptr; conn.CreateInstance(ADODB.Connection); conn-ConnectionString _bstr_t(ProviderSQLOLEDB;Data Source127.0.0.1; Initial Catalogtestdb;Integrated SecuritySSPI;); conn-Open(, , , adConnectUnspecified); _CommandPtr cmd; cmd.CreateInstance(ADODB.Command); cmd-ActiveConnection conn; cmd-CommandText _bstr_t(LUPDATE users SET name? WHERE id?); cmd-CommandType adCmdText; cmd-Parameters-Append( cmd-CreateParameter(Lname, adVarWChar, adParamInput, 64, _variant_t(name.c_str()))); cmd-Parameters-Append( cmd-CreateParameter(Lid, adInteger, adParamInput, 0, _variant_t(userId))); _variant_t rowsAffected; cmd-Execute(rowsAffected, nullptr, adCmdText); return true; }这里第一个参数是参数名称仅作标识第二个参数 adVarWChar 表示的是 SQL Server 的 nvarchar 类型长度 64 要跟表结构里的字段长度匹配不够长会报截断。第三、四个参数表示输入方向和最大长度第五个是真正的值。_variant_t(userId) 会把 long 自动包装成 VT_I4_variant_t(name.c_str()) 则产生 VT_BSTR这两个转换是 ADODB 代码里最常用的显式包装方式。值得提醒的是CreateParameter 返回的其实是 Property 或 Parameter 对象直接传进 Append 就行不用手动 Release_CommandPtr 会管理这些子对象的生命周期。参数化的收益不仅是防注入更重要的是 SQL 执行计划可以复用。同样的 UPDATE 语句参数不同数据库不必每次都重编译在高频更新场景下性能差异可以到 20% 以上。3.3 连接池与重建连接的策略ADODB 本身不带独立的连接池但它底层的 OLE DB 提供程序大多实现了会话池机制。默认情况下连接池是开启的关闭连接后物理连接并不一定马上断开而是回到池里待命。想验证这一点可以看 SQL Server 的活动连接数连续打开关闭多次连接数不会线性增长这就说明池生效了。但池也会带来困惑。比如你改了数据库账号权限旧连接从池里取出时可能还是旧权限。解决办法是显式地在连接串里关闭池服务ProviderSQLOLEDB;Data Source127.0.0.1;Initial Catalogtestdb; Integrated SecuritySSPI;OLE DB Services-2;OLE DB Services-2 表示关闭除连接池外的所有服务包括自动事务登记取值含义可以记成-2 是最常用的调优开关-4 禁用连接池但保留其他0 全部禁用。实际项目里我更喜欢保留池但在每次连接成功后执行一个轻量的 SELECT 1确认连接可用而不是信任池里对象的存活状态。对于长时间运行的服务进程还要考虑数据库重启导致的连接失效。我的策略是执行一条 SQL 前先检查 Connection-State如果为 adStateClosed 就先重连如果 State 正常但执行时报连接中断捕获后重连并重试一次。这种重试逻辑必须限定次数死循环重试在生产环境里比断线本身更可怕。4. ADODB C 五大避坑从 regtlibv12 注册失败到 0x800040054.1 64 位系统上 regtlibv12 注册不上的真问题现象以管理员身份运行 regtlibv12.exe 注册某个类型库命令行没有任何报错但程序运行时依然提示找不到类型库或者接口未注册。原因最常见的两种情况。一是 regtlibv12 是 32 位版本注册表写入路径在 WOW6432Node 下而你的 C 工程是 64 位编译读取的自然注册表路径里根本没有这条记录。二是你注册的 .tlb 本身依赖的 DLL 没有被正确注册类型库注册成功但底层组件不存在。解决先确认工程编译位数与注册工具一致。64 位工程就用 64 位版本的 regtlibv1232 位工程就用 32 位版。如果不确定工具是几位可以在任务管理器里看进程架构。对于那些依赖 DLL 的情况用依赖分析工具查看 .tlb 加载后到底缺哪个模块找到后单独注册或把 DLL 放到系统目录对应位置。另一个容易被忽略的点是regtlibv12 这类工具对 .NET 程序集无能为力如果你要注册的是托管类型库应该用 RegAsm.exe 而不是 regtlibv12。判断方法是看 .tlb 文件来源如果是 C 工程用 MIDL 编译出来的regtlibv12 可用如果是 C# 工程自动生成的请直接走 RegAsm。4.2 BSTR 内存泄漏没看清 SysFreeString 的代价现象程序长时间运行后内存稳定上涨执行同样的查询多次每次内存都比上一次多一点点频繁查询时尤为明显。原因从 Recordset 里取出字符串值后如果用了裸 BSTR 并自己没有释放就是典型的内存泄漏。BSTR 不是普通 C 字符串它由 SysAllocString 分配必须由 SysFreeString 释放。很多人从 Field-Value 取到 VARIANT 后直接取 bstrVal 用来构造 std::wstring用完就丢VARIANT 内部的 BSTR 没有被释放。解决最省心的办法是坚决不碰裸 BSTR全程用 _bstr_t 或 _variant_t 来传递。_variant_t 的析构函数会帮你处理 VARIANT 里所有需要清理的字段包括 BSTR。反例是每次从字段取完值都手工 SysFreeString一旦漏了一个分支就泄漏正例是下面这种写法让包装对象自己管理_variant_t fVal rs-Fields-GetItem(Lremark)-Value; if (fVal.vt VT_BSTR) { std::wstring text (BSTR)fVal.bstrVal ? (BSTR)fVal.bstrVal : L; // text 使用完毕fVal 析构时自动释放内存 }注意这里读到 _variant_t 之后原本的 VARIANT 已被“接管”不能再去手动释放原始值否则会造成二次释放崩溃。4.3 多线程下的 COM 初始化陷阱现象主线程里创建了连接工作线程里直接使用同一个 _ConnectionPtr 查询结果线程启动就报 0x800401F0也就是 CO_E_NOTINITIALIZED或者干脆访问被拒绝。原因COM 的初始化是线程级的。你在主线程调用 CoInitialize 只对主线程有效新的工作线程必须自己调用 CoInitialize 或 CoInitializeEx否则任何 COM 调用都会失败。更隐蔽的是即便初始化了若主线程用的是 Apartment 模型COINIT_APARTMENTTHREADED把连接对象传给多线程模型的工作线程使用也可能遇到列集Marshaling问题。解决每个线程的第一行代码就初始化 COM结束前逆操作。DWORD WINAPI WorkerThread(LPVOID param) { HRESULT hr CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); if (SUCCEEDED(hr)) { // 在这里创建或使用属于本线程的 ADODB 对象 CAdoHelper db; db.Connect(ProviderSQLOLEDB;...); // 执行查询... CoUninitialize(); } return 0; }如果多个线程必须共享同一个连接请使用连接池或每个线程独立建连接。把一个 COM 对象在线程间裸传递是比内存泄漏更隐蔽的隐患它不一定立刻崩溃但会在某个随机时间点直接中断你的服务进程。血泪教训换来的结论是给每个线程单独创建 _ConnectionPtr不要让对象跨线程游走。4.4 连接超时与 OLE DB 驱动缺失时的报错现象连不上数据库时错误信息就一句话未指定的错误 0x80004005。这其实是一大类错误的统称根本原因被掩盖了。另一种常见报错是“找不到指定的提供程序”说明 Provider 在这台机器上压根不存在。原因Provider 名称依赖于 OLEDB 驱动是否安装。SQLOLEDB 随 Windows 自带但 SQL Server 2012 之后微软主推 SQLNCLI11新机器不一定预装Microsoft.ACE.OLEDB.12.0 是用来读 Office 文件的很多服务器上默认没有MSDASQL 在部分精简系统里也会被禁用。解决先确认 Provider 是否已注册。查注册表或者直接尝试用该 Provider 建立连接报错后再反查。常见几个 Provider 的适用场景做一个对照Provider 名称适用数据源驱动依赖备注SQLOLEDBSQL ServerWindows 自带旧驱动跨版本兼容性好SQLNCLI11SQL Server需单独安装新版推荐支持较新的 SQL 特性Microsoft.ACE.OLEDB.12.0Access 2007 与 Excel需安装 Access Database Engine32 位与 64 位不可混装MSDASQL任意 ODBC 数据源系统需启用 ODBC性能有损耗非必要不优先选这里的取舍建议是新开发的项目直接上 SQLNCLI11不要再用 SQLOLEDB要读 Excel 或 Access提前评估服务器环境有没有 ACE 驱动没有就装好再部署别到现场才发现。4.5 字段存取类型不匹配与 NULL 值的兼容现象字段在数据库里允许为空程序取出 IS NULL 的值直接参与运算轻则得到垃圾值重则 _variant_t 在类型转换时抛出异常。还有一种情况字段存的是 nvarchar你用 adVarChar 参数去更新中文全部变问号。原因ADODB 返回的 VARIANT 类型和数据库实际类型不是严格一致而且 NULL 值对应的是 VT_NULL它既不是数值也不是字符串强行转换必然失败。类型不匹配的另一个来源是没搞清宽窄字符SQL Server 的 nvarchar 走 UTF-16必须用 BSTR 或宽字符串参数用 char* 传进去编码错乱是必然的。解决取字段值时先统一走一个转换函数见 NULL 就返回默认值类型不确定就根据 vt 分支处理。std::wstring GetFieldAsString(_RecordsetPtr rs, const wchar_t* fieldName) { _variant_t v rs-Fields-GetItem(fieldName)-Value; if (v.vt VT_NULL || v.vt VT_EMPTY) return L; if (v.vt VT_BSTR) return (BSTR)v.bstrVal; _variant_t casted; VariantChangeType(casted, v, 0, VT_BSTR); return (BSTR)casted.bstrVal; }VariantChangeType 是 VARIANT 类型转换的标准函数它能把 VT_I4、VT_R8 等数值类型转换成字符串。使用前确认 v.vt 不是 VT_NULL否则同样会报类型无效。这个函数的边界在于它不理解业务语义比如日期字段的显示格式由数据库的会话语言决定不同机器可能得到不同的字符串业务层最好统一按 ISO 格式存储。5. 把 ADODB 封装成业务模块连接串管理与可观测性5.1 连接串生成器与 SQL 日志连接串散落在各个模块是工程腐化的信号。我习惯在封装层提供连接串生成函数把 Provider、服务器、库名、认证方式作为参数集中在一个文件里维护std::string BuildConnStr(const std::string server, const std::string db, const std::string user, const std::string pwd) { std::string cs ProviderSQLNCLI11;Data Source server ;Initial Catalog db ;User ID user ;Password pwd ;; return cs; }集成认证模式则不传 user 和 pwd直接拼 Integrated SecuritySSPI。生成的连接串建议做一层脱敏后打印到日志里密码字段一律替换成 ******避免日志泄露凭据。SQL 日志是排查问题的利器。在 ExecuteSQL 和 Query 中记录时间戳、SQL 文本、执行耗时和结果状态。有线上问题需要复现时这些信息能省下大量沟通时间。记录方式用文件输出即可注意日志文件要按天滚动单日文件控制在几十兆以内超出就归档。5.2 统一错误码与重试机制把 _com_error 的 Error() 数值直接抛给业务层业务层是懵的。更好的做法是做一个 AdoResult 结构携带错误码、可读描述和建议动作struct AdoResult { bool success; long adoCode; std::wstring source; std::wstring description; std::string suggestion; };捕获 _com_error 时把 e.Description() 和 e.Source() 填进去再根据错误码预置 suggestion。比如 0x80004005 统一建议“检查连接串 Provider 与目标驱动是否匹配”0x800401F0 建议“检查当前线程是否已初始化 COM”超时错误建议“增大 ConnectionTimeout 或确认数据库负载”。重试机制只对部分错误生效网络闪断、数据库连接数打满、死锁导致的超时这些重试往往能解决问题而 SQL 语法错误、权限不足、字段类型不匹配重试一百次结果都一样。所以重试前必须判断错误码分类把无效重试挡在门外。5.3 性能与资源回收何时 Release、何时 Close_ConnectionPtr 和 _RecordsetPtr 作为智能指针析构时都会自动 Release。但 Release 和 Close 的语义不同Close 是让对象回到初始状态连接对象本身还活着Release 是销毁 COM 对象本身。如果只 Release 不 Close连接池可能不会立即感知连接释放导致池里的连接占位超过预期反过来只 Close 不 Release指针最终析构时会再次释放在严格的引用计数场景下可能重复引用。我的执行原则是Recordset 用完后先 Close 再让指针置空Connection 在程序退出前 Close整个助手的析构里再兜底一次。大批量插入数据时不要逐条 Execute 提交那样会频繁触发事务日志写入极慢正确做法是分批拼接每 500 到 1000 行执行一次并尽量放到一个事务里。ADODB 的批量更新模式 adLockBatchOptimistic 配合 UpdateBatch 也能显著减少往返次数但它要求连接的游标位置设置为客户端游标数据量大时要谨慎评估内存占用。超时设置同样是性能感知的一部分。ConnectionTimeout 和 CommandTimeout 默认是 30 秒但在报表类查询动辄十几秒的场景下30 秒不够用在高频事务系统里30 秒又太长。我一般把连接超时设为 5 秒命令超时按单个接口的预估耗时上浮 50%这样既能暴露网络故障又不会让用户为一次卡死的查询苦等半分钟。6. 把 ADODB 的坑变成你的顺手工具几个调试期最值得养成的习惯先说一个我踩过好几次的教训不要直接拿返回的 _RecordsetPtr 去和标准库的 recordset 之类的名字互相赋值虽然编译可能通过但遇到需要重新编译的工程头文件包含顺序一变冲突就冒出来。我在工程里统一在 #import 时做改名把 Recordset 改成 ADORecordset把 Connection 改成 ADOConnection从源头切断和业务代码命名空间的关联这一行预处理能省下不少莫名其妙的编译期报错。其次是在连接串里把 Provider 写完整并且显式化。不要省略掉 Provider 让 ADODB 去猜猜的代价是走默认 ODBC 桥接性能打折扣不说错误提示还更难定位。用 SQLNCLI11 就写 SQLNCLI11用 ACE 就写 ACE别让接手的人猜你的意图。调试 ADODB 时我还会在关键的 Execute 前后各打一条日志记录进入前的时间戳和出来后的耗时。很多看似“数据库很慢”的问题其实慢在连接建立阶段而不是查询本身。日志一打两段时间一比问题出在哪一段立刻清楚。如果是连接慢优先检查网络和认证如果是执行慢再去看 SQL 和索引。这个习惯让我避免了好几次把时间浪费在优化一个本来就没有问题的查询上。最后一个习惯是给 ADODB 的所有入口点做一层统一的 RAII 守卫。确保即使用 _com_error 中断Close 也会被调用。封装类可以考虑在析构函数里兜底。我在某个项目里曾经因为一次分支提前 return连接没关导致后面所有的连接都排队超时排查了半天才发现是资源回收问题此后所有数据库操作一律走封装类裸调用只保留在最底层的一次性验证脚本里。希望这些习惯能帮你少走几趟弯路也希望你的 C 数据库开发之路能顺畅些。本文还有配套的精品资源点击获取

相关新闻

进销存源码系统定制实战:从闭环设计到移动加权成本与多门店权限

进销存源码系统定制实战:从闭环设计到移动加权成本与多门店权限

做进销存源码系统定制这几年,被问得最多的一个问题不是系统好不好用,而是“为什么网上那些现成的进销存软件,一到我们店里就变成半成品”。 两年前一个做五金批发的朋友给我上了印象深刻的一课。他花了一万二买了套成品进销存,用…

2026/10/9 10:49:25 阅读更多 →
VMware Workstation Pro 17运行Linux虚拟机实战:选型、网络与优化

VMware Workstation Pro 17运行Linux虚拟机实战:选型、网络与优化

1. 为什么我一直在VMware上跑Linux虚拟机用VMware跑Linux虚拟机这事儿,说简单也简单,说折腾也真折腾。我这些年一直在VMware Workstation Pro上维护着好几台Linux虚拟机,从Ubuntu到CentOS,再到国产的统信UOS,几乎每个阶…

2026/10/9 10:49:25 阅读更多 →
CNN-Bi-LSTM中文情感分析实战:毕设可跑通、二次开发有抓手

CNN-Bi-LSTM中文情感分析实战:毕设可跑通、二次开发有抓手

简介:本资源是一套基于CNN-Bi-LSTM混合神经网络架构的中文情感分析系统完整源码,专为高校计算机、人工智能及相关专业学生设计,适用于毕业设计、课程设计、科研入门与深度学习实践。项目代码经过严格测试,功能完备、可直接运行&am…

2026/10/9 10:48:23 阅读更多 →

最新新闻

Codex 额度重置后,开发者如何用 TaoToken 统一管理 API 调用?

Codex 额度重置后,开发者如何用 TaoToken 统一管理 API 调用?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 11:17:09 阅读更多 →
t3code 多AI编程工具统一调度:Electron本地代理与配置切换实战

t3code 多AI编程工具统一调度:Electron本地代理与配置切换实战

1. 从"t3code"这个名字说起:它到底想解决什么问题第一次看到"t3code"这个项目名,我脑子里冒出来的第一个念头是:这大概率又是一个把当下几款主流 AI 编程工具串起来的中间层工具。事实也确实如此。从围绕它的热搜词能看出…

2026/10/9 11:17:09 阅读更多 →
t3code 全栈开发实战:Next.js + tRPC + Prisma 类型安全指南

t3code 全栈开发实战:Next.js + tRPC + Prisma 类型安全指南

1. 从“t3code”这个标题说起:它到底是什么 第一次看到“t3code”这个词,我脑子里蹦出来的第一反应是——这大概率是个技术圈的缩写或者代号,而不是某个大众消费品。事实也确实如此。在开发者社区里,t3code 通常指的是一类围绕“T…

2026/10/9 11:17:09 阅读更多 →
Cline 3.1 版本发布后,如何用 TaoToken 统一 Key 接入 VS Code 编程助手

Cline 3.1 版本发布后,如何用 TaoToken 统一 Key 接入 VS Code 编程助手

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 11:17:09 阅读更多 →
OpenMontage:用Agentic AI把视频剪辑变成可编程工程

OpenMontage:用Agentic AI把视频剪辑变成可编程工程

1. 从“剪辑苦力”到“AI导演”:OpenMontage 到底想解决什么问题做视频的人都有一个共同的痛:剪辑这件事,创意只占两成,剩下八成全是体力活。找素材、对时间轴、卡节奏点、加转场、调色、配字幕、导出不同平台的版本……一套流程走…

2026/10/9 11:17:09 阅读更多 →
操作系统期末复习重点:进程管理、PV操作与计算题全攻略

操作系统期末复习重点:进程管理、PV操作与计算题全攻略

学期末最让人头疼的,往往就是操作系统这种既像文科又像理科的课。你说它难吧,概念翻来覆去就那些;你说它简单吧,一到计算题和PV操作题就卡壳。我翻了不少“计算机操作系统考试知识点及重点总结”资料,又结合自己当年复…

2026/10/9 11:16:08 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →