p/invoke made easy

23
P/Invoke Made Easy Wei-Chen Wang

Upload: liang

Post on 24-Feb-2016

74 views

Category:

Documents


1 download

DESCRIPTION

P/Invoke Made Easy. Wei-Chen Wang. Interop Marshaling. Marshaling governs how data is passed between managed and unmanaged memory during platform invoke. Managed Client. Out Parameters. Unmanaged Library. Function Call. In Parameters. Pass Parameters. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: P/Invoke Made Easy

P/Invoke Made Easy

Wei-Chen Wang

Page 2: P/Invoke Made Easy

Marshaling governs how data is passed between managed and unmanaged memory during platform invoke.

Function Call

Interop Marshaling

In Parameters

Out ParametersManaged

ClientUnmanaged Library

Page 3: P/Invoke Made Easy

Pass Parameters3 ways to transfer parameters between managed code and unmanaged code

1. Marshaling the data by Marshaler2. Allocating an unmanaged memory

block, populating the data, and passing the address of the memory block

3. Just passing the address of the managed object without marshaling the object

Page 4: P/Invoke Made Easy

The unsafe Way

unsafe{ IntPtr ms = Marshal.AllocHGlobal(sizeof(MyStruct));

MyStruct *pms = (MyStruct*)ms; ms->a = MAGIC_NUMBER; ms->str1 = Marshal.AllocHGlobal(MAGIC_STRING.length*2); strcpy( ms->str1, MAGIC_STRING);

func( ms ); }

/* C Declarations */struct MyStruct{ DWORD num; LPWSTR str1;}void func(struct MyStruct *ms);

/* C# Wrapper */struct MyStruct{ Int32 num; IntPtr str1;}[DllImport("dll.dll")]void func(IntPtr ms);

directly operate the object via pointer

pass the address, IntPtr, of the struct

Page 5: P/Invoke Made Easy

A Better Way

{ IntPtr pms = Marshal.AllocHGlobal(sizeof(MyStruct));

MyStruct ms; ms.num = MAGIC_NUMBER; ms.str1 = Marshal.StringToHGlobalUni(MAGIC_STRING); Marshal.StructureToPtr(ms, pms, false);

func(ms);}

/* C Declarations */struct MyStruct{ DWORD num; LPWSTR str1;}void func(struct MyStruct *ms);

/* C# Wrapper */struct MyStruct{ Int32 num; IntPtr str1;}[DllImport("dll.dll")]void func(IntPtr ms);

operate the managed object

allocate memory and copy string by Mashaler utility

Page 6: P/Invoke Made Easy

