June 13, 2012 08:26 by
Scott
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:
1 public enum BeepTypes : uint
2 {
3 Ok = 0x00000000,
4 Error = 0x00000010,
5 Warning = 0x00000030,
6 Information = 0x00000040
7 }
To make it easy to take a string and cast it to the enumeration value, add this extension method as well:
01 public static class BeepTypeExtensions
02 {
03 public static BeepTypes AsBeepTypeEnum(this string beepType)
04 {
05 BeepTypes beepTypeEnum;
06 return Enum.TryParse(beepType, true, out beepTypeEnum)
07 ? beepTypeEnum
08 : BeepTypes.Error;
09 }
10 }
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:
1 public interface ISoundPlayer
2 {
3 void PlaySound(BeepTypes type);
4 }
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:
1 [DllImport("User32.dll")]
2 private static extern Boolean MessageBeep(UInt32 beepType);
Implement the contract and call the method you just imported using PInvoke:
1 public void PlaySound(BeepTypes type)
2 {
3 if (!MessageBeep((UInt32) type))
4 {
5 throw new Exception("Beep failed!");
6 }
7 }
That's all there is to it! Now wire up some Xaml in the main page to host four buttons:
01 <Grid x:Name="LayoutRoot" Background="White">
02 <Grid.RowDefinitions>
03 <RowDefinition Height="1*"/>
04 <RowDefinition Height="1*"/>
05 </Grid.RowDefinitions>
06 <Grid.ColumnDefinitions>
07 <ColumnDefinition Width="1*"/>
08 <ColumnDefinition Width="1*"/>
09 </Grid.ColumnDefinitions>
10 <Button x:Name="Ok" Content="OK" Grid.Row="0" Grid.Column="0" Click="Button_Click"/>
11 <Button x:Name="Error" Content="Error" Grid.Row="0" Grid.Column="1" Click="Button_Click"/>
12 <Button x:Name="Warning" Content="Warning" Grid.Row="1" Grid.Column="0" Click="Button_Click"/>
13 <Button x:Name="Information" Content="Information" Grid.Row="1" Grid.Column="1" Click="Button_Click"/>
14 </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:
1 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:
1 private void Button_Click(object sender, RoutedEventArgs e)
2 {
3 var button = sender as Button;
4 if (button == null)
5 {
6 return;
7 }
8 _soundPlayer.PlaySound(button.Name.AsBeepTypeEnum());
9 }
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.