C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C#与C++动态链接库DLL参数互传

C#与C++动态链接库DLL参数互传方式

作者:weixin_46846685

这篇文章主要介绍了C#与C++动态链接库DLL参数互传方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

一、C#中导入C++动态链接库

从界面程序开发的角度来说,C#语言效率较C++高,且通过WPF开发出的程序界面更为美观,但在开发实际项目中有时不可避免的需要使用C++程序库,通常的做法是将C++程序编译为动态链接库,及DLL文件,然后在C#中进行导入调用。

导出C++程序通常的做法是使用_declspec(dllexport) /_declspec(dllimport)来导入导出,C++示例代码如下:

#include <iostream>
using namespace std;
extern "C" _declspec(dllexport) void TestDll(char* Path, char* result);
_declspec(dllexport) void TestDll(char* Path, char* result)
{
***//函数功能具体实现
}

以VS2019中编译Dll为例,打开项目属性窗口,点击配置属性——常规,将配置类型选择为动态库(.dll),然后点击配置属性——高级,将目标文件扩展名选择为.dll,然后设置好解决方案配置和平台后生成dll文件。

导出为Dll文件后,例如导出文件名为TestDll.dll,在C#中调用示例代码如下:

[DllImport("TestDll.dll", EntryPoint = "TestDll")]
static extern void TestDll(string Path, [Out, MarshalAs(UnmanagedType.LPArray)] char[] result);

下面具体讲如何在C#与C++之间实现参数传递。

二、C#传入字符串参数

参数传递主要涉及C#调用dll文件时传入dll参数和调用结束dll文件传出参数。通常传入dll的参数类型为整数类型,整数数组类型和字符串类型。

在C#和C++中整数类型通常都为int类型,在参数传入时直接传入即可。

但对于字符串类型,C#中为string类型,而C++中通常是使用字符数组来存储字符串,即char[]或char*类型,而C++中也使用std::string类型来存储字符串,但在实际使用过程中发现当C++中接收参数类型是该类型时会报出错误,

示例代码如下:

//C++代码
#include <iostream>
using namespace std;
extern "C" _declspec(dllexport) void TestDll(char* Path);
_declspec(dllexport) void TestDll(char* Path)
{
***//函数功能具体实现
}
//C#代码
[DllImport("TestDll.dll", EntryPoint = "TestDll")]
static extern void TestDll(string Path);
string path = "hello";
TestDll(path);

上述代码中,C#传入字符串类型为string类型,而C++接收参数类型为char*类型,经实际测试,中文字符和英文字符都可以正确传输。

三、C++传出字符串参数

C++传出字符串参数较C#传入更为复杂,因C++中字符存储是以指针形式,所以可以通过如下方式来实现:C#传入一个数组参数,传入后C++对该数组指针进行赋值,然后传出。

实现方式有两种,一种为C#传入char数组类型,一种为C#传入byte数组类型,示例代码如下:

//C++代码
#include <iostream>
using namespace std;
extern "C" _declspec(dllexport) void TestDll(char* result);
_declspec(dllexport) void TestDll(char* result)
{
char s[20]="hello";
memcpy(result, s, strlen(s));
}

memcpy为C++内存拷贝函数,使用时需要注意strlen与sizeof函数的区别,两者都是获得变量的字节数,不同的是strlen只适用于char*类型,当遇到’\0’字节时停止计数,而sizeof适用于多种类型和对象,当用于数组类型时获得的时初始化时分配的字节数,而不是实际使用的字节数。

//C#代码
[DllImport("TestDll.dll", EntryPoint = "TestDll")]
static extern void TestDll([Out, MarshalAs(UnmanagedType.LPArray)] char[] result);
char[] result = new char[100];
TestDll(result);
string re = new string(result);
Console.WriteLine(re);

在C#中传入char[]即字符数组类型,待C++中对result赋值完成后再取出result,对字符串re赋值,这样就实现了C++字符串参数的传出。

需要注意的是在C#中数组是直接使用的,而在C++中返回的是数组的指针,[Out, MarshalAs(UnmanagedType.LPArray)]用来转化这两种不同的类型。

上述实现方式为传入char数组类型方式,但如果C++传出的字符串包含中文字符,那么可能在C#中会显示乱码,因为中文字符为UTF-8编码。

下面介绍使用byte数组方式,C++中代码不变,C#中代码如下:

//C#代码
[DllImport("TestDll.dll", EntryPoint = "TestDll")]
static extern void TestDll(ref byte t);
byte[] t = new byte[100];
TestDll(path, ref t[0]);
string re = Encoding.UTF8.GetString(t);
Console.WriteLine(re);

byte数组方式传输中文字符不算出现乱码情况。

四、C++传出vector<char*>参数

vector类型为C++中类似于列表的数据类型,能够自由添加、插入、删除元素,与C#中List类型功能相似。

但当要在C++传出vector类型时,在C#端的接收数据类型却不能为List,否则会报出错误,而应该使用C#中的IntPtr类型,示例代码如下:

//C++代码
#include <iostream>
using namespace std;
extern "C" _declspec(dllexport) void TestDll(int& num, char** &result);
_declspec(dllexport) void TestDll(int& num, char** &result)
{
std::vector<char*> res;
char* tmp = new char[5];
char s[20]="hello";
memcpy(tmp, s, strlen(s));
res.push_back(tmp);
num = 1;
result = res.data();
}

在上述C++代码中主要通过res.data()函数实现将vector<char*>类型转换为char**类型,从而方便C#端读取,而num变量而表示vector类型的元素个数,C#端代码如下:

//C#代码
[DllImport("TestDll.dll", EntryPoint = "TestDll")]
static unsafe extern void TestDll(ref int num, ref IntPtr t);
int num = 0;
IntPtr data = IntPtr.Zero;
TestDll(ref num, ref data);
for (int i = 0; i < num; ++i)
{
   int size = Marshal.SizeOf(typeof(IntPtr));
   IntPtr intPtr = Marshal.ReadIntPtr(data, size * i);
   string datastr = Marshal.PtrToStringAnsi(intPtr);
   Console.WriteLine(datastr);
}

C#代码中根据num个数依次从内存中读取字符串,从内存中读取字符串函数为Marshal.PtrToStringAnsi(intPtr),需要注意的是如果C++传出的包含中文字符,那么在C#端可能显示乱码。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文