在Windows平台上的Qt分为MSVC和Mingw版
上篇为MSVC版的Qt进行调用,有网友留言和发邮件询问MingW版的Qt能否使用上篇“Qt程序中调用C#编写的dll”的那些调用方法。
首先,Qt本身也是用C++的,因此问题实质上是Mingw版的C++如何调用MSVC版C++编写的dll。
本文介绍两种常规方式。
一.通常MSVC写的dll里面的导出接口如果是普通C++编译方式,函数名称会变更(如add函数会变为 ?add7Z@N 之类),MingW里面resolve时可以直接就按变更后的接口进行如QLibrary.resolve("?add7Z@N"),这虽然算一种方法,但会不会给人一种乱码感觉?
二.MingW和MSVC的C调用接口是兼容的,C++接口需要特殊转换重构才可以实现dll互相调用(这个问题展开就有点大篇幅了,本文只针对Qt里面快速调用C#编写的dll而言),在这里C调用接口需要用到extern "C" 进行导出。(在导出后可以通过在mingw版的Qt通过链接该lib文件;或者extern "C"导出后继续采用上面第一种方式进行resolve,此时的resolve就可以直接用函数名,而没有两端的多余的?@N之类的符号了)
下面分别介绍两种方式的做法,针对两种方法写一篇Demo,希望对查找该资料的人有帮助吧。demo的过程如下:
编写环境:Win7,Qt5.3.2(mingw_x86),VS2013
1.打开VS2013,新建一个C#的Class Library项目(这里选择的是.Net Framework 4),项目名为CSharpDllMingW(与前篇相比,后面加了个MingW以示区分)
2.由于默认没有引入Forms等UI库,先在reference中添加引用System.Windows.Forms以便可以在测试中使用MessageBox等
3.最终C#编写的dll如下图,命名空间为CSharpDllMingW,公共类为CSharpClassMingW
里面包含一个加法add,一个减法substract(为了测试指针,所以在减法的返回类型是void,而把计算结果通过ref参数c给返回),一个showBox方法(里面采用C#的MessageBox对话框显示用户输入的参数字串)
4.对project进行release build,在release目录下生成了CSharpDllMingW.dll(待会用到)
5.关闭CSharpDll项目,另外新建一个C++ CLR类型的Class Library项目(选择与C#项目相同的.Net Framework 4),项目名称为CppDllMingW(与前篇相比,后面加了个MingW以示区分)
6.选择Project->CppDllMingW Properties...,在弹出的属性页面选择“Add New Reference..”,点击“browsing.”后选择CSharpDllMingW项目中release目录下的CSharpDllMingW.dll
7.选择CSharpDllMingW.dll后,可以看到在项目属性的References中出现了CSharpDllMingW这个Library
8.在CppDll项目中的CppDll.h中利用__declspec(dllexport)导出对应的3个接口函数add,substract,showBox,这里为便于区分,前面加上mingw_和c_mingw前缀对这3个函数封装。需要using namespace System::Reflection,对于这里的mingw_showBox和c_mingw_showBox方法,其参数不能采用CSharpDll里面的showBox参数的string类型,而是使用const char* 类型。
注意,这里采用了两种方式进行导出(为了让resolve或者.lib链接方式均能使用该dll),其中一组是mingw_前缀(普通导出,即C++编译器按照C++方式改变导出后的函数名,其会添加如?@N之类),与前篇博文相同;另外一组是c_mingw_前缀(extern "C"导出,即C++编译器按照C调用方式导出,导出后的函数名不变),其前面加了个extern "C"。
即两组导出,(1)其中一组普通导出,是为了Mingw Qt中使用resolve方式进行调用(resolve采用函数名可能会是类似?mingw_add@@YAHHH@Z)(当然另外一组的extern "C"导出也能使MingW Qt进行resolve调用,此时的resolve可以直接采用函数名如c_mingw_add);(2)另外一组采用extern "C"导出,这一组的导出可以通过在mingw Qt中链接到.lib文件进而直接调用
9.选择release方式build CppDllMingW项目,在release文件夹中生成了CppDllMingW.lib和CppDllMingW.dll文件,可以看到同时其也将引用的CSharpDllMingw.dll也给拷贝到release文件夹中了
10.接下来在Qt中进行调用,分两种:
(一).Resolve方式(动态load)
在QtCreator中新建一个TestCSharpDllMingW_Resolve项目(Qt5.3.2版本的mingw 32bit),选择mingw
通过dumpbin工具可以查看CppDllMingW.dll导出的两组共计6个函数接口,可看出mingw_前缀的加入了C++编译器改名的部分,而c_mingw前缀的则保持不变。
在TestCSharpDllMingW_Resolve工程中通过typedef定义函数指针,同时采用QLibrary动态加载并resolve函数
在这里.dll的路径设为当前目录下“./CppDllMingW.dll”,也就是编译好的程序exe同一目录下的dll,其中一个去resolve由普通导出方式的接口即“?mingw_add@@YAHHH@Z”,另一个去resolve由extern "C"导出的接口即“c_mingw_substract”
编译TestCSharpDllMingW_Resolve工程,将CppDllMingW.dll和CSharpDllMingW.dll复制到同一目录下
执行TestCSharpDllMingW_Resolve.exe,可看出点击按钮后,通过QLibrary进行动态resolve,均正常调用
(二).extern "C"的.lib文件链接方式
在Qt中新建TestCSharpDllMingW_ExternC工程,首先将CppDllMingW.lib文件拷贝到TestCSharpDllMingW_ExternC项目下
在相应的位置采用extern "C" __declspec(dllimport)进行导入,链接器会在CppDllMingW.lib中去查找这3个函数的动态链接库dll的入口 (注意只能导入c_mingw前缀部分,在CppDllMingW项目里面只对c_mingw前缀的函数进行了extern "C'导出,这里与CppDllMingW里面的导出接口一一对应)
在引入.lib文件时,这里不能采用前篇博文的#pragama进行引入.lib文件(mingw不支持该命令,会直接忽略掉),而是通过LIBS命令或在项目上根据向导加入,这里采用向导方式,在TestCSharpDllMingW_ExternC工程上右键,选择add Library
选择external library
浏览选择TestCSharpDllMingW_ExternC工程目录下的CppDllMingW.lib文件
然后只选择windows平台,动态链接,不要勾选“add 'd' suffix for debug version”
可以看出其实际上最后是在TestCSharpDllMingW_ExternC.pro文件最下面加入了下图的三行
编译,生成TestCSharpDllMingW_ExternC.exe(此时通过链接的.lib文件找到了对应的dll入口),同时将CppDllMingW.dll和CSharpDllMingW.dll拷贝到同一目录下
运行TestCSharpDllMingW_ExternC.exe,可看出正常调用。
后记:最好是将相关dll置于同一目录下运行,不然会出现“未能加载文件或程序集”的异常。针对.lib链接方式,理应是置于同一目录下。而针对QLibrary进行resolve方式,可能通常一开始的想法是,CppDllMingW.dll和CSharpDllMingW.dll放在与程序不同目录的地方,程序中利用了QLibrary指定了CppDllMingW.dll的方式进行加载,而CppDllMingW.dll和CSharpDllMingW.dll,因此程序调用CppDllMingW.dll里面的函数时,CppDllMingW.dll会找到与CppDllMingW.dll同一目录下的和CSharpDllMingW.dll,然而CppDllMingW.dll在被程序进行加载时,其继承了程序的环境变量,因此会从程序的当前目录下去查找,所以最好还是将CppDllMingW.dll和CSharpDllMingW.dll放置于程序同一目录下,同时QLibrary加载当前目录下的CppDllMingW.dll。当然,部署到另外一台机器上时,目标机器还是需要安装.Net Framework,其版本至少不能低于当前CSharpDllMingW.dll所使用的版本。
相关源码下载链接: https://pan.baidu.com/s/1N7a0WgGohRiYPA3cXzEvOw 密码: 9i1s