C#与C++动态链接库DLL参数互传方式
作者:weixin_46846685
一、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#端可能显示乱码。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。