Platform Invoke

[TOC]

#1. 概述

P/Invoke(Platform Invoke) 是C#中一项允许你从托管代码(Managed code)中访问非托管代码(Unmanaged code)的技术。它允许你访问非托管代码中的函数(functions),结构体(structs),回调(callbacks)等。

1.1 P/Invoke工作流程

P/Invoke依赖于元数据(metadata)来定位导出函数并在运行时封装它们的参数。

platform-invoke-call.gif

当P/Invoke调用非托管函数(Unmanaged function)时,它将执行以下操作序列:

  1. 定位包含函数的DLL。
  2. 将DLL加载进内存。
  3. 在内存中定位函数的地址,将函数参数压入堆栈,根据需要对数据进行编组。
  4. 转移控制到非托管函数。

P/Invoke 会将非托管代码中产生的异常上抛给调用者(托管代码中)。

1.2 Managed and Unmanaged Code

1. Managed Code

C#中的托管代码是指那些依托于.NET framework中的CLR(Common Language Runtime)开发的代码。换句话说,这部分代码是由C#层解释执行的。CLR运行时给托管代码提供了各种服务,包括错误处理(Exception handling)、垃圾回收(Garbage collection)、类型检查(Type checking)等。以上这些服务由CLR提供,并且对编程者屏蔽称为托管服务。所以对于托管代码(Managed code)来说也可以称为安全代码(Safe code),编程者不需要关心内存分配(Memory allocation)、对象创建(Object creation)和对象回收(Object disposal)这些易引发不安全因素的操作。

对于托管代码的执行,CLR会先将其转变为MSIL(Microsoft Intermediate Language)。MSIL一般经由JIT(Just In Time)编译器编译产生。

2. Unmanaged Code

C#中的非托管代码依赖于计算机体系架构,譬如C++或者C语言直接编写的Native code。它们的目标是处理器体系结构,由操作系统直接执行。由于编程者直接参与内存管理,对象创建和销毁等动作,因此一旦不小心编写一些”坏代码“,很容易引发像内存泄漏之类的错误。

{56F43B75-71A3-4B44-9823-944B422B874C}.png

#2. 调用非托管函数

P/Invoke技术可以帮助托管代码调用在DLL(Dynamic link library)中实现的非托管代码,譬如Windows API。它定位并调用导出的函数,并根据需要跨互操作边界封送其参数(整数、字符串、数组、结构体等)。调用DLL中的非托管代码,需要进行以下步骤:

  1. 确认DLL中的目标函数。
  2. 在托管代码中创建包装类。
  3. 在托管类中创建对应的函数原型
  4. 调用托管函数

2.1 确认DLL中目标函数

譬如调用User32.dll中的MessageBox函数。Windows API包含两个不同版本的函数处理不同的字符集:ANSI版本和Unicode版本。MessageBoxA即MessageBox的ANSI版本,MessageBoxW即Unicode版本。

通过DllImportAttribute.EntryPoint域来指定DLL函数的名字或者序列号。如果定义的函数名字和DLL中的函数名字一致则不需要显式指明。否则必须通过Entry Point显式指明调用的函数。

using System;
using System.Runtime.InteropServices;

namespace PInvoke
{
    class NativeMethods
    {
        //声明的函数MsgBox与被调用的函数名字MessageBox不一致,必须通过EntryPoint显示指明。
        [DllImport("user32.dll",EntryPoint = "MessageBox")]
        internal static extern int MsgBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
        
        //声明的函数MessageBox即被调用的函数
        [DllImport("user32.dll")]
        internal static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
    }
}

2.2 在托管代码中创建包装类

将经常使用的DLL函数封装到托管类中是封装平台功能的有效方法。在封装类中,为每一个你想调用的DLL中的托管函数创建一个静态方法。包装类中函数的定义可以包含一些附加信息,譬如字符集(Character set),调用规约(Calling convention)等。

using System;
using System.Runtime.InteropServices;

namespace PInvoke
{
    class NativeMethods
    {
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
        public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
    }
}

2.3 在托管类中创建对应函数原型

为了调用非托管库(Unmanaged library)中导出的函数,.NET framework要求在托管代码中(Managed code)创建一个函数原型(Function prototype)来代表非托管函数(Unmanaged function)。为了确保P/Invoke凭借正确的函数原型进行对应的数据封装,必须执行以下操作:

  • 使用 DllImortAttribute 属性标注托管代码中的静态函数.
  • 用托管数据类型替换非托管数据类型
2.3.1 DllImportAttribute

C#通过DllImportAttribute 注解来表示被注解方法由非托管DLL公开为静态入口点。

