Export (0) Print
Expand All

Pocket PC Signature Application Sample

.NET Compact Framework 1.0
 

Ralph Arvesen
Vertigo Software, Inc.

March 2003

Applies to:
    Microsoft® .NET Compact Framework 1.0
    Microsoft® Visual Studio® .NET 2003

Summary: This article discusses the Pocket PC Signature sample application. The sample includes a client that runs on the Pocket PC that sends signature data over TCP sockets to a server that is running on the desktop. Data is encrypted and decrypted using the cryptography services. (23 printed pages)

Download SignatureCapture.msi.


The following topics are covered in this paper:

  • Transmitting data over TCP sockets
  • Notifying the UI from socket worker threads
  • Calling Control.Invoke to update UI elements
  • Flattening data to send over sockets
  • Using crypto API functions to encrypt and decrypt data
  • Storing settings in XML .config files
  • Accessing the registry
  • Custom controls double-buffering
  • Displaying bitmap resources
  • Server GDI functions
  • Sharing source files in multiple projects

Functionality is encapsulated into reusable classes. An overview of each class is listed at the end of the paper to help you identify pieces that can be used in your project. The sample code is available in C# and VB.NET.

Contents

Overview
Sockets
Data Encryption
Storing Settings in a Config File
Accessing the Registry
Signature Custom Control
Server GDI Functions
Sharing Files in Multiple Projects
Sample Code Class Summary

Overview

The sample consists of two projects: 1) the PocketSignature project is the client that runs on the Pocket PC and uses the .NET Compact Framework, and 2) the DesktopSignature project is the server that runs on the desktop and uses the full .NET Framework.

Pocket PC Client

The client collects signature data in a custom control as shown below. The data is encrypted using the CryptEncrypt API function and sent over a TCP socket to the server application.

Figure 1. Signature client running on the Pocket PC

Settings are stored in an XML .config file on the Pocket PC file system. These settings include the server IP address, port number and passphrase.

Figure 2. Settings are stored in an XML .config file

Desktop Server

The server receives the encrypted signature over a socket and decrypts the data using the CryptDecrypt API function. It displays four different views of the signature: signature, points, encrypt and decrypt. The main signature view draws and scales the signature segments to fit the current window size.

Figure 3. Signature is displayed in the server application

The x and y coordinates for each line segment in the signature are displayed in the points view.

Figure 4. Displaying the coordinates of each line segment

The signature data that is received over the TCP socket is displayed in the encrypt view.

Figure 5. Displaying the encrypted signature data

The decrypted data is displayed in the decrypt view. You can see that the encrypted and decrypted data are completely different.

Figure 6. Displaying the decrypted signature data

Sockets

Data is exchanged between the client and server over TCP sockets by using the Net.Sockets.Socket class. The client sends the encrypted signature to the server and the server sends an acknowledgment back to the client that indicates it received the signature.

Client Socket

The client uses asynchronous sockets when communicating with the server; the user interface is not blocked since socket operations occur in separate system supplied threads. The socket code is contained in the sample ClientSocket class and uses the following Socket class methods.

Socket method Comments
BeginConnect Requests a connection to the server.
BeginSend Sends the encrypted signature data to the server.
BeginReceive Receives the acknowledgment from the server.
Shutdown Prepares the socket to be shutdown by sending and receiving any data on the socket.
Close Closes the socket and frees up any underlying resources.

An AsyncCallback delegate is passed to all of the asynchronous socket methods and is invoked when the socket operation completes. The code fragment below shows how the client sends data to the server and notifies the UI when the operation completes.

C#

// hookup the callback method
AsyncCallback sendCallback = new AsyncCallback(SendCallback);

// data is a byte array that contains the signature
socket.BeginSend(data, 0, data.Length,
   SocketFlags.None, sendCallback, true);

// callback method that is called when Send completes
private void SendCallback(IAsyncResult ar)
{
   try
   {
      socket.EndSend(ar);
      RaiseNotifyEvent(NotifyCommand.SentData, null);
   }
   catch (Exception ex)
   {
      // raise error notificiation
      RaiseNotifyEvent(NotifyCommand.Error, ex.Message);
   }
}

VB.NET

' data is a byte array that contains the signature
socket.BeginSend(data, 0, data.Length, _
   SocketFlags.None, AddressOf SendCallback, True)

