Qt调用C#动态库:从创建CLR包装到工程实现的完整指南
本文将详细介绍在Qt框架下调用C#生成的动态链接库(.dll)的完整流程。核心思路是通过Visual Studio中的CLR托管C++项目构建一个中间层,将C#代码转换为C/C++可识别的原生函数接口。教程涵盖环境准备、两层DLL的创建、关键文件的依赖配置以及Qt中的两种具体实现方案。
结果或目标
最终实现在Qt项目中,通过C++代码成功调用编译自C#的业务逻辑函数(如加法运算、字符串处理等),解决不同编程语言之间的二进制兼容性问题。
准备条件
- 开发环境: Visual Studio 2019 + Qt 5.12(版本需匹配,且VS需集成Qt插件)。
- 基础工具: Visual Studio Installer,用于检查组件安装状态。
- 语言规范: C# (用于核心逻辑) 和 Managed C++ / CLI (用于桥接)。
第1步:确认并安装 C++/CLI 支持
创建CLR项目的前提是必须安装对应版本的 MSVC v142 - VS 2019 C++ x64/x86 build tools 及其子选项。
- 打开 Visual Studio Installer。
- 找到已安装的 VS2019,点击修改。
- 在左侧菜单选择“使用C++的桌面开发”,然后在右侧的功能列表中找到:V142生成工具的C++/CLI支持。
- 勾选该选项,点击右下角的修改进行安装。
- 安装完成后,重启软件即可。
真实操作顺序
第2步:创建业务逻辑C#类库
首先创建一个标准的 .NET Framework C#类库,用于编写具体的业务功能(非托管代码)。本例假设项目名为 ClassLibrary1。
此时解决方案的视图应如下所示:
在 Class1.cs 文件中添加测试方法。我们模拟几个场景:整数加减法和字符串传递。
using System;
namespace ClassLibrary1
{
public class Class1
{
// 简单的数值计算
public int myAdd(int a, int b)
{
return a + b + 5; // 返回值带偏移量5
}
// 引用参数传递减法结果
public void mySub(int a, int b, ref int c)
{
c = a - b - 5;
}
// 接收字符串参数
public void mySetText(string text)
{
Console.WriteLine("C# Received: {0}", text);
}
// 输出字符串(通过ref返回)
public void myGetText(ref string text)
{
text = "Hello from C#";
}
}
}
编写完成后,生成项目。此时会在 bin\Debug 目录下得到 ClassLibrary1.dll。请将此文件保留好,它是后续步骤的依赖项。
第3步:创建CLR封装类库(桥接层)
C#程序集默认不能直接被Qt加载。我们需要创建一个CLR Class Library (.NET Framework) 项目作为“桥梁”。本例命名为 ClassLibrary2。
该项目的目的是将上一步的C#对象包装为 extern "C" 形式的原生函数导出。注意此时的解决方案视图:
关键在于头文件 ClassLibrary2.h 的写法。我们需要引用第一个项目,并使用 #using 指令。
#pragma once
#include <windows.h>
#include <msclr/marshal_cppstd.h> // 用于字符串转换的工具
// 引用第一步生成的C# dll
#using "../ClassLibrary1/bin/Debug/ClassLibrary1.dll"
using namespace ClassLibrary1;
// 定义导出的C接口函数
extern "C" __declspec(dllexport) int MyAdd(int a, int b)
{
ClassLibrary1::Class1 obj;
return obj.myAdd(a, b);
}
extern "C" __declspec(dllexport) void MySub(int a, int b, int *c)
{
ClassLibrary1::Class1 obj;
// 注意指针传递方式
int temp = 0;
obj.mySub(a, b, temp); // C#需要ref或out
if(c) *c = temp;
}
extern "C" __declspec(dllexport) void MySetText(const char* text)
{
ClassLibrary1::Class1 obj;
// 类型转换:const char* -> String^ -> std::string (若需存储)
pin_ptr<const wchar_t> wch = PtrToStringChars(gcnew String(System::Text::Encoding::UTF8->GetBytes(text)));
// 更简单的 marshal 方法
msclr::interop::marshal_as<String^> sStr = msclr::interop::marshal_as<String^>(text);
obj.mySetText(sStr);
}
extern "C" __declspec(dllexport) const char* MyGetText()
{
ClassLibrary1::Class1 obj;
String^ result;
obj.myGetText(result);
// 返回静态或全局缓存,注意内存管理
static char buffer[256];
array<byte>^ bytes = System::Text::Encoding::Default->GetBytes(result);
pin_ptr<char> pch = &buffer[0];
memcpy(pch, bytes->GetData(), bytes->Length);
buffer[bytes->Length] = '\0';
return buffer;
}
注:上述代码展示了概念流程。实际工程中推荐直接使用 msclr::interop::marshal_as 自动处理字符串转换。对于复杂返回值,建议采用输出缓冲区指针的方式避免生命周期问题。
关键风险提醒:依赖分发
生成 ClassLibrary2.dll 后,必须确保以下两个文件位于Qt程序的同一运行目录下,否则Qt无法加载时会导致崩溃:
ClassLibrary2.dll(包含C接口的主DLL)ClassLibrary1.dll(C#业务逻辑DLL,因为ClassLibrary2引用了它)
Qt调用实现
方法一:QLibrary 动态加载(灵活但需谨慎)**
这种方法不需要修改 .pro 配置文件,适合运行时决定是否加载DLL。
#include <QCoreApplication>
#include <QLibrary>
#include <QDebug>
#include <type_traits>
// 根据导出的C函数定义函数指针类型
typedef int (*ADD_FUNC)(int, int);
typedef void (*SUB_FUNC)(int, int, int*);
typedef void (*SHOW_FUNC)(const char*);
typedef const char* (*GET_FUNC)();
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QLibrary mylib("ClassLibrary2.dll"); // 假设在exe同级目录
if (!mylib.load()) {
qDebug() << "Failed to load library:" << mylib.errorString();
return -1;
}
// 获取函数地址
ADD_FUNC add = (ADD_FUNC)mylib.resolve("MyAdd");
SUB_FUNC sub = (SUB_FUNC)mylib.resolve("MySub");
SHOW_FUNC show = (SHOW_FUNC)mylib.resolve("MySetText");
GET_FUNC get = (GET_FUNC)mylib.resolve("MyGetText");
if (add) {
qDebug() << "Result of Add(3,2):" << add(3, 2); // Expected: 3+2+5=10
}
if (sub) {
int res = 0;
sub(10, 5, &res); // Expected: 10-5-5=0
qDebug() << "Result of Sub(10,5):" << res;
}
if (show) {
show("Hello World");
}
if (get) {
const char* str = get();
qDebug() << "Result of GetText:" << QString(str);
}
return a.exec();
}
方法二:链接静态库 .lib 并直接调用(更稳定)**
这是C++工程中的标准做法,利用VS生成的导入库(.lib)让编译器在链接阶段解析符号。
- 右键Qt项目 (
.qmake/CMakeLists.txt) → 添加库 → 外部库。 - 浏览并选择
ClassLibrary2.lib文件。 - 确认平台设置(通常选Windows)。
在代码中包含头文件并声明外部函数(或直接将头文件放入项目include路径):
#include <iostream>
#include <QString>
// 前置声明 (如果已将 .h 加入Qt Include 则无需重写)
extern "C" __declspec(dllimport) void MySetText(const char*);
extern "C" __declspec(dllimport) const char* MyGetText();
int main() {
MySetText("Hello Qt");
const char* ret = MyGetText();
QString msg(ret);
qDebug() << msg;
return 0;
}
简要总结
Qt调用C#库的核心在于“CLR桥接”:纯C#不能被Win32 API加载,纯C++不能理解.NET对象。通过创建一个带有 __declspec(dllexport) 的CLR C++项目,我们将C#的行为映射为C风格的函数接口,从而实现了跨语言的无缝调用。只要注意 ClassLibrary1.dll 和 ClassLibrary2.dll 均在运行目录存在,并在字符串转换时处理好编码(建议使用 msclr 命名空间下的工具),即可高效集成。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