BestFitMapping Enables or disables best-fit mapping behavior when converting Unicode characters to ANSI characters.
CallingConvention Indicates the calling convention of an entry point.
CharSet Indicates how to marshal string parameters to the method and controls name mangling.
EntryPoint Indicates the name or ordinal of the DLL entry point to be called.
ExactSpelling Controls whether the CharSet field causes the common language runtime to search an unmanaged DLL for entry-point names other than the one specified.
PreserveSig Indicates whether unmanaged methods that have HRESULT return values are directly translated or whether HRESULT return values are automatically converted to exceptions.
SetLastError Indicates whether the callee sets an error (SetLastError on Windows or errno on other platforms) before returning from the attributed method.
ThrowOnUnmappableChar Enables or disables the throwing of an exception on an unmappable Unicode character that is converted to an ANSI "?" character.
1. CharSet
    //
    // Summary:
    //     Dictates which character set marshaled strings should use.
    [ComVisible(true)]
    public enum CharSet
    {
        //
        // Summary:
        //     This value is obsolete and has the same behavior as System.Runtime.InteropServices.CharSet.Ansi.
        None = 1,
        //
        // Summary:
        //     Marshal strings as multiple-byte character strings.
        Ansi = 2,
        //
        // Summary:
        //     Marshal strings as Unicode 2-byte characters.
        Unicode = 3,
        //
        // Summary:
        //     Automatically marshal strings appropriately for the target operating system.
        //     The default is System.Runtime.InteropServices.CharSet.Unicode on Windows NT,
        //     Windows 2000, Windows XP, and the Windows Server 2003 family; the default is
        //     System.Runtime.InteropServices.CharSet.Ansi on Windows 98 and Windows Me. Although
        //     the common language runtime default is System.Runtime.InteropServices.CharSet.Auto,
        //     languages may override this default. For example, by default C# marks all methods
        //     and types as System.Runtime.InteropServices.CharSet.Ansi.
        Auto = 4
    }
2. CallingConvention
//
// Summary:
//     Specifies the calling convention required to call methods implemented in unmanaged
//     code.
[ComVisible(true)]
public enum CallingConvention
{
    //
    // Summary:
    //     This member is not actually a calling convention, but instead uses the default
    //     platform calling convention. For example, on Windows the default is System.Runtime.InteropServices.CallingConvention.StdCall
    //     and on Windows CE.NET it is System.Runtime.InteropServices.CallingConvention.Cdecl.
    Winapi = 1,
    //
    // Summary:
    //     The caller cleans the stack. This enables calling functions with varargs, which
    //     makes it appropriate to use for methods that accept a variable number of parameters,
    //     such as Printf.
    Cdecl = 2,
    //
    // Summary:
    //     The callee cleans the stack. This is the default convention for calling unmanaged
    //     functions with platform invoke.
    StdCall = 3,
    //
    // Summary:
    //     The first parameter is the this pointer and is stored in register ECX. Other
    //     parameters are pushed on the stack. This calling convention is used to call methods
    //     on classes exported from an unmanaged DLL.
    ThisCall = 4,
    //
    // Summary:
    //     This calling convention is not supported.
    FastCall = 5
}
2.3.2 封装数据类型
1. P/Invoke基础数据类型
Unmanaged type in Windows APIs Unmanaged C language type Managed type Description
VOID void System.Void Applied to a function that does not return a value.
HANDLE void * System.IntPtr or System.UIntPtr 32 bits on 32-bit Windows operating systems, 64 bits on 64-bit Windows operating systems.
BYTE unsigned char System.Byte 8 bits
SHORT short System.Int16 16 bits
WORD unsigned short System.UInt16 16 bits
INT int System.Int32 32 bits
UINT unsigned int System.UInt32 32 bits
LONG long System.Int32 32 bits
BOOL long System.Boolean or System.Int32 32 bits
DWORD unsigned long System.UInt32 32 bits
ULONG unsigned long System.UInt32 32 bits
CHAR char System.Char Decorate with ANSI.
WCHAR wchar_t System.Char Decorate with Unicode.
LPSTR char * System.String or System.Text.StringBuilder Decorate with ANSI.
LPCSTR const char * System.String or System.Text.StringBuilder Decorate with ANSI.
LPWSTR wchar_t * System.String or System.Text.StringBuilder Decorate with Unicode.
LPCWSTR const wchar_t * System.String or System.Text.StringBuilder Decorate with Unicode.
FLOAT float System.Single 32 bits
DOUBLE double System.Double 64 bits
using System;
using System.Runtime.InteropServices;

namespace PInvoke
{
    class NativeMethods
    {
                /*
 int MessageBox(
[in, optional] HWND    hWnd,
[in, optional] LPCTSTR lpText,
[in, optional] LPCTSTR lpCaption,
[in]           UINT    uType
);
     */
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
        public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
    }
}
2. Marshalling Strings

P/Invoke拷贝string类型的参数,并且将它从.NET framework(Unicode)格式转换为非托管格式(ANSI)。在托管模式下,字符串是不可变的,所以在函数返回时,P/Invoke并不会将其从非托管内存拷贝回托管内存。

1). String作为函数参数

using System;
using System.Runtime.InteropServices;

