Tuesday, January 29, 2008

P/Invoke - Platform Invoke

We encapsulates API calls by using a class and declaring static methods with the DllImport Attribute. For data convertion with use the MarshalAs attribute. For structures, we use the LayoutKind attribute (normally the Layout.Sequential is used). Here an example how we can invoke the API MessageBox:
// API class declaration
public class Win32Api
{
    [DllImport("user32.dll")]
    public static extern IntPtr MessageBox(IntPtr hWnd, String text, String caption, uint type);
}
private void button1_Click(object sender, EventArgs e)
{
    // Calling the API Messagebox
    Win32Api.MessageBox(this.Handle, "test Message", "caption", 64);
}
Some fields for DllImport attribute:

CallingConvention - (Winapi, Cdecl, StdCall, ThisCall, FastCall). Winapi is default por windows APIs and Cdecl for Windows CE.NET.

CharSet - How string arguments should be marshaled (Ansi, Unicode, None, Auto)

EntryPoint - DLL entry point, this allows us for use a different name for the API in our class. See bellow

PreserveSig - The signature should be transformed into an unmanaged signature that returns an HRESULT and has an additional [out, retval] argument for the return value. This mean, the function will return an HRESULT instead of thrown an exception when an error occurs. More information see MSDN.

SetLastError - Enable the caller to use the Marshal.GetLasWin32Error API function to determine whether an error ocurred while executing the method.

Samples:
[DllImport("msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int printf(String format, int i, double d);

[DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "MessageBox")]
public static extern int MyNewMessageBoxMethod(IntPtr hWnd, String text, String caption, uint type);
More about PreserveSig: Unlike COM Interop scenarios, PInvoke signatures have PreserveSig semantics by default. For example, in the IOleClientSite interface, we need to preserve the signature:
[ComVisible(true), Guid("00000118-0000-0000-C000-000000000046"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleClientSite
{

    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int SaveObject();

    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int GetMoniker(
        [In, MarshalAs(UnmanagedType.U4)]          int dwAssign,
        [In, MarshalAs(UnmanagedType.U4)]          int dwWhichMoniker,
        [Out, MarshalAs(UnmanagedType.Interface)] out object ppmk);
        
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int GetContainer([MarshalAs(UnmanagedType.Interface)] out IOleContainer container);

    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int ShowObject();

    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int OnShowWindow(
        [In, MarshalAs(UnmanagedType.I4)] int fShow);

    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int RequestNewObjectLayout();
}
More about PreserveSig in the Adam Nathan's Blog.

Passing structures:

When passing structures or classes to unmanaged code using platform invoke, you must provide additional information to preserve the original layout and alignment. Here some examples from MSDN:

[StructLayout(LayoutKind.Sequential)]
public struct Point
{
    public int x;
    public int y;
}  

[StructLayout(LayoutKind.Explicit)]
public struct Rect
{
    [FieldOffset(0)] public int left;
    [FieldOffset(4)] public int top;
    [FieldOffset(8)] public int right;
    [FieldOffset(12)] public int bottom;
}  

class Win32API
{
    [DllImport("User32.dll")]
    public static extern bool PtInRect(ref Rect r, Point p);
}
Callback functions:

For invoking API functions that are expecting a callback function, we need to declare one delegate function and one method conforms the delegate declaration. Here an example from MSDN that enumerates every opened window in the system:
// The callback delegate declaration
public delegate bool theCallBack(int hwnd, int lParam);

// The API wrapper
public class EnumReportApp
{
    [DllImport("user32")]
    public static extern int EnumWindows(theCallBack x, int y);

    public static void Run()
    {
        // Here our callback
        theCallBack myCallBack = new theCallBack(EnumReportApp.Report);
        EnumWindows(myCallBack, 0);
    }

    // This is the callback that EnumWindows will call
    public static bool Report(int hwnd, int lParam)
    {
        Console.Write("Window handle is ");
        Console.WriteLine(hwnd);
        
        return true;
    }
}

// Main program
class Program
{
    public static void Main()
    {
        EnumReportApp.Run();
    }
}
Marshaling:

For APIs returning string values, is a best practice use the StringBuilder instead of normal strings classes when they expect a reference string type.
public class Win32Api
{
    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();
    
    [DllImport("user32.dll")]
    private static extern Int32 GetWindowText(IntPtr hWnd, StringBuilder textValue, Int32 counter);

    public static string GetForegroupWindowText()
    {
        // Our string builder prefixed to 256 characters
        StringBuilder sb = new StringBuilder(256);
        IntPtr hwnd = GetForegroundWindow();

        GetWindowText(hwnd, sb, 256);

        return sb.ToString();
    }
}
"For example, you can marshal a string to unmanaged code as either a LPStr, a LPWStr, a LPTStr, or a BStr. By default, the common language runtime marshals a string parameter as a BStr to COM methods. You can apply the MarshalAsAttribute attribute to an individual field or parameter to cause that particular string to be marshaled as a LPStr instead of a BStr." - from MSDN
[DllImport("user32.dll")]
public static extern int MessageBoxW(
    IntPtr hWnd,
    [MarshalAs(UnmanagedType.LPWStr)] String text,
    [MarshalAs(UnmanagedType.LPWStr)] String caption,
    int type);
More information about Marshaling with P/Invoke in MSDN.

0 comments:


View My Stats