[TOC]
#1. 概述
P/Invoke(Platform Invoke) 是C#中一项允许你从托管代码(Managed code)中访问非托管代码(Unmanaged code)的技术。它允许你访问非托管代码中的函数(functions),结构体(structs),回调(callbacks)等。
1.1 P/Invoke工作流程
P/Invoke依赖于元数据(metadata)来定位导出函数并在运行时封装它们的参数。
当P/Invoke调用非托管函数(Unmanaged function)时,它将执行以下操作序列:
- 定位包含函数的DLL。
- 将DLL加载进内存。
- 在内存中定位函数的地址,将函数参数压入堆栈,根据需要对数据进行编组。
- 转移控制到非托管函数。
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。它们的目标是处理器体系结构,由操作系统直接执行。由于编程者直接参与内存管理,对象创建和销毁等动作,因此一旦不小心编写一些”坏代码“,很容易引发像内存泄漏之类的错误。
#2. 调用非托管函数
P/Invoke技术可以帮助托管代码调用在DLL(Dynamic link library)中实现的非托管代码,譬如Windows API。它定位并调用导出的函数,并根据需要跨互操作边界封送其参数(整数、字符串、数组、结构体等)。调用DLL中的非托管代码,需要进行以下步骤:
- 确认DLL中的目标函数。
- 在托管代码中创建包装类。
- 在托管类中创建对应的函数原型
- 调用托管函数
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
P/Invoke
Marshalling Data with Platform Invoke
Managed and Unmanaged Code