本文转载自:http://blog.csdn.net/lhs198541/article/details/7593045

最近做的项目,需要在C# 中调用C++ 写的DLL,因为C# 默认的编码方式是Unicode,而调用的DLL规定只处理UTF8编码格式的字符串,DLL中的输入参数类型char*被我Marshal成byte[],输出参数类型char**被我Marshal成了string(C++和C#之间的类型转换请参阅相关资料),于是我就经历了无数次用于接收时的string-->string(UTF8-->Unicode)和用于发送时的string-->byte[](Unicode-->UTF8)这样频繁的编码转换,期间多次出现中文乱码,看来编码转换并非想象中那么简单,今天花时间google一番,原来以前的理解确实不够深,存在很多误区,现整理如下:
  (一)、Encoding和CharSet
  为什么先提这两个,实属问题之源。在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结构体数组注意事项(数据发送与接收时)的更多相关文章

  1. C#调用c++Dll 结构体数组指针的问题

    参考文章http://blog.csdn.net/jadeflute/article/details/5684687 但是这里面第一个方案我没有测试成功,第二个方案我感觉有点复杂. 然后自己写啦一个: ...

  2. 【C语言入门教程】7.2 结构体数组的定义和引用

    7.2 结构体数组的定义和引用 当需要使用大量的结构体变量时,可使用结构体定义数组,该数组包含与结构体相同的数据结构所组成的连续存储空间.如下例所示: struct student stu_a[50] ...

  3. C语言 结构体数组保存到二进制文件中

    在项目中我定义了一个结构体数组,头文件如下: C/C++ code   ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ...

  4. C#调用C++DLL传递结构体数组的终极解决方案

    在项目开发时,要调用C++封装的DLL,普通的类型C#上一般都对应,只要用DllImport传入从DLL中引入函数就可以了.但是当传递的是结构体.结构体数组或者结构体指针的时候,就会发现C#上没有类型 ...

  5. 绝对好文C#调用C++DLL传递结构体数组的终极解决方案

    C#调用C++DLL传递结构体数组的终极解决方案 时间 2013-09-17 18:40:56 CSDN博客相似文章 (0) 原文  http://blog.csdn.net/xxdddail/art ...

  6. C#调用C/C++动态库 封送结构体,结构体数组

    一. 结构体的传递 #define JNAAPI extern "C" __declspec(dllexport) // C方式导出函数 typedef struct { int ...

  7. C#调用C/C++动态库 封送结构体,结构体数组

    因为实验室图像处理的算法都是在OpenCV下写的,还有就是导航的算法也是用C++写的,然后界面部分要求在C#下写,所以不管是Socket通信,还是调用OpenCV的DLL模块,都设计到了C#和C++数 ...

  8. matlab 怎么建立结构体数组?

    https://zhidao.baidu.com/question/537198107.html 怎么定义一个结构体数组,使数组的每个元素是一个结构体变量.像这样:a=(1,2)a(1)=struct ...

  9. matlab学习笔记12_2创建结构体数组,访问标量结构体,访问非标量结构体数组的属性,访问嵌套结构体中的数据,访问非标量结构体数组中多个元素的字段

    一起来学matlab-matlab学习笔记12 12_2 结构体 创建结构体数组,访问标量结构体,访问非标量结构体数组的属性,访问嵌套结构体中的数据,访问非标量结构体数组中多个元素的字段 觉得有用的话 ...

随机推荐

  1. appium使用教程(三)-------------用例编写

    1. 驱动 import os, time, unittest from appium import webdriver PATH = lambda p:os.path.abspath(os.path ...

  2. 2019 前端面试题汇总(主要为 Vue)

    原文链接:点我 由于我的技术栈主要为Vue,所以大部分题目都是Vue开发相关的. 1. 谈谈你对MVVM开发模式的理解 MVVM分为Model.View.ViewModel三者. Model:代表数据 ...

  3. Linux 中挂载 ISO 文件

    在 Linux 中挂载 ISO 文件 用 mount 命令,在终端中输入如下命令即可: sudo mount -o loop filename.iso /cdrom 其中 filename.iso 是 ...

  4. select &amp; epoll

    同步.异步.堵塞和非堵塞差别 同步:发出一个功能调用时.在没有得到结果之前,该调用就不返回 异步:当一个异步过程调用发出后.调用者不能立马得到结果.实际处理这个调用的部件在完毕后.通过状态.通知和回调 ...

  5. 分布式文件存储FastDFS(一)初识FastDFS

    一.FastDFS简单介绍 FastDFS是一款开源的.分布式文件系统(Distributed File System),由淘宝开发平台部资深架构师余庆开发.作为一个分布式文件系统,它对文件进行管理. ...

  6. C语言keywordstatic的绝妙用途

    为什么要说static妙,它确实是妙,在软件开发或者单片机开发过程中,大家总以为static就是一个静态变量.在变量类型的前面加上就自己主动清0了.还有就是加上statickeyword的,无论是变量 ...

  7. vue2.0 vue-loader

    vue-cli npm install 脚手架: vue-loader 1.0 -> new Vue({ el: '#app', components:{App} }) 2.0-> new ...

  8. FIFO的设计与仿真

    本设计参照齐威王大哥的设计,采用模块化的设计方法,每个模块简单易懂,并进行了每个模块的仿真.最后进行顶层设计,编写了测试激励在modisim上仿真正确, 下面给出代码和测试激励,附上一篇比较好的英文文 ...

  9. 如何测试WCF Rest

    使用SoapUI 1.新建一个rest项目 2.双击上图中的Request1 查询的时候,Method选择post resource的地方要调整到对应的方法 查询的内容是用json格式发送 查询的的结 ...

  10. Android 使用Retrofit获取JSON数据

    在大家使用网络请求的时候,往往会出现一种情况:需要拿到服务器返回来的JSON字符串,而Retrofit会默认将Json解析,而又没有直接暴露出拿到Json字符串的方法: 今天测接口的时候,发现当数据正 ...