Sharing Platform Invoke code between the Desktop and Compact Framework

Previously we discussed techniques for backwards compatibility when accessing new APIs within native applications. This blog posting discusses how similar techniques can be used by .NET Compact Framework developers utilising Platform Invoke functionality.

.NET Compact Framework developers inherently have a head start, as utilising Platform Invoke functionality means they are always implicitly dynamically linking to native APIs.

Building Assemblies that work on Desktop and PDA
Screenshot of sample .NET Compact Framework application running on a Desktop PC running Microsoft VistaIf you build your application as a Smart Device project you will find that you can get your executable to also run on the full .NET Framework without modification.

The magic that allows this to happen is a new feature Microsoft introduced with .NET Framework v2.0 called retargetable assemblies. An assembly which is marked as retargetable can be substituted with a compatible assembly from a different publisher.

The .NET Compact Framework base class libraries (such as System.Data.dll) are retargetable. This allows the desktop assembly loader (fusion) to substitute them for the desktop versions of these assemblies when the executable is ran on a Desktop PC.

The reverse is not possible. I.e. an assembly built against the desktop assemblies will fail to run when placed on a device running the .NET Compact Framework.

You have to be careful while using this feature. If you manage to use a feature only present in the .NET Compact Framework version of an assembly, a TypeLoadException will occur when your application attempts to run on a desktop PC,.

Dealing with non existant APIs
Sometimes you may need to access an API which is only available on a particular platform or device and it is important to handle the error cases which may occur when the application runs on a platform that does not support the API.

Internally the Platform Invoke functionality calls the LoadLibrary and GetProcAddress system APIs discussed when we talked about native platform compatibility. If either of these APIs return failure codes the .NET Compact Framework Platform Invoke functionality will cause an exception to be thrown. We will again use the SndPlaySync API to demonstrate this.

// Declare the SndPlaySync API as being a native function present
// within the aygshell.dll file
[DllImport("aygshell.dll")]
private static extern int SndPlaySync(string pszSoundFile, UInt32 dwFlags);
 
...
 
try
{
  // Attempt to call the SndPlaySync function
  SndPlaySync(@"\Windows\Alarm2.wav", 0);
}
catch (DllNotFoundException ex)
{
  // deal with the DLL not being present
  // at all
}
catch (MissingMethodException ex)
{
  // deal with the method not being found
  // within the dll
}

If we ran this application on a desktop PC we would expect to receive a DllNotFoundException, this is because the aygshell.dll file does not exist on a desktop PC.

If we ran this application on a PDA running Windows Mobile 5.0 or lower we would expect a MissingMethodException, since these platforms have an aygshell.dll, but it does not provide the SndPlaySync API.

In a real application we may like to handle these exceptions by attempting to use an alternative, or less capable technique to implement the same functionality. For example we may fall back to using an older API with less features, or disable some element of our user interface.

Dealing with APIs that exist in different locations
Windows CE (the operating system Windows Mobile is based upon) aims to have a Win32 API implementation which is fairly compatible with the implementation found in desktop versions of the Microsoft Windows operating system. However for a number of technical and historical reasons the APIs are commonly exposed by different DLLs. This leads to a potential issue since the DLLImport attribute utilised to Platform Invoke a function requires hard coding the DLL’s name into your application as shown below.

// MessageBox is found within coredll.dll
[DllImport("coredll.dll")]
private static extern int MessageBox(IntPtr hWnd, String lpText,
  String lpCaption, uint uType);

We can work around this problem by having more than one Platform Invoke declaration for our desired function. We can have one declaration for each platform we target, with the only difference being the DLL specified. For example:

// MessageBox is found in coredll.dll on Windows Mobile
[DllImport("coredll.dll")]
private static extern int MessageBox(IntPtr hWnd, String lpText,
  String lpCaption, uint uType);
 
// MessageBox is found in user32.dll on Desktop PCs 
[DllImport("user32.dll")]
private static extern int MessageBox(IntPtr hWnd, String lpText,
  String lpCaption, uint uType);

However if we attempt to compile this the compiler would tell us that we have duplicate definitions for the MessageBox function. We can get around this by giving them different names and using the optional EntryPoint field of the DllImport attribute to define the actual name of the function within the DLL.

[DllImport("coredll.dll", EntryPoint="MessageBox")]
private static extern int MessageBox_WCE(IntPtr hWnd, String lpText,
  String lpCaption, uint uType);
 
[DllImport("user32.dll", EntryPoint="MessageBox")]
private static extern int MessageBox_WIN(IntPtr hWnd, String lpText,
  String lpCaption, uint uType);

Having these two definitions gives two uniquely named functions as far as the C# compiler is concerned, but both attempt to access a function called MessageBox in their respective DLLs.

We can then produce a small wrapper function which hides this difference and automatically calls the correct method based upon the current platform the application is running on.

int MessageBox(IntPtr hWnd, String lpText,
  String lpCaption, uint uType)
{
  if (Environment.OSVersion.Platform == PlatformID.WinCE)
    // call the Windows CE version of this function
    MessageBox_WCE(hWnd, lpText, lpCaption, uType);
  else
    // call the desktop version of this function
    MessageBox_WIN(hWnd, lpText, lpCaption, uType);
}

Sample Application

[Download CrossPlatformTest.zip - 9.9KB]

A small sample application is available to demonstrate these two techniques. Once the application is built you should be able to run it on both the Windows Mobile 5.0, and 6 Device Emulators, as well as your Desktop PC (for this navigate to the executable on your hard-drive and double click it).

The first button should display a message box (similar in functionality to the built in System.Windows.Forms.MessageBox class). If you step through the code you should see that different DllImport statements are used to take care of the different DLLs this API lives in on Windows Mobile devices and Desktop PCs.

The second button attempts to play a sound via the SndPlaySync API which is present on Windows Mobile 6 and above. If you run this example on any other platform you should get a message box indicating an error. The message displayed demonstrates the different type of exceptions which may be thrown when the API is not present on a given platform.

One Response to “Sharing Platform Invoke code between the Desktop and Compact Framework”

  1. Pratik says:

    How can I get aygshell.dll for all the devices. I want some of the features of them on my device running lower then windows mobile 5.

Leave a Reply