次の方法で共有


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.

Comments

  • Anonymous
    September 27, 2011
    Now I can finally get a precision timer for benchmarking!

  • Anonymous
    September 28, 2011
    Is there anyway to call the native code you wrote?

  • Anonymous
    September 29, 2011
    Does the example for processing windows messages work in browser everything i have tried so far causes a methodaccessexception

  • Anonymous
    September 30, 2011
    The comment has been removed

  • Anonymous
    September 30, 2011
    Could it be that "Require elevated trust when running in-browser" does only work when the page ís loaded froma web server but not when loaded from local file system?

  • Anonymous
    October 03, 2011
    I am asking as we are currently evaluating using Silverlight as UI technology and access local hardware like cardreader, dispenser etc.. Normally we are loading pages from a webserver but for some fallback scenarios we need to have files also stored locally and then loaded via "FILE:".

  • Anonymous
    October 04, 2011
    Why is P/Invoke available only on Windows? Was it a technical issue that necessitated not introducing Mac support?

  • Anonymous
    October 05, 2011
    @Sneaker I guess for your scenario you should use Silverlight OOB (Out of the Browser). Is it what you are doing?

  • Anonymous
    October 05, 2011
    No, we are not using OOB. Is there a difference between "elevated trust" in browser and out of browser concerning FILE access and P/Invoke? If yes, why does this difference exist? If it works with pages from a webserver why not also with those loaded from FILE? Is this currently a bug in the Release candidate and will be fixed or is there a special reason for that?

  • Anonymous
    October 06, 2011
    @Sneaker If you work with the Silverlight Application and Silverlight XAP files, full-trust should be the same in-browser and out of the browser. It's just that in Visual Studio there are two different check boxes in project properties to turn full-trust: one for OOB, and another one for in-browser. So you can enable full-trust OOB only, for example, without enabling it in-browser. However, I am not sure I really understand what you are doing. For example, when you say "pages" do you mean Silverlight XAP files or HTML pages? What is your scenario and what kind of errors are you getting?

  • Anonymous
    October 06, 2011
    We are developing applications accessing some local hardware via native access layer. We are now evaluating to use Silverlight as UI technology embedded in HTML pages (so in browser). We tested SL 5 with P/Invoke activating in browser "elevated trust". This works fine when the HTML pages are received from a webserver but fails if they are coming from local file system. The P/Invoke access returns the error "No elevated trust". We need this local files for fallback scenarios when webserver is not available. So our question is why does it work in browser when coming from a webserver but not when coming from the file system.

  • Anonymous
    October 10, 2011
    @Sneaker Can you please file your issue here: connect.microsoft.com/VisualStudio It would be nice if you can also attach a small repro app. My guess it's not about pInvoke, but about elevated trust.

  • Anonymous
    November 13, 2011
    lparam value in Callback(...) always comes 0 if plug/unplug USB device, whereas in non Silverlight application (.NET) in case of protected override void WndProc(ref Message m) { ...} it gives proper value.

  • Anonymous
    December 09, 2011
    Hi Alexandra, I'm curious about the reverse p/Invoke calls.  Does decorating your callback with a [AllowReversePInvokeCalls] also 'Pin' that callback?   Otherwise the garbage collector could shuffle your objects while you're executing native code right?  

  • Anonymous
    July 03, 2012
    To be able to call/use COM object is great! Provided that in the client there is the .NET 2.0 installed, is there a way also to call/use the .NET 2.0 from Silverlight 5 ? May be load the assembly, instantiate a class and use it. Thanks.

  • Anonymous
    March 21, 2014
    Hi, My boss asked if is posible use silverlight for a biometric device That device has a project sample, is a winform sample,but it needs a c++/cli dll and that dll calls another c dlls , also the project sample uses a thread for detect the part of the body

  • Anonymous
    February 27, 2015
    Hi, :(:(:(:(:(:(:(??? USB---->silverligth???????  "VolumeSerialNumber"