' callback method that is called when Send completes
Private Sub SendCallback(ar As IAsyncResult)
   Try
      socket.EndSend(ar)
      RaiseNotifyEvent(NotifyCommand.SentData, Nothing)
   Catch ex As Exception
      ' raise error notificiation
      RaiseNotifyEvent(NotifyCommand.SocketError, ex.Message)
   End Try
End Sub

Server Socket

The server receives the signature data from the client. It uses synchronous sockets so it is responsible for creating its own worker thread to process socket operations to prevent the UI from being blocked. The server socket code is contained in the sample ServerSocket class and uses the following methods:

Socket method Comments
Bind Binds the socket to the server’s network address.
Listen Listens for connection attempt from the client.
Accept Accepts the client connection.
Receive Receives encrypted signature data from the client.
Send Sends acknowledgement to the client that the signature was received.
Shutdown Prepares the socket to be shut down by sending and receiving any data on the socket.
Close Closes the socket and releases any underlying resources.

Stream-based TCP Classes

In addition to the Socket class, the .NET Framework contains the classes TcpClient, TcpListener and NetworkStream that exchange data over TCP. These classes sit on top of the Socket class and provide a higher-level interface to send and receive data using streams. The Signature application uses the Socket class for added flexibility, for example, to perform an asynchronous connection, but you might want to look at the TcpClient and TcpListener classes for your TCP-based applications.

Notifying the UI from Socket Worker Threads

Socket operations execute in a separate thread than the user interface. The Signature sample uses an event to notify the user interface when socket operations complete as shown in the diagram below.

Figure 7. Notifying the UI when socket operations complete

The following code fragment shows how the client application hooks up a callback method to handle the Notify event. The first argument to the callback method is the type of notification (connected, sent data, etc.) and the second argument is any related data.

C#

// create socket class
ClientSocket client = new ClientSocket();

// handle the Notify event
client.Notify += new ClientSocket.NotifyEventHandler(NotifyCallback);

// callback that handles the Notify event
private void NotifyCallback(NotifyCommand command, object data)
{
   // process socket notification
}

VB.NET

' create socket class
Dim client As New ClientSocket()

' callback that handles the Notify event
Private Sub NotifyCallback(ByVal command As NotifyCommand, _
   ByVal data As Object) Handles client.Notify
   ' process socket notification
End Sub

Calling Control.Invoke to Update UI Elements

Since the code is currently executing in the socket’s thread, Control.Invoke must be called if user interface elements will be updated. The full framework supports the two Invoke methods listed below. However, the .NET Compact Framework only supports the first Invoke method; it does not support the second method that passes a list of arguments to the delegate.

C#

public object Invoke(Delegate);
public object Invoke(Delegate, object[]);

VB.NET

Public Function Invoke(Delegate) As Object
Public Function Invoke(Delegate, Object()) As Object

So the arguments are saved to class fields and a synchronization object controls access to the block of code that uses the fields. You can use the Threading.Monitor class to synchronize access to a block of code, or use the lock statement in C# or SyncLock statement in VB.NET (they both use the Monitor class internally). The code fragment below shows how the client saves the arguments and synchronizes access to a block of code.

C#

private void NotifyCallback(NotifyCommand command, object data)
{
   // synchronize access to this block of code
   lock(this)
   {
      // save arguments to class fields
      notifyCommand = command;
      notifyData = data;

      // execute the method on the GUI's thread, this method
      // uses the notifyCommand and notifyData fields
      Invoke(processCommand);
   }
}

VB.NET

Private Sub NotifyCallback(ByVal command As NotifyCommand, _
   ByVal data As Object) Handles client.Notify
   SyncLock Me
      ' save arguments to class fields
      notifyCommand = command
      notifyData = data

      ' execute the method on the GUI's thread, this method
      ' uses the notifyCommand and notifyData fields
      Invoke(processCommand)
   End SyncLock
End Sub

Flattening Data

The signature is stored in the sample SignatureData class. Data is sent over the network as a series of bytes but the .NET Compact Framework does not support serialization; so, the SignatureData class must be manually serialized, or flattened, as shown below.

Figure 8. Flattening and unflattening the SignatureData class when sent over sockets

