Augmenting the Compact Framework

To paraphrase Lincoln, you can't please all of the people all of the time, and the Compact Framework is no exception. Because the Compact Framework is a subset of the desktop Framework, covering roughly 25% of its functionality, there will certainly be areas where you and your team will need to implement some functionality that the operating system supports (via the Windows CE API) but that the Compact Framework does not. For this reason, the Compact Framework includes the PInvoke mechanism also common to the desktop Framework. The first parts of this section will therefore provide an overview of PInvoke and its uses and then move on to more advanced issues.

Of course, technology never stands still, and so, on another front, since the release of the Compact Framework in the spring of 2003 (included in Visual Studio .NET 2003), Microsoft has continued to push forward by providing additional tools and support, including the SDK for the Pocket PC 2003 with devices to be released in the fall of 2003. Because you and your team will want to take advantage of these tools, the second part of this section will provide a rundown of what you should be looking for.

Using PInvoke

In order to take advantage of functionality supported in the operating system, but not in the Compact Framework, developers need to use the PInvoke service. This service (also exposed in the desktop Framework) allows managed code to invoke unmanaged functions residing in DLLs. In this section we'll explore the basics of PInvoke and how its implementation differs from that in the desktop Framework. Then, we'll raise a few of the advanced issues before discussing a couple of common uses of PInvoke for Compact Framework developers.

PInvoke Explained

To use PInvoke to invoke an operating system API, a developer needs to perform three basic functions: declaration, invocation, and error handling.