namespace PInvoke
{
    namespace StrMarshalling
    {
        class Invoker
        {
            /*
     int MessageBox(
    [in, optional] HWND    hWnd,
    [in, optional] LPCTSTR lpText,
    [in, optional] LPCTSTR lpCaption,
    [in]           UINT    uType
    );
         */ //Use DllImport to import the Win32 MessageBox function. 
            //Delcares managed prototypes for unmanaged functions.
            [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "MessageBox")]
            public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
            
            //Creates incorrect outout in the message box because the character type.
            [DllImport("user32.dll", CharSet = CharSet.Ansi, EntryPoint = "MessageBoxW")]
            public static extern int MessageBox_ErrOut(IntPtr hWnd, string lpText, string lpCaption, uint uType);
            
            //Creates a mismatch between the EntryPoint,CharSet, and ExactSpelling fields.
            [DllImport("user32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, EntryPoint = "MessageBox")]
            public static extern int MessageBox_ErrNotFound(IntPtr hWnd, string lpText, string lpCaption, uint uType);
        }    
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Invoker.MessageBox(IntPtr.Zero, "Correct text.", "MsgBox Sample", 0);
            Invoker.MessageBox_ErrOut(IntPtr.Zero, "Incorrect text", "MsgBox Sample", 0);
            try
            {
                Invoker.MessageBox_ErrNotFound(IntPtr.Zero, "No such function", "MsgBox Sample", 0);
            }
            catch(EntryPointNotFoundException e)
            {
                Console.WriteLine($"{nameof(EntryPointNotFoundException)} thrown as expected!");
            }
        }
    }
}

2). String作为函数返回值

using System;
using System.Runtime.InteropServices;
using static PInvoke.Type;
using PInvoke.StrMarshalling;

namespace PInvoke
{
    namespace StrMarshalling
    {
        class Invoker
        {
            [DllImport("PINVOKELIB.dll", EntryPoint = "TestStringAsResult")]
            public static extern string TestStringAsResult();

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestStringInStruct")]
            public static extern void TestStringInStruct(MYSTRSTRUCT_U mstruct);

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestStringInStructAnsi")]
            public static extern void TestStringInStructAnsi(MYSTRSTRUCT_A mstruct);
        }
    }
    
    class Type
    {
        /**
         *typedef struct _MYSTRSTRUCT {
         *    wchar_t *buffer;
         *    UINT size;
         *}MYSTRSTRUCT;
         */
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct MYSTRSTRUCT_U
        {
            public string buffer;
            public UInt32 size;
        }

        /**
         * typedef struct _MYSTRSTRUCT2 {
         *    char* buffer;
         *    UINT size;
         *}MYSTRSTRUCT2;
         */
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct MYSTRSTRUCT_A
        {
            public string buffer;
            public UInt32 size;
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            string result = Invoker.TestStringAsResult();
            Console.WriteLine(result);

            string buffer = "Hello from managed code.";
            MYSTRSTRUCT_U mystruct_u = new MYSTRSTRUCT_U();
            mystruct_u.buffer = buffer;
            mystruct_u.size = (UInt32)buffer.Length;
            Invoker.TestStringInStruct(mystruct_u);

            MYSTRSTRUCT_A mystruct_a = new MYSTRSTRUCT_A();
            mystruct_a.buffer = buffer;
            mystruct_a.size = (UInt32)buffer.Length;
            Invoker.TestStringInStructAnsi(mystruct_a);
        }
    }
}

3). String作为In/Out参数

GetCommandLineW方法返回指向一个缓冲区的指针而不是string的目的是为了阻止托管内存被自动回收。调用者通过Marshal提供的PtrToStringUni方法将指针转变为所需的字符串。

using System;
using System.Runtime.InteropServices;

namespace PInvoke
{
    namespace StrMarshalling
    {
        class Invoker
        {
            [DllImport("Kernel32.dll", EntryPoint = "GetCommandLineW")]
            public static extern IntPtr GetCommandLineW();
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            IntPtr cmdLine = Invoker.GetCommandLineW();
            string commandLine = Marshal.PtrToStringUni(cmdLine);
        }
    }
}
3. Marshalling Classes,Structures,and Unions

类(Classes)和结构体(Structures)在.NET framework中有相似之处,它们都含有字段(fileds)、属性(properties)以及事件(events)。它们之间的其中一个显著的差异在于类是引用类型,而结构体是值类型。

1). 封装类

using System;
using System.Runtime.InteropServices;
using static PInvoke.Type;

namespace PInvoke
{
    namespace ClassMarshalling
    {
        class Invoker
        {   
            [DllImport("Kernel32.dll", EntryPoint = "GetSystemTime")]
            public static extern void GetSystemTime([In, Out]SystemTime time);
        }
    }
    
    class Type
    {
        /**
         * typedef struct _SYSTEMTIME {
         *    WORD wYear;
         *    WORD wMonth;
         *    WORD wDayOfWeek;
         *    WORD wDay;
         *    WORD wHour;
         *    WORD wMinute;
         *    WORD wSecond;
         *    WORD wMilliseconds;
         *} SYSTEMTIME, *PSYSTEMTIME;
         */
        [StructLayout(LayoutKind.Sequential)]
        public class SystemTime
        {
            public UInt16 year;
            public UInt16 month;
            public UInt16 dayOfWeek;
            public UInt16 day;
            public UInt16 hour;
            public UInt16 minute;
            public UInt16 second;
            public UInt16 milliseconds;
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            SystemTime time = new SystemTime();
            ClassMarshalling.Invoker.GetSystemTime(time);
            Console.WriteLine($"{time.month} {time.day} {time.year}");
        }
    }
}

