Jeremy Likness' Blog

Silverlight 5 RC Released - Using PInvoke

It's exciting to see the progress of Silverlight 5 toward the final release that has been slated towards the end of the year. Today, the Silverlight Team announced the release of the Silverlight 5 Release Candidate. While it does not yet have a go-live license, it integrates many new features that were not available with the beta release, including the new PivotViewer integration and vector-based printing.

One of the new features included with the Silverlight 5 RC is the ability to interact with Platform Invocation Services, or PInvoke for short. This functionality allows managed code, like your Silverlight application, to make calls to unmanaged code like the C++ code that exists in DLLs on your system. PInvoke can be quite complex and covers a lot of different scenarios, so I've focused on a simple scenario to help you get started. You can learn more about PInvoke by reading this MSDN tutorial.

In this post, I will show you how to build a Silverlight Out-of-Browser (OOB) application that uses the native DLLs on your machine to play "beeps." With modern versions of Windows 7, the beeps have evolved into complex sounds that you can assign to various functions. Create a new Silverlight application called "PInvoke," set it to run out of browser and be sure to check the box that indicates it requires elevated trust.

Take a look at the MessageBeep function. It is documented here. The first thing you'll notice is that the type of beep or sound to play is not very friendly as it is passed in as an unsigned integer. Based on the documentation, you can create the following enumeration to simplify things and expose a few key types:

public enum BeepTypes : uint
{
    Ok = 0x00000000,
    Error = 0x00000010,
    Warning = 0x00000030,
    Information = 0x00000040
}

To make it easy to take a string and cast it to the enumeration value, add this extension method as well:

public static class BeepTypeExtensions
{
    public static BeepTypes AsBeepTypeEnum(this string beepType)
    {
        BeepTypes beepTypeEnum;
        return Enum.TryParse(beepType, true, out beepTypeEnum)
                    ? beepTypeEnum
                    : BeepTypes.Error;
    }
}

It is always good practice to encapsulate your calls to unmanaged code in a managed class. This limits the exposure and dependency on PInvoke. It also allows you to write more portable code because you can implement the calls in managed code or simply stub out empty methods for targets that don't support the PInvoke implementation. Here is a simple contract to play a sound:

public interface ISoundPlayer
{
    void PlaySound(BeepTypes type);
}

Now for the implementation. Create a class called SoundPlayer. The documentation gives you the message signature and the DLL that the method is located in. Bridging to the method in this example is very easy. First, include a using statement for System.Runtime.InteropServices. This is where the PInvoke bridge lives. Next, simply define the message signature as a static extern and import the DLL:

[DllImport("User32.dll")]
private static extern Boolean MessageBeep(UInt32 beepType);

Implement the contract and call the method you just imported using PInvoke:

public void PlaySound(BeepTypes type)
{
    if (!MessageBeep((UInt32) type))
    {
        throw new Exception("Beep failed!");
    }
}

That's all there is to it! Now wire up some Xaml in the main page to host four buttons:

<Grid x:Name="LayoutRoot" Background="White">
    <Grid.RowDefinitions>
        <RowDefinition Height="1*"/>
        <RowDefinition Height="1*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="1*"/>
    </Grid.ColumnDefinitions>
    <Button x:Name="Ok" Content="OK" Grid.Row="0" Grid.Column="0"  Click="Button_Click"/>
    <Button x:Name="Error" Content="Error" Grid.Row="0" Grid.Column="1"  Click="Button_Click"/>
    <Button x:Name="Warning" Content="Warning" Grid.Row="1" Grid.Column="0"  Click="Button_Click"/>
    <Button x:Name="Information" Content="Information" Grid.Row="1" Grid.Column="1"  Click="Button_Click"/>
</Grid>

Make sure that the name of the button matches the name of the enum. In the code-behind for the main page, create an instance of the sound player:

private readonly ISoundPlayer _soundPlayer = new SoundPlayer();

Now you can implement the button click event. Simply cast the name of the button to the enumeration and call the method on your managed class:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var button = sender as Button;
    if (button == null)
    {
        return;
    }
    _soundPlayer.PlaySound(button.Name.AsBeepTypeEnum());
}

That's it! Run the application in OOB mode and you will be able to press the buttons and hear your default Windows sounds play. If you change your theme or override the sounds, the application will play the correct sounds because it is bridging to the unmanaged code and asking the operating system to play the sound rather than trying to play it from a resource embedded within the Silverlight application.

Hopefully this light example will help you get started. PInvoke has a number of uses from interfacing directly with USB ports to enumerating drives and devices on the host system. It certainly opens the door to a new realm of possibilities with Silverlight.

Download the sample solution here (but remember, it requires that you install the Silverlight 5 RC first!).

Jeremy Likness

On Sep 1 2011 2:19 AMBy jlikness SilverlightWith 5 Comments

Comments (5)

  1. Nice post, do you know how to set the window position in OOB mode by mean of pInvoke,
    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

    I don't know how to convert from Application.MainWindow to InPtr

    Best regards, Paulo

    twitter: @paulovila

  2. it seems that is as 'easy as find a windows with the same title, but what if there are several instances?


    public const int SWP_SHOWWINDOW = 0x0040;

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

    [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
    static extern IntPtr FindWindowByCaption(IntPtr zeroOnly, string lpWindowName);


    public static void SetWindoPos(int x, int y, int cx, int cy)
    {
    const string titulo = "wwwww";
    Application.Current.MainWindow.Title = titulo;
    var v = FindWindowByCaption(IntPtr.Zero, titulo);
    SetWindowPos(v, IntPtr.Zero, x, y, cx, cy, SWP_SHOWWINDOW);
    }


  3. That's a good point. I started to go down that path but haven't fully vetted it - seems you've gotten farther along than me! Let me know if you find out anything new.

  4. My intent was to center the current window with a specific size, as there can be multiple intances, I set the title to a guid:


    public class PInvoke
    {
    public const int SwpShowWindow = 0x0040;

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

    [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
    static extern IntPtr FindWindowByCaption(IntPtr zeroOnly, string lpWindowName);

    [DllImport("user32.dll")]
    static extern int GetSystemMetrics(SystemMetric smIndex);

    public enum SystemMetric
    {
    SmCxScreen = 0,
    SmCyScreen = 1,
    }

    public static void CenterWindowWithSize(int width, int height)
    {
    string titulo = Guid.NewGuid().ToString();
    Application.Current.MainWindow.Title = titulo;
    var v = FindWindowByCaption(IntPtr.Zero, titulo);

    var yDesk = GetSystemMetrics(SystemMetric.SmCyScreen) - height;
    var xDesk = GetSystemMetrics(SystemMetric.SmCxScreen) - width;
    if (yDesk > 0) yDesk = yDesk / 2;
    else yDesk = 0;
    if (xDesk > 0) xDesk = xDesk / 2;
    else xDesk = 0;

    SetWindowPos(v, IntPtr.Zero, xDesk, yDesk, width, height, SwpShowWindow);
    }
    }

  5. For your interest, I have published a small project that shows a splash screen when the application is loading.
    https://skydrive.live.com/redir.aspx?cid=fe6e8678c9930f17&resid=FE6E8678C9930F17!7391&parid=FE6E8678C9930F17!173
    It would be nice that the image be partially transparent so that it can be overlayed on the desktop.
    Maybe by capturing the desktop's screen rectangle bellow the current silverlight window position, then taking this capture and pasting on its background...

Leave a Comment