The System.BitConverter class and the Marshal.SizeOf method are used to convert the SignatureData class into one contiguous stream of bytes. The server reconstructs the class when it receives the byte stream over the socket. This code is located in the Network source file and is shared between both projects. The Network.WriteInt32 and Network.WriteString methods are shown below.

C#

static public void WriteInt32(MemoryStream stream, Int32 data)
{
   // write int bytes to stream
   stream.Write(BitConverter.GetBytes(data), 
      0, Marshal.SizeOf(data));
}

static public void WriteString(MemoryStream stream, string data)
{
   // write length of string followed by the string bytes
   WriteInt32(stream, data.Length);
   stream.Write(ASCIIEncoding.ASCII.GetBytes(data), 
      0, data.Length);
}

VB.NET

Public Shared Sub WriteInt32(stream As MemoryStream, data As Int32)
   ' write int bytes to stream
   stream.Write(BitConverter.GetBytes(data), 0, Marshal.SizeOf(data))
End Sub 'WriteInt32

Public Shared Sub WriteString(stream As MemoryStream, data As String)
   ' write length of string followed by the string bytes
   WriteInt32(stream, data.Length)
   stream.Write(ASCIIEncoding.ASCII.GetBytes(data), _
      0, data.Length)
End Sub 'WriteString

Data Encryption

The client application encrypts the signature data before sending it over the network. The .NET Compact Framework does not support the Security.Cryptography namespace so the data is encrypted by calling the crypto API functions directly. All of the crypto functionality is encapsulated in the sample Crypto class that exposes two methods: Encrypt and Decrypt. This allows applications to easily encrypt and decrypt data using the powerful cryptography services without worrying about the details.

C#

static public byte[] Encrypt(string passphrase, byte[] data)
static public byte[] Decrypt(string passphrase, byte[] data)

VB.NET

Public Shared Function Encrypt( _
   passphrase As String, data() As Byte) As Byte()
Public Shared Function Decrypt( _
   passphrase As String, data() As Byte) As Byte()

This Crypto class is shared between the client and server projects. The code fragment below shows how the client uses the Crypto.Encrypt method to encrypt the signature before it’s sent to the server.

C#

// encrypt the data
byte[] encryptData = Crypto.Encrypt(
   Global.Settings.GetString(SettingKeys.CryptPassphrase),
   signature.SignatureBits);

// send to server
client.Send(encryptData);

VB.NET

' encrypt the data
Dim encryptData As Byte() = Crypto.Encrypt( _
   Global.Settings.GetString(SettingKeys.CryptPassphrase), _
   signature.SignatureBits)

' send to server
client.Send(encryptData)

The server decrypts the data by calling the Crypto.Decrypt method.

C#

// decrypt the signature data
byte[] data = Crypto.Decrypt(textPassphrase.Text, encryptData);

VB.NET

' decrypt the signature data
Dim data As Byte() = Crypto.Decrypt(textPassphrase.Text, encryptData)

Passphrase

A crypto key is required to encrypt and decrypt data. The passphrase does not define the strength of encryption; it’s an input that defines how the crypto key is generated. First, a 128-bit hash object is created from the passphrase and then a 40-bit crypto key is generated from the hash. Changing the passphrase dramatically changes the encryption key, but the key strength is always the same (a 40-bit key contains over 1 trillion possible combinations). The diagram below shows how the same passphrase, ultimately the crypto key, is required to encrypt and decrypt the data.

Figure 9. A passphrase is used to generate a crypto key that encrypts and decrypts the data

The sample uses a GUID for the passphrase but it can be any text string. The server displays an error if it cannot decrypt the signature data as shown below.

Figure 10. The correct passphrase is required to decrypt the signature data

Storing Settings in a Config File

The server and client applications save and restore settings but the .NET Compact Framework does not support the Configuration.ConfigurationSettings class. The sample code contains a class called Settings that allows applications to easily read and write settings to standard XML .config files. The class stores settings in a Hashtable object that is persisted to the .config file. The XmlTextReader class is used to read the .config file and populate the hash table. The diagram below shows the pieces involved in the sample Settings class.

Figure 11. The Settings class reads and writes settings to a .config file

The Settings class is used in both the client and server projects. The PocketSignature.exe.config file is shown below.

