This article has been provided courtesy of MSDN.
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)
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
The sample consists of two projects:
- The PocketSignature project is the client that runs on the Pocket PC and uses the .NET Compact Framework, and
- 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
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#
AsyncCallback sendCallback = new AsyncCallback(SendCallback);
socket.BeginSend(data, 0, data.Length,
SocketFlags.None, sendCallback, true);
private void SendCallback(IAsyncResult ar)
{
try
{
socket.EndSend(ar);
RaiseNotifyEvent(NotifyCommand.SentData, null);
}
catch (Exception ex)
{
RaiseNotifyEvent(NotifyCommand.Error, ex.Message);
}
}
VB.NET
socket.BeginSend(data, 0, data.Length, _
SocketFlags.None, AddressOf SendCallback, True)
Private Sub SendCallback(ar As IAsyncResult)
Try
socket.EndSend(ar)
RaiseNotifyEvent(NotifyCommand.SentData, Nothing)
Catch ex As Exception
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#
ClientSocket client = new ClientSocket();
client.Notify += new ClientSocket.NotifyEventHandler(NotifyCallback);
private void NotifyCallback(NotifyCommand command, object data)
{
}
VB.NET
Dim client As New ClientSocket()
Private Sub NotifyCallback(ByVal command As NotifyCommand, _
ByVal data As Object) Handles client.Notify
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)
{
lock(this)
{
notifyCommand = command;
notifyData = data;
Invoke(processCommand);
}
}
VB.NET
Private Sub NotifyCallback(ByVal command As NotifyCommand, _
ByVal data As Object) Handles client.Notify
SyncLock Me
notifyCommand = command
notifyData = data
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)
{
stream.Write(BitConverter.GetBytes(data),
0, Marshal.SizeOf(data));
}
static public void WriteString(MemoryStream stream, string data)
{
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)
stream.Write(BitConverter.GetBytes(data), 0, Marshal.SizeOf(data))
End Sub
Public Shared Sub WriteString(stream As MemoryStream, data As String)
WriteInt32(stream, data.Length)
stream.Write(ASCIIEncoding.ASCII.GetBytes(data), _
0, data.Length)
End Sub
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#
byte[] encryptData = Crypto.Encrypt(
Global.Settings.GetString(SettingKeys.CryptPassphrase),
signature.SignatureBits);
client.Send(encryptData);
VB.NET
Dim encryptData As Byte() = Crypto.Encrypt( _
Global.Settings.GetString(SettingKeys.CryptPassphrase), _
signature.SignatureBits)
client.Send(encryptData)
The server decrypts the data by calling the Crypto.Decrypt
method.
C#
byte[] data = Crypto.Decrypt(textPassphrase.Text, encryptData);
VB.NET
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
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)
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#
const string RegPath =
@"Software\Microsoft\Windows CE Services\Partners";
int curPartner = Registry.GetInt(
Registry.RootKey.LocalMachine, RegPath, "PCur");
VB.NET
Const RegPath As String = _
"Software\Microsoft\Windows CE Services\Partners"
Dim curPartner As Integer = Registry.GetInt( _
Registry.RootKey.LocalMachine, RegPath, "PCur")
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
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#
g.SmoothingMode = SmoothingMode.AntiAlias;
Matrix matrix = new Matrix();
matrix.Scale(
(float)pictSignature.Width / (float)signature.Width,
(float)pictSignature.Height / (float)signature.Height);
g.Transform = matrix;
if (checkSmooth.Checked)
g.DrawCurve(Pens.Firebrick, line, 0.5F);
else
g.DrawLines(Pens.Firebrick, line);
VB.NET
g.SmoothingMode = SmoothingMode.AntiAlias
Dim matrix As New Matrix()
matrix.Scale( _
CSng(pictSignature.Width) / CSng(signature.Width), _
CSng(pictSignature.Height) / CSng(signature.Height))
g.Transform = matrix
If checkSmooth.Checked Then
g.DrawCurve(Pens.Firebrick, line, 0.5F)
Else
g.DrawLines(Pens.Firebrick, line)
End If
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#
#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);
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
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
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. |
Links