多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Qt连Oracle 11g卡在QOCI驱动?32位环境编译与部署全攻略

Qt连Oracle 11g卡在QOCI驱动?32位环境编译与部署全攻略 简介针对32位Windows系统下使用QT 5.13框架开发桌面程序并需要访问Oracle 11g数据库的场景这份压缩包给出了完整的MSVC版QOCI数据库驱动及配套依赖可解决驱动未预编译、OCI客户端库配置繁琐、运行时报缺少动态链接库等实际问题。包内文件数量为51个压缩包大小约60.63MB主要包含头文件、动态库、导入库、符号文件与说明文档头文件用于声明数据库接口的函数和数据结构导入库负责编译链接阶段的符号解析动态库支撑程序运行时的真正调用符号文件为异常崩溃时的调试定位提供线索说明文档则给出关键配置步骤与注意事项整体结构清晰便于按需提取。目前已有363人学习下载适合在QT中实现数据库增删改查、需要快速对接Oracle的企业应用开发者。获得这套驱动后无需再手工编译qsqloci直接配置好路径即可让QSqlDatabase连接Oracle 11g完成数据查询与事务操作同时也能避开MSVC与MinGW混用带来的兼容性问题减少环境调试时间让数据库接入更稳妥。1. 为什么Qt连Oracle11卡在驱动上先分清QOCI、Oracle Client和位数第一次用Qt5.13写程序连Oracle11g我以为和MySQL一样装个驱动就能跑。结果程序启动就提示“QMYSQL driver not loaded”之类的错当时连的是Oracle报的是QOCI才意识到Qt连Oracle的驱动机制完全是另一套Qt官方只有QOCI插件插件本质是个壳真正干活的是Oracle自带的客户端库。也就是说你光把qsqloci.dll拷到plugins/sqldrivers里没用还得有对应位数的Oracle Client依赖库而且Qt编译器、QOCI插件、Oracle Client三者必须是同一套位数。这个资源就是帮你在32位MSVC环境下一次性配齐这套依赖。适合那些用Qt5.13 MSVC2017 32位写数据管理工具、ERP客户端或者维护老项目的开发者。搞明白这套流程你至少不会在驱动加载上反复浪费一整天。2. 搭建32位连接环境Qt版本、编译器与Oracle Client选型2.1 确认你的Qt确实是MSVC 32位而不是MinGW或64位很多人在第一步就栽了。打开Qt Creator在“工具 → 选项 → Kits”里看编译套件。如果显示的是“Qt 5.13.0 MSVC2017 32bit”那说明你用的是MSVC2017 32位工具链这套资源和你的环境是对口的。如果显示的是“MinGW 32bit”那不好意思这个资源不一定能用因为MinGW版的Qt虽然在语法上兼容MSVC但C运行库不同生成的qsqloci.dll依赖的库和MSVC版本不匹配运行时依然会报“无法定位程序输入点”之类的错。如果是“MSVC2017 64bit”那也不用看这个资源了你需要的是64位版本。检查完Kit再确认你实际构建时用的哪个Kit。有些人Qt装了两个版本默认Kit是64位的结果一直加载不上驱动还以为是依赖缺失。我一般会在.pro文件里加一行message(Build with: $$QMAKE_COMPILER)编译时在“编译输出”面板里就能看到当前用的是哪套编译器。检查无误后再进入下一步。2.2 下载Oracle Instant Client 32位不要选错版本Oracle官方的Oracle Client有两种形态完整安装版和Instant Client轻量包。日常开发用Instant Client就够它不需要安装解压即用正好适合做依赖。你需要下载的是32位版本的Instant Client注意不是64位哪怕你的操作系统是64位的Win10只要Qt是32位就必须用32位的Oracle Client。这是最容易混淆的地方。版本上你不需要下载最新版Oracle 11g对应的客户端最低版本是11.2但客户端有向下兼容性用更高版本比如12.1或12.2也能连11g不过要小心字符集和NLS环境变量。我一般直接用11.2的Instant Client和11g数据库最匹配省得踩一些莫名奇妙的兼容性坑。下载到的压缩包解压后文件夹里应该有oci.dll、oraocci11.dll、oraociei11.dll等文件其中oci.dll就是QOCI驱动最核心的依赖。2.3 解压与路径规划别把依赖散落一堆解压Instant Client后有两种做法。第一种是把它放到某个固定目录比如D:\oracle\instantclient_11_2_32然后把该目录加入系统PATH。第二种是不加PATH直接把oci.dll这些文件复制到你的程序运行目录或者Qt的bin目录。我的建议是开发阶段用PATH方式因为Qt的驱动加载机制会去PATH里找oci.dll发布阶段用复制到运行目录的方式因为终端用户机器上不会有这个路径。路径规划时要注意如果之前装过Oracle完整客户端又加了PATH可能同时存在多个oci.dll系统会优先加载PATH中靠前的那个。如果你PATH里既有11g客户端又有12c客户端强烈建议把当前项目需要的路径放到最前或者干脆临时把其他路径注释掉。否则驱动加载的是旧版客户端行为会很诡异。配置完成后打开命令提示符输入where oci.dllWindows下确认能找到且指向正确的32位文件。如果where命令显示找不到说明PATH没生效如果显示的是64位客户端的路径说明PATH顺序有问题。这一步很关键最好养成习惯。2.4 验证Oracle Client本身可工作在继续之前建议先用Oracle自带的工具验证一下客户端环境。比如进入Instant Client目录执行sqlplus -version如果包里有sqlplus的话。很多Instant Client精简包不含sqlplus那就改用tnsping或者写几句Python调cx_Oracle验证。不过这又引入新的语言依赖。我在Windows上常用的方式是写一个C或Python小程序直接调用oci.dll里的OCIAttrGet函数——但这对新手不友好。更简单的办法在PATH正确配置后用Qt尝试加载驱动方式直接跳到第四章的测试代码。如果连QSqlDatabase::drivers()里都看不到QOCI那说明插件没有部署如果看到QOCI但连接报错再回头检查OCI依赖。所以你可以把2.4这一步当作可选但至少确认where oci.dll能命中对的文件才有底气继续做驱动编译。3. 正式编译QOCI驱动手工编译与依赖搬运3.1 打开编译器命令行环境这一步不要用普通的cmd窗口必须用MSVC编译环境否则找不到qmake和nmake。安装Qt5.13时开始菜单里会有“Qt 5.13.0 → Qt 5.13.0 for Desktop (MSVC 2017 32-bit)”这样的快捷方式。打开这个命令行后先验证一下qmake的位置。执行qmake -v正常会输出类似于QMake version 3.1和Using Qt version 5.13.0 in D:\Qt\Qt5.13.0\5.13.0\msvc2017\lib的信息。注意路径里是msvc2017而不是msvc2017_64确认无误后进入Qt的QOCI源码目录。路径通常是cd D:\Qt\Qt5.13.0\5.13.0\msvc2017\src\plugins\sqldrivers\qsqloracle如果你的Qt安装时没有勾选源代码Sources那这个目录不存在。这种情况下资源包里通常会带上修改过的qsqloracle工程文件你可以把资源里的qsqloracle目录拷贝到自己的源码目录下再继续编译。这也是这个资源包最常见的用途之一——省去你下载Qt源码的麻烦。3.2 修改qsqloracle.pro文件指定Oracle客户端路径qsqloracle.pro是Qt的工程文件里面默认的OCI路径多半是/usr/include或/usr/lib在Windows上编译不过。你需要把它改成Instant Client实际路径。用记事本打开这个文件找到类似下面的内容unix { OCI_PLUGIN_PATH $$(ORACLE_HOME) ... } win32 { OCI_PLUGIN_PATH C:/oracle/instantclient_11_2 ... }如果没有win32块就手动添加。我的习惯是让路径硬编码为当前机器上的有效路径同时用环境变量OCI_PATH来保持灵活。改完后的关键部分如下win32 { OCI_PATH D:/oracle/instantclient_11_2_32 INCLUDEPATH $$OCI_PATH/sdk/include LIBS -L$$OCI_PATH -loci }这段代码的作用是告诉编译器头文件在OCI_PATH/sdk/include链接库在OCI_PATH并且链接oci.lib。如果你下载的Instant Client包里没有sdk/include子目录说明你下的是精简运行包还需要单独下载SDK开发包。一般来说完整版Instant Client会包含sdk文件夹如果没有就从资源包里找找是否有oci.h和occi.h。这个资源通常会把编译时所需的头文件和库文件也打包进去否则光有运行时DLL没法编译驱动。3.3 执行编译qmake到nmake改完pro文件后在当前qt命令行窗口里依次执行qmake qsqloracle.pro nmake如果运气好两三分钟后会在plugins/sqldrivers下生成qsqloci.dll。但通常没那么顺利最常见的报错是找不到oci.h。这多半是INCLUDEPATH路径写错了或者sdk/include目录里没有oci.h。遇到这种报错先检查路径里的大小写Windows下不区分但注意目录分隔符要统一用/。链接阶段的报错通常是LNK1104: cannot open file oci.lib。这是因为Oracle Instant Client的SDK目录里原本只有oci.dll而oci.lib可能要用工具生成。Qt的pro文件里-loci默认会去找oci.lib。如果在Instant Client目录里没找到可以这样生成dumpbin /exports oci.dll oci_exports.txt rem 或者用lib命令生成导入库 lib /machine:x86 /def:oci.def /out:oci.lib不过手工生成oci.def很麻烦。更省事的办法是在pro文件里直接改指名链接DLL。把LIBS -L$$OCI_PATH -loci改成LIBS $$OCI_PATH/oci.dll这样Qt会直接链接这个DLL文件不需要oci.lib。但这个技巧要配合QMAKE_LFLAGS实际操作时可能遇到MSB8003这类问题。我更推荐一种更简单的方式打开这个资源里可能已经预编译好的qsqloci.dll文件直接跳到3.5部署省掉编译这一步。3.4 编译成功后的插件产物确认如果编译顺利生成路径是D:\Qt\Qt5.13.0\5.13.0\msvc2017\plugins\sqldrivers\qsqloci.dll。不要急着把它拷走先检查它的依赖。用命令dumpbin /dependents qsqloci.dll输出里应该能看到oci.dll和msvcp140.dll之类的依赖项。如果看不到oci.dll说明链接的时候没连上可能是用-loci但没有实际确认导入库。如果能看到oci.dll那至少说明依赖关系是存在的。注意dumpbin是VS自带工具在Qt命令行里可能没有需要额外把VS的tools目录加进PATH或者直接打开“VS2017 x86 Native Tools Command Prompt”再进入Qt命令。3.5 复制依赖库到正确目录QOCI驱动最终要在运行时找到两个东西一是qsqloci.dll在Qt的plugins/sqldrivers目录下二是oci.dll在系统PATH或者Qt的bin目录下。我的习惯是为了开发调试方便把Instant Client目录里所有.dll文件复制到Qt的D:\Qt\Qt5.13.0\5.13.0\msvc2017\bin目录下。注意不是直接全拷有些如ociw32.dll可能不需要但多拷无妨只要不覆盖同名关键文件就行。复制完成后在Qt Creator里重新启动让环境变量生效然后写一个最简单的小程序验证驱动是否加载。如果这一步还报错那就是第四章要解决的坑。4. 部署时的避坑指南常见报错与排查路径4.1 报错“QSqlDatabase: QOCI driver not loaded”这是最典型的现象程序能启动但QSqlDatabase::drivers()列表里没有“QOCI”或者open()时抛出Driver not loaded。原因通常有两个插件本身没放到Qt的插件目录或者插件的依赖DLL缺失。前者很好检查确认qsqloci.dll在plugins/sqldrivers下后者的坑在于Qt插件加载失败时不会弹出缺DLL的对话框只在调试输出里打一行Cannot load library ...。你可以用Windows工具Dependencies或旧版Dependency Walker打开qsqloci.dll看它的依赖项是否能解析。如果显示oci.dll缺失说明复制遗漏了。解决把Instant Client目录下的oci.dll、oraocci11.dll、oraociei11.dll、msvcr100.dll等全部复制到Qt的bin目录。如果你确定所有依赖都在但依然报错就检查PATH顺序。有些机器上装过Oracle完整客户端PATH里先找到老版本oci.dll新版本被忽略。把Agent的Instant Client路径移到最前面再试。4.2 报错“OCIEnvNlsCreate failed: -1”或“ORA-12705”现象是驱动已经加载连接时返回一个奇怪的NLS错误。这个问题在Oracle 11g上常出现尤其是客户端字符集配置不对时。原因OCIEnvNlsCreate是OCI初始化时根据NLS_LANG环境变量创建环境句柄的函数返回-1通常意味着NLS_LANG设置的语言/字符集组合无效或者Instant Client缺少对应的字符集文件。比如你把NLS_LANG设成了SIMPLIFIED CHINESE_CHINA.ZHS16GBK但Instant Client精简包里没有zhs16gbk的字符集数据文件就会失败。解决先不要设置NLS_LANG或者在连接前用代码统一指定在程序启动处加一句qputenv(NLS_LANG, AMERICAN_AMERICA.AL32UTF8)。如果客户端本身不支持AL32UTF8就换成US7ASCII。另外确认你的Instant Client包是否完整有些“lite”包不含全部字符集遇到生产环境的老库时相当折腾。我一般建议下载Instant Client时选择“Full”版本体积大一点但字符集文件齐全能省掉以后很多乱码和心理问题。4.3 32位和64位混用最隐蔽的翻车点很多人在部署时认为“我的系统是64位所以Oracle Client也得用64位”结果Qt是32位加载64位的oci.dll时Windows 64位系统下的32位进程无法加载64位DLL程序会直接崩溃或者报“应用程序无法启动”。反过来如果在64位Qt下加载32位oci.dll同样不行。还有一个隐蔽坑即使Qt是32位但你在命令行里手动设置了PATH指向64位客户端然后启动32位程序进程依然会尝试加载64位DLL。Windows的DLL搜索顺序里PATH排在应用程序目录后面但如果你没有把应用程序目录下的oci.dll复制过去它就会去PATH找。所以排查时一定要确认当前进程实际加载的oci.dll路径。用Process Explorer或ListDLLs查看进程加载的模块确认路径中包含execute32这种标记或者直接看路径是x86目录还是x64目录。我曾经遇到过一台机器上同时装了32位和64位客户端程序明明在x86目录下却把x64的一个DLL加载了进来原因就是某个第三方库在初始化时动态改动了PATH。解决统一位数是唯一出路。如果你必须用32位Qt那就强制所有Oracle依赖都是32位。发布时在你的程序exe同级目录下放一套32位Instant Client并且在代码里用SetDllDirectoryWindows API把这个目录指定为DLL搜索路径优先。注意要调用SetDllDirectoryW并且传入绝对路径防止被PATH干扰。4.4 发布程序时到底要拷贝哪些文件开发环境能连上不等于发布到客户机器上还能连。很多开发者只拷贝了exe和Qt的sqldrivers插件结果客户的机器上没有Oracle客户端直接报“找不到oci.dll”。这里我给出一份我实际部署过的最小文件清单你的exe、Qt目录下的plugins\sqldrivers\qsqloci.dll、platforms\qwindows.dllWindows平台插件以及Instant Client目录下的oci.dll、oraocci11.dll、oraociei11.dll、ocijdbc11.dll、orannzsbb11.dll、oraons.dll、msvcr100.dll、msvcp100.dll如果之前没装过VC运行库。注意有些Instant Client版本带的是vc14或vc15运行库比如msvcp140.dll你需要把Instant Client目录下所有vcruntime、msvcp后缀的DLL都拷上别吝啬体积。另一个容易漏的是时区文件。如果你的程序要处理时间字段而客户机器没有timezone目录会报“ORA-01882”。所以发布时直接把整个Instant Client目录拷贝过去最省心大约一两百MB但对现在的硬件不算什么。拷贝时记得保持目录结构不要为了省空间只挑几个DLL。5. 用最小测试程序验证连接并让你的驱动部署一次到位5.1 两分钟测通QOCI的最小代码无论驱动编译多顺利最终都要用一个最小的连接测试来验证。在Qt Creator里新建一个控制台项目main.cpp写#include QCoreApplication #include QSqlDatabase #include QSqlQuery #include QSqlError #include QDebug int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() Available drivers: QSqlDatabase::drivers(); QSqlDatabase db QSqlDatabase::addDatabase(QOCI, oracle_test); db.setHostName(192.168.1.100); db.setPort(1521); db.setDatabaseName(ORCL); db.setUserName(scott); db.setPassword(tiger); if (!db.open()) { qWarning() Open failed: db.lastError().text(); return 1; } qDebug() Opened successfully!; QSqlQuery query(db); query.exec(SELECT 1 FROM DUAL); if (query.next()) qDebug() Query result: query.value(0).toInt(); return 0; }这段代码里setDatabaseName里的ORCL是Oracle的实例名或服务名不是数据库文件名。如果你的Oracle是服务名写host:port/service_name的格式比如192.168.1.100:1521/orclpdb。在连接之前务必保证QSqlDatabase::drivers()的输出里包含QOCI。如果没有回到第二章检查插件目录如果有但连接失败查看lastError()的文本它会直接告诉你原因。5.2 设置连接超时与NLS参数在实际项目里客户端直接连接一个不存在的IP时TCP层默认可能等几十秒甚至更久才报错。我习惯在连接前设置Oracle的网络超时。在Qt里可以通过OCI属性设置也可以简单地在sqlnet.ora里配置SQLNET.OUTBOUND_CONNECT_TIMEOUT10。sqlnet.ora放在Instant Client目录的network\admin下。另一个常用配置是SQLNET.RECV_TIMEOUT和SQLNET.SEND_TIMEOUT防止数据量大时卡死。NLS参数前面提过建议代码里统一设置qputenv(NLS_LANG, SIMPLIFIED CHINESE_CHINA.ZHS16GBK);注意这里的中文字符串编码在MSVC2017下源代码保存成UTF-8但qputenv接受的是本地编码。如果你在中文Windows上用GBK保存源代码这个字符串就是GBK字节没问题。如果用UTF-8保存就必须用QString(仿真模拟).toLocal8Bit()来转换否则乱码。这个坑很细节但造成了很多人字符集问题。5.3 一条命令完成驱动和依赖部署发布时手动复制文件既慢又容易遗漏。我后来写了一个批处理放在工程目录下双击就能把驱动和依赖部署到目标目录echo off set TARGET_DIR.\deploy set QT_PLUGINSD:\Qt\Qt5.13.0\5.13.0\msvc2017\plugins set ORACLE_CLIENTD:\oracle\instantclient_11_2_32 if not exist %TARGET_DIR% mkdir %TARGET_DIR% if not exist %TARGET_DIR%\sqldrivers mkdir %TARGET_DIR%\sqldrivers copy %QT_PLUGINS%\sqldrivers\qsqloci.dll %TARGET_DIR%\sqldrivers\ copy %QT_PLUGINS%\platforms\qwindows.dll %TARGET_DIR%\platforms\ copy %ORACLE_CLIENT%\*.dll %TARGET_DIR%\ rem 如果你的exe还在其他地方自行加一条copy echo Done.这个脚本的作用很直白把qsqloci.dll放到目标sqldrivers目录把平台插件放到platforms目录再把Oracle客户端所有DLL放到exe同级目录。注意platforms\qwindows.dll是GUI程序必需的控制台程序可以不要。但如果你就按这个脚本做那么程序发布时只需把整个deploy目录打包。还有个细节这个脚本不会拷贝qsqlite.dll等其它驱动如果你的程序还连了SQLite需要把对应插件也加进去。从那以后我每次发布Qt-Oracle项目都强制走一遍这个脚本至少没有再因为少拷一个DLL被客户打电话叫过去。希望这套方法能让你少踩几个坑顺利把Oracle连接搞定。本文还有配套的精品资源点击获取
返回列表