Summary, So Far...• unsafe code is hard to write (in C#), hard to debug,

and lack of compiling-time and runtime checking. In most case, we don't have to use unsafe pointers.

• We should operate everything on managed objects, and convert them to unmanaged objects only when we want to perform platform invoke.

• It is free to encapsulate the converting code as methods of the object. Methods won't change the memory layout of the object.

• Using AllocHGlobal to manually allocate an unmanaged memory block is necessary, if we want to pass the object for an asynchronous call.

Page 7: P/Invoke Made Easy

Marshaling by Marshaler(the best way)

{ MyStruct ms; // operate on managed object ms.num = MAGIC_NUMBER; ms.str = MAGIC_STRING; // pass the object // the Marsahler will convert it to unmanaged object func(ref ms);}

/* C Declarations */struct MyStruct{ DWORD num; LPWSTR str1;}void func(struct MyStruct *ms);

/* C# Wrapper */struct MyStruct{ Int32 num; [MarshalAs(UnmanagedType.LPWStr)] String str1;}[DllImport("dll.dll")]void func(ref MyStruct ms); indicate the string should be

converted to a pointer, which points to a string buffer

Page 8: P/Invoke Made Easy

Marshaling Internals

9. Return from C# wrapper

Heap

Object

Code

Stack

HeapCode

Stack

C# Wrapper() Function()1. Call C#

wrapper

Object

2. Marshal managed In-parameters to native

3. Call native code

Parameters6. Clean up

managed Out-

parameters

7. Marshal native Out-parameters to managed

Object8. Clean up native data

Managed Memory

Unmanaged Memory

5. Return from native code

4. native code operate on unmanaged object

Page 9: P/Invoke Made Easy

Default Marshaling for Blittable Typesyou best friend

8. Return from C# wrapper

HeapCode

Stack

HeapCode

Stack

C# Wrapper() Function()1. Call C#

wrapper4. Call native code

Parameters

Object

Managed Memory

Unmanaged Memory

6. Return from native code

5. native code operate on managed object

2. Pin the object

3. Pass the address (call-by-

reference)

7. Un-Pin the object

Page 10: P/Invoke Made Easy

Delegate/Callback

Heap

Code

Stack

Code

Stack

delegate

delegate()

Managed Memory

Unmanaged Memory

callback()C#

Wrapper() Function()

2. pass delegate marshaled as a function pointer

4. invoke callback

Parameters

1. Call C# wrapper

7. Return from C# wrapper

3. Call native code6. Return from

native code

5. return from callback

Page 11: P/Invoke Made Easy

ICustomMarshaler in Action

Heap

Object

Code

Stack

HeapCode

Stack

C# Wrapper() Function()

Object

Parameters

Object

Managed Memory

Unmanaged Memory

1. GetInstance()

to get a instance of the marshaler

2. MarshalManaged ToNative()

3. CleanUp ManagedData()

3. Call/Return from native code

4. MarshalNativeToManaged()

5. CleanUpNativeData()

Page 12: P/Invoke Made Easy

Windows String at A Glance

• ANSI String=char array=user locale encoding• Unicode String=wchar array=UTF-16 Little-

Endian (Unicode is not necessary UTF-16LE, but in Windows, it is)

• T-String=TCHAR array, char or wchar depends on compiling configuration

WINUSERAPI int WINAPI MessageBoxA( __in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption, __in UINT uType);WINUSERAPI int WINAPI MessageBoxW( __in_opt HWND hWnd, __in_opt LPCWSTR lpText, __in_opt LPCWSTR lpCaption, __in UINT uType);#ifdef UNICODE#define MessageBox MessageBoxW#else#define MessageBox MessageBoxA#endif // !UNICODE

Page 13: P/Invoke Made Easy

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]struct MyStruct{ [MarshalAs(UnmanagedType.LPStr)] string str1; [MarshalAs(UnmanagedType.LPWStr)] string str2; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = N)] string str3;}

More on Marshaling String

struct MyStruct{ LPSTR str1; LPWSTR str2; WCHAR str3[N];};

MyStruct ms;ms.str1 = "Hello";ms.str2 = "world";ms.str3 = "!!!";function( ref ms);

Just pass the struct. The marshaler will do the rest for you

Page 14: P/Invoke Made Easy

Get a String Outvoid GetString( LPWSTR *text, int nMaxCount );

[DllImport("dll.dll")]static extern void GetString(StringBuilder text, int nMaxCount);

1 [DllImport("dll.dll")]static extern void GetString(out String text, int nMaxCount);

3

Don't Do It!

Just Do It ü

StringBuilder sbtext = new Stringbuilder(STR_LENGTH);GetString( sbtext, sbtext.Capacity );

The memory allocated on text in GetString() will leak.

[DllImport("dll.dll")]static extern void GetString(ref String text, int nMaxCount);

2Don't Do It Too

You cannot specify the size of the output buffer.

void GetString( LPWSTR text, int nMaxCount );

Page 15: P/Invoke Made Easy

InAttribute and OutAttribute

• InAttribute indicates that data should be marshaled from the caller to the callee, but not back to the caller

• OutAttribute indicates that data should be marshaled from callee back to caller

• Use the proper attributes to reduce unnecessary data copy/* Example */[DllImport("mydll.dll")]static extern void func([in] ref MyStruct);// pass the reference(pointer) of the structure// parameter will be copied in, but not out

Page 16: P/Invoke Made Easy

Keep Objects in Memory• Local variable can be garbage

collected as it reach a point where it appears no longer being referenced// suppose WriteFile is a native function{ File file = new File("file.txt"); IntPtr h = file.Handle;

// file is eligible for finalization WriteFile( h ); // WriteFile( file.h ); doesn't help anything

// file.WriteFile(); // Write a wrapper method in File and call // WriteFile in the method // This doesn't help too}

Page 17: P/Invoke Made Easy

