Call Unmanaged Code Part 2 - Marshal Class
来源:互联网 发布:mac删除系统自带软件 编辑:程序博客网 时间:2024/06/04 18:05
Call Unmanaged Code Part 2 - Marshal Class By Vyacheslav Biktagirov
In previous part we seen, how we can interop with unmanaged code and some simple examples. We observed MarshalAs attribute, that helps us to work with a lot of different unmanaged types,like LPStruct,LPArray,LPStr,LPWStr and others.
But, you can ask,- there is unlimited(!) number of possible unmanaged types, while number of different MarshalAs variables is limited, of course. So, how we can marshal any, any type? How we can manage non-standart application-specific types like, for instance, char****(pointer to pointer to pointer to string)? What about more complicated cases?
I can see 3 possible ways of doing such a thing in C#:
1. MarshalAs Attribute Combining different special-defined managed user types with complex metadata attributes with metadata in external function declaration. In most cases it will work. This way is nice & clean, but will not work for some cases and pretty complex.
2. Unsafe code Write unsafe external function declarations with same types as in unmanaged library and than call there functions from unsafe code. Will work for any case, almost simple to implement. Anyway, avoid this way, becouse "unsafe" code is unsafe - with all what it means.
3. Marshal class There is a special class that can do all possible interop with unmanaged code. In some cases it can be not so simple to use, but when you have no choice - it's a good choice. It can take any unmanaged object, allocate/deallocate it, and move data between managed and unmanaged objects. In cases of complex interop, use of Marshal class is more simple, that first possibility - MarshalAs attribute.
Conclusion:
When you have some functions to call in unmanaged code, try first using MarshalAs attribute. For simple interop it's simple, nice and work without surprices, becouse it's standart mechanism. For cases of complex interop, try first also possibility 1. When it's complex or takes a lot of time to find proper combinetion of attributes, try possibiltity 3 - it's more simple and gives more control, but in this case programmer forced to allocate/deallocate/copy unmanaged memory, that probably unsafe. Of course, possibility 2 is opened always, but be careful using it.
Uff.. After a bit of theory, let's see some examples.
Example 1 : Print char****
Imagine that I wrote some unmanaged DLL in C++,for example, contains function:
extern "C" __declspec(dllexport) void GetUserMessage(char**** Message){strcpy(***Message,"Hello unmanaged");}Imagine also, that we want call this fuction from C# and present a message box with this beautiful string. Point that before we can call this function, we must appropriately allocate the memory and, after we call it, it will be just nice to free it. Lets's try all 3 approaches one after one.
Example 1 : Approach 1 - MarshalAs attribute
Hello, I'm glad to say you, that I see no any "legal" way to marshal "char****". Do you?Some facts:* In Beta1 was such way(define multi-nested class), but not in Beta2* There is a way to marshal char*** only.Ok, we need to difine some helper method, that will cast down one level:extern "C" __declspec(dllexport) void GetUserMessageHelper(char**** Message){GetUserMessage(&Message);}Now we can write .NET declarstions:[StructLayout(LayoutKind.Sequential)]public class StringP{[MarshalAs(UnmanagedType.LPStr)]public string str;}This declaration will add one level of indirectance. Put attention, that we defined class, and not struct, becouse struct we can marshal by-value only, ans class also by reference. Class to be mashalled must expose layout information. In our case it does not make sence, becouse class has one member only, but it's MarshalAsAttribute class restriction. And, of course, we must say, how we want to treat a string in unmanaged code - as LPStr.
Now we can declare the function:
[DllImport("TestDll.dll",EntryPoint="GetUserMessageHelper",CharSet=CharSet.Ansi)]static extern void GetUserMessageHelper([MarshalAs(UnmanagedType.LPStruct)]ref StringP str);EntryPoint and CharSet is not so necessary in out case, becouse we explicitely sayd, that we want 'LPStr' and defined our function as 'extern "C"'. Impotrant is define parameter as 'ref', says input and output(it add last,third level of indirectance) and say that we want marshal our class by reference - LPStruct and not by value.
The work is done, just call it:
private void button1_Click(object sender, System.EventArgs e){ StringP strP=new StringP(); strP.str=new string('*',20); GetUserMessageHelper(ref strP); MessageBox.Show(strP.str);}Not so simple, but works fine, not?
Example 1 : Approach 2 - unsafe code Let's define our extern function as unsafe:
[DllImport("TestDll.dll",EntryPoint="GetUserMessage")]unsafe static extern void GetUserMessage2(byte**** str);[DllImport("user32.dll")]unsafe static extern int MessageBoxA(int hwnd,byte* Text,byte* Caption,int Type);I defined parameter like 'byte****' and not 'char****', becouse managedchar is 2-byte long..I also explicitely sayd that my function is unsafe, else I can't use pointersemantic.I imported also MessageBox function, becouse managed MessageBox class don'tknow to operate with byte pointer, but only with managed string class. Now use it:unsafe private void button2_Click(object sender, System.EventArgs e){ byte* str=(byte*)Marshal.AllocCoTaskMem(20); byte** strP=&str; byte*** strPP=&strP; GetUserMessage2(&strPP); MessageBoxA(0,str,(byte*)0,0); Marshal.FreeCoTaskMem((IntPtr)str);}I used Marshal class for memory allocation. For good example I must import "CoTaskMemAlloc" and "CoTaskMemFree" from "ole32.dll" and use them. But me is lazy, so please excuse me for just using Marshal class. So what happen here? I allocated 20 bytes from unmanaged heap, that called GetUserMessage with 'pointer to pointer to pinter', use the result and freed the memory. Very,very simple.But, like all unmanaged operations, it waits from developer to be ver careful..
Example 1 : Approach 3 - Marshal class
[DllImport("TestDll.dll",EntryPoint="GetUserMessage")]static extern void GetUserMessage3(ref IntPtr str);IntPtr is just system-deined integer(like C++ int). For Win32, it's 4-byte length.Let's use Marshal class:private void button3_Click(object sender, System.EventArgs e){ // Ok, of course firstly allocate memoryIntPtr str=Marshal.AllocCoTaskMem(20);// add level of indirectionIntPtr strP=Marshal.AllocCoTaskMem(4);Marshal.StructureToPtr(str,strP,false);// add one more level of indirectionIntPtr strPP=Marshal.AllocCoTaskMem(4);Marshal.StructureToPtr(strP,strPP,false);// call itGetUserMessage3(ref strPP);// remove 2 levels of indirectionstrP=Marshal.ReadIntPtr(strPP);str=Marshal.ReadIntPtr(strP);// get string class from pointerMessageBox.Show(Marshal.PtrToStringAnsi(str));// Free used memoryMarshal.FreeCoTaskMem(str);Marshal.FreeCoTaskMem(strP);Marshal.FreeCoTaskMem(strPP);}Ok, only point of this code it that is no 'address of' operation like C++ '&'. So we use Marshal class to get this functionality. For adding indirection level, we must allocate memory for new pointer and that ask Marhal class to put the pointer to given location.
Such method is more safe that using unsafe code.
Here is full program code that explains all 3 methods of marshalling 'char****':
using System;using System.Drawing;using System.Collections;using System.ComponentModel;using System.Windows.Forms;using System.Data;using System.Runtime.InteropServices;using System.Text;namespace WindowsApplication8{///Example 2: GetUserName()/// Summary description for Form1./// public class Form1 : System.Windows.Forms.Form{private System.Windows.Forms.Button button1;private System.Windows.Forms.Button button2;private System.Windows.Forms.Button button3;/// /// Required designer variable./// private System.ComponentModel.Container components = null;public Form1(){//// Required for Windows Form Designer support//InitializeComponent();//// TODO: Add any constructor code after InitializeComponent call//}/// /// Clean up any resources being used./// protected override void Dispose( bool disposing ){if( disposing ){if (components != null) {components.Dispose();}}base.Dispose( disposing );}#region Windows Form Designer generated code/// /// Required method for Designer support - do not modify/// the contents of this method with the code editor./// private void InitializeComponent(){this.button1 = new System.Windows.Forms.Button();this.button2 = new System.Windows.Forms.Button();this.button3 = new System.Windows.Forms.Button();this.SuspendLayout();// // button1// this.button1.Location = new System.Drawing.Point(64, 40);this.button1.Name = "button1";this.button1.Size = new System.Drawing.Size(120, 56);this.button1.TabIndex = 0;this.button1.Text = "MarshalAs attribute";this.button1.Click += new System.EventHandler(this.button1_Click);// // button2// this.button2.Location = new System.Drawing.Point(64, 112);this.button2.Name = "button2";this.button2.Size = new System.Drawing.Size(120, 56);this.button2.TabIndex = 0;this.button2.Text = "Unsafe code";this.button2.Click += new System.EventHandler(this.button2_Click);// // button3// this.button3.Location = new System.Drawing.Point(64, 184);this.button3.Name = "button3";this.button3.Size = new System.Drawing.Size(120, 56);this.button3.TabIndex = 0;this.button3.Text = "Marshal class";this.button3.Click += new System.EventHandler(this.button3_Click);// // Form1// this.AutoScaleBaseSize = new System.Drawing.Size(6, 15);this.ClientSize = new System.Drawing.Size(240, 282);this.Controls.AddRange(new System.Windows.Forms.Control[] {this.button3,this.button2,this.button1});this.Name = "Form1";this.Text = "Form1";this.ResumeLayout(false);}#endregion/// /// The main entry point for the application./// [STAThread]static void Main() {Application.Run(new Form1());}[StructLayout(LayoutKind.Sequential)]public class StringP{[MarshalAs(UnmanagedType.LPStr)]public string str;} [DllImport("TestDll.dll",EntryPoint="GetUserMessageHelper",CharSet=CharSet.Ansi)]static extern void GetUserMessageHelper([MarshalAs(UnmanagedType.LPStruct)]ref StringP str);private void button1_Click(object sender, System.EventArgs e){StringP strP=new StringP();strP.str=new string('*',20);GetUserMessageHelper(ref strP);MessageBox.Show(strP.str);}[DllImport("TestDll.dll",EntryPoint="GetUserMessage")]unsafe static extern void GetUserMessage2(byte**** str);[DllImport("user32.dll")]unsafe static extern int MessageBoxA(int hwnd,byte* Text,byte* Caption,int Type);unsafe private void button2_Click(object sender, System.EventArgs e){byte* str=(byte*)Marshal.AllocCoTaskMem(20);byte** strP=&str;byte*** strPP=&strP;GetUserMessage2(&strPP);MessageBoxA(0,str,(byte*)0,0);Marshal.FreeCoTaskMem((IntPtr)str);}[DllImport("TestDll.dll",EntryPoint="GetUserMessage")]static extern void GetUserMessage3(ref IntPtr str);private void button3_Click(object sender, System.EventArgs e){IntPtr str=Marshal.AllocCoTaskMem(20);IntPtr strP=Marshal.AllocCoTaskMem(4);Marshal.StructureToPtr(str,strP,false);IntPtr strPP=Marshal.AllocCoTaskMem(4);Marshal.StructureToPtr(strP,strPP,false);GetUserMessage3(ref strPP);strP=Marshal.ReadIntPtr(strPP);str=Marshal.ReadIntPtr(strP);MessageBox.Show(Marshal.PtrToStringAnsi(str));Marshal.FreeCoTaskMem(str);Marshal.FreeCoTaskMem(strP);Marshal.FreeCoTaskMem(strPP);}}}
But not every day you want marshal 'char****'. Let's try to marshal something more meaningful.Try to imagine that your strongest wish is to know what's username of currently logged user. So far, I don't know managed class that can answer this question. So we must call Windows API. As I can see from MSND, there is function that helps:
BOOL GetUserName( LPTSTR lpBuffer, // name buffer LPDWORD nSize // size of name buffer);This function expects me, a caller, to provide buffer for ANSI string will contain currently loged user name. Length of name will be returned in second argument, given by pointer. Of course, I can't know how many space I need to allocate before I call the function. Due to it, I must call GetUserName() twice:
1. GetUserName(NULL,&Size); // Size is DWORD type
After this call, Size will contain length of buffer I need to allocate. After I allocate the buffer, I can call secondly to the function:
2. GetUserName(pointer_to_buffer,&Size);
At this point buffer contains needed username. Now we can see, how we can implement this API call in 3 methods. And,so long:
Example 2: Approach 1 - MarshalAs attribute.
For start, you must understand, that we can't just define
[DllImport("Advapi32.dll"]static extern bool GetUserName2Length([MarshalAs(UnmanagedType.LPStr)] string lpBuffer,[MarshalAs(UnmanagedType.LPArray)] int& nSize );for following reasons:* we have no pointers - int& will not ever compile* we can't marshal lpBuffer as string, becouse marshaller concludes, that it's by value parameter,so it will only marshal string as "in" parameter, so after call to function lpBuffer will remain unchanged. For getting string back we must declare lpBuffer as 'out' or 'ref', but in this case we will get pointer to pointer to string, while GetUserName() expects pointer.* In first call we must marshal NULL,or 0,and this is no any way cast 0 to string, unlike C++, where (char*)0 is good. So, what we can do? We will write 2 declarations:[DllImport("Advapi32.dll", EntryPoint="GetUserName",ExactSpelling=false, CharSet=CharSet.Ansi, SetLastError=true)]static extern bool GetUserName2([MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer,[MarshalAs(UnmanagedType.LPArray)] Int32[] nSize );[DllImport("Advapi32.dll", EntryPoint="GetUserName",ExactSpelling=false, CharSet=CharSet.Ansi,SetLastError=true)]static extern bool GetUserName2Length([MarshalAs(UnmanagedType.I4)] int lpBuffer,[MarshalAs(UnmanagedType.LPArray)] Int32[] nSize );First parameter will be marshalled as 'int' for first calland byte array for second one. Second parameter is justint array(in our case, it will contain 1 member only). Afterthis, calling is simple:private void button2_Click(object sender, System.EventArgs e){// allocate 1-member array for lengthint[] len=new int[1];len[0]=0;// get lengthGetUserName2Length(0,len);// allocate byte array for ANSI stringbyte[] str=new byte[len[0]];// Get username to preallocated bufferGetUserName2(str,len);// Use text decoder to get string from byte array of ASCII symbolsMessageBox.Show(System.Text.Encoding.ASCII.GetString(str));}Example 2: Approach 1 - unsafe code.In this approach, declaration is more naturally:[DllImport("Advapi32.dll", EntryPoint="GetUserName",ExactSpelling=false, CharSet=CharSet.Ansi,SetLastError=true)]unsafe static extern bool GetUserName3(byte* lpBuffer,int* nSize );Also we need MessageBox() declaration to represent it:unsafe static extern int MessageBoxA(int hwnd,byte* Text,byte* Caption,int Type);Calling it also very C++ like:unsafe private void button3_Click(object sender, System.EventArgs e){int Size=0;GetUserName3((byte*)0,&Size);byte* buffer=(byte*)Marshal.AllocCoTaskMem(Size);GetUserName3(buffer,&Size);MessageBoxA(0,buffer,(byte*)0,0);Marshal.FreeCoTaskMem((IntPtr)buffer);}I use Marshal class for allocations the same reason like in char****example: I have no power to declare external allocators and use them.Example 2: Approach 1 - Marshal class.While working with Marshall class, all problematic arguments must bedefined as IntPtr. Recall, that IntPtr is not pointer class, it just platform-dependentinteger, just like C++ int.[DllImport("Advapi32.dll", EntryPoint="GetUserName", ExactSpelling=false,CharSet=CharSet.Ansi,SetLastError=true)]static extern bool GetUserName1(IntPtr lpBuffer,[MarshalAs(UnmanagedType.LPArray)] Int32[] nSize );private void button1_Click(object sender, System.EventArgs e){string str;Int32[] len=new Int32[1];len[0]=0;GetUserName1((IntPtr)0,len);IntPtr ptr=Marshal.AllocCoTaskMem(len[0]);GetUserName1(ptr,len);str=Marshal.PtrToStringAnsi(ptr);Marshal.FreeCoTaskMem(ptr);MessageBox.Show(str);}As you can see, I casted 0 to IntPtr - no problem, value type. After I know length, I can allocate needed memory, than call GetUSerName(),ask Marshal get string from pointer to ASCII string,free the memory, and I have the string. Using of Marshal for such problems can be very elegant.In this 2 parts we covered calling DLL libraries from .NET code.What about:* Call COM servers* Call managed components from unmanaged code.I'll be writing about these topics soon!
- Call Unmanaged Code Part 2 - Marshal Class
- Call Unmanaged Code. Part 1 - Simple DLLImport
- Passing Managed Structures With Arrays To Unmanaged Code Part 2
- Passing Managed Structures With Arrays To Unmanaged Code Part 1
- Passing Managed Structures With Arrays To Unmanaged Code Part 3
- Marshal Class
- Managed and Unmanaged code
- Managed/Unmanaged Code Interoperability
- Managed code and Unmanaged code
- Managed Code and unmanaged Code
- Call Unmanaged DLLs from C#
- 在C#中使用P/Invoke调用Unmanaged Code (2)
- managed code&unmanaged code&native code
- Using Unmanaged Code in C#
- Unmanaged Code's Code Coverage Analyze
- managed code和unmanaged code混合debug
- vc ++.net2005 managed unmanaged mixing code
- An Overview of Managed/Unmanaged Code Interoperability
- db2 索引 设计准则
- UltraEdit-32 v14.10 简体中文版
- Visual Studio 2008中的Code Performance Analysis
- Ruby中的二维数组初始化的一个小问题
- Call Unmanaged Code. Part 1 - Simple DLLImport
- Call Unmanaged Code Part 2 - Marshal Class
- 欲擒故纵——我看微软的自由作息时间制度
- Microsoft SQL Server 2000 的数据转换服务
- 1个完整的上传图片的类
- 定时器:为 Windows 实现一个连续更新,高精度的时间供应器
- jsf(一)
- VC点滴
- 创建windows服务
- Maven使用注意