<configuration>
   <appSettings>
      <add key="IpAddress" value="192.168.0.1" />
      <add key="PortNumber" value="10200" />
      <add key="Passphrase" value=" 212a658c-2edf-46e4-b550-ebe53b91e6b3" />
   </appSettings>
</configuration>

The sample SettingValues source file defines setting keys and default values. The code fragment below shows how the Settings class is used to read the IP address setting.

C#

Settings settings = new Settings(SettingDefaults.Values);
string ip = settings.GetString(SettingKeys.IpAddress);

VB.NET

Dim settings As New Settings(SettingDefaults.Values)
Dim ip As String = settings.GetString(SettingKeys.IpAddress)

Accessing the Registry

The client application determines the IP address of the server (system running ActiveSync) by reading information from the registry. However, the .NET Compact Framework does not support the registry classes so the registry API functions are used directly. The sample Registry class P/Invokes the RegOpenKeyEx, RegQueryValue, and ExRegCloseKey API functions. The following code fragment shows how the class is used to read a value from the registry.

C#

// registry path
const string RegPath = 
   @"Software\Microsoft\Windows CE Services\Partners";

// get current partner setting from the registry
int curPartner = Registry.GetInt(
   Registry.RootKey.LocalMachine, RegPath, "PCur");

VB.NET

' registry path
Const RegPath As String = _
   "Software\Microsoft\Windows CE Services\Partners"

' get current partner setting from the registry
Dim curPartner As Integer = Registry.GetInt( _
   Registry.RootKey.LocalMachine, RegPath, "PCur")

Signature Custom Control

The signature control is a custom control that collects and draws the signature. The class derives from the Windows.Forms.Control class and is contained in the sample SignatureControl class. An overview of the control is shown below.

Figure 12. Custom signature control on the Pocket PC

Coordinates for the signature are collected by overriding the OnMouseDown, OnMouseMove and OnMouseUp methods. The container is notified when a new segment is added to the signature by a custom SignatureUpdate event.

Double-buffering

The signature is drawn in the OnPaint method. The SignatureControl class implements double-buffering, since it’s not supported by the .NET Compact Framework. This is accomplished by doing the following:

  • Override the OnPaintBackground method to prevent the control from being erased (prevents flickering).
  • Perform all drawing on a memory bitmap instead of the PaintEventArgs.Graphics object that is passed into the OnPaint method.
  • Use the Graphics.DrawImage method to draw the memory bitmap to the screen with one call in the OnPaint method.

For better performance, the memory bitmap and other GDI objects are created once instead of each time in the paint event.

Using Bitmap Resources

Bitmap resources are images that are embedded in the executable file. The control loads bitmap resources with the following helper function located in the sample Global class.

C#

static public Bitmap LoadImage(string imageName)
{
   return new Bitmap(Assembly.GetExecutingAssembly().
      GetManifestResourceStream("PocketSignature.Images." + imageName));
}

VB.NET

Public Shared Function LoadImage(imageName As String) As Bitmap
   Return New Bitmap(System.Reflection.Assembly.GetExecutingAssembly(). _
      GetManifestResourceStream("PocketSignature." + imageName))
End Function

The Build Action of the image must be set to Embedded Resource as shown below so the compiler knows that the image should be embedded in the executable file.

Figure 13. Setting the Build Action for bitmap resources

Server GDI Functions

The server takes advantage of GDI features that are not available on the Pocket PC when drawing the signature.

  • Smoothes the line segments by setting the Graphics.SmoothingMode property to AntiAlias.
  • Uses the Drawing.Drawing2D.Matrix class to scale the signature to fit the current window size.
  • Provides the option to display the signature as straight lines (Graphics.DrawLines) or cardinal splines (Graphics.DrawCurve).

The code fragment below shows how the server uses these GDI features.

C#

// setup drawing surface
g.SmoothingMode = SmoothingMode.AntiAlias;

// scale the signature
Matrix matrix = new Matrix();
matrix.Scale(
   (float)pictSignature.Width / (float)signature.Width,
   (float)pictSignature.Height / (float)signature.Height);
g.Transform = matrix; 

// draw the signature segments
if (checkSmooth.Checked)
   g.DrawCurve(Pens.Firebrick, line, 0.5F);
else
   g.DrawLines(Pens.Firebrick, line);   

VB.NET

' setup drawing surface
g.SmoothingMode = SmoothingMode.AntiAlias

