asddasd

导航

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
数组作为一种常用的数据结构,各种高级语言都提供了相应的支持,在这些高级语言之间交互操作的时候,数组也是传送集合类型数据的重要结构,希望今天的内容能对大家有所帮助。

posted on 2021-04-22 14:20  asddasd  阅读(791)  评论(0编辑  收藏  举报