Qt程序中调用C#编写的dll

最近在使用Qt整合以前的一个工具时,发现有几个dll是采用C#写的,当然可以把C#写的dll重新在Qt中用C++写,虽然这是几个C#写的dll的函数接口使用的参数都比较简单,只用了int和string类型等,但在函数内部却还使用了C#的专用一些类,如果重写还是很麻烦,就查找了一些文章,多数都是说采用把C#写的dll采用COM注册方式让Qt调用,但是这样又要重新编译以前的C#项目,实在是很麻烦,所以是否有种方式可以直接利用以前C#写的dll呢?

C#写的dll是没有dllMain入口函数的,是一种中间语言,需要.Net运行时进行做本地化工作,因此如果要调用C#写的dll,需要依赖.Net运行时,然而Qt中还无法直接调用.Net运行时,最好的方式是能够在Qt中直接调用C#dll的函数,但是Qt明显只能调用C++写的dll,所以就只能通过编写一个C++的dll导出接口供Qt调用,这个C++编写的dll对C#写的dll进行封装,这个C++的dll可以采用/CLR方式对C#编写的dll进行引用的,即将C++编写的dll中生成的.lib文件供Qt进行链接,由于该接口符合C++规范,所以Qt可以链接到对应的C++编写的dll。为此,写一篇Demo,希望对查找该资料的人有帮助吧。demo的过程如下:

编写环境:Win7,Qt5.7.1(MSVC2013_x86),VS2013

1.打开VS2013,新建一个C#的Class Library项目(这里选择的是.Net Framework 4),项目名为CSharpDll

Qt程序中调用C#编写的dll

2.由于默认没有引入Forms等UI库,先在reference中添加引用System.Windows.Forms以便可以在测试中使用MessageBox等

Qt程序中调用C#编写的dll

Qt程序中调用C#编写的dll

3.最终C#编写的dll如下图,命名空间为CSharpDll,公共类为CSharpClass