2). 封装结构体

using System;
using System.Runtime.InteropServices;
using static PInvoke.Type;

namespace PInvoke
{
    namespace StructureMarshalling
    {
        class Invoker
        {
            [DllImport("PINVOKELIB.dll", EntryPoint = "TestStructInStrcut")]
            public static extern int TestStructInStrcut(ref MyPerson2 person);

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestStructInStruct3")]
            public static extern void TestStructInStruct3(MyPerson3 person);

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestArrayInStruct")]
            public static extern void TestArrayInStruct(ref MyArrayStruct mstruct);
        }
    }
    
    class Type
    {
        /*
         * typedef struct _MYPERSON {
         *    char *first;
         *    char *last;
         *} MYPERSON;
         */
        [StructLayout(LayoutKind.Sequential)]
        public struct MyPerson
        {
            public string first;
            public string last;
        }

        /*
         * typedef struct _MYPERSON2 {
         *    MYPERSON *person;
         *    int age;
         *} MYPERSON2;
         */
        [StructLayout(LayoutKind.Sequential)]
        public struct MyPerson2
        {
            public IntPtr person;
            public int age;
        }

        /*
         * typedef struct _MYPERSON3 {
         *    MYPERSON person;
         *    int age;
         *} MYPERSON3;
         */
        [StructLayout(LayoutKind.Sequential)]
        public struct MyPerson3
        {
            public MyPerson person;
            public int age;
        }

        /*
         * typedef struct _MYARRAYSTRCUT {
         *    bool flag;
         *    int vals[3];
         *} MYARRAYSTRUCT;
         */
        [StructLayout(LayoutKind.Sequential)]
        public struct MyArrayStruct
        {
            public bool flag;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
            public int[] values;
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            //Structure with a pointer to another structure.
            MyPerson person;
            person.first = "Henry";
            person.last = "Hu";

            MyPerson2 person2;
            person2.age = 29;

            IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(person));
            Marshal.StructureToPtr(person, buffer, false);
            person2.person = buffer;

            Console.WriteLine("\nPerson before call:");
            Console.WriteLine("first = {0}, last = {1}, age = {2}.",
                person.first, person.last, person2.age);
            int res = StructureMarshalling.Invoker.TestStructInStrcut(ref person2);
            MyPerson personRes = (MyPerson)Marshal.PtrToStructure(person2.person, typeof(MyPerson));
            Marshal.FreeCoTaskMem(buffer);
            Console.WriteLine("Person after call:");
            Console.WriteLine("first = {0}, last = {1}, age = {2}",
                personRes.first,personRes.last,person2.age);

            //Structure with an embeded structure.
            MyPerson3 person3 = new MyPerson3();
            person3.person.first = "Henry";
            person3.person.last = "Hu";
            person3.age = 29;
            StructureMarshalling.Invoker.TestStructInStruct3(person3);

            //Structure with an embeded array.
            MyArrayStruct myArrayStruct = new MyArrayStruct();
            myArrayStruct.flag = true;
            myArrayStruct.values = new int[3];
            myArrayStruct.values[0] = 1;
            myArrayStruct.values[1] = 2;
            myArrayStruct.values[2] = 3;
            Console.WriteLine("\nStructure with array before call:");
            Console.WriteLine(myArrayStruct.flag);
            Console.WriteLine("{0}-{1}-{2}",
                myArrayStruct.values[0],
                myArrayStruct.values[1],
                myArrayStruct.values[2]);
            StructureMarshalling.Invoker.TestArrayInStruct(ref myArrayStruct);
            Console.WriteLine("\nStructure with array after call:");
            Console.WriteLine(myArrayStruct.flag);
            Console.WriteLine("{0}-{1}-{2}",
                myArrayStruct.values[0],
                myArrayStruct.values[1],
                myArrayStruct.values[2]);
        }
    }
}

3). 封装联合体

using System;
using System.Runtime.InteropServices;
using static PInvoke.Type;

namespace PInvoke
{
    namespace UnionMarshalling
    {
        class Invoker
        {
            [DllImport("PINVOKELIB.dll", EntryPoint = "TestUnion")]
            public static extern int TestUnion(MyUnion munion, int type);

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestUnion2")]
            public static extern int TestUnion2(MyUnion2_1 myunion, int type);

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestUnion2")]
            public static extern int TestUnion2(MyUnion2_2 myunion, int type);
        }
    }
    
    class Type
    {
        /*
         * union MYUNION
         * {
         *    int i;
         *    double d;
         * };
         */
        [StructLayout(LayoutKind.Explicit)]
        public struct MyUnion
        {
            [FieldOffset(0)]
            public int i;
            [FieldOffset(0)]
            public double d;
        }

