Platform Invoke

[TOC]

#1. 概述

P/Invoke(Platform Invoke) 是C#中一項(xiàng)允許你從托管代碼(Managed code)中訪(fǎng)問(wèn)非托管代碼(Unmanaged code)的技術(shù)。它允許你訪(fǎng)問(wèn)非托管代碼中的函數(shù)(functions),結(jié)構(gòu)體(structs),回調(diào)(callbacks)等。

1.1 P/Invoke工作流程

P/Invoke依賴(lài)于元數(shù)據(jù)(metadata)來(lái)定位導(dǎo)出函數(shù)并在運(yùn)行時(shí)封裝它們的參數(shù)。

platform-invoke-call.gif

當(dāng)P/Invoke調(diào)用非托管函數(shù)(Unmanaged function)時(shí),它將執(zhí)行以下操作序列:

  1. 定位包含函數(shù)的DLL。
  2. 將DLL加載進(jìn)內(nèi)存。
  3. 在內(nèi)存中定位函數(shù)的地址,將函數(shù)參數(shù)壓入堆棧,根據(jù)需要對(duì)數(shù)據(jù)進(jìn)行編組。
  4. 轉(zhuǎn)移控制到非托管函數(shù)。

P/Invoke 會(huì)將非托管代碼中產(chǎn)生的異常上拋給調(diào)用者(托管代碼中)。

1.2 Managed and Unmanaged Code

1. Managed Code

C#中的托管代碼是指那些依托于.NET framework中的CLR(Common Language Runtime)開(kāi)發(fā)的代碼。換句話(huà)說(shuō),這部分代碼是由C#層解釋執(zhí)行的。CLR運(yùn)行時(shí)給托管代碼提供了各種服務(wù),包括錯(cuò)誤處理(Exception handling)、垃圾回收(Garbage collection)、類(lèi)型檢查(Type checking)等。以上這些服務(wù)由CLR提供,并且對(duì)編程者屏蔽稱(chēng)為托管服務(wù)。所以對(duì)于托管代碼(Managed code)來(lái)說(shuō)也可以稱(chēng)為安全代碼(Safe code),編程者不需要關(guān)心內(nèi)存分配(Memory allocation)、對(duì)象創(chuàng)建(Object creation)和對(duì)象回收(Object disposal)這些易引發(fā)不安全因素的操作。

對(duì)于托管代碼的執(zhí)行,CLR會(huì)先將其轉(zhuǎn)變?yōu)镸SIL(Microsoft Intermediate Language)。MSIL一般經(jīng)由JIT(Just In Time)編譯器編譯產(chǎn)生。

2. Unmanaged Code

C#中的非托管代碼依賴(lài)于計(jì)算機(jī)體系架構(gòu),譬如C++或者C語(yǔ)言直接編寫(xiě)的Native code。它們的目標(biāo)是處理器體系結(jié)構(gòu),由操作系統(tǒng)直接執(zhí)行。由于編程者直接參與內(nèi)存管理,對(duì)象創(chuàng)建和銷(xiāo)毀等動(dòng)作,因此一旦不小心編寫(xiě)一些”壞代碼“,很容易引發(fā)像內(nèi)存泄漏之類(lèi)的錯(cuò)誤。

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

#2. 調(diào)用非托管函數(shù)

P/Invoke技術(shù)可以幫助托管代碼調(diào)用在DLL(Dynamic link library)中實(shí)現(xiàn)的非托管代碼,譬如Windows API。它定位并調(diào)用導(dǎo)出的函數(shù),并根據(jù)需要跨互操作邊界封送其參數(shù)(整數(shù)、字符串、數(shù)組、結(jié)構(gòu)體等)。調(diào)用DLL中的非托管代碼,需要進(jìn)行以下步驟:

  1. 確認(rèn)DLL中的目標(biāo)函數(shù)。
  2. 在托管代碼中創(chuàng)建包裝類(lèi)。
  3. 在托管類(lèi)中創(chuàng)建對(duì)應(yīng)的函數(shù)原型
  4. 調(diào)用托管函數(shù)

