Pocket PC 签名应用程序示例






4.84/5 (28投票s)
Pocket PC 签名应用程序示例
本文由 MSDN 提供。
摘要
本文讨论 Pocket PC 签名示例应用程序。该示例包含一个在 Pocket PC 上运行的客户端,该客户端通过 TCP 套接字将签名数据发送到在桌面计算机上运行的服务器。数据使用加密服务进行加密和解密。(23 页)
本文涵盖以下主题
- 通过 TCP 套接字传输数据
- 从套接字工作线程通知 UI
- 调用
Control.Invoke
更新 UI 元素 - 将数据“展平”以便通过套接字发送
- 使用加密 API 函数加密和解密数据
- 将设置存储在 XML .config 文件中
- 访问注册表
- 自定义控件双缓冲
- 显示位图资源
- 服务器 GDI 函数
- 在多个项目中共享源文件
功能被封装到可重用类中。本文末尾列出了每个类的概述,以帮助您识别可以用于您项目中的部分。示例代码有 C# 和 VB.NET 版本。
目录
概述
该示例包含两个项目
- PocketSignature 项目是运行在 Pocket PC 上的客户端,使用了 .NET Compact Framework;
- DesktopSignature 项目是运行在桌面计算机上的服务器,使用了完整的 .NET Framework。
Pocket PC 客户端
客户端在自定义控件中收集签名数据,如下图所示。使用 CryptEncrypt
API 函数加密数据,并通过 TCP 套接字发送到服务器应用程序。
图 1. 运行在 Pocket PC 上的签名客户端
设置存储在 Pocket PC 文件系统上的 XML .config 文件中。这些设置包括服务器 IP 地址、端口号和密码。
图 2. 设置存储在 XML .config 文件中
桌面服务器
服务器通过套接字接收加密的签名,并使用 CryptDecrypt
API 函数解密数据。它显示了签名的四种不同视图:签名、点、加密和解密。主签名视图绘制和缩放签名段以适应当前窗口大小。
图 3. 签名显示在服务器应用程序中
签名中每条线段的 x 和 y 坐标显示在“点”视图中。
图 4. 显示每条线段的坐标
通过 TCP 套接字接收的签名数据显示在“加密”视图中。
图 5. 显示加密的签名数据
解密后的数据显示在“解密”视图中。您可以看到加密和解密后的数据完全不同。
图 6. 显示解密后的签名数据
套接字 (Sockets)
数据通过 TCP 套接字使用 Net.Sockets.Socket
类在客户端和服务器之间交换。客户端发送加密的签名到服务器,服务器发送一个确认消息给客户端,表示它已收到签名。
客户端套接字
客户端在与服务器通信时使用异步套接字;用户界面不会被阻塞,因为套接字操作在单独的系统提供的线程中进行。套接字代码包含在示例 ClientSocket
类中,并使用以下 Socket 类方法。
套接字方法 | 注释 |
---|---|
BeginConnect |
请求连接到服务器。 |
BeginSend |
将加密的签名数据发送到服务器。 |
BeginReceive |
从服务器接收确认消息。 |
关机 |
准备关闭套接字,通过发送和接收套接字上的任何数据。 |
Close |
关闭套接字并释放任何底层资源。 |
一个 AsyncCallback
委托被传递给所有异步套接字方法,并在套接字操作完成时调用。下面的代码片段展示了客户端如何发送数据到服务器并在操作完成后通知 UI。
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
服务器套接字
服务器从客户端接收签名数据。它使用同步套接字,因此它负责创建自己的工作线程来处理套接字操作,以防止 UI 被阻塞。服务器套接字代码包含在示例 ServerSocket
类中,并使用以下方法
套接字方法 | 注释 |
---|---|
Bind |
将套接字绑定到服务器的网络地址。 |
Listen |
侦听来自客户端的连接尝试。 |
接受 |
接受客户端连接。 |
Receive |
从客户端接收加密的签名数据。 |
发送 |
向客户端发送确认消息,表明签名已收到。 |
关机 |
准备关闭套接字,通过发送和接收套接字上的任何数据。 |
Close |
关闭套接字并释放任何底层资源。 |
基于流的 TCP 类
除了 Socket 类之外,.NET Framework 还包含 TcpClient
、TcpListener
和 NetworkStream
类,它们通过 TCP 交换数据。这些类建立在 Socket 类之上,并提供了一个更高级别的接口,用于使用流发送和接收数据。Signature 应用程序使用 Socket
类以获得更大的灵活性,例如执行异步连接,但您可能需要查看 TcpClient
和 TcpListener
类用于您的基于 TCP 的应用程序。
从套接字工作线程通知 UI
套接字操作在与用户界面不同的线程中执行。Signature 示例使用一个事件来在套接字操作完成时通知用户界面,如下图所示。
图 7. 套接字操作完成时通知 UI
以下代码片段展示了客户端应用程序如何挂钩回调方法来处理 Notify
事件。回调方法的第一个参数是通知的类型(已连接、已发送数据等),第二个参数是任何相关数据。
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
调用 Control.Invoke 更新 UI 元素
由于代码当前在套接字线程中执行,因此如果将更新用户界面元素,则必须调用 Control.Invoke
。完整的框架支持下面列出的两个 Invoke 方法。但是,.NET Compact Framework 只支持第一个 Invoke 方法;它不支持第二个方法,该方法将参数列表传递给委托。
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
因此,参数被保存到类字段中,一个同步对象控制对使用这些字段的代码块的访问。您可以使用 Threading.Monitor
类来同步对代码块的访问,或者在 C# 中使用 lock
语句,在 VB.NET 中使用 SyncLock
语句(它们都在内部使用 Monitor
类)。下面的代码片段展示了客户端如何保存参数并同步对代码块的访问。
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
展平数据
签名存储在示例 SignatureData
类中。数据以字节序列的形式通过网络发送,但 .NET Compact Framework 不支持序列化;因此,必须手动序列化 SignatureData
类,或将其“展平”,如下图所示。
图 8. 通过套接字发送时展平与反展平 SignatureData 类
System.BitConverter
类和 Marshal.SizeOf
方法用于将 SignatureData
类转换为一个连续的字节流。服务器在接收到套接字上的字节流时重建类。此代码位于 Network 源文件中,并在两个项目中共享。下面显示了 Network.WriteInt32
和 Network.WriteString
方法。
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
数据加密
客户端应用程序在发送签名数据之前对其进行加密。.NET Compact Framework 不支持 Security.Cryptography
命名空间,因此通过直接调用加密 API 函数来加密数据。所有加密功能都封装在示例 Crypto
类中,该类公开了两个方法:Encrypt
和 Decrypt
。这使得应用程序无需担心细节即可轻松地使用强大的加密服务加密和解密数据。
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()
此 Crypto 类在客户端和服务器项目之间共享。下面的代码片段展示了客户端如何使用 Crypto.Encrypt
方法在发送到服务器之前加密签名。
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)
服务器通过调用 Crypto.Decrypt
方法来解密数据。
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)
密码
加密和解密数据需要加密密钥。密码不定义加密的强度;它是一个输入,定义了如何生成加密密钥。首先,从密码创建一个 128 位哈希对象,然后从哈希值生成一个 40 位加密密钥。更改密码会极大地改变加密密钥,但密钥强度始终相同(40 位密钥包含超过一万亿种可能的组合)。下图显示了如何需要相同的密码,最终是加密密钥,来加密和解密数据。
图 9. 使用密码生成用于加密和解密数据的加密密钥
该示例使用 GUID 作为密码,但它可以是任何文本字符串。如果服务器无法解密签名数据,则会显示错误,如下图所示。
图 10. 解密签名数据需要正确的密码
将设置存储在配置文件中
服务器和客户端应用程序保存和恢复设置,但 .NET Compact Framework 不支持 Configuration.ConfigurationSettings
类。示例代码包含一个名为 Settings
的类,该类允许应用程序轻松地读取和写入标准 XML .config 文件中的设置。该类将设置存储在持久化到 .config 文件的 Hashtable
对象中。XmlTextReader
类用于读取 .config 文件并填充哈希表。下图显示了示例 Settings 类涉及的各个部分。
图 11. Settings 类读写 .config 文件中的设置
Settings 类在客户端和服务器项目中使用。下面显示了 PocketSignature.exe.config 文件。
<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>
示例 SettingValues
源文件定义了设置键和默认值。下面的代码片段展示了如何使用 Settings 类读取 IP 地址设置。
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)
访问注册表
客户端应用程序通过读取 ActiveSync 运行系统的信息来确定服务器的 IP 地址。但是,.NET Compact Framework 不支持注册表类,因此直接使用注册表 API 函数。示例 Registry
类通过 P/Invoke 调用 RegOpenKeyEx
、RegQueryValue
和 ExRegCloseKey
API 函数。以下代码片段展示了该类如何用于从注册表中读取值。
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")
签名自定义控件
签名控件是一个自定义控件,用于收集和绘制签名。该类派生自 Windows.Forms.Control
类,包含在示例 SignatureControl
类中。下面显示了控件的概述。
图 12. Pocket PC 上的自定义签名控件
通过重写 OnMouseDown
、OnMouseMove
和 OnMouseUp
方法来收集签名的坐标。通过自定义 SignatureUpdate
事件,在向容器添加新签名段时通知容器。
双缓冲
签名在 OnPaint
方法中绘制。SignatureControl
类实现了双缓冲,因为 .NET Compact Framework 不支持双缓冲。这通过以下方式实现:
- 重写
OnPaintBackground
方法以防止控件被擦除(防止闪烁)。 - 在内存位图上执行所有绘图,而不是在传递到
OnPaint
方法的PaintEventArgs.Graphics
对象上。 - 使用
Graphics.DrawImage
方法一次将内存位图绘制到屏幕上,该方法在OnPaint
方法中调用一次。
为了获得更好的性能,内存位图和其他 GDI 对象只创建一次,而不是在每次绘制事件时都创建。
使用位图资源
位图资源是嵌入在可执行文件中的图像。控件使用位于示例 Global
类中的以下辅助函数加载位图资源。
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
图像的 **生成操作** 必须设置为 **嵌入资源**,如下图所示,这样编译器就知道该图像应该嵌入到可执行文件中。
图 13. 设置位图资源的生成操作
服务器 GDI 函数
服务器在绘制签名时利用了 Pocket PC 上不可用的 GDI 功能。
- 通过将
Graphics.SmoothingMode
属性设置为AntiAlias
来平滑线段。 - 使用
Drawing.Drawing2D.Matrix
类将签名缩放到适应当前窗口大小。 - 提供将签名显示为直线(
Graphics.DrawLines
)或卡迪斯样条(Graphics.DrawCurve
)的选项。
下面的代码片段显示了服务器如何使用这些 GDI 功能。
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
在多个项目中共享文件
您可以通过链接到现有文件来在多个项目中共享源文件,如下图所示。
图 14. 链接文件以在多个项目中共享源文件
PocketSignature 和 DesktopSignature 示例项目共享 Crypto、Network 和 Settings 源文件。当不同项目需要不同的执行路径时,使用编译器指令。例如,下面的代码片段展示了编译器指令如何为 Pocket PC 和桌面项目编译不同的代码。
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
示例代码类摘要
您可以从此示例中提取部分内容用于您的项目。下表提供了类及其在您的项目中可能如何使用的概述。
示例类 | 注释 |
---|---|
Crypto |
独立类。 通过 P/Invoke 调用加密 API 函数来加密和解密数据。可用于 Pocket PC 和桌面项目。使用密码生成加密密钥。您可以修改此类以使用不同的哈希和加密算法。 |
设置 |
独立类。 将设置读写到标准 XML .config 文件。可用于 Pocket PC 和桌面项目。SettingValues 文件(指定设置键和默认值)可与 Settings 类结合使用,但非必需。 |
注册表 |
独立类。 从注册表中读取值。您可以扩展此类以执行其他注册表操作,例如创建键( RegCreateKeyEx )、写入值(RegSetValueEx )等。 |
SignatureControl |
独立类或修改以适应您的应用程序。 一个自定义控件,通过处理鼠标事件来收集签名坐标。执行双缓冲以防止控件闪烁。引发一个事件来通知容器签名已更新。 |
ClientSocket |
独立或修改以适应您的应用程序。 通过 TCP 套接字连接、发送和接收数据到服务器。在套接字操作完成时引发事件。 |
ServerSocket |
独立或修改以适应您的应用程序。 连接并接收来自客户端套接字的数据。在套接字操作发生时引发事件。 |
Signature / Network |
修改以适应您的应用程序。 演示如何将对象“展平”并“反展平”为字节流,以便通过套接字发送。如果您的类包含所有值类型,您还可以 P/Invoke LocalAlloc 和 LocalFree 。 |
MainForm |
用法示例。 使用 ClientSocket 类通过 TCP 套接字发送数据,并挂钩回调方法以在套接字事件完成时获得通知。使用 Control.Invoke 从工作线程更新 UI 元素。使用自定义 SignatureControl 类并处理更新事件。 |
OptionsForm |
用法示例。 显示程序集中的版本信息。使用 Settings 类读写 .config 文件中的值。使用 Registry 类获取运行 ActiveSync 的系统的 IP 地址。 |
全局 |
用法示例。 创建并初始化 Settings 对象。加载位图资源。 |