• 在C#调用C++的DLL简析


    在C#调用C++的DLL简析(一)——生成非托管dll
    2013-09-05 21:37:29
    标签:C++ C# 非托管C++
    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://joeyliu.blog.51cto.com/3647812/1289614
    经过一晚上的折腾,还是下点决心将些许的心得写下来,以免以后重复劳动。
     
    C#与C/C++相比,前者的优势在于UI,后者的优势在于算法,C++下的指针虽然恶心,若使用得当还是相当方便的,最重要的问题是,市面上很多流行的开发工具库,几乎没有不支持C++的,但全面支持C#只能说是难得,在CPU发展到今天,若说C#的执行效率跟C++相比有很大的差距并不是那么靠谱,若非万不得已我还是宁愿用C#来写代码,调试什么的也很方便。
     
    不得已的情况下,要在C#下使用C++的函数或类,最好的方式就是使用动态链接库(dll),至于COM什么的我是至今没弄明白其原理,也许主要是因为使用起来太麻烦了(还要注册什么的),使用dll的话,可以很方便将一个工程细分到成为两部分,协同编程可以加速进展。当然,用C#的代码改写一遍也不是不可能的,可当你有现成的几万行代码那就真头痛得要命,还是安心的使用动态链接库吧。
     
    当你打开VS2012的时候,新建工程项目,也许你可能发现会有一个“CLR类库”的项目类型,直到今天我才知道,CLR原来指的是托管C++,托管C++跟非托管C++虽然有一定的关系,但很多人更愿意将他俩看成为两种不同的程序语言,托管C++是一种很恶搞的存在,它的唯一作用是用披着C++的马甲来写C#的内容,且最终是为C#服务的,使用CLR生成的dll可以直接在C#下引用,要用CLR还不如直接用C#更简单一点(纯粹个人观点)。
     
    这是最常见的技俩,网上的资料是一大票,但为了我这健忘脑袋,我还是逐步贴图讲明吧,据本人一通宵的成果,几经折腾,终于证明了想在native C++下导入类那是不可能的事,所以,以下讲的仅是如何导入函数而已——就如你们在网上看到的文章一样。
     
    (1)建立生成dll的工程
     
    我用的是VS2012,不过好像跟前面的版本没什么太大的差别。打开VS,选择"新建项目"-“VC++”-"Win32"-"Win32项目",工程的名字叫"MyNativeDll",配置如下图所示,因为我有可能用到MFC的类,所以我就勾选了“MFC”的选项,在此需要注意的是,如果你新建时没有勾选MFC,但在后面却想动用MFC的内容,就会遇到“MFC apps must not #include ”的Error,只是在工程的配置里修改是根本没有用的,必做要重建工程。
     
    0
     
    (2)实现dll导出的函数
     
    新建好工程后,在VS的“解决方案资源管理器”中可以看到如下图的目录,其实你完全可以不用管这些默认的文件,如果你要用,可以在看一下MyNativeDll.h里的注释说明,大概能看得懂的。
     
    0
     
    在工程里添加几个文件,Define.h,CFunction.h,CFunction.cpp,其内容如下所示:
     
    //Define.h 用于导入dll的宏定义。
     
    1
     
     
    2
     
     
    3
     
     
    4
     
     
    5
     
     
    6
     
     
    7
     
    //Define.h
    ///////////////////////////////////////////
    //////////////////////////////////////////
    #ifndef _DEFINE_H_
    #define _DEFINE_H_
    #define _EXTERN_C_  extern "C"  _declspec(dllexport)
    #endif
     
    //CFunction.h 函数定义,这里我特意定义了一组结构,我的用意稍后再讲。
     
    1
     
     
    2
     
     
    3
     
     
    4
     
     
    5
     
     
    6
     
     
    7
     
     
    8
     
     
    9
     
     
    10
     
     
    11
     
     
    12
     
     
    13
     
     
    14
     
     
    15
     
     
    16
     
     
    17
     
     
    18
     
     
    19
     
     
    20
     
     
    21
     
     
    22
     
     
    23
     
     
    24
     
     
    25
     
     
    26
     
     
    27
     
     
    28
     
     
    29
     
     
    30
     
     
    31
     
     
    32
     
     
    33
     
     
    34
     
    //CFunction.h
    ////////////////////////////////////////////
    ///////////////////////////////////////////
    #ifndef _C_FUNCTION_H_
    #define _C_FUNCTION_H_
    #include "Define.h"
    #include <str< span="">ing>
    #include <ist< span="">ream>
    struct SystemTime
    {
        int year;
        int month;
        int day;
        int hour;
        int minute;
        int second;
        int millsecond;
        SystemTime & operator= (SystemTime st)
        {
            this->year = st.year;
            this->month = st.month;
            this->day = st.day;
            this->hour = st.hour;
            this->minute = st.minute;
            this->second = st.second;
            this->millsecond = st.millsecond;
            return *this;
        }
    };
    _EXTERN_C_ int add(int x, int y);
    _EXTERN_C_ int sub(int x, int y);
    _EXTERN_C_ int testChar(char * src, char * res, int nCount);
    _EXTERN_C_ int testStruct(SystemTime & stSrc, SystemTime & stRes);
    #endif //_C_FUNCTION_H_
     
    //CFunction.cpp dll函数的实现,简单的赋值而已,大家应该看得明白的。
     
    1
     
     
    2
     
     
    3
     
     
    4
     
     
    5
     
     
    6
     
     
    7
     
     
    8
     
     
    9
     
     
    10
     
     
    11
     
     
    12
     
     
    13
     
     
    14
     
     
    15
     
     
    16
     
     
    17
     
     
    18
     
     
    19
     
     
    20
     
     
    21
     
     
    22
     
     
    23
     
     
    24
     
    //CFunction.cpp
    ////////////////////////////////////////////
    ////////////////////////////////////////////
    #include "stdafx.h"
    #include "CFunction.h"
    #include <stdio< span="">.h>
    int add(int x, int y)
    {
        return x + y;
    }
    int sub(int x, int y)
    {
        return x - y;
    }
    int testChar(char * src, char * res, int nCount)
    {
        memcpy(res, src, sizeof(char) * nCount);
        return 1;
    }
    int testStruct(SystemTime & stSrc, SystemTime & stRes)
    {
        stRes = stSrc;
        return 1;
    }
     
    添加好代码之后,选择“生成”的选项,因在工程目录下的Debug文件就已经存在我们所需要的MyNativeDll.dll文件,一起的还有lib的静态库文件(稍后要用到),及其他相关的调试文件,至此,我们已经成功的生成了native C++的动态链接库,我只能说,这是相当简单的第一步在而已。
     
    (3)在C#工程下使用生成的dll
     
    新建一个C#的窗口工程(我个人是很讨厌控制台的程序的),工程命名为“DllTest”,这就不教了。
     
    在新建的窗体工程中添加一个CFunction.cs的类,这个类主要是用于导出上面dll里的函数,废话不多说,直接贴代码:
     
    //CFunction.cs dll的函数接口
     
    1
     
     
    2
     
     
    3
     
     
    4
     
     
    5
     
     
    6
     
     
    7
     
     
    8
     
     
    9
     
     
    10
     
     
    11
     
     
    12
     
     
    13
     
     
    14
     
     
    15
     
     
    16
     
     
    17
     
     
    18
     
     
    19
     
     
    20
     
     
    21
     
     
    22
     
     
    23
     
     
    24
     
     
    25
     
     
    26
     
     
    27
     
     
    28
     
     
    29
     
     
    30
     
     
    31
     
     
    32
     
     
    33
     
     
    34
     
     
    35
     
     
    36
     
     
    37
     
     
    38
     
     
    39
     
     
    40
     
     
    41
     
     
    42
     
     
    43
     
     
    44
     
     
    45
     
     
    46
     
     
    47
     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Runtime.InteropServices;
    namespace DllTest
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct SystemTime
        {
            public int year;
            public int month;
            public int day;
            public int hour;
            public int minute;
            public int second;
            public int millsecond;
            public SystemTime(DateTime dt)
            {
                this.year = dt.Year;
                this.month = dt.Month;
                this.day = dt.Day;
                this.hour = dt.Hour;
                this.minute = dt.Minute;
                this.second = dt.Second;
                this.millsecond = dt.Millisecond;
            }
            public override string ToString()
            {
                return this.year.ToString() + "-" + this.month.ToString() + "-" + this.day.ToString() + "  "
                    + this.hour.ToString() + ":" + this.minute.ToString() + "-" + this.second.ToString() + "-"
                    + this.millsecond.ToString();
            }
        };
        public class CFunction
        {
            [DllImport("MyNativeDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
            public extern static int add(int x, int y);
            [DllImport("MyNativeDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
            public extern static int sub(int x, int y);
            [DllImport("MyNativeDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
            public extern static int testChar(ref byte src, ref byte res, int nCount);
            [DllImport("MyNativeDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
            public extern static int testStruct(ref SystemTime stSrc, ref SystemTime stRes);
        }
    }
    上面代码的格式看起来不好看,大家自己下附件里的文件看好了,上面的做法相当是作了一个CFunction的静态类而已。然后在Form1.cs窗体里直接写测试代码,我是直接写在Form1的初始化函数里,懒,没办法。
     
    //Form1.cs 在C#的窗体初始化函数添加测试代码
     
    1
     
     
    2
     
     
    3
     
     
    4
     
     
    5
     
     
    6
     
     
    7
     
     
    8
     
     
    9
     
     
    10
     
     
    11
     
     
    12
     
     
    13
     
     
    14
     
     
    15
     
     
    16
     
     
    17
     
     
    18
     
     
    19
     
     
    20
     
     
    21
     
     
    22
     
     
    23
     
     
    24
     
     
    25
     
     
    26
     
     
    27
     
     
    28
     
     
    29
     
     
    30
     
     
    31
     
     
    32
     
     
    33
     
     
    34
     
     
    35
     
     
    36
     
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Diagnostics;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    namespace DllTest
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                int a = CFunction.add(100, 50);
                int b = CFunction.sub(100, 50);
                Debug.WriteLine("add = " + a.ToString() + "  b = " + b.ToString());
                Debug.WriteLine("\r\n");
                string src = "123456";
                byte[] srcBytes = System.Text.Encoding.ASCII.GetBytes(src);
                byte[] resBytes = new byte[100];
                a = CFunction.testChar(ref srcBytes[0], ref resBytes[0], src.Length);
                string res = (System.Text.Encoding.ASCII.GetString(resBytes, 0, resBytes.Length)).TrimEnd();
                Debug.WriteLine(res.ToString());
                Debug.WriteLine("\r\n");
                SystemTime stSrc = new SystemTime(DateTime.Now);
                SystemTime stRes = new SystemTime();
                a = CFunction.testStruct(ref stSrc, ref stRes);
                Debug.WriteLine(stRes.ToString());
                Debug.WriteLine("\r\n");
            }
        }
    }
     
    在你进行调试之前,务必记得要将在第二步生成的MyNativeDll.dll拷贝至C#工程下的bin\Debug\目录下,然后点击“调试”,看输出窗口,应该会有东西输出的,我就不贴出来了。
     
    (4)总结
     
    1)总体上来讲,生成一个native C++的dll不是很困难的事,重点在于在C#下的dll导出函数那里;
     
    2)个人的经验来看,使用native C++可以导入函数,至于导出C++类,通过指针的方式并非不可能,可是方法过于费解,建议不要那么做;
     
    3)在书写dll导出函数时,变量的传递是关键,建议使用C++的基本类型,如int,float,double等,因为C#下指针的概念很纠结,在C++下的引用符“&”,在C#中则使用ref的标识,需要紧记的一点是,C#与C++的类型并不全然通用(结构对齐问题),注意做变换。像上面的testChar函数,原本string(C#)对应的是char*(C++),但可能由于各种Unicode或多字节的关系,我是没法返回正确的值,于是我采用了byte的传入类型。关于C#与C++混编的类型问题,可以查看下面的文章:C++与C#的类型转换文章2文章3,在网上google到好的文章也是不容易的啊。
     
    4)观察我写的结构,在C++下使用的结构体,在C#必须要重新定义一次,使用    [StructLayout(LayoutKind.Sequential)]的标识用于结构的对齐,如果你变量中使用了string这样的类型,还需要使用MarshalAs这样的方法支定义其长度——才可以跟char *相对应;
     
    5)函数的返回值别用什么string了,我是找为到方法取得其正确的返回值,最好使用ref的引用方法回传回来。
     
    6)指针的参数的传递在C#下使用IntPtr类型作转换,这我先不细说,网上相关文章还是不少的。
     
     
     
    对于不太了解.Net的人,如果想要了解.Net,我必须给他介绍P/Invoke。P/Invoke是什么呢?简单地说,就是在.Net中调用本地代码(Native code)的一种解决方案。所谓“本地代码”是相对于托管代码(Managed code)来说的。
    P/Invoke实在是一个非常棒(awesome)的特性。本来,.Net 这项技术充分印证了托管程序(Managed program)的种种好处,但是它不够“底层”。可是,这又有什么关系呢?我们有P/Invoke!这样,托管代码的优势和调用本地API的需求就无缝地融合在一起了。
    我经常在论坛里看到一些新手提的这种问题:“我刚刚学会了C#,觉得它非常棒,很方便。我的问题是:我能用它调用短信猫(SMS Modem)厂商提供的接口API吗?这些接口API可是C++的耶~~”……OK,现在一旦你了解到了P/Invoke,你就可以完全打消这方面的顾虑了。
     
    闲言少叙,来看我们的例子。
    我们的例子是:把一个C语言写的函数封装到一个动态链接库里面,然后在一个C#程序中很方便地调用它。
    实现这样的一个例子对很多人来说真是意义重大,从此可以不再担心.Net不够“底层”了。
     
    先看我们的C语言函数:
     
    1. int sum(int a, int b)  
    2. {  
    3.     return a + b;  
    4. }  
     
    够简单吧。
     
     
    一、为动态链接库暴露出函数接口
     
    现在我们决定把它封装到一个动态链接库里面。为了让它能封装到动态链接库里面,我们给这个函数申明的前面加上这个:
     
    1. __declspec(dllexport)  
     
    源代码就变成了这样的:
     
     
    1. __declspec(dllexport) int sum(int a, int b)  
    2. {  
    3.     return a + b;  
    4. }  
     
     
    二、编译,得到动态链接库
     
    然后,我们利用Visual C++自带的命令行工具cl、link将它封装成动态链接库。假设文件名为Test.c,我们希望得到Test.dll,命令如下:
     
    1. cl /c Test.c    
    2. link /dll Test.obj  
     
    我们也可以用gcc来编译得到Test.dll。命令如下:
     
     
    1. gcc -shared -o Test.dll Test.c  
     
    现在我们就得到了Test.dll。
     
     
    注:从Test.c得到Test.dll的办法很多,想详细了解的话请阅读一下两篇小文:
    1. 将C语言源代码编译成动态链接库 http://blog.csdn.net/xinyaping/article/details/7284899
    2. Visual C++ 2010 Express Tips: 用 C 和 C++ 创建动态链接库 http://blog.csdn.net/xinyaping/article/details/7288164
     
    三、在C#中通过P/Invoke调用Test.dll中的sum()方法
     
    P/Invoke很简单。请看下面这段简单的C#代码:
     
    1. // -----------------------------------------------------------------------  
    2. //   
    3. // P/Invoke example.  
    4. //   
    5. // -----------------------------------------------------------------------  
    6.   
    7. namespace Invoke  
    8. {  
    9.     using System;  
    10.     using System.Runtime.InteropServices;  
    11.   
    12.     ///   
    13.     /// .Net P/Invoke example.  
    14.     ///   
    15.     internal class Program  
    16.     {  
    17.         ///   
    18.         /// Entry point of the application.  
    19.         ///   
    20.         /// Console arguments.  
    21.         internal static void Main(string[] args)  
    22.         {  
    23.             int result = Sum(2, 3);  
    24.             Console.WriteLine("DLL func execute result: {0}", result);  
    25.         }  
    26.   
    27.         ///   
    28.         /// Call method int sum(int, int) defined in Test.dll  
    29.         ///   
    30.         /// parameter a  
    31.         /// parameter b  
    32.         /// sum of a and b  
    33.         [DllImport("Test.dll", EntryPoint = "sum")]  
    34.         private static extern int Sum(int a, int b);  
    35.     }  
    36. }  
     
    上面这段代码够简单吧。除去注释,除去控制台输出,除去七零八碎的部分,剩下的东西就是这个了:
     
     
    1. [DllImport("Test.dll", EntryPoint = "sum")]  
    2. private static extern int Sum(int a, int b);  
     
    这个就是大名鼎鼎的P/Invoke。注意在这里我故意用了一个和C语言源代码中不一样的函数名Sum。C语言源代码中的函数名是sum,如果C#也用sum这个函数名,那句DLLImport就可以这样写了:
     
     
    1. [DllImport("Test.dll")]  
     
     
    在这里不过是向您展示一下当C#中的函数名和DLL中的函数名不一致时,可以通过EntryPoint来进行映射(Mapping)。
     
    编译并执行这段C#程序,执行时别忘了把Test.dll拷贝到执行目录中。结果当然是我们所预期的:
    0
     
    例子很简单,意义不简单。
    0
     
    参考文献:
    1. 将C语言源代码编译成动态链接库 http://blog.csdn.net/xinyaping/article/details/7284899
    2. Visual C++ 2010 Express Tips: 用 C 和 C++ 创建动态链接库 http://blog.csdn.net/xinyaping/article/details/7288164
    3. An Introduction to P/Invoke and Marshaling on the Microsoft .NET Compact Frameworkhttp://msdn.microsoft.com/en-us/library/aa446536.aspx
    4. Essential P/Invoke http://www.codeproject.com/Articles/12121/Essential-P-Invoke
    5. .Net可以做什么 http://blog.csdn.net/xinyaping/article/details/6722015
     
      因为公司一直都是做C++开发的,因客户需要要提供C#版本接口,研究了一下C#,发现其强大简洁, 在跨语言调用方面封装的很彻底,提供了强大的API与之交互.这点比JNA方便多了. Java与C#都只能调用C格式导出动态库,因为C数据类型比较单一,容易映射. 两者都是在本地端提供一套与之映射的C#/java描述接口,通过底层处理这种映射关系达到调用的目的.
     
      一. 结构体的传递
     
    Cpp代码  
    0
    1. #define JNAAPI extern "C" __declspec(dllexport) // C方式导出函数  
    2.   
    3. typedef struct      
    4. {    
    5.     int osVersion;    
    6.     int majorVersion;    
    7.     int minorVersion;    
    8.     int buildNum;    
    9.     int platFormId;    
    10.     char szVersion[128];    
    11. }OSINFO;    
    12.   
    13. // 1. 获取版本信息(传递结构体指针)    
    14. JNAAPI bool GetVersionPtr( OSINFO *info );    
    15. // 2.获取版本信息(传递结构体引用)    
    16. JNAAPI bool GetVersionRef(OSINFO &info);    
     
     
      可以通过二种方式来调用:
     
    C#代码  
    0
    1. // OSINFO定义  
    2. [StructLayout(LayoutKind.Sequential)]  
    3. public struct OSINFO  
    4. {  
    5.     public int osVersion;  
    6.     public int majorVersion;  
    7.     public int minorVersion;  
    8.     public int buildNum;  
    9.     public int platFormId;  
    10.     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]  
    11.     public string szVersion;  
    12. }  
     
      1. 方式一(传入结构体引用),在C#中,结构体是以传值方式传递,类才是以传地址方式传递,加关键字ref即可. C端传递了两种不同类型的参数,都可以通过引用来解决.
     
    C#代码  
    0
    1. [DllImport("jnalib.dll", EntryPoint = "GetVersionPtr")]  
    2. public static extern bool GetVersionPtr(ref OSINFO info);  
    3. public static extern bool GetVersionRef(ref OSINFO info);  
     
      2. 方式二(传入IntPtr(平台通用指针))
     
    Cpp代码  
    0
    1. IntPtr pv = Marshal.AllocHGlobal(148); //结构体在使用时一定要分配空间(4*sizeof(int)+128)  
    2. Marshal.WriteInt32(pv,148); //向内存块里写入数值  
    3. if (GetVersionPtr(pv)) //直接以非托管内存块地址为参数  
    4. {  
    5.     Console.WriteLine("--osVersion:{0}", Marshal.ReadInt32(pv, 0));  
    6.     Console.WriteLine("--Major:{0}",Marshal.ReadInt32(pv, 4)); //移动4个字节  
    7.     Console.WriteLine("--BuildNum: " + Marshal.ReadInt32(pv, 12));  
    8.     Console.WriteLine("--szVersion: "+Marshal.PtrToStringAnsi((IntPtr)(pv.ToInt32()+20)));  
    9. }  
    10. Marshal.FreeHGlobal(pv); //处理完记得释放内存  
     
     
       二.结构体数组的传递
     
    Cpp代码  
    0
    1. // 传递结构体指针  
    2. JNAAPI bool GetVersionArray(OSINFO *info,int nLen);  
       调用代码:
     
    C#代码  
    0
    1. /** 
    2.  * C#接口,对于包含数组类型,只能传递IntPtr 
    3.  */   
    4. [DllImport("jnalib.dll", EntryPoint = "GetVersionArray")]  
    5. public static extern bool GetVersionArray(IntPtr p, int nLen);    
    6.   
    7. // 源目标参数  
    8. OSINFO[] infos = new OSINFO[2];  
    9. for (int i = 0; i < infos.Length; i++)  
    10. {  
    11.     infos[i] = new OSINFO();  
    12. }  
    13.   
    14. IntPtr[] ptArr = new IntPtr[1];  
    15. ptArr[0] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(OSINFO)) * 2); //分配包含两个元素的数组  
    16. IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(OSINFO)));   
    17. Marshal.Copy(ptArr, 0, pt, 1); //拷贝指针数组  
    18. GetVersionArray(pt, 2); //调用  
    19.   
    20. //还原成结构体数组  
    21. for (int i = 0; i < 2; i++)    
    22. {  
    23.     infos[i]=(OSINFO)Marshal.PtrToStructure((IntPtr)(pt.ToInt32()+i*Marshal.SizeOf(typeof(OSINFO))),typeof(OSINFO));  
    24.     Console.WriteLine("OsVersion:{0} szVersion:{1}", infos[i].osVersion, infos[i].szVersion);  
    25. }  
     
     
      三. 复杂结构体的传递
     
       1. 输出参数,结构体作为指针传出
    Cpp代码  
    0
    1. typedef struct  
    2. {  
    3.     char name[20];  
    4.     int age;  
    5.     double scores[30];  
    6. }Student;  
    7.   
    8. // Class中包含结构体数组类型  
    9. typedef struct  
    10. {  
    11.     int number;  
    12.     Student students[50];  
    13. }Class;  
    14.   
    15. // 传入复杂结构体测试  
    16. JNAAPI int GetClass(Class *pClass,int len);  
     
    Cpp代码  
    0
    1. // 接口定义   
    2. [DllImport("jnalib.dll", EntryPoint = "GetClass")]  
    3. public static extern int GetClass(IntPtr pv,int len);  
    4.   
    5. // 结构体定义  
    6. // Student  
    7. [StructLayout(LayoutKind.Sequential)]  
    8. public struct Student  
    9. {  
    10.     [MarshalAs(UnmanagedType.ByValTStr,SizeConst=20)]  
    11.     public string name;  
    12.     public int age;  
    13.     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]  
    14.     public double[] scores;  
    15. }  
    16.   
    17. // Class  
    18. [StructLayout(LayoutKind.Sequential)]  
    19. public struct Class  
    20. {  
    21.     public int number;  
    22.     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)] // 指定数组尺寸   
    23.     public Student[] students; // 结构体数组定义  
    24. }  
    25.   
    26. // 调用复杂结构体测试  
    27. int size = Marshal.SizeOf(typeof(Class)) * 50;  
    28. IntPtr pBuff = Marshal.AllocHGlobal(size); // 直接分配50个元素的空间,比Marshal.copy方便多了  
    29. GetClass(pBuff, 50);  
    30.   
    31. Class[] pClass = new Class[50];  
    32. for (int i = 0; i < 50; i++)  
    33. {  
    34.     IntPtr ptr = new IntPtr(pBuff.ToInt64() + Marshal.SizeOf(typeof(Class)) * i);  
    35.     pClass[i] = (Class)Marshal.PtrToStructure(ptr, typeof(Class));  
    36. }  
    37. Marshal.FreeHGlobal(pBuff); // 释放内存  
     
       2. 输入参数, 给复杂结构体赋值后作为输入参数传入
       对于比较大的结构体指针,无法直接应用结构体类型,转化成IntPtr类型, 此时需要将原生类型转化为指针,并给指针赋值
       调用方法: Marshal.StructureToPtr(stu, ptr1, true) 
     
     
  • 相关阅读:
    Android 弹性布局 FlexboxLayout了解一下
    设计模式——适配器模式
    UML类图中的六种关系(物理设计阶段)
    设计模式——策略模式
    Kaggle-tiantic数据建模与分析
    数据预处理—独热编码
    推荐系统-协同过滤
    推荐系统实战-冷启动问题
    推荐系统-协同过滤原理与实现
    Hadoop生态系统之Yarn
  • 原文地址:https://www.cnblogs.com/devgis/p/16524197.html
Copyright © 2020-2023  润新知