        /*
         * union MYUNION2 {
         *    int i;
         *    char str[128];
         *};
         */
        [StructLayout(LayoutKind.Explicit, Size = 128)]
        public struct MyUnion2_1
        {
            [FieldOffset(0)]
            public int i;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MyUnion2_2
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string str;
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            MyUnion munion = new MyUnion();
            munion.i = 99;
            UnionMarshalling.Invoker.TestUnion(munion, 1);

            munion.d = 99.99;
            UnionMarshalling.Invoker.TestUnion(munion, 2);

            MyUnion2_1 mu2_1 = new MyUnion2_1();
            mu2_1.i = 999;
            UnionMarshalling.Invoker.TestUnion2(mu2_1, 1);

            MyUnion2_2 mu2_2 = new MyUnion2_2();
            mu2_2.str = "This is the test string from managed mem.";
            UnionMarshalling.Invoker.TestUnion2(mu2_2, 2);
        }
    }
}
4. Marshalling Different Types of Arrays
using System;
using System.Runtime.InteropServices;
using static PInvoke.Type;

namespace PInvoke
{
    namespace ArrayMarshalling
    {
        class Invoker
        {
            [DllImport("PINVOKELIB.dll", EntryPoint = "TestArrayOfInts")]
            public static extern int TestArrayOfInts([In, Out]int[] arr, int size);

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestRefArrayOfInts")]
            public static extern int TestRefArrayOfInts(ref IntPtr array, ref int size);

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestMatrixOfInts")]
            public static extern int TestMatrixOfInts([In, Out] int[,] pMatrix, int row);

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestArrayOfStrings")]
            public static extern int TestArrayOfStrings([In, Out] string[] strArray, int size);

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestArrayOfStructs")]
            public static extern int TestArrayOfStructs([In, Out]MyPoint[] pointArray, int size);

            [DllImport("PINVOKELIB.dll", EntryPoint = "TestArrayOfStructs2")]
            public static extern int TestArrayOfStructs2([In, Out]MyPerson[] personArray, int size);
        }
    }
    
    class Type
    {
        /*
         * typedef struct _MYPERSON {
         *    char *first;
         *    char *last;
         *} MYPERSON;
         */
        [StructLayout(LayoutKind.Sequential)]
        public struct MyPerson
        {
            public string first;
            public string last;

            public MyPerson(string first, string last)
            {
                this.first = first;
                this.last = last;
            }

            public override string ToString()
            {
                return string.Format("[First={0}, Last={1}]", first, last);
            }
        }

        /*
         * typedef struct _MYPOINT {
         *    int x;
         *    int y;
         *} MYPOINT;
         */
        [StructLayout(LayoutKind.Sequential)]
        public struct MyPoint
        {
            public int x;
            public int y;

            public MyPoint(int x, int y)
            {
                this.x = x;
                this.y = y;
            }

            public override string ToString()
            {
                return string.Format("[x={0}, y={1}]", x, y);
            }
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            // Array ByVal
            int[] arr1 = new int[10];
            Console.WriteLine("Integer array passed ByVal before call:");
            for (int i = 0; i < arr1.Length; i++)
            {
                arr1[i] = i;
                Console.Write(" " + arr1[i]);
            }
            int sum1 = ArrayMarshalling.Invoker.TestArrayOfInts(arr1, arr1.Length);
            Console.WriteLine("\nSum of elements: " + sum1);
            Console.WriteLine("\nInteger array passed ByVal after call:");
            foreach (int i in arr1)
            {
                Console.Write(" " + i);
            }

            // Array ByRef
            int[] arr2 = new int[10];
            int size = arr2.Length;
            for (int i = 0; i < arr2.Length; i++)
            {
                arr2[i] = i;
                Console.Write(" " + arr2[i]);
            }
            IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(size) * arr2.Length);
            Marshal.Copy(arr2, 0, buffer, size);
            int sum2 = ArrayMarshalling.Invoker.TestRefArrayOfInts(ref buffer, ref size);
            Console.WriteLine("\nSum of elements: " + sum2);
            if (size > 0)
            {
                int[] arrres = new int[size];
                Marshal.Copy(buffer, arrres, 0, size);
                Marshal.FreeCoTaskMem(buffer);
                Console.WriteLine("\nInteger array passed ByRef after call:");
                foreach (int i in arrres)
                {
                    Console.Write(" " + i);
                }
            } else
            {
                Console.WriteLine("\nArray aftere call is empty.");
            }

            // Matrix ByVal
            const int DIM = 5;
            int[,] matrix = new int[DIM, DIM];
            for (int i = 0; i < DIM; i++)
            {
                for (int j = 0; j < DIM; j++)
                {
                    matrix[i, j] = j;
                    Console.Write(" " + matrix[i, j]);
                }
                Console.WriteLine(" ");
            }
            int sum3 = ArrayMarshalling.Invoker.TestMatrixOfInts(matrix, DIM);
            Console.WriteLine("\nSum of elements:" + sum3);
            Console.WriteLine("\nMatrix after call: ");
            for (int i = 0; i < DIM; i++)
            {
                for (int j = 0; j < DIM; j++)
                {
                    Console.Write(" " + matrix[i, j]);
                }
                Console.WriteLine(" ");
            }