里面包含一个加法add,一个减法substract(为了测试指针,所以在减法的返回类型是void,而把计算结果通过ref参数c给返回),一个showBox方法(里面采用C#的MessageBox对话框显示用户输入的参数字串)

Qt程序中调用C#编写的dll

4.对project进行release build,在release目录下生成了CSharpDll.dll(待会用到)

Qt程序中调用C#编写的dll

5.关闭CSharpDll项目,另外新建一个C++ CLR类型的Class Library项目(选择与C#项目相同的.Net Framework 4),项目名称为CppDll

Qt程序中调用C#编写的dll

6.选择Project->CppDll Properties...,在弹出的属性页面选择“Add New Reference..”,点击“browsing.”后选择CSharpDll项目中release目录下的CSharpDll.dll

Qt程序中调用C#编写的dll

Qt程序中调用C#编写的dll

7.选择CSharpDll.dll后,可以看到在项目属性的References中出现了CSharpDll这个Library

Qt程序中调用C#编写的dll

8.在CppDll项目中的CppDll.h中利用__declspec(dllexport)导出对应的3个接口函数add,substract,showBox,这里为便于区分,前面加上api_前缀对这3个函数封装。需要using namespace System::Reflection,对于这里的api_showBox方法,其参数不能采用CSharpDll里面的showBox参数的string类型,而是使用const char* 类型

Qt程序中调用C#编写的dll

9.选择release方式build CppDll项目,在release文件夹中生成了CppDll.lib和CppDll.dll文件,可以看到同时其也将引用的CSharpDll.dll也给拷贝到release文件夹中了

Qt程序中调用C#编写的dll


10.接下来在Qt中进行调用,在QtCreator中新建一个TestCSharpDll项目(MSVC2013 32bit)

Qt程序中调用C#编写的dll

11.将CppDll.lib拷贝到Qt的TestCSharpDll项目文件夹内

Qt程序中调用C#编写的dll

12.在mainwindow.ui中,设计调用api_add函数,api_substract函数和api_showBox函数的界面

Qt程序中调用C#编写的dll

13.分别对add,sub和showBox按钮的clicked信号进行槽函数编写,其调用对应的api_add,api_substract和api_showBox函数,需要注意两点:

第一点是需要在mainwindow.cpp文件前通过#pragma comment(lib,“lib文件的绝对路径”),这里的lib文件的绝对路径需根据CppDll.lib的实际位置进行设置(在这里本机上为'E:/SourceCode/Qt/TestCSharpDll/CppDll.lib')(绝对路径的分隔符最好采用'/',而不是'\')

第二点是需要在开始处通过 __declspec(dllimport)的方式将导出函数进行声明,链接器会在CppDll.lib中去查找这3个函数的动态链接库dll的入口                                                                                           Qt程序中调用C#编写的dll

14.选择Build对Qt中的TestCSharpDll项目进行Debug构建,将CSharpDll.dll和CppDll.dll拷贝到项目生成的Debug目录中

Qt程序中调用C#编写的dll

15.执行TestCSharpDll.exe,输入add函数的两个加法参数,substract函数的两个减法参数,以及输入showBox的QString参数(这里是QString,在槽函数里面会通过QString的toLocal8Bit().data()函数转换为char*参数)进行测试,可看出调用成功,并且在showBox中,弹出的对话框标题是CSharpDll中写的“这是C#的MessageBox”,而在内容中则是Qt中输入的“这个字串是Qt界面输入的”

Qt程序中调用C#编写的dll

Qt程序中调用C#编写的dll

Qt程序中调用C#编写的dll

16.后记:Qt中实际上是通过链接中间层C++ CLR编写的.lib,而C++ CLR可以直接调用C#的dll,因此需要在Qt与C# dll中构建一个中间层,这个中间层既能被Qt中的C++代码进行链接,同时该中间层又能访问C# dll中的函数。所以最终Qt生成的项目中同一目录下需要CppDll.dll和CSharpDll.dll。

自然会有人想到如果将CppDll项目采用生成静态库static library(.lib)形式,通过在Qt中链接该静态库,最终运行Qt程序时就可以不用在同一目录下放置CppDll.dll,而只需要放置CSharp.dll即可。理论上是这样没错,但是经过尝试发现Qt还是无法通过静态链接CppDll的方式。

具体实际原因不太确定,不过我猜原因可能是,Qt程序是纯native c++代码(unmanaged代码),而CppDll.dll和CSharpDll.dll是managed代码,因此Qt程序通过动态链接到CppDll.dll时,Qt程序本身是以native c++代码执行,而在调用CppDll.dll中的代码时,CppDll.dll中代码的加载是以.Net运行时进行托管,也即CppDll.dll被加载时,是被.Net Framework进行host启动的,然后CppDll.dll中的代码可以进一步访问CSharpDll.dll(此时CSharpDll.dll也是被.Net Framework进行host启动的)。所以如果在Qt程序中将CppDll进行静态链接,其生成的Qt程序中最后必然包括maneged代码(即Qt程序中既有native又有managed代码),最终就会造成Qt程序在运行中无法明确其启动方式(按navtive启动,里面的执行都是navtive方式,遇到managed代码,无法正常调用;如果按.Net进行host启动,则其native代码无法被.Net host识别),从而导致链接失败,当然,至于是否有其他方式绕过managed和unmanaged的限制并能以这种静态链接方式进行构建,还需进一步查阅了。同时在将managed代码编译时,编译器选项需要指定'/clr',然而这个'/clr'与qt需要的编译指令'/EH'冲突,无法兼容,所以也无法静态链接。

不过,估计这种静态链接的做法也应该没多少必要性了,无论是从维护角度还是运行角度。

相关源码下载:链接: https://pan.baidu.com/s/1vuns3yKiqD-CFj4ZXM116w 密码: i6p2