十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

dmoci实战:C/C++在Windows 64位下访问达梦数据库

dmoci实战:C/C++在Windows 64位下访问达梦数据库 简介针对达梦7数据库的C/C开发工具包专为Windows 64位环境下使用Visual C的开发者设计提供与达梦数据库交互所需的完整接口支持。压缩包共21个文件以18个dll动态库为主体辅以头文件、静态导入库及配置ini文件整体约3.33MB可直接集成至Visual Studio工程。目录按bin、include、lib划分各组件职责清晰便于快速定位所需文件。借助DMOCI标准API开发者能够完成连接初始化、SQL执行、结果集遍历、事务管理和错误处理等典型数据库操作有效降低国产数据库应用的开发与迁移成本。已有895人学习下载适合需要对接达梦7、构建高性能64位数据访问层的工程技术人员也可作为国产数据库应用开发的入门参考。 干什么活就得用什么工具搞C/C的人绕不开数据库操作这套破事。最近项目里在Windows 64位环境下做底层数据访问翻来覆去比对之后定了dmoci开发库把连接、执行、绑参、取数的整套流程都过了一遍踩了不少坑也摸清了这库的设计套路。如果你正打算用C/C接国产数据库或者被“64位程序连不上库”这种问题折磨过这篇东西应该能帮你省一两天时间。先把这个库的本质说清楚。dmoci不是那种大而全的ORM框架它是借鉴Oracle OCI调用风格封装的一套底层数据库访问接口。也就是说你写的代码从结构上看跟在Linux上写Pro*C、在Windows上写ODBC有点像但它的函数命名、句柄模型、绑定机制都是OCI那一路的。这对于从Oracle迁过来的老C程序员非常友好几乎可以平移过去的经验。而它干的事也非常纯粹让C/C程序通过一段相对固定的调用链完成对数据库的增删改查。说到适合谁我的判断是这样如果你的项目是纯C或C写的没有Java虚拟机、没有.NET运行时又需要直连数据库执行SQL那dmoci是很有竞争力的选择。它没有强依赖不需要额外起服务就是一组库文件加头文件。但如果你是个Java党或者只写Python脚本这东西对你就没什么意义不用硬看。1. 先理解这套库的设计逻辑后面才不踩坑1.1 从OCILogon到句柄管理为什么是这一套我第一次看dmoci文档的时候第一反应是“这不就是Oracle OCI换了个皮吗”。后来仔细翻了函数签名和句柄类型发现它确实沿用了OCI的核心模型环境句柄OCIEnv、错误句柄OCIError、服务上下文句柄OCISvcCtx、语句句柄OCIStmt、绑定和定义句柄OCIBind、OCIDefine。这个模型看着啰嗦但非常符合C程序员的思维习惯。每个句柄代表一类资源资源间有明确的从属关系。环境句柄是全局的负责统一管理内存和线程初始化服务上下文句柄代表一个数据库连接语句句柄代表一条SQL的状态。任何一次数据库操作本质上就是在一堆句柄上做文章。这种设计与ODBC那种“一个句柄走天下”的风格完全不同。ODBC把语句、连接、环境分成了三层但实际用起来经常一个SQL执行错误你根本不知道是该查语句句柄还是连接句柄。dmoci这套OCI风格的错误处理反而更精准几乎每个API都会接收一个错误句柄作为入参错误码和错误信息直接写入这个句柄。排查问题的时候顺着错误句柄一路查信息非常直白。1.2 64位版本到底解决了什么问题现在这个时间点还在纠结32位还是64位的多半是已经被内存撞爆过的人。32位进程用户态地址空间也就2GB稍微开点缓冲、存点大对象就顶不住。dmoci的64位版本从底层数据类型上做了区分指针、句柄、长度参数全部按64位宽度声明不再有4字节/8字节混用的事情。具体到写代码的时候有几个地方必须留意首先是OCIEnvCreate这类函数的参数里凡是传指针的一律改用size_t或者指针类型不要图省事强转成int。其次是所有缓冲区长度变量我们一般直接定义为ub4或者sb4这种专用类型而不是unsigned int。最后是动态分配的内存块比如用来存大字段的缓冲区分配大小尽量用64位整型计算避免在极端场景下长度溢出。页不能说64位就是快但至少数据量大了之后内存管理明显从容了至少不用担心“内存不够用”这种低级问题。1.3 为什么最终选了dmoci而不是其他方案项目里不是没有备选方案。ODBC、JDBC这些通用接口也考虑过还有个开源方案FreeTDS我们也试过一版。最后选dmoci核心原因有两条。第一是部署形态足够简单——一个头文件加一个动态库就完了不像ODBC那样还要配数据源名称DSN在客户的机器上少了一层环境变量和注册表的折腾。第二是连接参数可控性强DMSERVER、端口、用户名、密码全部作为参数传入改库换环境非常灵活。Oracle OCI的另一个好处就是它不强绑定驱动管理器。你在Windows上编译出来的程序拷到另一台装有对应运行时组件的机器上点开就能跑。对于做工具类程序或者集成到工控软件里的场景这种省事的部署方式太重要了。2. 环境准备与64位编译链配置这一步卡住了后面白搭2.1 拿到dmoci开发包之后先确认这几样东西先说清楚dmoci不是凭空来的它是达梦数据库DM Database客户端开发体系里的一部分。所以第一步不是去搞什么乱七八糟的第三方包而是确认你本机有完整可用的DM客户端组件。一个可用的开发环境包括以下几样少一样后面都容易出幺蛾子DM数据库服务端或者至少一个能连上的远程实例官方提供的dmoci头文件通常是dmoci.h里面还引用了几个基础类型头文件与编译器位数匹配的导入库文件Windows下是dmsoci.lib这种Linux下是libdmoci.so运行时动态库dmsoci.dll或libdmoci.so如果你的机器上装了达梦数据库的客户端工具那这些文件大概率已经躺在安装目录里了。但要注意很多人装完客户端发现编译器链接时依然报找不到库十有八九是没把路径加进编译器的搜索目录或者用的库文件位数跟编译器不匹配。2.2 Windows下用VSCode配置64位C/C编译环境的思路网上全是“VSCode配置C/C环境”的教程但大都是hello world级别的没人提怎么跟数据库开发库联动。我的建议是不要用VSCode内置的默认task自己建一个独立的构建任务把include路径、lib路径和链接参数一次配好。举个例子我用mingw-w64的gcc编译器注意必须是x86_64版本的在.vscode/tasks.json里新建一个编译数据库访问小程序的任务大致参数是这种风格gcc -c main.c -I D:/dmdbms/include -o main.o gcc main.o -L D:/dmdbms/bin -ldmsoci -o dmtest.exe这里有个很容易被忽略的点dmoci的导入库文件可能不叫dmoci.lib而是dmsoci.lib或者libdmoci.a不同版本命名有差异。建议先去bin目录看一眼实际文件名再写链接参数。如果出现undefined reference之类的链接错误优先怀疑是这里的名字写错了。2.3 用Visual Studio MSVC的配置还有一个隐藏坑如果你更喜欢Visual Studio操作会更顺手一些。在项目属性里配置好“附加包含目录”和“附加库目录”之后链接器输入那里把对应的.lib文件填进去基本就能编译通过。但这里有个隐藏坑MSVC对运行时库的选择非常敏感。dmoci的64位动态库如果依赖某个版本的Universal C RuntimeUCRT而你项目选的是“多线程/MT”或者“多线程调试/MTd”运气不好就会在运行时出现初始化失败。我的做法是在这个场景下统一使用“多线程DLL/MD”也就是动态链接运行时库省得跟dmsoci.dll自身的运行时产生冲突。同时建议在部署目标机器上装上对应架构的Visual C Redistributablex64版本。别看这步不起眼真到了客户现场数据库驱动没装、VC运行库缺失程序起不来的情况我见太多了。3. 核心调用流程与代码实现从登录到取数一张流程就能讲完3.1 整个流程的骨架环境初始化、连接、执行SQL、取数、清理跟OCI一致dmoci的调用流程可以归纳成这么五步我直接用一个伪流程代码描述出来#include dmoci.h #include stdio.h #include string.h int main() { OCIEnv *envhp NULL; OCIError *errhp NULL; OCISvcCtx *svchp NULL; OCIStmt *stmthp NULL; sword status OCI_SUCCESS; char errbuf[512] {0}; sb4 errcode 0; // 第一步初始化环境句柄 status OCIEnvCreate(envhp, OCI_DEFAULT, NULL, NULL, NULL, NULL, 0, NULL); if (status ! OCI_SUCCESS) { printf(OCIEnvCreate failed\n); return -1; } // 第二步分配错误句柄和服务上下文句柄 OCIHandleAlloc((dvoid *)envhp, (dvoid **)errhp, OCI_HTYPE_ERROR, 0, NULL); OCIHandleAlloc((dvoid *)envhp, (dvoid **)svchp, OCI_HTYPE_SVCCTX, 0, NULL); // 第三步直接登录数据库 status OCILogon2(envhp, errhp, svchp, (const OraText *)SYSDBA, 6, (const OraText *)SYSDBA, 6, (const OraText *)192.168.1.10:5236, 18, OCI_DEFAULT); if (status ! OCI_SUCCESS) { OCIErrorGet((dvoid *)errhp, 1, NULL, errcode, (OraText *)errbuf, sizeof(errbuf), OCI_HTYPE_ERROR); printf(login failed: %s\n, errbuf); return -1; } // 第四步分配语句句柄并执行SQL这里省略了绑参等细节 OCIHandleAlloc((dvoid *)envhp, (dvoid **)stmthp, OCI_HTYPE_STMT, 0, NULL); OCILogon2已经帮你建立了连接接下来直接OCIStmtPrepare OCIStmtExecute // 第五步清理句柄释放资源 OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT); OCILogoff(svchp, errhp); OCIHandleFree((dvoid *)svchp, OCI_HTYPE_SVCCTX); OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR); OCIHandleFree((dvoid *)envhp, OCI_HTYPE_ENV); return 0; }这个流程把这套库的根本逻辑说得很清楚先有环境再有错误句柄然后通过服务上下文建立连接在连接之上创建语句语句处理完逐级逆序释放。我见过有人在程序里反复调用OCILogon2建立连接最后忘记OCILogoff数据库连接数直接被打满。这种问题找半天都想不到是句柄没释放。3.2 参数绑定与结果集读取这里是最容易写出bug的地方上面只演示了连接和直执行SQL真正干活的时候肯定要带参数要取结果集。dmoci的参数绑定沿用了OCIBindByPos的套路按占位符的位置绑定变量。例如下面这条带占位符的SQLinsert into user_info (id, name, age) values (:1, :2, :3)对应的绑定代码思路是这样的OCIBind *bndp[3] { NULL, NULL, NULL }; int id 1001; char name[50] dmoci_test; int age 28; OCIBindByPos(stmthp, bndp[0], errhp, 1, (dvoid *)id, sizeof(id), SQLT_INT, NULL, NULL, NULL, 0, NULL, OCI_DEFAULT); OCIBindByPos(stmthp, bndp[1], errhp, 2, (dvoid *)name, sizeof(name), SQLT_STR, NULL, NULL, NULL, 0, NULL, OCI_DEFAULT); OCIBindByPos(stmthp, bndp[2], errhp, 3, (dvoid *)age, sizeof(age), SQLT_INT, NULL, NULL, NULL, 0, NULL, OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 1, 0, NULL, NULL, OCI_DEFAULT);查询语句的取数过程则是另一个套路先定义结果集的列到C变量int id_out 0; char name_out[50] {0}; int age_out 0; OCIDefine *defp[3] { NULL, NULL, NULL }; OCIDefineByPos(stmthp, defp[0], errhp, 1, (dvoid *)id_out, sizeof(id_out), SQLT_INT, NULL, NULL, NULL, OCI_DEFAULT); OCIDefineByPos(stmthp, defp[1], errhp, 2, (dvoid *)name_out, sizeof(name_out), SQLT_STR, NULL, NULL, NULL, OCI_DEFAULT); OCIDefineByPos(stmthp, defp[2], errhp, 3, (dvoid *)age_out, sizeof(age_out), SQLT_INT, NULL, NULL, NULL, OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 1, 0, NULL, NULL, OCI_DEFAULT); while (OCIStmtFetch2(stmthp, errhp, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT) OCI_SUCCESS) { printf(id%d name%s age%d\n, id_out, name_out, age_out); memset(name_out, 0, sizeof(name_out)); }这里有个经验之谈绑定的变量生命周期必须覆盖整个执行过程。我试过在某个函数里绑定了一个局部std::string函数返回后SQL才真正执行结果取出来的全是乱码。这个问题排查了将近一下午最后才发现是变量提前析构了。3.3 64位程序里那两个特定的内存细节我专门把64位单独拿出来说是因为它确实坑过不少人。第一个细节是在64位模式下dmoci里跟长度、大小相关的参数类型比如r4type、sb4这类如果你在代码里直接用了int或者long在某些编译环境下会踩到类型宽度不匹配的问题。Windows的long是4字节而Linux的long是8字节跨平台编译时这种差异极容易引发问题。我的建议是所有跟OCI函数打交道的长度参数、大小参数一律用库里定义的类型。一眼看上去是多了几个字符但跨编译器、跨平台编译的时候能省无数排查时间。第二个细节是处理大字段比如CLOB/BLOB时缓冲区大小别用固定4KB这种小值。64位环境下虚拟内存空间充足建议至少分配1MB的读取缓冲区。dmoci对大对象的读写是分批进行的缓冲区越大分段读写次数越少性能提升非常明显。4. 常见问题与排查技巧实录这些坑都是真金白银踩出来的4.1 链接期报错LNK2019、LNK2038到底是谁的锅在Windows上用MSVC编译时最常见的链接错误就是LNK2019无法解析的外部符号和LNK2038运行时库不匹配。遇到这两类错别急着去翻代码逻辑先检查链接参数。LNK2019基本可以锁定为两个原因要么lib文件路径没配对要么函数名的大小写或调用约定cdecl vs stdcall不一致。我建议先在工程里把附加依赖项写成全名比如dmsoci.lib别用#pragma comment(lib, dmsoci.lib)这种隐式方式至少排错的时候能一眼看清楚是不是所有需要的库都加进去了。LNK2038是MSVC特有的“运行时库不匹配”报错。dmoci的64位动态库如果依赖的是MD动态多线程运行时你项目却用了MT静态多线程直接报这个错。解决方案就是在“项目属性 - C/C - 代码生成 - 运行时库”里统一改成“多线程DLL/MD”同时注意Debug/Release的配置都要改。4.2 运行时初始化失败或连接超时先查环境变量和端口程序编译通过了双击运行却弹窗说“初始化DMSOCILib失败”或者直接给个连接超时的提示。这种问题我在现场遇到过好几回原因基本上逃不过这三类dmsoci.dll不在系统搜索路径中。解决方法是把dmsoci.dll所在目录加到PATH环境变量或者直接把dll拷到exe同目录。目标数据库的端口不通。达梦默认端口是5236但有人改过。排查时用telnet或者PowerShell的Test-NetConnection测一下端口。客户端与服务端版本不匹配。dmoci是绑定版本发布的拿新版客户端连老版本服务端有时会握手失败尽量保证主版本一致。4.3 中文乱码与编码混乱一个字符集问题引发的血案搞国产数据库的项目几乎没有不碰中文乱码的。dmoci默认的字符集跟服务端的初始化参数有关你程序里写死的中文字符串如果编码与服务端不一致查出来就能看见一堆“????”。我的建议是在连接之后显式确认或设置字符集。dmoci提供了类似CLIENT_CHARSET的参数可以用UNICODE或者GBK。写代码时统一用UTF-8保存源文件执行SQL之前先把中文字符串转成服务端预期的编码取数据时再转换回来。千万别指望数据库自动帮你做转换底层接口不会替你处理这种东西。4.4 32位dll和64位exe混用这种事情真的别再犯了这个坑说起来有点蠢但我见过不止一次链接的时候用的是64位的dmsoci.lib程序跑起来之后却从一个只装了32位客户端的机器上找dmsoci.dll。这就直接导致程序启动失败报0xC0000005之类的错误。排查方式很简单用dumpbin或者Dependencies工具看一下dll的机器类型确认是x64的就别再往32位程序的目录里拷。如果你是在同一台机器上同时装了32位和64位的DM客户端那更要小心PATH环境变量的优先级。系统路径里到底先找到哪个dll直接决定你的exe能不能跑起来。这个问题的排查手段是写一个小程序启动时打印出LoadLibrary找到的dll绝对路径一眼就能看出问题。5. 还有几个值得留意的点算是给后来人的额外提醒到这里dmoci开发库的集成路线已经算是完整了。不过在实际动手之前我还想再分享几个从项目里沉淀下来的判断和取舍。第一dmoci的上手曲线比ODBC略微陡峭但理解句柄模型之后后续维护会非常痛快。它的错误处理机制非常明确每步操作都绑定一个错误句柄靠OCIErrorGet就能拿到完整错误信息不用像某些库一样到处打日志猜原因。第二如果只是做简单的数据库工具你当然可以绕开dmoci去选别的方案但一旦涉及高性能、高频读写、大批量绑定这种场景这种OCI风格接口的优势就出来了。它函数粒度细、控制力强你能精细管理游标、批量提交间隔和事务边界。最后再给一条非常实际的建议不要一上来就照着网上的教程把各种参数抄一遍。先在本地搭好一个最小环境写好连接和建表查询的demo跑通一遍全流程再往里面加业务逻辑。我见过太多人在正式业务代码里调了半天连接不上最后发现是第一步的环境初始化就写错了。先把地基打牢后面就顺了。如果你在集成dmoci的过程中碰到什么让我没说到的怪问题欢迎带着你的报错信息来交流。C/C这条路本来就是踩坑填坑的循环多分享几次大家都能少走点弯路。本文还有配套的精品资源点击获取
返回列表