2

1Solutions

{ File file = new File("file.txt"); IntPtr h = file.Handle; PInvoke(h); GC.KeepAlive(file);}

{ File file = new File("file.txt"); IntPtr h = file.Handle; GCHandle gh = GCHnalde.Alloc(file); PInvoke(h); gh.Free();}

3 { File file = new File("file.txt"); IntPtr h = file.Handle; PInvoke(new HandleRef(file, h));}

The purpose of KeepAlive is to keep a reference the object. Besides that, KeepAlive have no side-effect.Allocating a GCHandle prevents the object from being collected.

HandleRef guarantees that the object is not collected until the p/invoke completes

Page 18: P/Invoke Made Easy

GCHandle• GCHandle can be used to

1. prevent an objects being garbage collected

2. pin an object in memory, so it won't be relocated by GC (the object has to be of a value-type)

3. get the address of a pinned object

Page 19: P/Invoke Made Easy

Pin an Objectstruct PinMe{ int a; int b;}

void PinYou(){ struct PinMe pm; pm.a = MAGIC_NUM1; pm.b = MAGIC_NUM2;

GCHandle gh = GCHandle.Alloc(pm, GCHandleType.Pinned);

// manipulate the object in CFunction() CFunction(gh.AddrOfPinnedObject());

gh.Free();}

Only blittable object can be pinned.The only reason to pin an blittable object it that you want to pass it to unmanaged code for an asynchronous operation, hence you don't want GC relocate it.

Page 20: P/Invoke Made Easy

Get the Handle of an Object

void function(){ Object obj = new Object(); GCHandle gh = GCHandle.Alloc(obj); CFunction( GCHandle.ToIntPtr(gh), Callback );}

void Callback(IntPtr p){ GCHandle gh = GCHandle.FromIntPtr(p); Object obj = gh.Target; gh.Free();}

We can't manipulate a managed object in unmanaged code, but we can pass the handle of it between managed and unmanaged code as callback data.

Page 21: P/Invoke Made Easy

Default Marshaling for Blittable Types

struct MyStruct{ Int32 a; Int32 b; public MyStruct(Int32 _a, Int32 _b) { a = _a; b = _b; }}[DllImport("dll.dll")]void CFunction(ref MyStruct ms);

MyStruct ms = new MyStruct(MAGIC_A, MAGIC_B);

CFunction(ref ms);

struct MyStruct{ DWORD a; DWORD b;};void f(struct MyStruct *ms);

Page 22: P/Invoke Made Easy

Write Your Own Marshaler

class MyClass{ /* ... */}

class MyClassMarshaler: ICustomMarshaler{ public void CleanUpManagedData(object ManagedObj); public void CleanUpNativeData(IntPtr pNativeData); public IntPtr MarshalManagedToNative(object ManagedObj); public object MarshalNativeToManaged(IntPtr pNativeData);}

[DllImport("dll.dll")]static extern void Function( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(MyClassMarshaler))] MyClass mc );

Custom marshaler used For marshaling MyClass

Use MyClassMarshaler to marshal MyClass

Page 23: P/Invoke Made Easy

References• Interop Marshaling

http://msdn.microsoft.com/en-us/library/04fy9ya1.aspx • Marshaling Data with Platform Invoke

http://msdn.microsoft.com/en-us/library/fzhhdwae.aspx• Blittable and Non-Blittable Types

http://msdn.microsoft.com/en-us/library/75dwhxf7.aspx• HandleRef Structure

http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.handleref.aspx

• SafeHandles: the best V2.0 feature of the .NET Frameworkhttps://blogs.msdn.com/bclteam/archive/2005/03/15/396335.aspx

• SafeHandle: A Reliability Case Study http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

• The Truth About GCHandleshttp://blogs.msdn.com/clyon/archive/2005/03/18/398795.aspx

• GCHandle.ToIntPtr vs. GCHandle.AddrOfPinnedObjecthttp://blogs.msdn.com/jmstall/archive/2006/10/09/gchandle_5F00_intptr.aspx

• GCHandles, Boxing and Heap Corruptionhttp://blogs.msdn.com/clyon/archive/2004/09/17/230985.aspx

• SafeHandle: A Reliability Case Study [Brian Grunkemeyer]http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx