pInvoke in Silverlight5 and .NET Framework

One of the new features in Silverlight 5 RC is the ability to call native code including Win32 APIs through platform invocation, or pInvoke.

The technology itself is pretty well-known and it’s been in .NET since version 1.0. Here is one of the tutorials for .NET that explains what pInvoke is about. So instead of explaining what pInvoke is, let me concentrate on how this feature works in Silverlight 5 and what differences with .NET you might expect.

First of all, calling native APIs is available only in full-trust applications. This is a security restriction; there is simply too much power in Windows APIs for a partial-trust Web application to have. The good news is that Silverlight 5 allows your application to be full trust and consequently use pInvoke in both in and out of the browser. One more restriction is that pInvoke is available only on Windows, there is no Mac support.

To get the things going, let me first re-introduce an example that Nick Kramer first presented at MIX. It shows how you can detect a removable drive (think about USB sticks or card readers). You click a button, and the program tells you whether you have a removable drive and if yes, what letter it’s assigned.

 using System;
using System.Windows;
using System.Windows.Controls;
using System.Runtime.InteropServices;

namespace SamplePInvoke
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        // Import native method.
        [DllImport("kernel32.dll")]
        static extern int GetDriveType(string lpRootPathName);

        private void detectDrive_Click(object sender, 
                                       RoutedEventArgs e)
        {
            String[] drives = new string[] 
                { @"c:\", @"d:\", @"e:\", 
                  @"f:\", @"g:\", @"h:\", @"i:\" };
            bool driveInserted = false;
            string driveLetter = "";
            foreach (String drive in drives)
            {
                // Calling the native method. 
                // “2” means that the drive is a removable drive.
                if (GetDriveType(drive) == 2)
                {
                    driveInserted = true;
                    driveLetter = drive;
                }
            }
            if (driveInserted)
                textBlock1.Text = "Removable drive is " + driveLetter;
            else
                textBlock1.Text = "No removable drive";
        }
    }
}

As I mentioned already and as you can see from the example above, in most cases the feature works exactly the same as in .NET. This code is basically identical in both Silverlight and WPF.

However, there are also some differences as well, so keep on reading.

Marshaling

For marshaling, many applications use the methods from the System.Runtime.InteropServices.Marshal class. But you might notice that .NET and Silverlight versions of this class have different number of methods. As usual, Silverlight provides you with only a subset of the full .NET, which we believe represents the most used part. In fact, this is the same set of methods Silverlight itself uses for native interop and it is usually sufficient.

Many of the methods from the Marshal class in the full .NET Framework are just managed wrappers around native APIs. Often, if you can’t find a certain method from the Marshal class in Silverlight, you can use the native API directly.

For example, the method Marshal.AllocHGlobal exists in .NET and doesn’t exist in Silverlight. However, this method is just a wrapper around the Win32 LocalAlloc function from Kernel32.dll. So you need to call this native function directly instead of its managed wrapper and refactor you code accordingly.

 [DllImport("kernel32.dll", EntryPoint = "LocalAlloc")]
internal static extern IntPtr LocalAlloc_NoSafeHandle(
    int uFlags, IntPtr sizetdwBytes);

Usually, you can find what native method is being wrapped in the documentation of the managed method itself.

Reverse pInvoke Calls

In the first example in this article, the only call I make is from managed code to native code. However, sometimes you need to do the opposite: call a managed method from within the native code. It usually happens when a native method uses a callback or accepts a pointer to a function as one of its parameters and you want to use your managed method for this callback.

In the full .NET such reverse calls and direct calls are treated the same. In Silverlight, however, you need to explicitly specify the “entry points” for reverse pInvoke calls by using the AllowReversePInvokeCalls attribute. Basically, all you need to do is to apply this attribute to managed methods that need to be called from the native code.

Here is an example. Consider that you have the following C++ method:

 typedef int (*NumberSource) (void); 

int MultiplyByTen(NumberSource numberSource)
{
      int returnValue = numberSource() * 10;
      return returnValue;
}

This sample method takes a pointer to a function returning integer as a parameter and multiplies the function’s return value by 10.

In managed code, I need to declare this method as follows:

 [DllImport("testCPlusPlus.dll")]
static extern int MultiplyByTen(NumberSource src);

public delegate int NumberSource();

Note that instead of the function pointer I use a delegate. Again, this is identical to what you would do in the full .NET. Next, I create a managed method and pass it to the native method through a delegate binding.

 public int ManagedSource()
{
    return 42;
}

public MainPage()
{
    InitializeComponent();
    int res = MultiplyByTen(new NumberSource(ManagedSource));
}

This code works fine in the full .NET, but in Silverlight the last line throws a security exception with the following message:

Delegate 'SilverlightApplication1.MainPage+NumberSource' must be bound to a method in a fully-trusted assembly and the method must have the AllowReversePinvokeCallsAttribute.

To fix this problem, all I need to do is apply the AllowReversePinvokeCalls attribute to the ManagedSource method.

 [AllowReversePInvokeCalls]
public int ManagedSource()
{
    return 42;
}

This feature is just an additional security measure, so you can have better control over what managed methods can and cannot be called by native APIs.

Processing Window Messages

Anybody familiar with Win32 API probably knows that Windows sends different messages to each registered window through the WindowProc function. For example, it sends messages whenever a USB device is inserted or removed.

In WPF and even in Windows Form, you could take an existing window or subclass a Window class and override its WndProc method. However, in Silverlight the Window class doesn’t expose this method. One way to solve this problem is to define a hidden window by using native APIs. Here is another code example that detects the removal and insertion of a USB.

 public partial class MainPage : UserControl
{

// Importing a set of necessary native methods from Win32 API.
[DllImport("User32", EntryPoint = "CreateWindowEx", 
    CharSet = CharSet.Auto,SetLastError = true)]
static extern IntPtr CreateWindowEx(int dwExStyle, 
    string lpszClassName, string lpszWindowName, int style, 
    int x, int y, int width, int height,
    IntPtr hWndParent, IntPtr hMenu, IntPtr hInst,
    [MarshalAs(UnmanagedType.AsAny)] object pvParam);

[DllImport("user32.dll")]
static extern IntPtr DefWindowProc(IntPtr hWnd, int uMsg, 
    IntPtr wParam, IntPtr lParam);

[DllImport("user32", CharSet = CharSet.Auto, SetLastError = true)]
public static extern short RegisterClass(WNDCLASS wc);

// Marshaling the Window structure.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class WNDCLASS
{
    public int style;
    public WndProc lpfnWndProc;
    public int cbClsExtra;
    public int cbWndExtra;
    public IntPtr hInstance;
    public IntPtr hIcon;
    public IntPtr hCursor;
    public IntPtr hbrBackground;
    public string lpszMenuName;
    public string lpszClassName;
}

//system detects USB insertion/removal
const int WM_DEVICECHANGE = 0x0219;
// system detects a new device
const int DBT_DEVICEARRIVAL = 0x8000;
// device removed
const int DBT_DEVICEREMOVECOMPLETE = 0x8004;

// Callbacks must have AllowReversePInvokeCalls attribute.
[AllowReversePInvokeCalls]
private IntPtr Callback(
    IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam)
{
    if (msg == WM_DEVICECHANGE)
    {
        if (wparam.ToInt32() == DBT_DEVICEARRIVAL) 
            textBlock1.Text = "USB inserted";
        if (wparam.ToInt32() == DBT_DEVICEREMOVECOMPLETE) 
            textBlock1.Text = "USB removed";
    }
    return DefWindowProc(hWnd, msg, wparam, lparam);
}

public delegate IntPtr WndProc(
    IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

// Preventing garbage collection of the delegate
private static WndProc dontGCthis;

public MainPage()
{
    InitializeComponent();

    WNDCLASS wc = new WNDCLASS();

    // Preventing garbage collection of the delegate
    dontGCthis = new WndProc(Callback);
    wc.lpfnWndProc = dontGCthis;

    // Note that you need to ensure unique names 
    // for each registered class.
    // For example, if you open the same plugin 
    // in two different tabs of the browser,
    // you still should not end up with 
    // two registered classes with identical names.
    wc.lpszClassName = "foobar" + (new Random()).Next();

    RegisterClass(wc);

    IntPtr createResult = CreateWindowEx(0, wc.lpszClassName, 
        "Window title", 0, 100, 100, 500, 500, 
        IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0);
}
}

This example prints to a text block when a USB device is removed or inserted. Note that if you use the same code in WPF, it will work just fine. However, it’s important to remember that porting your existing pInvoke code from WPF to Silverlight might require some extra work (as usual).

More Examples

There were several good articles about pInvoke already written after Silverlight RC. I just wanted to share the list, since there never can be too many code examples, especially when you are dealing with such advanced topics as interop between managed and advanced code.