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


    因为实验室图像处理的算法都是在OpenCV下写的,还有就是导航的算法也是用C++写的,然后界面部分要求在C#下写,所以不管是Socket通信,还是调用OpenCV的DLL模块,都设计到了C#和C++数据类型的对应,还有结构体的封装使用。在夸语言调用方面,Java和C#都只能调用C格式导出的动态库,因为C数据类型比较单一,容易映射,两者都是在本地端提供一套与之映射的C#或者Java的描述接口,通过底层处理这种映射关系达到调用的目的。

     一. 结构体的传递

    #define JNAAPI extern "C" __declspec(dllexport) // C方式导出函数 
       
    typedef struct     
    {   
        int osVersion;   
        int majorVersion;   
        int minorVersion;   
        int buildNum;   
        int platFormId;   
        char szVersion[128];   
    }OSINFO;  
    
    //获取版本信息(传递结构体指针)
    JNAAPI bool SetVersionPtr(OSINFO *info)
    {
    	char * str = "Hello DLL";
    	info->osVersion = 100;
    	strcpy(info->szVersion, str);
    
    	return true;
    }
    //获取版本信息(传递结构体引用)
    JNAAPI bool SetVersionRef(OSINFO &info)
    {
    	char * str = "Hello DLL";
    	info.osVersion = 101;
    	strcpy(info.szVersion, str);
    
    	return true;
    }
    

      

    在C#下对应的结构体定义:

    // OSINFO定义  
    [StructLayout(LayoutKind.Sequential)]  
    public struct OSINFO  
    {  
        public int osVersion;  
        public int majorVersion;  
        public int minorVersion;  
        public int buildNum;  
        public int platFormId;  
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]  
        public string szVersion;  
    }  
    

      

    可以通过两种方式来调用非托管的函数SetVersionPtr:

    1. 方式一(传入结构体引用),在C#中,结构体是以传值的方式传递的,类才是一传地址的方式传递的,加上关键字ref就可以了。C端传递了两种不同类型的参数,都可以通过引用来解决。

    [DllImport("jnalib.dll", EntryPoint = "GetVersionPtr")]  
    public static extern bool GetVersionPtr(ref OSINFO info);  
    public static extern bool GetVersionRef(ref OSINFO info);
    

      

    2. 方式二(传入IntPtr(平台通用指针))

     对应的函数在C#中的声明:

    [DllImport("CSharpInvokeCpp_CppDemo.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool SetVersionPtr(IntPtr pv);
    

        

    测试代码:

    IntPtr pv = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(OSINFO)));
    //对申请的非托管的内存赋值
    //但是没有对最后一个string赋值
    for (int i = 0; i < 5; i++)
    {
    	Marshal.WriteInt32(pv, i * Marshal.SizeOf(typeof(Int32)), ((Int32)(i + 1)));
    }
    	
    if (CppDLL.SetVersionPtr(pv))
    {
    	Console.WriteLine("--osVersion:{0}", Marshal.ReadInt32(pv, 0));
    	Console.WriteLine("--Major:{0}", Marshal.ReadInt32(pv, 4));
    	Console.WriteLine("--Minor:{0}", Marshal.ReadInt32(pv, 8));
    	Console.WriteLine("--BuildNum:{0}", Marshal.ReadInt32(pv, 12));
    	Console.WriteLine("--szVersion:{0}", Marshal.PtrToStringAnsi((IntPtr)(pv.ToInt32()+20)));
    }
    Marshal.FreeHGlobal(pv);
    

      

    上面的两种方法得区别是:

    第一种方法是先创建一个等价的结构体,然后把这个结构体的引用传递给DLL中的接口函数。第二种方法是申请一块结构体大小的非托管内存,然后向内存中写入数据。调用的时候,只把这块非托管的内存的指针传递给DLL中的接口函数。其实第二种方法可以不用事先定义结构体,只需要知道结构体的大小,申请相应的非托管内存就可以了。

    二.结构体数组的传递

     DLL中非托管代码:

    //传递结构体数组指针
    JNAAPI bool SetVersionArray(OSINFO *info, int nLen)
    {
    	char *str = "Hello DLL";
    
    	for(int i = 0; i < nLen; i++)
    	{
    		info[i].osVersion = 100;
    		strcpy(info[i].szVersion, str);
    	}
    
    	return true;
    }
    

    调用代码接口:

    //C#接口,对于包含数组类型,只能传递IntPtr
    [DllImport("CSharpInvokeCpp_CppDemo.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool SetVersionArray(IntPtr pv, int nLen);
    

    测试代码:

    //结构体数组的传递测试代码
    OSINFO[] infos = new OSINFO[2];
    for (int i = 0; i < infos.Length; i++ )
    {
    	infos[i] = new OSINFO();
    }
    IntPtr[] ptrArr = new IntPtr[1]; 
    ptrArr[0] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(OSINFO)) * 2);
    IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(OSINFO)));
    
    Marshal.Copy(ptrArr, 0, pt, 1);
    CppDLL.SetVersionArray(pt, 2);
    
    for (int i = 0; i < infos.Length; i++)
    {
    	infos[i] = (OSINFO)Marshal.PtrToStructure((IntPtr)(pt.ToInt32() + i * Marshal.SizeOf(typeof(OSINFO))), typeof(OSINFO));
    	Console.WriteLine("OsVersion:{0} szVersion:{1}", infos[i].osVersion, infos[i].szVersion);
    }
    
    Marshal.FreeHGlobal(ptrArr[0]);
    Marshal.FreeHGlobal(pt);
    

      上面的代码中有两处需要说明一下:

    (1)C#中的IntPtr指针做相应的运算的时候,要首先把IntPtr做相应的转换,然后运算。运算结束之后还要再做相应的转换,还原成原来的IntPtr。

    (2)Marshal.Copy这个函数的功能是:将数据从托管数组复制到非托管内存指针,或从非托管内存指针复制到托管数组。

    三. 复杂结构体的传递

    1. 输出参数,结构体作为指针传出

     非托管部分代码:

    typedef struct 
    {
    	char name[20];
    	int age;
    	double scores[32];
    }Student;
    
    //Class中包含结构体数组类型
    typedef struct
    {
    	int number;
    	Student stedents[50];
    }Class;
    
    JNAAPI int GetClass(Class *pClass,int len)
    {
    	for(int i = 0; i < len; i++)
    	{
    		pClass[i].number = i;
    		for(int j = 0; j< 50; j++)
    		{
    			//把name中的前20个字节用0代替
    			memset(pClass[i].stedents[j].name, 0, 20);
    			//给每个同学命名
    			sprintf(pClass[i].stedents[j].name, "name_%d_%d", i, j);
    			pClass[i].stedents[j].age = j % 2 == 0 ? 15:20;
    		}//for
    	}//for
    
    	return 0;
    }
    

      

    上面DLL 的导出函数要求传递的参数为它自定义的Class结构体数组, 那么我们在C#调用它时也要自定义对应的结构体了,

    我们可以定义为如下:

    [StructLayout(LayoutKind.Sequential)]
    struct Student
    {
    	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
    	public string name;
    	public int age;
    	[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
    	public double[] scores;
    }
    [StructLayout(LayoutKind.Sequential)]
    struct Class
    {
    	public int number;
    	[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
    	public Student[] students;
    
    }
    

      需要注意的是,这2个结构体中的数组大小一定要跟C++中的限定一样大小哦,接下来如何使用这个API来正确的获取数据呢,大多数人可能想到像这样的处理方式:

    Class myclass = new Class();
    IntPtr ptr=Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Class)));
    GetClass(ptr);
    Marshal.FreeHGlobal(ptr);
    

      

    没错,这样的处理是没问题的,但是我们的API的参数是Class数组,这种处理方式只是传递一个Class结构体参数,所以这种方式在这里就不太合适了,!

     那大家就想到先Class[] myclass = new Class[MaxClass]; 然后在用Marshal.AllocHGlobal 来获取myclass 数据的指针,

    其实这样也是错的, 因为 Class结构中包含了,不能直接封送的Student结构,所以无论如何上面的想法是错误的!

    那要怎么办呢,其实很简单,就是先分配一段非托管内存,并调用API后,再将非托管内容数据读取到托管结构体数据中!

    示例演示代码如下:

    // 接口定义   
    [DllImport("CSharpInvokeCpp_CppDemo.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int GetClass(IntPtr pv, int len);
    //复杂结构体传递测试代码
    int size = Marshal.SizeOf(typeof(Class)) * 50;
    IntPtr pBuff = Marshal.AllocHGlobal(size);
    CppDLL.GetClass(pBuff, 50);
    
    Class[] pClass = new Class[50];
    for (int i = 0; i < 50; i++)
    {
    	IntPtr pr = new IntPtr(pBuff.ToInt64() + Marshal.SizeOf(typeof(Class)) * i);
    	pClass[i] = (Class)Marshal.PtrToStructure(pr, typeof(Class));
    }
    Marshal.FreeHGlobal(pBuff);
    

      

    5月19日学习内容:

    http://tcspecial.iteye.com/blog/1675621

    http://www.cnblogs.com/naiking/archive/2013/01/17/2864132.html

    http://blog.csdn.net/zhangj1012003_2007/article/details/6283032

    http://blog.csdn.net/xiaowei_cqu/article/details/7693985

  • 相关阅读:
    【蓝桥杯】基础练习 十六进制转十进制
    【蓝桥杯】 基础练习 十六进制转八进制
    【蓝桥杯】 入门训练 序列求和
    【蓝桥杯】 入门训练 圆的面积
    【蓝桥杯】入门训练 Fibonacci数列
    6次考试小结
    搜索
    sudoku
    生日蛋糕
    木棒
  • 原文地址:https://www.cnblogs.com/stemon/p/4515522.html
Copyright © 2020-2023  润新知