First, a developer must tell the Compact Framework at design time which unmanaged function is the intended target by specifying the DLL name (also called the module), the function name (also referred to as the entry point), and the calling convention to use. For example, in order to call the CeRunAppAtEvent function (used to start a Compact Framework application when ActiveSync synchronization finishes), a developer could use the DllImportAttribute or in VB either the DllImportAttribute or the Declare statement as follows (the C# code snippet is followed by the equivalent in VB):


[DllImport("coredll.dll", SetLastError=True)]
private static extern bool CeRunAppAtEvent(string appName,
  ceEvents whichEvent);

Private Declare Function CeRunAppAtEvent Lib "coredll.dll" ( _
  ByVal appName As String, _
  ByVal whichEvent As ceEvents) As Boolean

In both cases you'll notice that the declaration includes the name of the DLL in which the function resides (Coredll.dll, in which many of the Windows CE APIs reside, is analogous to Kernel32.dll and User32.dll in the Win32 API) and the name of the function (CeRunAppAtEvent). In addition, the function is marked as Private so that it can be called only from within the class in which it is declared. It could also be marked as Public or Friend (internal in C#), depending on how your application will call it.

In the C# declaration, the static and extern keywords are required to indicate that the function is implemented externally and can be called without creating an instance of the class (although the function is also marked as private, so it remains hidden).

You'll also notice that the function requires an argument of type ceEvents. In actuality, the function requires a CSIDL value that is a 32-bit integer and maps to one of several Windows CE API constants. In these cases, it is a best practice to expose the constant values (found in the API documentation) in an enumeration, as shown in the following snippet:


Public Enum ceEvents
  None = 0
  TimeChange = 1
  SyncEnd = 2
  DeviceChange = 7
  RS232Detected = 9
  RestoreEnd = 10
  Wakeup = 11
  TzChange = 12
End Enum

You'll notice that the SetLastError property is set to True in the C# declaration (it is False by default). This specifies that the EE calls the Windows CE GetLastError function to cache the error value returned so that other functions don't override the value. The developer can then safely retrieve the error using Marshal.GetLastWin32Error. In the Declare statement, a value of True is assumed, as it is when using DllImportAttribute from VB.

graphics/key point_icon.gif

Second, the developer must invoke the function. As in Windows .NET Framework applications and as discussed in Chapter 3, and as shown in Listing 3-5, it is a best practice to group unmanaged API declarations in a class within an appropriate namespace such as Atomic.CeApi and to expose their functions via shared wrapper methods. This class can be packaged in its own assembly and distributed to all of the developers on your team or in your organization. As you might expect, it is a best practice to expose only those arguments in the wrapper that the client needs to supply; the remainder can be defaulted to appropriate values. The class should also contain a private constructor to prevent the creation of instances of the class. A class that abstracts the call to CeRunAppAtEvent is shown in Listing 11-1.

Listing 11-1 A Wrapper Class. This class wraps the CeRunAppAtEvent Windows CE function using a static method.
Public Class Environment
   Private Sub New()
   End Sub

   <DllImport("coredll.dll", SetLastError:=True)> _
   Private Shared Function CeRunAppAtEvent(ByVal appName As String, _
    ByVal whichEvent As ceEvents) As Boolean
   End Function

   Public Shared Function ActivateAfterSync() As Boolean
     Dim ret As Boolean
       Try
         Dim app As String
         app = [Assembly].GetExecutingAssembly().GetName().CodeBase
         ret = CeRunAppAtEvent(app, ceEvents.SyncEnd)
         If Not ret Then
           Dim errorNum As Integer = Marshal.GetLastWin32Error()
           HandleCeError(New WinCeException( _
            "CeRunAppAtEvent returned false", errorNum), _
            "ActivateAfterSync")
         End If
         Return ret
       Catch ex As Exception
         HandleCeError(ex, "ActivateAfterSync")
         Return False
       End Try
   End Function

    ' Also create a DeactivateAfterSync method
End Class

After the ActivateAfterSync method is called, an instance of the application will automatically be started with a specific command-line parameter after the ActiveSync synchronization ends. Additional code is typically added to an SDP in order to detect the command line and activate an existing instance of the application.

Third, developers must be ready to handle two different kinds of errors that can result when an unmanaged function is called. The first is an exception generated by the PInvoke service itself. This occurs if the arguments passed to the method contain invalid data or the function is declared with improper arguments. In this case a NotSupportedException will be thrown. Alternatively, PInvoke may throw a MissingMethodException, which, as the name implies, is produced if the entry point cannot be found. In the ActivateAfterSync method, these exceptions are trapped in a Try-Catch block and then passed to another custom method called HandleCeError, which can log the exception and perhaps wrap the exception in a custom exception, if needed.

Although the declaration and invocation shown here are the same in the Compact Framework as in the desktop Framework (with the exception of the module name), there are several subtle differences:

  • All Unicode all the time: In the desktop Framework the default character set, which controls the marshaling behavior of string parameters and the exact entry point name to use (whether PInvoke appends an "A" for ANSI or a "W" for Unicode, depending on the ExactSpelling property), can be set using the Ansi, Auto, or Unicode clause in the Declare statement and the CharSet property in DllImportAttribute. Although in the desktop Framework this defaults to ANSI, the Compact Framework supports only Unicode and, consequently, includes only the CharSet.Unicode (and CharSet.Auto, which equals Unicode) value and does not support any of the clauses of the Declare statement. This means that the ExactSpelling property is also not supported.

  • One calling convention: The desktop Framework supports three different calling conventions (which determine issues such as the order arguments are passed to the function and who is responsible for cleaning the stack), which employ the CallingConvention enumeration used in the CallingConvention property of DllImportAttribute. In the Compact Framework, however, only the Winapi value (the default platform convention) is supported, which defaults to the calling convention for C and C++ referred to as Cdecl.

  • Unidirectional: Although parameters can be passed to a DLL function by value or by reference, allowing the DLL function to return data to the Compact Framework application, PInvoke in the Compact Framework does not support callbacks, as the desktop Framework does, for example, to call the EnumWindows API function to enumerate all of the top-level windows.

  • Different exceptions: In the desktop Framework, the EntryPointNotFoundException or ExecutionEngineException will be thrown if the function cannot be located or if it is misdeclared, respectively. In the Compact Framework the MissingMethodException and NotSupportedException types are thrown.

  • Processing Windows messages: graphics/key point_icon.gif Often when dealing with operating system APIs, it becomes necessary to pass the handle (hwnd) of a Window to a function or to add custom processing for messages sent by the operating system. In the desktop Framework, the Form class exposes a Handle property to satisfy the former requirement and the ability to override the DefWndProc method to handle the latter. The Form class in the Compact Framework contains neither, but it does include MessageWindow and Message classes in the Microsoft.WindowsCE.Forms namespace. The MessageWindow class can be inherited, and its WndProc method overridden to catch specific messages of type Message. Compact Framework applications can even send messages to other windows using the SendMessage and PostMessage methods of the MessageWindow class. For example, PostMessage can be used to broadcast a custom message to all top-level Windows when checking to determine if the Compact Framework application is already running.[1]

    [1] See the article referenced in the "Related Reading" section for more information.

Advanced PInvoke

Because the PInvoke service works slightly differently in the Compact Framework than it does in the desktop Framework, there are several issues that developers need to consider when passing data to an unmanaged function.

Passing Strings

A string (System.String) in the Compact Framework is a blittable type and, therefore, has the same representation as a null-terminated array of Unicode characters in the Compact Framework as it does in unmanaged code.[2] As a result, developers can simply pass strings to unmanaged functions in the same way they would to managed functions, as in the example of CeRunAppEvent discussed earlier.

[2] Along with System.Byte, SByte, Int16, Int32, Int64, UInt16, UInt64, and IntPtr.

However, when the string represents a fixed-length character buffer that must be filled by the unmanaged function, as is the case when calling the SHGetSpecialFolderPath function shown in Listing 3-5, the string must be initialized before it is passed to the unmanaged function. Because the Compact Framework supports only Unicode, developers can simply declare a new string and pad it with the requisite number of spaces, as shown here:


Dim sPath As String = New String(" "c, 255)

This behavior is quite different from that in the desktop Framework, where the marshaler must take the character set into consideration. As a result, the desktop Framework does not support passing strings by value or by reference into unmanaged functions and allowing the unmanaged function to modify the contents of the buffer. Instead, both desktop and Compact Framework developers use the System.Text.StringBuilder class, as shown in the following snippet:


Private Declare Function SHGetSpecialFolderPath Lib "coredll.dll" ( _
     ByVal hwndOwner As Integer, _
     ByVal lpszPath As StringBuilder, _
     ByVal nFolder As ceFolders, _
     ByVal fCreate As Boolean) As Boolean

The function can then be called as follows:


Dim sPath As New StringBuilder(MAX_PATH)
ret = SHGetSpecialFolderPath(0, sPath, folder, False)

Because using the StringBuilder is also slightly more efficient, it is recommended when the unmanaged function is passed a string buffer.

Passing Structures

Many Windows CE APIs require that structures be passed to the function. Fortunately, developers can also pass structures to unmanaged functions without worrying, as long as the structure contains blittable types (types represented identically in managed and unmanaged code). For example, the GlobalMemoryStatus Windows CE API is passed a pointer to a MEMORY_STATUS structure that it populates to return information on the physical and virtual memory of the device. Because the structure contains only blittable types, the structure can easily be called, as shown in the following snippet:


Private Structure MEMORY_STATUS
  Public dwLength As UInt32
  Public dwMemoryLoad As UInt32
  Public dwTotalPhys As UInt32
  Public dwAvailPhys As Integer
  Public dwTotalPageFile As UInt32
  Public dwAvailPageFile As UInt32
  Public dwTotalVirtual As UInt32
  Public dwAvailVirtual As UInt32
End Structure

<DllImport("coredll.dll", SetLastError:=True)> _
Private Shared Sub GlobalMemoryStatus(ByRef ms As MEMORY_STATUS)
End Sub

In this case, the calling code would instantiate a MEMORY_STATUS structure and pass it the GlobalMemoryStatus function, as shown here:


Dim ms As New MEMORY_STATUS
GlobalMemoryStatus(ms)

Because the function requires a pointer to the structure (defined as LPMEMORYSTATUS in Windows CE), the declaration of GlobalMemoryStatus indicates that the ms parameter is ByRef. In C# both the declaration and the invocation would require the use of the ref keyword.

Alternatively, this function could have been called by declaring MEMORY_STATUS as a class, rather than a structure. Because the class is a reference type, the parameter would automatically be passed by reference as a four-byte pointer.

It is also important to note that reference types are always marshaled in the order in which they appear in managed code. This means that the fields will be laid out in memory as expected by the unmanaged function. As a result developers needn't decorate the structure with StructLayoutAttribute and LayoutKind.Sequential, although they are supported, as in the desktop Framework.

NOTE

Although developers can pass structures (and classes) to unmanaged functions, the Compact Framework marshaler does not support marshaling a pointer to a structure returned from an unmanaged function. In these cases you will need to marshal the structure manually using the PtrToStructure method of the Marshal class.


Passing Complex Types

graphics/key point_icon.gif

One of the major differences between the marshaler in the Compact Framework and that in the desktop Framework is that the Compact Framework marshaler cannot marshal complex objects (reference types) within structures. This means that if any fields in a structure have types that are not blittable (excluding strings or arrays of strings), the structure cannot be fully marshaled. This is because the Compact Framework does not support the MarshalAsAttribute used in the desktop Framework to supply explicit instructions to the marshaler as to how to marshal the data.

In order to solve this problem, Compact Framework developers can employ one of four strategies:[3]

[3] These three strategies are discussed in detail in our Advanced P/Invoke white paper referenced in the "Related Reading" section at the end of the chapter.

  1. Using a thunking layer: Simply put, a thunking layer is an unmanaged function that accepts the arguments that make up the structure, that creates the unmanaged structure, and that calls the appropriate function. This is the technique for passing complex objects in structures presented in the Visual Studio .NET help and on MSDN. In order to implement it, however, the developer must create an unmanaged DLL to house the thunking function using eVC++. Developers unfamiliar with eVC++, including eVB developers and desktop Framework developers, will want to use one of the other methods.

  2. Using unsafe string pointers: The second option for passing strings in structures is to use the unsafe and fixed keywords in C# (there are no equivalents in VB). While this option allows developers to write only managed code, it does so at the cost of disabling the code-verification feature of the EE, which verifies that the managed code accesses only allocated memory and that all method calls conform to the method signature in both the number and type of arguments. This is the case because using unsafe code implies that developers wish to do direct memory management using pointers. In this approach, the fixed keyword is used in conjunction with the unsafe keyword and ensures that the GC does not attempt to deallocate an object while it is being accessed by the unmanaged function. In addition to the aforementioned downside of losing code verification, this technique cannot, of course, be used directly from VB. Developers can, however, create an assembly in C# that uses unsafe code and then call that assembly from VB.

  3. Using a managed string pointer: The third technique used to marshal strings inside of a structure in the Compact Framework is to create a managed string pointer class. The benefits of this approach are that it can be used in both C# and VB and that it can be leveraged in a variety of situations. It does, however, require a little interaction with the unmanaged memory allocation APIs LocalAlloc, LocalFree, and LocalReAlloc.[4]

    [4] An example of this class can be found in our Advanced P/Invoke white paper referenced in the "Related Reading" section at the end of the chapter.

  4. Custom marshaling: In some cases, for example when a structure contains fixed-length strings or character arrays, developers may need to resort to custom marshaling because the Compact Framework does not support the MarshalAs attribute. This can be done by copying the individual fields of the structure into and out of a byte array, as appropriate, using the methods of the Marshal class. A pointer can then be created to the byte array and passed to the unmanaged function.

Common Techniques

There are many reasons that developers might need to use PInvoke to call unmanaged DLLs, a few of which are listed here:[5]

[5] Many of these common techniques have been implemented by developers in the Compact Framework community. Several of the forums developers might look for implementations are listed in the "Related Reading" section at the end of this chapter.

  • Calling custom functionality: First and foremost, using PInvoke allows your organization to leverage existing code already written and, therefore, to save developer effort. Custom functionality housed in DLLs can then be called using the techniques shown in this chapter; however, there is a slight performance penalty when calling unmanaged code, and, so, developers may see performance improvements by rewriting frequently called unmanaged functions in managed code.

  • Checking the platform of the device: In order to take advantage of specific functionality on the target platform (Pocket PC versus Windows CE .NET or Pocket PC 2002 versus Pocket PC 2000), developers can use the Windows CE API to check the version and then take the appropriate action (for example, to modify the UI or enable functionality). This can be done by calling the SystemParametersInfo API.

  • Enabling notification: Windows CE includes a notification API (the CeSetUserNotificationEx function) that developers can use to display a notify dialog or to cause an application to execute at a specific time or in response to an event, such as at synchronization or when a PC card is changed. Because the Compact Framework doesn't include a managed class that performs this functionality, developers who need it will need PInvoke to make the correct operating system calls.

  • Displaying an icon in the system tray: Although the desktop Framework includes a toolbox item to allow an application to place icons in the system tray, the Compact Framework does not. As a result, developers can use the Shell_NotifyIcon function to place and remove application icons from the system tray.

  • Manipulating the registry: As mentioned several times in this book, the Compact Framework does not include a managed API to read and write the system registry on the device. Developers can use PInvoke to create a managed wrapper around the requisite Windows CE functions, including RegOpenKeyExW and RegQueryValueExW.

  • Activating the Pocket PC voice recorder: Some field service applications require the user to take voice notes using the voice recorder built into some Pocket PC devices. Using an unmanaged function, an application can record the voice note and save it on a storage card.

  • Using the clipboard: The Compact Framework does not include a class for manipulating the clipboard, and, so, developers will need to wrap the unmanaged OpenClipboard, CloseClipboard, GetClipboardData, and SetClipboardData functions in a managed assembly.

  • Using cryptography: Because the Compact Framework does not include the classes in the System.Security.Cryptography namespace, as does the desktop Framework, developers will need to invoke these functions using PInvoke.

  • Making calls on a Pocket PC Phone Edition device: As mentioned in Chapter 1, Compact Framework applications can be deployed on Pocket PC 2002 (and 2003, as we'll discuss shortly) Phone Edition devices; however, because the Compact Framework does not include a managed API to manipulate the phone functionality, developers will need to wrap the various functions (exposed in Phone.dll) in their own custom class, such as that shown in Listing 11-2. You'll notice in this listing that the class relies on a custom StringPtr class, as discussed in the previous section.

Listing 11-2 A Phone Class. This class wraps the functionality needed to make a call on Pocket PC 2002 and 2003 Phone Edition devices.
public class Phone
{
  private class PHONEMAKECALLINFO
  {
    private const uint PMCF_DEFAULT             = 0x00000001;
    private const uint PMCF_PROMPTBEFORECALLING = 0x00000002;

    uint cbSize    = (uint)InteropServices.Marshal.SizeOf(
     typeof(PHONEMAKECALLINFO) );
    uint dwFlags            = PMCF_DEFAULT;
    StringPtr pszDestAddress;
    StringPtr pszAppName; // always NULL
    StringPtr pszCalledParty;
    StringPtr pszComment; // always NULL

    public PHONEMAKECALLINFO( string phoneNumber )
    {
      this.pszDestAddress = new StringPtr( phoneNumber );
    }

    public PHONEMAKECALLINFO( string phoneNumber, string calledParty )
    {
      this.pszDestAddress = new StringPtr( phoneNumber );
      this.pszCalledParty = new StringPtr( calledParty );
    }

    public PHONEMAKECALLINFO( string phoneNumber,
     string calledParty, bool prompt )
    {
      this.pszDestAddress = new StringPtr( phoneNumber );
      this.pszCalledParty = new StringPtr( calledParty );
      if( prompt )
        this.dwFlags = PMCF_PROMPTBEFORECALLING;
    }
    ~PHONEMAKECALLINFO()
    {
      this.pszDestAddress.Free();
      this.pszCalledParty.Free();
    }
  }

  private Phone(){}

  [DllImport ("Phone.dll")]
  private static extern int PhoneMakeCall( PHONEMAKECALLINFO ppmci );

  public static bool MakeCall( string phoneNumber,
   string calledParty, bool prompt )
  {
    PHONEMAKECALLINFO phoneMakeCallInfo = new PHONEMAKECALLINFO(
     phoneNumber, calledParty, prompt );
    return( PhoneMakeCall(phoneMakeCallInfo)==0 );
  }

  public static bool MakeCall( string phoneNumber, string calledParty )
  {
    return( MakeCall(phoneNumber, calledParty, false) );
  }

  public static bool MakeCall( string phoneNumber )
  {
    return( MakeCall(phoneNumber, "", false) );
  }

}

An application can then use the class, as shown here, to make a phone call and prompt the user:


Phone.MakeCall("9135551212", "Dan's House", true);

Additional Tools

Just as the Compact Framework does not include a managed API for every function that your organization might need, Visual Studio .NET and SDP do not ship with all the tools you'll want to use. Several of the most important tools that you'll want to check out include the following.

Pocket PC Remote Display Control

Even though developers can test their applications using the emulators that ship with SDP in VS .NET 2003, it is always preferred to perform testing on the device itself. To enable a developer to do this easily, Microsoft created the Pocket PC RDC and makes it available in the Power Toys for the Pocket PC on the Microsoft Web site.[6]

[6] www.microsoft.com/windowsmobile/resources/downloads/pocketpc/powertoys.mspx.

This product installs a client (device) and a server (desktop) piece that together allow the device to be manipulated through a window on the desktop. As a result, developers can deploy their applications to the device using VS .NET and then test the application by invoking the RDC and using both the mouse and keyboard for easy user input. The RDC uses TCP/IP and, so, works with ActiveSync connections via Ethernet or dial-up.

TIP

The RDC can be integrated into VS .NET as an external tool by clicking on Tools/External Tools in the IDE, and adding a reference to the C:\Program Files\Remote Display Control\cerhost.exe executable to start the host application on the desktop machine.


Windows CE Utilities for VS .NET 2003 Add-on Pak

A second download from the Microsoft site[7] that you should be aware of is the Windows CE Utilities for VS .NET 2003 Add-on Pak. This add-on addresses the problem of VS .NET 2003 connecting to devices that do not run ActiveSync, when it cannot dynamically determine the CPU on non?Pocket PC devices. As a result, after installing this software, developers should be able to use VS .NET to connect, deploy, and debug applications on any device running Windows CE .NET 4.1 or higher. Specifically, the download includes the following:

[7] www.microsoft.com/downloads/details.aspx?FamilyId=7EC99CA6-2095-4086-B0CC-7C6C39B28762&displaylang=en.

  • Windows CE CPU Picker for devices with ActiveSync

  • Smart Device Authentication Utility for devices without ActiveSync

  • Settings to enable debugging with x86-based Windows CE devices

  • Components needed to enable debugging with certain devices

SDK for Windows Mobile 2003?based Pocket PCs

Even though this book and the code samples we've presented were developed on the Pocket PC 2002, Microsoft continues to innovate, and in late June of 2003, Microsoft made available the SDK for the Pocket PC 2003 platform. Devices for this platform should be hitting the shelves before this book is printed. The SDK allows developers to use VS .NET 2003 and the Compact Framework to develop applications that target this platform.

graphics/key point_icon.gif

Among the changes in the Pocket PC 2003, the most important are that the devices use the Windows CE .NET 4.2 operating system and that the Compact Framework v1.0 is installed in ROM on all Pocket PC 2003 devices. The following is a short list of additional improvements:

  • Improved emulators that include drive mapping through folder sharing, that run in three modes (Pocket PC 2003, Pocket PC 2003 Phone Edition, which supports external GSM radio modules, and Pocket PC 2003 Phone Edition with Virtual Radio), and that can be used to test the phone and SMS capabilities of applications

  • New and improved BlueTooth support

  • Kernel enhancements that lead to improved performance and ROM size savings

  • Support for next-generation network layer protocols, such as TCP/IPv6, which supports a significantly larger address space

  • Improved display drivers

  • New tools, including RapiConfig, an XML-based configuration tool that can be used to configure a device or emulator easily

Additional Developer Tools

In July of 2003, the Compact Framework product team posted to the Microsoft site an additional set of tools that developers can take advantage of, including the following:

  • A set of command-line tools that use the RAPI to copy files easily to the device, to debug process on the device, to send debug output to the standard debug window in VS .NET, and to start processes on the device

  • A tool called LScript to automate functional testing on the device by capturing and replaying scripts on the device

  • A stress-testing tool called Hopper that creates random taps on the emulator to test how the application responds