280 likes | 527 Views
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.
E N D
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 UnmanagedLibrary Function Call In Parameters
Pass Parameters 3 ways to transfer parameters between managed code and unmanaged code • Marshaling the data by Marshaler • Allocating an unmanaged memory block, populating the data, and passing the address of the memory block • Just passing the address of the managed object without marshaling the object
The unsafe Way /* C# Wrapper */ structMyStruct { Int32 num; IntPtr str1; } [DllImport("dll.dll")] void func(IntPtr ms); /* C Declarations */ structMyStruct { DWORD num; LPWSTR str1; } void func(structMyStruct *ms); 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 ); } directly operate the object via pointer pass the address,IntPtr, of the struct
A Better Way /* C# Wrapper */ structMyStruct { Int32 num; IntPtr str1; } [DllImport("dll.dll")] void func(IntPtr ms); /* C Declarations */ structMyStruct { DWORD num; LPWSTR str1; } void func(structMyStruct *ms); { IntPtrpms = Marshal.AllocHGlobal(sizeof(MyStruct)); MyStruct ms; ms.num = MAGIC_NUMBER; ms.str1 = Marshal.StringToHGlobalUni(MAGIC_STRING); Marshal.StructureToPtr(ms, pms, false); func(ms); } operate the managed object allocate memory and copy string by Mashaler utility
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.
Marshaling by Marshaler(the best way) /* C# Wrapper */ structMyStruct { Int32 num; [MarshalAs(UnmanagedType.LPWStr)] String str1; } [DllImport("dll.dll")] void func(ref MyStruct ms); /* C Declarations */ structMyStruct { DWORD num; LPWSTR str1; } void func(structMyStruct *ms); indicate the string should be converted to a pointer, which points to a string buffer { 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); }
Marshaling Internals Unmanaged Memory Managed Memory 2. Marshal managed In-parameters to native Stack Stack Parameters 6. Clean up managed Out-parameters 4. native code operate on unmanaged object Heap Heap Object Object 8. Clean up native data Object 7. Marshal native Out-parameters to managed Code Code 1. Call C# wrapper 3. Call native code C# Wrapper() Function() 9. Return from C# wrapper 5. Return from native code
you best friend Default Marshaling for Blittable Types Unmanaged Memory Managed Memory 3. Pass the address (call-by-reference) Stack Stack Parameters 5. native code operate on managed object Heap Heap 2. Pin the object Object 7. Un-Pin the object Code Code 1. Call C# wrapper 4. Call native code C# Wrapper() Function() 8. Return from C# wrapper 6. Return from native code
Delegate/Callback 2. pass delegate marshaled as a function pointer Unmanaged Memory Managed Memory Stack Stack Parameters Heap delegate Code 5. return from callback Code delegate() callback() 1. Call C# wrapper 4. invoke callback 3. Call native code C# Wrapper() Function() 7. Return from C# wrapper 6. Return from native code
ICustomMarshaler in Action Unmanaged Memory Managed Memory 2. MarshalManagedToNative() Stack Stack Parameters 3. CleanUpManagedData() Heap Heap Object Object 5. CleanUp NativeData() Object 4. MarshalNative ToManaged() 1. GetInstance() to get a instance of the marshaler Code Code 3. Call/Return from native code C# Wrapper() Function()
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_optLPCSTRlpText, __in_optLPCSTRlpCaption, __in UINT uType); WINUSERAPI int WINAPI MessageBoxW( __in_opt HWND hWnd, __in_optLPCWSTRlpText, __in_optLPCWSTRlpCaption, __in UINT uType); #ifdef UNICODE #define MessageBoxMessageBoxW #else #define MessageBoxMessageBoxA #endif // !UNICODE
More on Marshaling String Just pass the struct. The marshaler will do the rest for you [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] structMyStruct { [MarshalAs(UnmanagedType.LPStr)] string str1; [MarshalAs(UnmanagedType.LPWStr)] string str2; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = N)] string str3;} structMyStruct { LPSTR str1; LPWSTR str2; WCHAR str3[N]; }; MyStruct ms; ms.str1 = "Hello"; ms.str2 = "world"; ms.str3 = "!!!"; function( ref ms);
Get a String Out void GetString(LPWSTR *text,int nMaxCount ); 1 • [DllImport("dll.dll")] • static extern void • GetString(out String text, intnMaxCount); The memory allocated on text in GetString() will leak. Don't Do It! 2 • [DllImport("dll.dll")] • static extern void • GetString(ref String text, intnMaxCount); You cannot specify the size of the output buffer. Don't Do It Too void GetString(LPWSTR text,int nMaxCount ); 3 • [DllImport("dll.dll")] • static extern void • GetString(StringBuilder text, intnMaxCount); • Just Do It ü • StringBuildersbtext = new Stringbuilder(STR_LENGTH); • GetString( sbtext, sbtext.Capacity );
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
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 }
Solutions 1 { File file = new File("file.txt"); IntPtr h = file.Handle; PInvoke(h); GC.KeepAlive(file); } The purpose of KeepAlive is to keep a reference the object. Besides that, KeepAlive have no side-effect. 2 { File file = new File("file.txt"); IntPtr h = file.Handle; GCHandlegh = GCHnalde.Alloc(file); PInvoke(h); gh.Free(); } Allocating a GCHandle prevents the object from being collected. 3 { File file = new File("file.txt"); IntPtr h = file.Handle; PInvoke(new HandleRef(file, h)); } HandleRef guarantees that the object is not collected until the p/invoke completes
GCHandle • GCHandle can be used to • prevent an objects being garbage collected • pin an object in memory, so it won't be relocated by GC (the object has to be of a value-type) • get the address of a pinned object
Pin an Object structPinMe { int a; int b;} void PinYou() {structPinMe pm; pm.a = MAGIC_NUM1; pm.b = MAGIC_NUM2; GCHandlegh = GCHandle.Alloc(pm, GCHandleType.Pinned); // manipulate the object inCFunction() 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.
Get the Handle of an Object 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. void function() { Object obj = new Object(); GCHandlegh = GCHandle.Alloc(obj); CFunction( GCHandle.ToIntPtr(gh), Callback ); } void Callback(IntPtr p) { GCHandlegh = GCHandle.FromIntPtr(p); Object obj = gh.Target; gh.Free();}
Default Marshaling for Blittable Types structMyStruct { 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); structMyStruct { DWORD a; DWORD b;}; void f(structMyStruct *ms);
Write Your Own Marshaler class MyClass { /* ... */ } class MyClassMarshaler: ICustomMarshaler { public void CleanUpManagedData(object ManagedObj); public void CleanUpNativeData(IntPtrpNativeData); public IntPtrMarshalManagedToNative(object ManagedObj); public object MarshalNativeToManaged(IntPtrpNativeData);} [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
References • Interop Marshaling http://msdn.microsoft.com/en-us/library/04fy9ya1.aspx • Marshaling Data with Platform Invokehttp://msdn.microsoft.com/en-us/library/fzhhdwae.aspx • Blittable and Non-Blittable Typeshttp://msdn.microsoft.com/en-us/library/75dwhxf7.aspx • HandleRef Structurehttp://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