            // String array ByVal
            string[] str_arr = { "one", "two", "three", "four", "five" };
            Console.WriteLine("\n\nString array before call: ");
            foreach (string s in str_arr)
            {
                Console.Write(" " + s);
            }
            int lenSum = ArrayMarshalling.Invoker.TestArrayOfStrings(str_arr, str_arr.Length);
            Console.WriteLine("\nSum of string length: " + lenSum);
            Console.WriteLine("\nString array after call: ");
            foreach (string s in str_arr)
            {
                Console.Write(" " + s);
            }

            // Struct array ByVal
            MyPoint[] points = { new MyPoint(1, 1), new MyPoint(2, 2), new MyPoint(3, 3) };
            Console.WriteLine("\n\nPoints array before call:");
            foreach(MyPoint p in points)
            {
                Console.Write(" " + p);
            }
            int structSum = ArrayMarshalling.Invoker.TestArrayOfStructs(points, points.Length);
            Console.WriteLine("\nSum of points: "+ structSum);
            Console.WriteLine("\nPoints array after call:");
            foreach(MyPoint p in points)
            {
                Console.Write(" " + p);
            }

            // Struct with strings array ByVal
            MyPerson[] persons =
            {
                new MyPerson("Henry","Hu"),
                new MyPerson("John","Tyler"),
                new MyPerson("Jimmy","Carter")
            };
            Console.WriteLine("\n\nPersons array before call: ");
            foreach(MyPerson p in persons)
            {
                Console.Write(" " + p);
            }
            int nameSum = ArrayMarshalling.Invoker.TestArrayOfStructs2(persons, persons.Length);
            Console.WriteLine("\nSum of name lengths: " + nameSum);
            Console.WriteLine("\n\nPersons array after call:");
            foreach(MyPerson p in persons)
            {
                Console.Write(" " + p);
            }
        }
    }
}
5. PINVOKELIB.dll

PInvokeLib.h

// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the PINVOKELIB_EXPORTS
// symbol defined on the command line. This symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// PINVOKELIB_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef PINVOKELIB_EXPORTS
#define PINVOKELIB_API __declspec(dllexport)
#else
#define PINVOKELIB_API __declspec(dllimport)
#endif

//Define the test structures
typedef struct _MYPOINT {
    int x;
    int y;
}MYPOINT;

typedef struct _MYPERSON {
    char *first;
    char *last;
}MYPERSON;

typedef struct _MYPERSON2 {
    MYPERSON *person;
    int age;
}MYPERSON2;

typedef struct _MYPERSON3 {
    MYPERSON person;
    int age;
}MYPERSON3;

union MYUNION
{
    int i;
    double d;
};

union MYUNION2 {
    int i;
    char str[128];
};

typedef struct _MYSTRSTRUCT {
    wchar_t *buffer;
    UINT size;
}MYSTRSTRUCT;

typedef struct _MYSTRSTRUCT2 {
    char* buffer;
    UINT size;
}MYSTRSTRUCT2;


typedef struct _MYARRAYSTRCUT {
    bool flag;
    int vals[3];
}MYARRAYSTRUCT;

// Constants and pointer definitions
const int COL_DIM = 5;

typedef bool (CALLBACK *FPTER)(int i);

typedef bool (CALLBACK *FPTER2)(char *str);

// Data type codes
enum DataType {
    DT_I2 = 1,
    DT_I4,
    DT_R4,
    DT_R8,
    DT_STR
};

// This class is exported from the dll
class PINVOKELIB_API CTestClass {
public:
    CTestClass(void);
    int DoSomething(int i);
private:
    int m_member;
};

// Exports for PInvokeLib.dll
#ifdef __cplusplus
extern "C"
{
#endif
    PINVOKELIB_API CTestClass* CreateTestClass();

    PINVOKELIB_API void DeleteTestClass(CTestClass* instance);

    PINVOKELIB_API int TestArrayOfInts(int* pArray, int size);

    PINVOKELIB_API int TestRefArrayOfInts(int** ppArray, int* size);

    PINVOKELIB_API int TestMatrixOfInts(int pMatrix[][COL_DIM], int row);

    PINVOKELIB_API int TestArrayOfStrings(char* ppStrArray[], int size);

    PINVOKELIB_API int TestArrayOfStructs(MYPOINT* pPointArray, int size);

    PINVOKELIB_API int TestArrayOfStructs2(MYPERSON* pPersonArray, int size);

    PINVOKELIB_API int TestStructInStrcut(MYPERSON2* pPerson2);

    PINVOKELIB_API void TestStructInStruct3(MYPERSON3 person3);

    PINVOKELIB_API void TestUnion(MYUNION u, int type);

    PINVOKELIB_API void TestUnion2(MYUNION2 u, int type);

    PINVOKELIB_API void TestCallBack(FPTER pf, int value);

    PINVOKELIB_API void TestCallback2(FPTER2 pf2, char* value);

    PINVOKELIB_API char* TestStringAsResult();

    PINVOKELIB_API void TestStringInStruct(MYSTRSTRUCT* pStruct);

    PINVOKELIB_API void TestStringInStructAnsi(MYSTRSTRUCT2* pStruct);

    PINVOKELIB_API void TestOutArrayOfStructs(int* pSize, MYSTRSTRUCT2** ppStruct);

    PINVOKELIB_API void SetData(DataType type, void* object);

    PINVOKELIB_API void TestArrayInStruct(MYARRAYSTRUCT* pStruct);

#ifdef __cplusplus
}
#endif // _cplusplus

PInvokeLib.cpp

// PInvokeLib.cpp : Defines the exported functions for the DLL.
//

#include "pch.h"
#include "framework.h"
#include "PInvokeLib.h"

#include 
#include 

// This is the constructor of a class that has been exported.
CTestClass::CTestClass()
{
    m_member = 1;
}

int CTestClass::DoSomething(int i) {
    return i * i + m_member;
}

PINVOKELIB_API CTestClass* CreateTestClass() {
    return new CTestClass();
}

PINVOKELIB_API void DeleteTestClass(CTestClass* instance) {
    delete instance;
}

PINVOKELIB_API int TestArrayOfInts(int* pArray, int size) {
    int result = 0;
    for (int i = 0; i < size; i++) {
        result += pArray[i];
        pArray[i] += 100;
    }
    return result;
}

PINVOKELIB_API int TestRefArrayOfInts(int **ppArray, int* pSize) {
    int result = 0;

    //CoTaskMemAlloc must be used instead of the new operator
    //because code on the managed side will call Marshal.FreeCoTaskMem
    //to free this memory.
    int* newArray = (int*)CoTaskMemAlloc(sizeof(int) * 5);
    for (int i = 0; i < *pSize; i++) {
        result += (*ppArray)[i];
    }

    for (int j = 0; j < 5; j++) {
        newArray[j] = (*ppArray)[j] + 100;
    }

    CoTaskMemFree(*ppArray);
    *ppArray = newArray;
    *pSize = 5;

    return result;
}

PINVOKELIB_API int TestMatrixOfInts(int pMatrix[][COL_DIM], int row) {
    int result = 0;

    for (int i = 0; i < row; i++) {
        for (int j = 0; j < COL_DIM; j++) {
            result += pMatrix[i][j];
            pMatrix[i][j] += 100;
        }
    }

    return result;
}

PINVOKELIB_API int TestArrayOfStrings(char* ppStrArray[], int size) {
    int result = 0;

    STRSAFE_LPSTR tmp;
    size_t len;
    const size_t alloc_size = sizeof(char) * 10;

    for (int i = 0; i < size; i++) {
        len = 0;

        StringCchLengthA(ppStrArray[i], STRSAFE_MAX_CCH, &len);
        result += len;

        tmp = (STRSAFE_LPSTR)CoTaskMemAlloc(alloc_size);
        StringCchCopyA(tmp, alloc_size, (STRSAFE_LPCSTR)"123456789");

        //CoTaskMemFree must be used instead of delete to free memory.

        CoTaskMemFree(ppStrArray[i]);
        ppStrArray[i] = (char *)tmp;
    }

    return result;
}

PINVOKELIB_API int TestArrayOfStructs(MYPOINT* pPointArray, int size) {
    int result;
    MYPOINT* pCur = pPointArray;

    for (int i = 0; i < size; i++) {
        result += pCur->x + pCur->y;
        pCur->y = 0;
        pCur++;
    }

    return result;
}

PINVOKELIB_API int TestArrayOfStructs2(MYPERSON* pPersonArray, int size) {
    int result = 0;
    MYPERSON* pCur = pPersonArray;
    STRSAFE_LPSTR tmp;
    size_t len;

    for (int i = 0; i < size; i++) {
        len = 0;
        StringCchLengthA(pCur->first, STRSAFE_MAX_CCH, &len);
        len++;
        result += len;
        len = 0;
        StringCchLengthA(pCur->last, STRSAFE_MAX_CCH, &len);
        len++;
        result += len;

        len = sizeof(char) * (len + 2);
        tmp = (STRSAFE_LPSTR)CoTaskMemAlloc(len);
        StringCchCopyA(tmp, len, (STRSAFE_LPSTR)"Mc");
        StringCbCatA(tmp, len, (STRSAFE_LPCSTR)pCur->last);
        result += 2;

        CoTaskMemFree(pCur->last);
        pCur->last = (char*)tmp;
        pCur++;
    }

    return result;
}

