C#调用C++的dll库怎么传递结构体中不定长度的char数组
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#调用C++的dll库怎么传递结构体中不定长度的char数组相关的知识,希望对你有一定的参考价值。
C++结构体:
typedef struct InsertData_struct
int type; //为 0 则为开关量,为 1 则为摸拟量
int status; //写入点记录的状态
double value; //写入点记录的值
long time; //写入点记录的时间,秒
char pointName[RTDB_TAGNAME_LENGTH]; //写入点的完整点名,这个点名长度不固定
InsertData
在C#中我定义的结构体:
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
internal struct InsertData
public int type;//类型 0为开关量 1为模拟量
public int status;//写入点的状态
public double value;//要写入的值
public int time;//写入点记录的时间(秒)
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 15)]//长度最长为15字节
public string name;
;
不知我定义结构体有问题还是传入方式有问题,下面是调用c++的原型函数
int AppendRTTagDataByTagName(InsertData *pInsertData)
我在C#中声明如下:
[DllImport("RTDBInterface.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int AppendRTTagDataByTagName(byte[] pInsertData);
我用byte数组和Intptr都试过了 返回值都是错误的 估计还是struct转换有问题,如下图所示:
返回值是0时才证明传递传递成功
以及RTDB_TAGNAME_LENGTH的长度,呃……看样子是个常量应该固定的吧?
然后inkInfoBytes不用new初始化其实……调用处检查一下inkInfoBytes的长度
DLL那边有源代码的话最好对着dll调试,在函数入口点下断点,看看传递进去的东西是什么样的,这样比较容易判断故障。
调试方法就是打开dll的工程,C#的exe复制到debug文件夹里,在工程设置里面调试那边把启动程序设置成C#的那个exe,然后dll代码里下断点,然后开始调试追问
改为byvalstr vs直接报错
dll文件源代码不是我写的 是第三方厂家的
如果是自己写的 就不需要这么费事了
手工查看一下送进去参数的数组吧。
人工对比一下正常送进去的应该是哪些字节(就是C++里调用的情况)
数据、数据偏移之类的对不对。
哦对了只是测试参数传递的话,你自己写一个函数,函数声明(和结构体)和它那个dll的完全一样,然后你去调用自己的dll就可以检查参数传递对不对了。函数声明一样的话传递也是一样的。
pointname最长不过20
我也试过
将struct转化为byte数组 我试过也不行
IntPtr pTr = Marshal.AllocHGlobal(1024 * 1024 * 2);
Marshal.StructureToPtr(“你的结构体”, pTr, false);
AppendRTTagDataByTagName(pTr );追问
这样还是不行 返回的值还是-1
char pointName[RTDB_TAGNAME_LENGTH]; 这个我对应成string没问题吧?每个结构体变量中的
pointName的长度是不固定的,但对于这一个来说是固定的
为什么先提这两个,实属问题之源。在C#中包装DLL的时候,DllImportAttribute当中的选项CharSet着实让我糊涂了很久,MSDN曰:规定封送字符串应使用何种字符集,其中枚举值有Ansi和Unicode,我真不知道到底改选哪一个。于是乎, google一番,Encoding这棵救命草被我找到,同时也释疑了不少疑惑。
首先,字符集不同于编码,以前总将它们混为一谈,CharSet是字符集,Encoding是编码。字符集是字符的集合,规定这个集合里有哪些字符,每个字符都有一个整数编号(只是编号不是编码);而编码是用来规定字符编号如何与二进制交互,每个“字符”分别用一个字节还是多个字节存储。啊呜,原来这样,那我这里接触到的Ansi、Unicode、UTF8等等等等究竟是怎么回事呢,借此机会,一探究竟!^_^
(二)、Ansi、Unicode、UTF8、bala bala
提到字符集,有ASCII、GB2312、GBK、GB18030、BIG5、JIS等等多种,与此相对应的编码方式为ASCII、GB2312、GBK、GB18030、BIG5、JIS(囧,难怪糊涂如我般的人如此多),但是Unicode字符集却有多种编码方式:UTF-8、 UTF-7、UTF-16、 UnicodeLittle、UnicodeBig。原来如此,字符集与编码原来是这个样子。ˇˍˇ|||
那Ansi又是什么呢?
Ansi:系统编码的发展经历了三个阶段,ASCIIàAnsi(不同国家语言本地化)àUnicode(标准化),原来Ansi编码也好,Ansi字符集也好,都是指本地化的东西,在简体中文系统下,ANSI 编码代表 GB2312 编码,Windows下自带的记事本程序,默认的就是ANSI编码。呵呵,那偶就去试试吧,输入“anhui合肥”(不含引号),保存编码方式选“ANSI”,查看,哦,9个字节,明白了,原来Ansi编码保留了对ASCII编码的兼容,当遇到ASCII字符时,采用单字节存储,当遇到非ASCII编码时,采用双字节表示(GB2312编码)。
(三)、DllImportAttribute中的CharSet枚举值选择
对字符集和编码的概念清楚了以后,终于可以研究C#中调用非托管的DLL的方法咯。从托管应用程序去调用非托管代码,如果CharSet=Unicode,则DLL中的接口函数将出现的所有字符串(包括参数和返回值)视为Unicode字符集,Ansi一样的道理。真不错,了解到这里总算有点拨云见日的感觉了!O(∩_∩)O
好像目前需要了解的知识点都差不多了,终于可以开始来解决我的问题了, (*^__^*)。回顾整理一下,第一:我需要调用非托管代码,第二:非托管代码只处理UTF8编码格式的字符,第三,万恶的中文乱码,接口函数签名中,无论参数还是返回值,接收到或是发送出的字符串都含有中文。呵呵,看来上面的知识应该可以解决这个问题了:首先接收字符串,因为接收到的是UTF8编码格式,CharSet属性肯定要设置成Unicode了,接收后要正确显示中文,在当前系统中,需要将编码方式转换成GB2312,即Encoding.Default;另外关于发送字符数组,因为无论是C#中默认的Unicode编码方式,还是DLL处理时规定的UTF8编码方式,都是Unicode字符集的一种编码方式,所以CharSet也要设置成Unicode,只是要在发送前需要将字符数组转换一下编码方式。以下附带两个简单的函数实现。
// 转换接收到的字符串
public string UTF8ToUnicode(string recvStr)
byte[] tempStr = Encoding.UTF8.GetBytes(recvNotify);
byte[] tempDef = Encoding.Convert(Encoding.UTF8, Encoding.Default, tempStr);
string msgBody = Encoding.Default.GetString(tempDef);
return msgBody;
// 转换要发送的字符数组
public byte[] UnicodeToUTF8(string sendStr)
string tempStr = Encoding.UTF8.GetString(sendStr);
byte[] msgBody = Encoding.UTF8.GetBytes(tempUTF8);
return msgBody;
总结一下,本文因项目完成的需要,难免存在局限性,只讨论了两种情况:Unicode编码的字符串转UTF8格式的字符数组,以及UTF8格式的字符串转Unicode格式的字符串,上面两个方法均通过测试,至此总算解决了中文乱码的问题。平台调用的知识点很多,只有真正掌握必需的基础知识和平台调用的原理,才能做到活学活用,我要继续努力。
结构体:typedef struct tagDownDepInfo
char DPCODE1[6]; //一级部门
char DPCODE2[6]; //二级部门
char DPCODE3[8]; //三级部门
char DPCODE4[8]; //四级部门
char DPNAME1[60]; //
char DPNAME2[60]; //
char DPNAME3[60]; //
char DPNAME4[60]; //
DOWNDEPINFO, FAR *PDOWNDEPINFO;
函数:int CapGetDepList(DOWNDEPINFO ** pINFO)
我在c#中的写法是:
结构体: [StructLayout(LayoutKind.Sequential)]
internal struct tagDownDepInfo
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
public string DPCODE1; //一级部门
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
public string DPCODE2; //二级部门
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
public string DPCODE3; //三级部门
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
public string DPCODE4; //四级部门
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)]
public string DPNAME1; //
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)]
public string DPNAME2; //
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)]
public string DPNAME3; //
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)]
public string DPNAME4; //
函数: [DllImport("Change.dll", CharSet = CharSet.Ansi)]
internal unsafe static extern int CapGetDepList(ref tagDownDepInfo[] downinfo);
调用: tagDownDepInfo[] downinfo = new tagDownDepInfo[100];//现已知道有100条数据
int ret = Class1.CapGetDepList(ref downinfo);
结果:返回正确,但downinfo 却变成了1行,这是为什么呢?请大家指教谢谢!在线等!分数不多,请谅解!
DOWNDEPINFO *pInfo=new DOWNDEPINFO[cou];
memset(pInfo, 0, sizeof(DOWNDEPINFO)*cou);
int nFlag=ccFun.CapGetDepList(&pInfo); DOWNDEPINFO *pInfo=new DOWNDEPINFO[cou];
memset(pInfo, 0, sizeof(DOWNDEPINFO)*cou);
int nFlag=ccFun.CapGetDepList(&pInfo);
int CapGetDepList(DOWNDEPINFO ** pINFO)
这个定义是说里面的DOWNDEPINFO是CapGetDepList分配的还是外面分配的?
如果内部分配 定义ok
不是 应改成int CapGetDepList(DOWNDEPINFO *pINFO)
看你程序的用法,ms是下面这种
不说这个 你要知道这点 C#的数组的内存是manage的,而c++不是,所以当用ref传出去时,不知道传出去实际数组指针的大小,所以,internal unsafe static extern int CapGetDepList(ref tagDownDepInfo[] downinfo); 这种写法有点问题的
一般,遇到ref数组之类的都得用IntPtr 如果数组是传入的,嗯,这有点麻烦,要首先申请一块对应的内存,调用Marshal.AllocHGlobal分配相应的内存,大小可以用Marshal.sizeof 自己调用Marshal.Copy 一个一个拷进去(我就是这么用的,没找到好的方法)用后释放内存
所以 我猜你的dll导入函数定义应为
[DllImport("Change.dll", CharSet = CharSet.Ansi)]
private static extern int CapGetDepList(IntPtr p);
用Marshal.PtrToStructure获得执行结果
还有传入指针,而没有大小,是一个不怎么好的函数定义方式
[DllImport("Change.dll", CharSet = CharSet.Ansi)]
private static extern int CapGetDepList(IntPtr p);
可能的一些步骤为
IntPtr[] ptArray = new IntPtr[1];
ptArray[0] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(tagDownDepInfo)) * 100);
IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)) * 1);
Marshal.Copy(ptArray, 0, pt, 1);
然后
CapGetDepList(pt);
拿到数据
ps:你可以说下写这个接口的人,定义个数组尽然没有数组大小的参数,还有只需要1维的搞个2维的,
那2维变3维啊哈(can 参考监控软件MEMORY中相应方法,注意该例子为结构体数组指针)
取数据方法
for (int i = 0; i < size; i++)
tagDownDepInfo tagInfo = (tagDownDepInfo)Marshal.PtrToStructure((IntPtr)((UInt32)ptArray[0] + i * s), typeof(tagDownDepInfo));
Marshal.FreeHGlobal(ptArray[0]);
Marshal.FreeHGlobal(pt);
总结::
typedef struct tagDownDepInfo
char DPCODE1[6]; //一级部门
char DPCODE2[6]; //二级部门
char DPCODE3[8]; //三级部门
char DPCODE4[8]; //四级部门
char DPNAME1[60]; //
char DPNAME2[60]; //
char DPNAME3[60]; //
char DPNAME4[60]; //
DOWNDEPINFO, FAR *PDOWNDEPINFO;
int CapGetDepList(DOWNDEPINFO ** pINFO)
c++中使用方法:
DOWNDEPINFO *pInfo=new DOWNDEPINFO[cou];
memset(pInfo, 0, sizeof(DOWNDEPINFO)*cou);
int nFlag=ccFun.CapGetDepList(&pInfo);
c#中调用方法:
[DllImport("Change.dll", CharSet = CharSet.Ansi)]
private static extern int CapGetDepList(IntPtr p);
可能的一些步骤为
IntPtr[] ptArray = new IntPtr[1];
ptArray[0] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(tagDownDepInfo)) * 100);
IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)) * 1);
Marshal.Copy(ptArray, 0, pt, 1);
然后
CapGetDepList(pt);
for (int i = 0; i < size; i++)
tagDownDepInfo tagInfo = (tagDownDepInfo)Marshal.PtrToStructure((IntPtr)((UInt32)ptArray[0] + i * s), typeof(tagDownDepInfo));
Marshal.FreeHGlobal(ptArray[0]);
Marshal.FreeHGlobal(pt);
C#调用c++dll文件是一件很麻烦的事情,首先面临的是数据类型转换的问题,相信经常做c#开发的都和我一样把学校的那点c++底子都忘光了吧(语言特性类)。
网上有一大堆得转换对应表,也有一大堆的转换实例,但是都没有强调一个更重要的问题,就是c#数据类型和c++数据类型占内存长度的对应关系。
如果dll文件中只包含一些基础类型,那这个问题可能可以被忽略,但是如果是组合类型(这个叫法也许不妥),如结构体、类类型等,在其中的成员变量的长度的申明正确与否将决定你对dll文件调用的成败。
如有以下代码,其实不是dll文件的源码,而是厂商给的c++例子代码
c++中的结构体申明
typedef struct
unsigned char Port;
unsigned long Id;
unsigned char Ctrl;
unsigned char pData[8];
HSCAN_MSG;
c++中的函数申明(一个c++程序引用另一个c++的dll文件)
extern "C" int _stdcall HSCAN_SendCANMessage(unsigned char nDevice,unsigned char nPort,HSCAN_MSG *msg,int nLength);
c++中的调用:
....
HSCAN_MSG msg[100];
.....
HSCAN_SendCANMessage(m_nDevice,m_nPort,msg,nFrames);
由上述代码可见,msg是个结构体的数组。
下面是我的c#的代码
c#结构体申明:(申明成)
[StructLayout(LayoutKind.Sequential)]
public struct HSCAN_MSG
// UnmanagedType.ByValArray, [MarshalAs(UnmanagedType.U1)]这个非常重要,就是申明对应类型和长度的
[MarshalAs(UnmanagedType.U1)]
public byte Port;
[MarshalAs(UnmanagedType.U4)]
public uint nId;
[MarshalAs(UnmanagedType.U1)]
public byte nCtrl;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] pData;
;
c#函数申明
[DllImport("HS2106API.dll")]
public static extern int HSCAN_SendCANMessage(
byte nDevice, byte nPort, HSCAN_MSG[] pMsg, int nLength);
C#函数调用
HSCAN_MSG[] msg = new HSCAN_MSG[1]; //发送缓冲区大小可根据需要设置;
for (int yy = 0; yy < msg.Length; yy++)
msg[yy] = new HSCAN_MSG();
//...结构体中的成员的实例化略
HSCAN_SendCANMessage(0x0, 0x0, msg, 1)
那些只能用指针不能用结构体和类的地方
c++中的结构体申明
typedef struct
unsigned char Port;
unsigned long Id;
unsigned char Ctrl;
unsigned char pData[8];
HSCAN_MSG;
c++中的函数申明(一个c++程序引用另一个c++的dll文件)
extern "C" int _stdcall HSCAN_SendCANMessage(unsigned char nDevice,unsigned char nPort,HSCAN_MSG *msg,int nLength);
c#中的结构体申明:
[StructLayout(LayoutKind.Sequential)]
public struct HSCAN_MSG
[MarshalAs(UnmanagedType.U1)]
public byte Port;
/// <summary>
/// 节点标识,nEFF=1 时(扩展帧),为29 位nEFF=0(标准帧)时,为11 位;
/// </summary>
[MarshalAs(UnmanagedType.U4)]
public uint nId;
[MarshalAs(UnmanagedType.U1)]
public byte nCtrl;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] pData;
;
c#函数的调用:包含使用指针IntPtr替代结构体数组和读取IntPtr的方法
HSCAN_MSG[] msg1 = new HSCAN_MSG[10];
for (int i = 0; i < msg1.Length; i++)
msg1[i] = new HSCAN_MSG();
msg1[i].pData = new byte[8];
IntPtr[] ptArray = new IntPtr[1];
ptArray[0] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(HSCAN_MSG)) * 10);
IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(HSCAN_MSG)));
Marshal.Copy(ptArray, 0, pt, 1);
int count = HSCAN_ReadCANMessage(0x0, 0,pt, 10);
textBoxStatus.Text += "\r\n" + "读取0口:" + count.ToString() + "帧数据";
for (int j = 0; j < 10; j++)
msg1[j] =
(HSCAN_MSG)Marshal.PtrToStructure((IntPtr)((UInt32)pt+ j * Marshal.SizeOf(typeof(HSCAN_MSG)))
, typeof(HSCAN_MSG));
textBoxStatus.Text += "\r\n收到0口" + Convert.ToByte(msg1[j].pData[0]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[1]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[2]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[3]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[4]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[5]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[6]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[7]).ToString();
如何用c#调用c++的dll传递各种稀奇古怪的参数。
如何用c#调用c++的dll传递各种稀奇古怪的参数
声明
我知道用c#调用c++是个神烦的事情,在此分享部分传参心得和技巧,不一定是最好的方法,欢迎指正补充。
各种稀奇古怪的参数
1. 传递基础数据 int float double等
// h
extern "C" __declspec(dllexport) int TransData(int a, float b, double c);
// cpp
int TransData(int a, float b, double c)
{
double sum = a + b + c;
return floor(sum);
}
//cs
[DllImport("贫道不是秃驴只是秃.dll", EntryPoint = "TransData", CallingConvention = CallingConvention.Cdecl)]
public static extern int TransData(int a, float b, double c);
2. 传递 string 并接收 string
// h
extern "C" __declspec(dllexport) const char* TransString(const char * inpStr);
// cpp
const char* TransString(const char* inpStr)
{
// 从const char * 到 std::string
// std::string str = inpStr;
// 从std::string 到 const char *
// const char* pStr = str.c_str();
std::string recive = inpStr;
std::string demoStr = "我已经收到了:" + recive;
return demoStr.c_str();
}
// cs
[DllImport("贫道不是秃驴只是秃.dll", EntryPoint = "TransString", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr TransString(String msg);
// option
IntPtr str_ptr = DllLinker.TransString("我是你papa!");
string result = Marshal.PtrToStringAnsi(str_ptr);
Console.WriteLine(result);
3. 传递数组操作
// h
extern "C" __declspec(dllexport) int TransArr(int * arr, int arrLen);
// cpp
int TransArr(int* arr, int arrLen)
{
int sum = 0;
for (int i = 0; i < arrLen; i++) {
sum += arr[i];
}
return sum;
}
// cs
[DllImport("贫道不是秃驴只是秃.dll", EntryPoint = "TransArr", CallingConvention = CallingConvention.Cdecl)]
public static extern int TransArr(int[] arr, int len);
// option
int[] arr = { 1,2,3,4,5};
int arrSum = DllLinker.TransArr(arr, arr.Length);
Console.WriteLine("数组值的和是:" + arrSum);
3. 传递结构体操作(包含数组,string等)(两种方式)
// h
extern "C" __declspec(dllexport) bool TransStructType1(YourSelf & yourSelf);
extern "C" __declspec(dllexport) void TransStructType2(YourSelf * yourSelf);
// cpp
bool TransStructType1(YourSelf& yourSelf)
{
yourSelf.age += 5;
yourSelf.childrenId[0] = 1000;
yourSelf.health = HealthType::DEATH;
return true;
}
void TransStructType2(YourSelf* yourSelf)
{
yourSelf->age += 5;
yourSelf->childrenId[0] = 1000;
yourSelf->health = HealthType::DEATH;
}
// cs
[DllImport("DLLS/Dll_DataFile.dll", EntryPoint = "TransStructType1", CallingConvention = CallingConvention.Cdecl)]
public static extern bool TransStructType1(ref YourSelf yourSelf);
[DllImport("DLLS/Dll_DataFile.dll", EntryPoint = "TransStructType2", CallingConvention = CallingConvention.Cdecl)]
public static extern void TransStructType2([In, Out] IntPtr yourSelf);
// option
int[] cid= { 1,2,3,4,5,6,7,8,9,0};
YourSelf yourSelf = new YourSelf();
yourSelf.age = 18;
yourSelf.money = 10.8f;
yourSelf.name = "ZhuDaChang";
yourSelf.childrenId = cid;
yourSelf.health = HealthType.OK;
// ============== 传参方式一 =============
bool res = DllLinker.TransStructType1(ref yourSelf);
Console.WriteLine("反馈bool:" + res + " ref影响yourself的属性age:" + yourSelf.age);
// ============== 传参方式二 =============
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(YourSelf)));
Marshal.StructureToPtr(yourSelf, ptr, false);
DllLinker.TransStructType2(ptr);
YourSelf effectStruct = (YourSelf)Marshal.PtrToStructure(ptr, typeof(YourSelf));
Console.WriteLine("[in, out]影响yourself的属性age:" + effectStruct.age);
Marshal.FreeHGlobal(ptr);
4. 传递图片操作
这个可以参考我之前的一些博文,连接在下面:
c#调用c/c++打包的dll程序实现人脸检测(暨c#和c++之间传递图片解决方案)
c#的opencvsharp与c++封装opencv之间的mat对象传递(BitmapData造成图片扭曲的问题记录)
以上是关于C#调用C++的dll库怎么传递结构体中不定长度的char数组的主要内容,如果未能解决你的问题,请参考以下文章