Making use of new APIs within older applications

Each new release of the Windows Mobile platform tends to introduce a couple of new APIs. This blog post discusses a technique that Native developers can use to allow conditional access to these APIs while still allowing the application to run on older devices.

Introduction
Typically a developer creating a C or C++ application within Visual Studio would choose a device SDK that matches their target device. For example when creating a project for a Windows Mobile 6 Professional device the developer would typically select the “Windows Mobile 6 Professional” SDK within the New Project wizard. Compiling with a particular Device SDK “marks” the executable in such a way that it requires as a minimum that particular version of Windows Mobile in order to run.

Since the Windows Mobile platform strives for backwards compatibility this should also mean that the application would work on any newer version of the OS (but it would be constrained to accessing the subset of functionality exposed by the selected SDK).

Visual Studio includes a tool called Dependency Walker. Opening an exe or dll file within this application allows you to determine which version of the Windows Mobile OS an executable is designed for. This tool can typically be found within the C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin directory. The “Subsystem Ver” field as indicated in the above screen-shot shows the minimum version of the Windows CE kernel required for the selected program to run (see “What device is my application running on?” for a table of Windows CE kernel versions and their associated Windows Mobile releases). Trying to run an executable compiled for a newer version of the OS on an older version will result in an error message similar to the following:

Dependency Walker (as it’s name suggests) also allows us to determine which DLLs an executable depends upon and hence must be present on the device in order for the application to run. For example in the following screenshot the application has dependencies on aygshell.dll, phone.dll, and coredll.dll. If any of these DLLs are not present on a device, the OS will fail to start the application with the same error message shown above.

Accessing APIs from newer versions of the OS
Some times it is necessary to compile against an older device SDK while still allowing access to new APIs whenever the application is running on a newer device.

As an example, a previous project I worked on was compiled against the Pocket PC 2002 SDK, and there were a couple of hundred Pocket PC 2002 devices out in the field. When Windows Mobile 2003 Second Edition devices started showing up (which support dynamic screen orientation changes), it was desirable to access a new API to ensure the application always ran in Portrait mode, without switching the project over to the Pocket PC 2003 SDK (since that would mean needing to compile two versions of the app, or loosing support for the existing Pocket PC 2002 devices).

As an easy to follow example we will use the SndPlaySync API introduced in Windows Mobile 6, and discuss how to access this while building our application against the Pocket PC 2003 SDK.

When targeting the Windows Mobile 6 Professional SDK we could make use of this API as follows:

// Play the Alarm2.wav sound effect
#include <soundfile.h>
SndPlaySync(_T("\\Windows\\Alarm2.wav"), 0);

However attempting to run this executable on a Pocket PC 2003 device will fail because:

  1. The executable is marked as requiring Windows CE OS version 5.02 or higher.
  2. The executable indicates it depends upon a function called SndPlaySync being present within a dll called aygshell.dll. Although aygshell.dll exists on a Pocket PC 2003 device, it does not export a function called SndPlaySync.

Both of these facts can be confirmed via the use of the Dependency Walker utility as discussed above.

If we change our project to target the Pocket PC 2003 Device SDK we will notice that the project fails to compile. This makes sense, since changing the SDK changes the version of the Windows header files our project compiles against. The Pocket PC 2003 version of the Windows header files are clearly highlighting the fact that the SndPlaySync function does not exist on this version of the platform.

One solution to this problem is to dynamically link to this API. In other words when our application runs we manually load aygshell.dll and attempt to find the SndPlaySync function within it. If we find it we can make use of it, otherwise we can fail gracefully and provide some kind of fallback.

Within the Win32 programming environment we do this by making use of a function pointer typedef and the LoadLibrary and GetProcAddress APIs.

By looking at the MSDN documentation for SndPlaySync we can typically determine which DLL the API lives within, and determine a function prototype that is suitable for converting into a function pointer typedef. Depending upon the API in question, we may also need to copy any structs or #define’s utilised by the function that are not present in the older Windows Mobile header files.

An example of this technique is shown below:

// A typedef that defines a function pointer called SND_PLAY_SYNC that
// is capable of pointing to any function that has the same
// prototype as SndPlaySync
typedef HRESULT (* SND_PLAY_SYNC)(LPCTSTR pszSoundFile, DWORD dwFlags);
 
// Attemp to load aygshell.dll
HMODULE hLib = LoadLibrary(_T("aygshell.dll"));
if (hLib)
{
  // If we manage to load it attempt to get the address
  // of the SndPlaySync API located within it.
  SND_PLAY_SYNC pfnSndPlaySync =
    (SND_PLAY_SYNC)GetProcAddress(hLib, _T("SndPlaySync"));
 
  // If SndPlaySync is found then invoke it. Otherwise
  // provide a simple fallback, in this case a simple
  // message box. A more complete example may
  // have used an alternative sound playing API.
  if (pfnSndPlaySync)
    pfnSndPlaySync(_T("\\Windows\\Alarm2.wav"), 0);
  else
    MessageBox(GetForegroundWindow(),
      _T("Sorry we can not play a sound"),
      _T("Status"), MB_OK);
 
  CloseHandle(hLib);
}

This solution doesn’t magically make the API available on older devices. The key thing is it delays the check for the SndPlaySync API being present from launch time (by the OS) until runtime (by our code). If you use the Dependency Walker utility you will notice that there is no hard-coded dependency on the SndPlaySync API within this version of the application.

Sample Application

[Download BinaryCompatabilityExample.zip - 16KB]

A small sample application is available for download. It contains two projects. The first called “BadApp” targets the Windows Mobile 6 SDK and will fail to run on devices using an older version of the OS. The second called “GoodApp” targets the Pocket PC 2003 SDK and shows how to modify the source code of “BadApp” in order to make the application also run on older devices. As an added bonus the sample demonstrates how this solution can also resolve issues with devices using the same OS version but having different hardware capabilities. This issue has been discussed by Mel Sampat in regards to accessing phone functionality (some PDAs still come without any cellular capabilities).

A future blog posting will discuss how similar techniques can be implemented for .NET Compact Framework applications which use Platform Invoke (DllImport) functionality. This can be particularly handy for getting an application that makes use of PInvokes to work on both the desktop and PDA devices without the need of conditional compilation and multiple versions of the executable.

One Response to “Making use of new APIs within older applications”

  1. Guy says:

    It seems good, but if this program run in older devices (Windows Mobile 5 or earlier), it can’t play the sound, right? It’s possible to use the aygshell.dll in older device (for example copying this file in the windows directory during the installation process)?

Leave a Reply