PINVOKELIB_API int TestStructInStrcut(MYPERSON2* pPerson2) {
    size_t len = 0;

    StringCchLengthA(pPerson2->person->last, STRSAFE_MAX_CCH, &len);
    len = sizeof(char) * (len + 2) + 1;

    STRSAFE_LPSTR tmp = (STRSAFE_LPSTR)CoTaskMemAlloc(len);
    StringCchCopyA(tmp, len, (STRSAFE_LPSTR)"Mc");
    StringCbCatA(tmp, len, (STRSAFE_LPSTR)pPerson2->person->last);

    CoTaskMemFree(pPerson2->person->last);
    pPerson2->person->last = (char *)tmp;

    return pPerson2->age;
}

PINVOKELIB_API void TestStructInStruct3(MYPERSON3 person3) {
    printf("\n\nperson passed by value:\n");
    printf("first = %s,last = %s, age = %i\n\n",
        person3.person.first,
        person3.person.last,
        person3.age);
}

PINVOKELIB_API void TestUnion(MYUNION u, int type) {
    if ((type != 1) && (type != 2)) {
        return;
    }
    if (type == 1) {
        printf("\n\nInteger passed: %i.", u.i);
    }
    else if (type == 2) {
        printf("\n\nDouble passed: %f.", u.d);
    }
}

PINVOKELIB_API void TestUnion2(MYUNION2 u, int type) {
    if ((type != 1) && (type != 2)) {
        return;
    }
    if (type == 1) {
        printf("\n\nInteger passed: %i.", u.i);
    }
    else if (type == 2) {
        printf("\n\nString passed: %s.", u.str);
    }
}

PINVOKELIB_API void TestCallBack(FPTER pf, int value) {
    printf("\nReceived value: %i", value);
    printf("\nPassing to callback...");
    bool res = pf(value);

    if (res) {
        printf("Callback returned true.\n");
    }
    else {
        printf("Callback returned false.\n");
    }
}

PINVOKELIB_API void TestCallback2(FPTER2 pf2, char* value) {
    printf("\nReceived value: %s", value);
    printf("\nPassing to callback...");
    bool res = pf2(value);

    if (res) {
        printf("Callback2 returned true.\n");
    }
    else {
        printf("Callback2 returned false.\n");
    }
}

PINVOKELIB_API char* TestStringAsResult() {
    const size_t alloc_size = 64;
    STRSAFE_LPSTR result = (STRSAFE_LPSTR)CoTaskMemAlloc(alloc_size);
    STRSAFE_LPCSTR test = "This is the return value.";
    StringCchCopyA(result, alloc_size, test);

    return (char *)result;
}

PINVOKELIB_API void TestStringInStruct(MYSTRSTRUCT* pStruct) {
    wprintf(L"\nUnicode buffer content: %s\n", pStruct->buffer);

    StringCbCatW(pStruct->buffer, pStruct->size, (STRSAFE_LPWSTR)L"++");
}

PINVOKELIB_API void TestStringInStructAnsi(MYSTRSTRUCT2* pStruct) {
    printf("\nAnsi buffer content: %s\n", pStruct->buffer);

    StringCbCatA(pStruct->buffer, pStruct->size, (STRSAFE_LPSTR)"++");
}

PINVOKELIB_API void TestOutArrayOfStructs(int* pSize, MYSTRSTRUCT2** ppStruct) {
    const int cArraySize = 5;
    *pSize = 0;
    *ppStruct = (MYSTRSTRUCT2*)CoTaskMemAlloc(cArraySize * sizeof(MYSTRSTRUCT2));

    if (ppStruct != NULL) {
        MYSTRSTRUCT2* pCurStruct = *ppStruct;
        LPSTR buffer;
        *pSize = cArraySize;

        STRSAFE_LPCSTR teststr = "***";
        size_t len = 0;
        StringCchLengthA(teststr, STRSAFE_MAX_CCH, &len);
        len++;

        for (int i = 0; i < cArraySize; i++, pCurStruct++) {
            pCurStruct->size = len;
            buffer = (LPSTR)CoTaskMemAlloc(len);
            StringCchCopyA(buffer, len, teststr);
            pCurStruct->buffer = (char *)buffer;
        }
    }
}

PINVOKELIB_API void SetData(DataType type, void* object) {
    switch (type) {
    case DT_I2:
        printf("Short %i\n.", *((short *)object));
        break;
    case DT_I4:
        printf("Long %i\n.", *((long*)object));
        break;
    case DT_R4:
        printf("Float %f\n.", *((float *)object));
        break;
    case DT_R8:
        printf("Double %f\n.", *((double *)object));
        break;
    case DT_STR:
        printf("String %s\n.", *((char*)object));
        break;
    default:
        printf("Unknown type.");
        break;
    }
}

PINVOKELIB_API void TestArrayInStruct(MYARRAYSTRUCT* pStruct) {
    pStruct->flag = true;
    pStruct->vals[0] += 100;
    pStruct->vals[1] += 100;
    pStruct->vals[2] += 100;
}

#3. References

  1. P/Invoke

  2. Marshalling Data with Platform Invoke

  3. Managed and Unmanaged Code

你可能感兴趣的:(Platform Invoke)