• C#与C++类型互转


    原文出处:https://www.jianshu.com/p/d3ac316104f8

    一、C#调用DLL文件时参数对应表

    Wtypes.h 中的非托管类型    非托管 C 语言类型  托管类名    说明
    HANDLE  void*   System.IntPtr   32 位
    BYTE    unsigned char   System.Byte 8 位
    SHORT   short   System.Int16    16 位
    WORD    unsigned short  System.UInt16   16 位
    INT int System.Int32    32 位
    UINT    unsigned int    System.UInt32   32 位
    LONG    long    System.Int32    32 位
    BOOL    long    System.Int32    32 位
    DWORD   unsigned long   System.UInt32   32 位
    ULONG   unsigned long   System.UInt32   32 位
    CHAR    char    System.Char 用 ANSI 修饰。
    LPSTR   char*   System.String 或 System.StringBuilder    用 ANSI 修饰。
    LPCSTR  Const char* System.String 或 System.StringBuilder    用 ANSI 修饰。
    LPWSTR  wchar_t*    System.String 或 System.StringBuilder    用 Unicode 修饰。
    LPCWSTR Const wchar_t*  System.String 或 System.StringBuilder    用 Unicode 修饰。
    FLOAT   Float   System.Single   32 位
    DOUBLE  Double  System.Double   64 位
    

    二、C#调用C++编写的DLL函数, 以及各种类型的参数传递

    1. 如果函数只有传入参数,比如:
    1.  //C++中的输出函数 
    2.  int __declspec(dllexport) test(const int N) 
    3.  { 
    4.  return N+10; 
    5.  } 
    对应的C#代码为:
    C# Code Copy Code To Clipboard
    1.  [DllImport("test.dll", EntryPoint = "#1")] 
    2.  public static extern int test(int m); 
    3.   
    4.  private void button1_Click(object sender, EventArgs e) 
    5.  { 
    6.  textBox1.Text= test(10).ToString(); 
    7.  } 
    2. 如果函数有传出参数,比如:
    
    1.  //C++
    2.  void __declspec(dllexport) test(const int N, int& Z) 
    3.  { 
    4.  Z=N+10; 
    5.  } 
    对应的C#代码:
    C# Code Copy Code To Clipboard
    1.  [DllImport("test.dll", EntryPoint = "#1")] 
    2.  public static extern double test(int m, ref int n); 
    3.   
    4.  private void button1_Click(object sender, EventArgs e) 
    5.  { 
    6.  int N = 0; 
    7.  test1(10, ref N); 
    8.  textBox1.Text= N.ToString(); 
    9.  } 
    3. 带传入数组:
    
    1.  void __declspec(dllexport) test(const int N, const int n[], int& Z) 
    2.  { 
    3.  for (int i=0; i<N; i++) 
    4.  { 
    5.  Z+=n[i]; 
    6.  } 
    7.  } 
    C#代码:
    
    1.  [DllImport("test.dll", EntryPoint = "#1")] 
    2.  public static extern double test(int N, int[] n, ref int Z); 
    3.   
    4.  private void button1_Click(object sender, EventArgs e) 
    5.  { 
    6.  int N = 0; 
    7.  int[] n; 
    8.  n = new int[10]; 
    9.  for (int i = 0; i < 10; i++) 
    10. { 
    11. n[i] = i; 
    12. } 
    13. test(n.Length, n, ref N); 
    14. textBox1.Text= N.ToString(); 
    15. } 
    4. 带传出数组:
    C++不能直接传出数组,只传出数组指针,
    
    1.  void __declspec(dllexport) test(const int M, const int n[], int *N) 
    2.  { 
    3.  for (int i=0; i<M; i++) 
    4.  { 
    5.  N[i]=n[i]+10; 
    6.  } 
    7.  } 
    对应的C#代码:
    
    1.  [DllImport("test.dll", EntryPoint = "#1")] 
    2.  public static extern void test(int N, int[] n, [MarshalAs(UnmanagedType.LPArray,SizeParamIndex=1)] int[] Z); 
    3.   
    4.  private void button1_Click(object sender, EventArgs e) 
    5.  { 
    6.  int N = 1000; 
    7.  int[] n, Z; 
    8.  n = new int[N];Z = new int[N]; 
    9.  for (int i = 0; i < N; i++) 
    10. { 
    11. n[i] = i; 
    12. } 
    13. test(n.Length, n, Z); 
    14. for (int i=0; i<Z.Length; i++) 
    15. { 
    16. textBox1.AppendText(Z[i].ToString()+"n"); 
    17. } 
    18. } 
    

    这里声明函数入口时,注意这句 [MarshalAs(UnmanagedType.LPArray,SizeParamIndex=1)] int[] Z
    在C#中数组是直接使用的,而在C++中返回的是数组的指针,这句用来转化这两种不同的类型.
    关于MarshalAs的参数用法以及数组的Marshaling,可以参见这篇转帖的文章: http://www.kycis.com/blog/read.php?21

    1. 传出字符数组:
    C++定义:
    1.  void __declspec(dllexport) test(int i, double &a, double &b, char t[5])  
    C#对应声明:
    
    1.  [DllImport("dll.dll", EntryPoint = "test")]  
    2.  public static extern void test(int i, ref double a, ref double b, [Out, MarshalAs(UnmanagedType.LPArray)] char[] t);   
    4.              char[] t = new char[5];  
    5.              test(i, ref a, ref b, t);  
    

    字符数组的传递基本与4相似,只是mashalAs 时前面加上Out。

    三、
    1.普通传值,如下面代码中MotionDetect的第4个参数;
    2.传引用,MotionDetect的第3个参数,nNum传进动态库后赋值再传回来;
    3.引用传一个结构体,MotionDetect的第2个参数,这里其实是传一个结构体的数组,具体像加ref的传参还真不会;
    4.传一个有分配内存的变量,需要用到GCHandle,因为C#是可以自动回收内存的,而GCHandle在这里的作用就是把它的内存空间Pin住,传递给C++动态库后再手动回收资源。

    using UnityEngine;
    using System.Collections;
    using System.Runtime.InteropServices;
    
    public class D : MonoBehaviour 
    {
        struct Color_32
        {
            public byte r;
            public byte g;
            public byte b;
            public byte a;
        }
        Color_32[]  imageDataResult;
        
        GCHandle pixelsHandle;
        
        [DllImport("MotionDetectionDll")]
        private static extern bool MotionDetect( System.IntPtr colors, Color_32[] imageDataResult, ref int nNum, int nChannels );
        
        void Start()
        {
            imageDataResult = new Color_32[128*128];
        }
        
        void Update () 
        {
            int nNum = 0;
            pixelsHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
            bool wfDf = MotionDetect( pixelsHandle.AddrOfPinnedObject(), imageDataResult, ref nNum, 4 );
            pixelsHandle.Free();
        }
    }
    C++中是这么写的
    //头文件中多加个结构体定义
    #include "stdafx.h"
    struct Color_32
    {
        byte r;
        byte g;
        byte b;
        byte a;
    };
    extern "C" _declspec(dllexport) bool MotionDetect ( char* imageData, Color_32 imageDataResult[], int *nNum, int nChannels );
    
    // CPP文件中
    extern "C" _declspec(dllexport) bool MotionDetect ( char* imageData, Color_32 imageDataResult[], int *nNum, int nChannels )
    {
        IplImage* imgSrc = cvCreateImageHeader(cvSize(width, height), IPL_DEPTH_8U, 4);
        if(!imgSrc)
        {
            return false;
        }
        cvSetData( imgSrc, imageData, imgSrc->widthStep );
        
        ......
        
        for ( int i=0; i< ......; i++ )
        {
            imageDataResult[i].r = ......;
            imageDataResult[i].g = ......;
            imageDataResult[i].b = ......;
            imageDataResult[i].a = ......;
        }
        
        ......
        
        *nNum = 5;
        nChannels = 4;
    }



    在托管代码和本地代码之间传递数组,是interop marshaling中间比较复杂的一个问题。本文从数组的定义开始,介绍数组marshalling的三种方法,并对blittable类型等概念做进一步的讨论。

    当托管代码需要和本地代码互操作时,我们就进入了interop的领域。interop的场景形形色色,不变的是我们需要把数据从一个世界marshal到另一个世界。

    在讨论数组marshalling之前,请各位和我一起思考一个问题,什么是数组?之所以要讨论这个问题,原因在于不同的术语在不同的语境中含有不同的意思。在使用c语言的时候,我认为数组就是一个指针。但是熟悉c#的朋友可能不同意我的观点,数组是System.Array或者Object[]。我认为,这两种回答都是出自语言领域的正确观点。那么如果有一个项目含有两个模块,一个用本地代码撰写,另一个用托管代码撰写,两者之间的接口要求传递一个数组,这个”数组”包含着怎样的语义呢?我觉得有两点是很重要的:

    1. 如何访问数组元素。就好比c语言中的数组指针,c#中的数组引用,都是访问数组必不可少的线索。
    2. 数组的大小。数组的大小不仅仅是System.Array.Length。它还可以包括诸如数组的维数,每个维上的启始边界和结束边界。

    .NET在marshal数组的时候,很大程度上也是从以上两点出发,架起托管世界和本地代码之间的桥梁。根据操作的具体数据类型不同,数组marshal又可以分为以下两个大类,三个小类,我们分别介绍:

    1. 数组作为参数传递
    a) c/c++类型的数组
    c类型的数组,也就是由指针指明存储空间首地址的数组,是一个自描述很低的数据结构。尽管有些编译器支持在固定偏移量上写入数组的长度,但是因为各个编译器处理的具体方法不同,没有一个标准让CLR来参考。所以我们在marshal一个c类型的数组的时候,不得不用其他方法考虑传递数组的大小,有以下两种:

    1) 约定指针数组长度
    这种方法需要调用者和被调用者之间有一个约定,给出一个数组长度的固定值。在托管端声明一个interop方法的时候,只需要用SizeConst这个属性,把这个约定告诉CLR。CLR在进行Marshal的时候,会根据这个值,在本地堆上分配相应的空间。

    public static extern void Ex([In, Out][MarshalAs(UnmanagedType.LPArray, SizeParamConst=3)]string[] a);

    2)通过一个额外的参数指定数组长度
    可能有的朋友觉得,约定一个静态的数组长度还不够灵活,希望能够动态的传递数组的长度,从而使得数组marshalling不必受制于固定数组长度的限制。我们先来看普通的函数调用是如何解决这个问题的:caller和callee通过约定一个额外的参数,来传递数组的长度,这可以被视作是一个调用者和被调用者的约定。由于marshalling需要clr的参与,那么就需要把这个约定用clr能够理解的方式,进行扩充,形成一个三方约定。CLR,用属性SizeParamIndex来描述此类约定。

    public static extern void Ex2([In, Out][MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]string[] a,int len);


    b) SafeArray
    SafeArray是COM引入的数据类型,是一个自描述度很高的数据结构。他可以很清楚的告诉用户,该数组的元素类型,数组包含了多少维,每一维的起始位置和终止位置。所以marshal这类safearray的时候,只需要通过设定属性,告诉CLR,当前array对应的本地代码是safearray即可。举例如下:


    public void DumpSafeArrayStringIn( [In][MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BSTR)]Object[] array);

    大家可以看到,SafeArraySubType可以用来指定数组元素的类型


    2. 数组作为字段传递
    很久以来,对于interop,一直有这样的评价,简单数据结构的marshalling其实并不复杂,但是一旦进入了struct或者class这种你中有我,我中有你的层叠数据结构之后,marshalling就成了bug的温床。所以在这里,我们也要提提数组作为struct/class的一个字段的方法。在这里首先要给这个stuct/class加一个限制,是byval。由于这个限制,大家可以想象的出,CLR在marshal的时候,做的事情是类似于浅copy的内存复制,所以对数组marshal的时候,也就只支持固定长度的数组marshal。

    C# 代码复制内容到剪贴板
    1. public class StructIntArray   
    2. {   
    3. [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]   
    4. public int[] array;   
    5. }  

    原文出处:http://www.kycis.com/blog/read.php?21
    数组作为一种常用的数据结构,各种高级语言都提供了相应的支持,在这些高级语言之间交互操作的时候,数组也是传送集合类型数据的重要结构,希望今天的内容能对大家有所帮助。

  • 相关阅读:
    [.net 面向对象程序设计深入](2)UML——在Visual Studio 2013/2015中设计UML用例图
    [.net 面向对象程序设计深入](1)UML——在Visual Studio 2013/2015中设计UML类图
    [.net 面向对象程序设计进阶] (28) 结束语——告别2015
    [.net 面向对象程序设计进阶] (27) 团队开发利器(六)分布式版本控制系统Git——在Visual Studio 2015中使用Git
    [.net 面向对象程序设计进阶] (26) 团队开发利器(五)分布式版本控制系统Git——图形化Git客户端工具TortoiseGit
    [.net 面向对象程序设计进阶] (25) 团队开发利器(四)分布式版本控制系统Git——使用GitStack+TortoiseGit 图形界面搭建Git环境
    [.net 面向对象程序设计进阶] (24) 团队开发利器(三)使用SVN多分支并行开发(下)
    [.net 面向对象程序设计进阶] (23) 团队开发利器(二)优秀的版本控制工具SVN(上)
    [.net 面向对象程序设计进阶] (22) 团队开发利器(一)简单易用的代码管理工具VSS
    [.net 面向对象程序设计进阶] (21) 反射(Reflection)(下)设计模式中利用反射解耦
  • 原文地址:https://www.cnblogs.com/chinayixia/p/14689398.html
Copyright © 2020-2023  润新知