2.1 確認(rèn)DLL中目標(biāo)函數(shù)

譬如調(diào)用User32.dll中的MessageBox函數(shù)。Windows API包含兩個(gè)不同版本的函數(shù)處理不同的字符集:ANSI版本和Unicode版本。MessageBoxA即MessageBox的ANSI版本,MessageBoxW即Unicode版本。

通過(guò)DllImportAttribute.EntryPoint域來(lái)指定DLL函數(shù)的名字或者序列號(hào)。如果定義的函數(shù)名字和DLL中的函數(shù)名字一致則不需要顯式指明。否則必須通過(guò)Entry Point顯式指明調(diào)用的函數(shù)。

using System;
using System.Runtime.InteropServices;

namespace PInvoke
{
    class NativeMethods
    {
        //聲明的函數(shù)MsgBox與被調(diào)用的函數(shù)名字MessageBox不一致,必須通過(guò)EntryPoint顯示指明。
        [DllImport("user32.dll",EntryPoint = "MessageBox")]
        internal static extern int MsgBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
        
        //聲明的函數(shù)MessageBox即被調(diào)用的函數(shù)
        [DllImport("user32.dll")]
        internal static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
    }
}

2.2 在托管代碼中創(chuàng)建包裝類(lèi)

將經(jīng)常使用的DLL函數(shù)封裝到托管類(lèi)中是封裝平臺(tái)功能的有效方法。在封裝類(lèi)中,為每一個(gè)你想調(diào)用的DLL中的托管函數(shù)創(chuàng)建一個(gè)靜態(tài)方法。包裝類(lèi)中函數(shù)的定義可以包含一些附加信息,譬如字符集(Character set),調(diào)用規(guī)約(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 在托管類(lèi)中創(chuàng)建對(duì)應(yīng)函數(shù)原型

為了調(diào)用非托管庫(kù)(Unmanaged library)中導(dǎo)出的函數(shù),.NET framework要求在托管代碼中(Managed code)創(chuàng)建一個(gè)函數(shù)原型(Function prototype)來(lái)代表非托管函數(shù)(Unmanaged function)。為了確保P/Invoke憑借正確的函數(shù)原型進(jìn)行對(duì)應(yīng)的數(shù)據(jù)封裝,必須執(zhí)行以下操作:

  • 使用 DllImortAttribute 屬性標(biāo)注托管代碼中的靜態(tài)函數(shù).
  • 用托管數(shù)據(jù)類(lèi)型替換非托管數(shù)據(jù)類(lèi)型
2.3.1 DllImportAttribute

C#通過(guò)DllImportAttribute 注解來(lái)表示被注解方法由非托管DLL公開(kāi)為靜態(tài)入口點(diǎn)。

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 封裝數(shù)據(jù)類(lèi)型
1. P/Invoke基礎(chǔ)數(shù)據(jù)類(lèi)型
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類(lèi)型的參數(shù),并且將它從.NET framework(Unicode)格式轉(zhuǎn)換為非托管格式(ANSI)。在托管模式下,字符串是不可變的,所以在函數(shù)返回時(shí),P/Invoke并不會(huì)將其從非托管內(nèi)存拷貝回托管內(nèi)存。

1). String作為函數(shù)參數(shù)

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作為函數(shù)返回值

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參數(shù)

GetCommandLineW方法返回指向一個(gè)緩沖區(qū)的指針而不是string的目的是為了阻止托管內(nèi)存被自動(dòng)回收。調(diào)用者通過(guò)Marshal提供的PtrToStringUni方法將指針轉(zhuǎn)變?yōu)樗璧淖址?/p>

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

類(lèi)(Classes)和結(jié)構(gòu)體(Structures)在.NET framework中有相似之處,它們都含有字段(fileds)、屬性(properties)以及事件(events)。它們之間的其中一個(gè)顯著的差異在于類(lèi)是引用類(lèi)型,而結(jié)構(gòu)體是值類(lèi)型。

1). 封裝類(lèi)

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). 封裝結(jié)構(gòu)體

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). 封裝聯(lián)合體

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 <objbase.h>
#include <strsafe.h>

// 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容