本文转载自: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. 记intel杯比赛中各种bug与debug【其四】:基于长短时记忆神经网络的中文分词的实现

    (标题长一点就能让外行人感觉到高大上) 直接切入主题好了,这个比赛还必须一个神经网络才可以 所以我们结合主题,打算写一个神经网络的中文分词 这里主要写一下数据的收集和处理,网络的设计,代码的编写和模型 ...

  2. Android编译环境配置

    Android编译环境配置 网上关于Android编译环境配置的整理资料有不少,经整理亲测后,希望能给需要的亲们提供帮助. 主要分为四步: 1.安装JDK(Java Standard Edition ...

  3. What is x86 Conforming Code Segment?

    SRC=Microprocessor Based Systems SRC=Computer Architecture: A Quantitative Approach

  4. 【转载】C# 跨线程调用控件

    转自:http://www.cnblogs.com/TankXiao/p/3348292.html 感谢原作者,转载以备后用 在C# 的应用程序开发中, 我们经常要把UI线程和工作线程分开,防止界面停 ...

  5. ArcGIS api for javascript——查询,然后单击显示信息窗口

    描述 本例展示如何配置查询任务为示例的工作流程: 1.用户单击一个要素来加亮显示. 2.用户再一次单击要素来查看属性信息的 InfoWindow. 本例查询USA州,因此ESRI_StateCityH ...

  6. [Poi] Build and Analyze Your JavaScript Bundles with Poi

    Ever wonder where those extra KB in your bundle are coming from? This lesson walks you through runni ...

  7. CSS元素选择器 element selector(type selector)

    http://www.w3school.com.cn/css/css_selector_type.asp 元素选择器 最常见的 CSS 选择器是元素选择器.换句话说,文档的元素就是最基本的选择器. 如 ...

  8. mysql 5.6 安装教程

    首先是下载 mysql-installer-community-5.6.14.0.msi ,大家可以到 mysql 官方网去下载,也可以到笔者所提供的地址去下载,下载方法在这里就不多说了,我想大家都明 ...

  9. 在Windows下如何创建指定的虚拟环境

    前几天给大家分享了如何在默认的情况下创建虚拟环境,没来得及上车的伙伴,可以戳这篇文章:在Windows下如何创建虚拟环境(默认情况下).今天小编给大家分享一下,如何创建的指定的Python环境. 创建 ...

  10. python 协程学习

    协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来 ...