' scale the signature
Dim matrix As New Matrix()
matrix.Scale( _
   CSng(pictSignature.Width) / CSng(signature.Width), _
   CSng(pictSignature.Height) / CSng(signature.Height))
g.Transform = matrix

' draw the signature segments
If checkSmooth.Checked Then
   g.DrawCurve(Pens.Firebrick, line, 0.5F)
Else
   g.DrawLines(Pens.Firebrick, line)
End If

Sharing Files in Multiple Projects

You can share source files between multiple projects by linking to existing files as shown below.

Figure 14. Link to files to share source files in multiple projects

The PocketSignature and DesktopSignature sample projects share the source files Crypto, Network and Settings. Compiler directives are used when different execution paths are required for different projects. For example, the following code fragment shows how a compiler directive compiles different code for the Pocket PC and desktop projects.

C#

// specify what DLL contains the crypto functions
#if COMPACT_FRAMEWORK
   const string CryptDll = "coredll.dll";
#else
   const string CryptDll = "advapi32.dll";
#endif

[DllImport(CryptDll)] 
public static extern bool CryptAcquireContext(
   ref IntPtr phProv, string pszContainer, string pszProvider,
   uint dwProvType, uint dwFlags);

// append .config to the executable file
private string GetFilePath()
{
   #if COMPACT_FRAMEWORK
      return System.Reflection.Assembly.GetExecutingAssembly().
         GetName().CodeBase + ".config";
   #else
      return System.Windows.Forms.Application.
         ExecutablePath + ".config";
   #endif
}

VB.NET

#If COMPACT_FRAMEWORK Then
   Const CryptDll As String = "coredll.dll"
#Else
   Const CryptDll As String = "advapi32.dll"
#End If

Public Shared<DllImport(CryptDll)>  _
Function CryptAcquireContext( _
   ByRef phProv As IntPtr, pszContainer As String, _
   pszProvider As String, dwProvType As Integer, _
   dwFlags As Integer) As Boolean

' append .config to the executable file
Private Function GetFilePath() As String
#If COMPACT_FRAMEWORK Then
   Return System.Reflection.Assembly.GetExecutingAssembly(). _
      GetName().CodeBase + ".config"
#Else
   Return System.Windows.Forms.Application. _
      ExecutablePath + ".config"
#End If
End Function

Sample Code Class Summary

You can take bits and pieces out of this sample for your project. The following table provides an overview of the classes and how they might be used in your project.

Sample class Comments
Crypto Standalone class.
Encrypts and decrypts data by P/Invoking crypto API functions. Can be used in Pocket PC and desktop projects. A passphrase is used to generate the crypto key. You can modify the class to use different hash and encryption algorithms.
Settings Standalone class.
Reads and writes settings to a standard XML .config file. Can be used in Pocket PC and desktop projects. The SettingValues file (specifies setting keys and default values) can be used in conjunction with the Settings class, but is not required.
Registry Standalone class.
Reads values from the registry. You can extend this class to perform other registry actions like creating keys (RegCreateKeyEx), writing values (RegSetValueEx), etc.
SignatureControl Standalone class or modify for your application.
A custom control that collects signature coordinates by handling mouse events. Performs double-buffering to prevent control flickering. Raises an event to notify the container that the signature had been updated.
ClientSocket Standalone or modify for your application.
Connects, sends and receives data to the server over TCP sockets. Raises an event when socket operations complete.
ServerSocket Standalone or modify for your application.
Connects and receives data from the client socket. Raises an event when socket operations occur.
Signature / Network Modify for your application.
Demonstrates how to flatten and unflatten an object to a stream of bytes so it can be sent over a socket. You can also P/Invoke LocalAlloc and LocalFree if you have a class that contains all value types.
MainForm Usage example.
Uses the ClientSocket class to send data over TCP sockets and hookup callback methods to be notified when socket events complete. Updates UI elements from worker threads using Control.Invoke. Uses the custom SignatureControl class and handles the update event.
OptionsForm Usage example.
Displays version information from the assembly. Uses the Settings class to read and write values to a .config file. Uses the Registry class to get the IP address of the system that is running ActiveSync.
Global Usage example.
Creates and initializes a Settings object. Loads bitmap resources.
Show:
© 2014 Microsoft