Reed Robison
Microsoft
适用于:
Windows Mobile™ 2003 software for Smartphones
Windows Mobile 2003 software for Pocket PC Phone Editions
Windows Mobile 2003 Second Edition software for Pocket PC
Windows Mobile 2003 Second Edition software for Smartphones
Microsoft® Visual Studio® .NET 2003
Microsoft eMbedded Visual C++® 4.0 SP3(或更新版本)
Microsoft ActiveSync®
摘要:Smartphone 2003 和 Pocket PC 2003 SDK 包含带有虚拟无线电支持的模拟环境。在本文中我们利用仿真程序的虚拟无线电创建诸如传入的 SMS 消息和电话呼叫的事件。这是一种有效的方法,它可以在不使用实际设备的情况下帮助我们开发响应这些事件的应用程序。
从 Microsoft 下载中心下载 CallEvents.msi。
| 概述 | |
| 要求 | |
| 虚拟无线电接口层 | |
| 发送 SMS 消息 | |
| 截获 SMS 消息 | |
| 创建电话呼叫事件 | |
| 从桌面发送事件 | |
| 小结 | |
| 附录 |
Windows Mobile™ 2003 开发平台有一个强大的新功能,它能够以编程方式截获 SMS 消息。SMS 消息是一种简单易行的方法,它可以为各种各样的事件提供通知和创建自定义操作。遗憾的是,不是每个人都有多余的设备和数据计划让他们的开发人员来体验 SMS。由于一些新的 2003 仿真程序包含 Virtual Radio(虚拟无线电),这就避免了“如何才能将 SMS 消息发送到我的模拟环境中?”的问题。这是一个很好的问题,但直到现在还没有一个比较满意的答案。
Windows Mobile 小组已经封装了仿真程序的虚拟无线电接口层。在本文中,我们会利用该库将 SMS 消息发送给我们的模拟环境并实现一些其他的有用技巧。
本文包含针对 Smartphone 2003 和 Pocket PC Phone Edition 2003 的托管和非托管源代码示例 (SendSMS)。对于用 C++ 编写的非托管示例,您需要 Microsoft® eMbedded Visual C++® 4.0 SP2。对于托管 C# 示例,您需要 Microsoft Visual Studio® .NET 2003。在这两种情况下,您都必须安装 2003 平台的 SDK。可以从 Microsoft 下载中心下载到大部分工具和 SDK:
| • | |
| • | |
| • | |
| • |
Smartphone 仿真程序安全
Smartphone 仿真程序中的虚拟无线电 API 要求特权访问。在 Smartphone 仿真程序上运行这些示例以前,您必须用仿真程序认可的特权证书来对其进行签名。Pocket PC 不要求以下步骤。
要了解更多有关基于 Windows Mobile 的 Smartphone 应用程序的安全模型,请参阅 A Practical Guide to the Smartphone Application Security and Code Signing Model for Developers。
有关配置您的开发环境以签名 FakeRIL 的组件的指导信息,请参考附录。
虚拟无线电接口层已经封装到 FRilDLL.dll 中,该文件包含在本文的下载示例中。非托管示例要求设备中必须有 FRilDll.dll,这样可以保证在尝试运行 SendSMS 示例之前完全复制该文件(使用远程文件查看器)。对于托管示例,该文件包含在项目中并自动与可执行文件一起部署。
就本文而言,我们将考察 FrilDll 导出的三个函数:
// Init() initializes the fake radio for use void Init(); // ReceiveSMS simulates receiving an SMS message void ReceiveSMS(LPTSTR pszMessage, LPTSTR pszPhoneNumber); // ReceiveCall simulates receiving an incoming phone call void ReceiveCall(LPTSTR pszPhoneNumber, DWORD dwAddressType, DWORD dwRestriction, DWORD dwDuration);
为了利用这些 API,我们只需要将 FRilDll 加载到我们的进程中,先调用 Init() 函数,然后调用 ReceiveSMS 或 ReceiveCall。
要用 C++ 动态加载该库,我们可以使用 LoadLibrary API:
typedef int (STDAPICALLTYPE *PReceiveSMS)(LPTSTR , LPTSTR );
PReceiveSMS lpReceiveSMS;
// Dynamically load and initialize fake radio interface layer
HINSTANCE hFRIL = LoadLibrary(_T("FRilDll.dll"));
if (hFRIL)
lpReceiveSMS = (PReceiveSMS)GetProcAddress(hFRIL,_T("ReceiveSMS"));
// … Do something impressive!
if (hFRIL)
FreeLibrary(hFRIL);
对于 C#,我们将使用 P/Invoke 来调用本机代码,因此可以添加 System.Runtime.InteropServices,然后使用 DllImport 来简单地将引用添加到导出中:
using System.Runtime.InteropServices;
[DllImport("FRilDll.dll", EntryPoint="Init", SetLastError=true)]
private extern static void FakeRILInit();
[DllImport("FRilDll.dll", EntryPoint="ReceiveSMS", SetLastError=true, CharSet=CharSet.Unicode)]
private extern static void ReceiveSMS(string Message, string PhoneNumber);
[DllImport("FRilDll.dll", EntryPoint="ReceiveCall", SetLastError=true, CharSet=CharSet.Unicode)]
private extern static void ReceiveCall(string PhoneNumber,int AddressType,int Restriction,int Duration);
注 如果还没有初始化无线电,则第一次调用 ReceiveSMS 时会自动调用 Init()。
一旦已加载并初始化虚拟无线电,那么您只需调用 ReceiveSMS 即可。
在 C++ 中它如下所示:
lpReceiveSMS(szMsg, szFrom);
在 C# 中它仅仅如此:
ReceiveSMS(Message, PhoneNumber);
图 1 显示发送 SMS 消息的屏幕,图 2 显示接收 SMS 消息的屏幕。

图 1. 将 SMS 消息发送到仿真程序

图 2. 在仿真程序上接收 SMS 消息
既然您可以发送 SMS 消息,那么让我们来试着截获一个消息。加载 MAPIRule 示例(包含在 2003 SDK Win32 Samples 目录中)。该示例说明如何实现一个 MAPI Rule Client,它是一个可以处理/筛选传入 MAPI 消息的 COM 对象。在默认情况下,SMS 是作为 Rule Client 的传输工具支持的,设计该示例的目的在于截获内容包含“zzz”的任何消息。
构建该示例,然后给自己发送一条主题包含“zzz”的消息。注意如何截获该消息以及如何在接收端显示 MessageBox,如图 3 所示。
注 MAPIRule 示例要求特权访问。

图 3. 截获 SMS 消息
SendCallEvents 是另一个示例,它说明如何使用虚拟无线电接口在仿真程序上生成 SMS 消息和电话呼叫事件。我们可以简单地使用前面部分讨论的 API,并从仿真程序的应用程序中直接调用它们,但是我们为什么不开心点同时学习一些新东西呢?
让我们将事件从您的桌面发送到仿真程序,如图 4 所示!

图 4. 桌面呼叫事件应用程序
为了达到该目的,SendCallEvents 使用一些容易得到的库(作为 Visual Studio.NET 2003 开发环境的组件附带的)。有了 DeviceConnectivity 命名空间的帮助,我们就能够将文件放入仿真程序中,启动我们的辅助进程 (FrilStub.exe),让它来处理虚拟无线电呼叫。
第一步是将项目引用添加到适当的库 — ConMan.dll、ConManServer.dll 和 ConManDataStore.dll。通常这些文件位于 Microsoft Visual Studio .NET 2003\CompactFrameworkSDK\ConnectionManager\Bin 目录中。一旦您将引用添加到项目中,那么只包括 DeviceConnectivity 命名空间即可。我们创建一个简单的类和几个主要的方法来完成大部分工作:
using Microsoft.VisualStudio.DeviceConnectivity;
namespace Microsoft.VisualStudio.DeviceConnectivity
{
public class RemoteToolHelper
{
private Microsoft.VisualStudio.DeviceConnectivity.ConMan m_conman = null;
private IServer m_deviceAPI = null;
private IConManDataStore m_ds = null;
private IPlatform m_platform = null;
private IDevice m_device = null;
public RemoteToolHelper() {}
// Setup a connection to the device/emulator
public IServer ConnectToDevice(String platformName, String deviceName)
{
m_conman = new Microsoft.VisualStudio.DeviceConnectivity.ConMan();
m_conman.SetLocale(1033); // English
m_conman.Initialize();
m_ds = m_conman.DataStore;
m_platform= m_ds.Platforms.FindPlatformByInvariantName(platformName);
m_device = m_platform.FindDeviceByInvariantName(deviceName);
m_deviceAPI = (IServer)(m_conman.Connect(m_device));
if (!m_deviceAPI.EstablishConnection())
{
throw new Exception("Could not connect to - " + deviceName + ". Please retry and reset the device if the problem pesists");
}
return m_deviceAPI;
}
// Deploy a file to the connected device
public void DeployRuntimePieceToDevice(String remoteDirectory, String fileName)
{
String [] aszTemp = new String[1];
aszTemp[0] = fileName;
DeployRuntimePieceToDevice(remoteDirectory, aszTemp);
}
// Deploy files to the connected device
public void DeployRuntimePieceToDevice(String remoteDirectory, String [] fileNames)
{
bool bPiecesExist = true;
if (m_deviceAPI == null)
{
throw new Exception("You must connect to the device before deploying files, call ConnectToDevice first");
}
for(int i = 0; i < fileNames.Length; i++) // Check to see if files already exist
{
bPiecesExist &= m_deviceAPI.FileExists(remoteDirectory + "\\" + fileNames[i]);
}
//Deploy the files
if (!bPiecesExist)
{
//Create the directory, if it's not there the file copy fails
try
{
m_deviceAPI.CreateDirectory(remoteDirectory);
}
catch {}
//Copy the files
try
{
String localPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
localPath = localPath.Substring(0, localPath.LastIndexOf("\\"));
for(int i = 0; i < fileNames.Length; i++)
{
m_deviceAPI.SendFile(localPath + "\\" + fileNames[i], remoteDirectory + "\\" + fileNames[i], QUEUING_ACTION.CREATE, CUSTOM_ATTRIBUTE.None,null);
}
}
catch
{
throw new Exception("Could not deploy the device components of the tool, please make sure the files are available and not already in-use.");
}
}
}
public TcpClient StartProgramAndConnect(String path, String program)
{
//Get Desktop IP Address to pass to the emulator
System.Net.IPHostEntry iphost = System.Net.Dns.GetHostByName(System.Net.Dns.GetHostName());
if (iphost.AddressList.Length < 1)
{
throw new Exception("Need an IP address to conenct to the emulator");
}
System.Net.IPAddress ipAddress = System.Net.Dns.Resolve(System.Net.Dns.GetHostName()).AddressList[0];
//Start the program on the emulator and pass the Desktop IP as a command line param
m_deviceAPI.StartProcess( path + "\\" + program, ipAddress.ToString());
//Start a listener and wait for the emulator process to connect
TcpListener tcpListener = new TcpListener(ipAddress, 8001);
tcpListener.Start();
return tcpListener.AcceptTcpClient();
}
}
}
存在一个支持 RemoteToolHelp 类,我们现在只需要连接到设备上,然后发送一些指令给我们的远程处理:
// Connect to an emulator and setup the communication stream
private void btnConnect_Click(object sender, System.EventArgs e)
{
//Show the wait cursor
this.stpConnected.Text = "Connecting";
Cursor currentCursor = this.Cursor;
this.Cursor = Cursors.WaitCursor;
//Connect to the device and copy supporting files over
try
{
string selectedPlatform = cmbDevices.Items[cmbDevices.SelectedIndex].ToString();
String [] aszFiles = new String[2];
String szDestDir;
if ( selectedPlatform.IndexOf("Smartphone") >= 0 )
{
m_remoteHelper.ConnectToDevice("Smartphone", "Smartphone 2003 Emulator (Virtual Radio)");
szDestDir = "\\Storage\\FrilStub";
}
else
{
m_remoteHelper.ConnectToDevice("Pocket PC", "Pocket PC 2003 Phone Edition (Virtual Radio) Emulator");
szDestDir = "\\Program Files\\FrilStub";
}
aszFiles[0] = "frilstub.exe"; // Emulator stub program
aszFiles[1] = "frildll.dll"; // Fake radio interface layer
Application.DoEvents();
m_remoteHelper.DeployRuntimePieceToDevice(szDestDir, aszFiles);
Application.DoEvents();
m_deviceTcpClient = m_remoteHelper.StartProgramAndConnect(szDestDir, "frilstub.exe");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
this.Cursor = currentCursor;
return;
}
// Enable/Disable applicable controls in the UI
this.Cursor = currentCursor;
btnSendMessage.Enabled = true;
btnCall.Enabled = true;
btnConnect.Enabled = false;
cmbDevices.Enabled = false;
this.stpConnected.Text = "Connected";
this.stpConnectedIcon.Icon = this.m_icConnected;
this.Cursor = currentCursor;
}
为了将呼叫事件发送到远程存根处理,我们简单地通过部署到仿真程序之后建立的 TcpClient 流发送一个呼叫(或 SMS)消息和一些参数。
// Send Incoming Call Event to emulator
private void btnCall_Click(object sender, System.EventArgs e)
{
try
{
Int32.Parse(this.txtDuration.Text);
}
catch
{
MessageBox.Show("Call duration must be a valid integer", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
try
{
// Get the reader/writer stream to the emulator
ParamReader reader = new ParamReader(m_deviceTcpClient.GetStream());
ParamWriter writer = new ParamWriter(m_deviceTcpClient.GetStream());
// Write the event information to the stream
writer.WriteInt((int)(RILFunctionCall.ReceiveCall));
writer.WriteString(this.txtCallNumber.Text);
writer.WriteInt(this.cmbAddressType.SelectedIndex);
writer.WriteInt(this.cmbRestriction.SelectedIndex);
writer.WriteInt(Int32.Parse(this.txtDuration.Text));
// Get the result
if (reader.ReadInt() > 0)
this.stpMessageBar.Text = "Voice Call Initiated";
else
this.stpMessageBar.Text = "Voice Call Failed";
}
catch
{
MessageBox.Show("Communication with the emulator has failed. If you have closed the emulator, please restart this utility and re-connect.");
}
}
现在让我们来看看在仿真程序上将要发生什么事情,如图 5 所示。FrilStub 是一个简单程序,它通过 TcpClient 流与我们的桌面建立了一个通信会话,它还利用我们在第一个示例中学到的 API 给虚拟无线电发送事件。然后,我们可以在桌面上使用该流,将命令发送到模拟环境。

图 5. FRilStub 状态显示
private void Form1_Load(object sender, System.EventArgs ea)
{
lblHost.Text = ipHost.ToString();
statusMsg = "Initializing...";
// Setup a communication thread back to the desktop
ThreadStart del1 = new ThreadStart(ConnectToDesktop);
Thread commThread = new Thread(del1);
commThread.Start();
}
private void ConnectToDesktop()
{
statusMsg = "Connecting...";
lblStatus.Invoke(new EventHandler(UpdateUI));
try
{
int nCommand = 0;
IPAddress ipDesktop = IPAddress.Parse(ipHost);
TcpClient client = new TcpClient();
client.Connect( new IPEndPoint(ipDesktop, 8001));
statusMsg = "Ready";
ParamReader reader = new ParamReader(client.GetStream());
do
{
lblStatus.Invoke(new EventHandler(UpdateUI));
Application.DoEvents();
nCommand = reader.ReadInt();
switch ( nCommand )
{
case -1:
statusMsg = "[Shutting Down]";
break;
case 1:
statusMsg = "[SMS]";
ParseReceiveMessage(client.GetStream());
break;
case 2:
statusMsg = "[Phone Call]";
ParseReceiveCall(client.GetStream());
break;
default:
statusMsg = "[Unknown: " + nCommand.ToString() + "]";
break;
}
}
while ( nCommand > 0 );
client.Close();
}
catch (InvalidOperationException)
{
MessageBox.Show("Unable to connect to Desktop at : {0}", ipHost);
}
catch (SocketException exc)
{
StringBuilder strB = new StringBuilder("");
strB.Append(String.Format("Cannot connect to Desktop: {0}\r\n",ipHost));
strB.Append(exc.Message+"\r\n");
strB.Append("Socket Error Code: " + exc.ErrorCode.ToString());
MessageBox.Show( strB.ToString() );
}
statusMsg = "Disconnecting...";
lblStatus.Invoke(new EventHandler(UpdateUI));
Application.DoEvents();
this.Close();
}
private void UpdateUI(object sender, EventArgs e)
{
lblStatus.Text = statusMsg;
}
// Read call information from Desktop and initiate phone call
private void ParseReceiveCall(NetworkStream s)
{
ParamReader reader;
ParamWriter writer;
string callNumber;
int restriction;
int addressType;
int duration;
reader = new ParamReader(s);
writer = new ParamWriter(s);
try
{
callNumber = reader.ReadString(); // Read params from the stream
addressType = reader.ReadInt();
restriction = reader.ReadInt();
duration = reader.ReadInt();
// Call the fake radio interface
fakeRil.receiveCall(callNumber,addressType,restriction,duration);
writer.WriteInt(1); // Write a success result
}
catch (Exception)
{
writer.WriteInt(-1);
MessageBox.Show("Failed");
}
}
// Read SMS message information from Desktop and send SMS
private void ParseReceiveMessage(NetworkStream s)
{
ParamReader reader;
ParamWriter writer;
string smsMessage;
string smsNumber;
reader = new ParamReader(s);
writer = new ParamWriter(s);
try
{
smsMessage = reader.ReadString(); // Read params from the stream
smsNumber = reader.ReadString();
fakeRil.receiveSMS(smsMessage, smsNumber);
writer.WriteInt(1); // Write a success result
}
catch (Exception)
{
writer.WriteInt(-1);
}
}
正如您所看到的,运行在仿真程序上的代码正好截获一个消息来标识操作(呼叫、SMS 等),从流中读取参数,然后将呼叫传递给虚拟无线电接口,如图 6 所示。

图 6. 仿真程序上的呼叫事件
您可能发现自己想使用 SendCallEvents 来测试用 eMbedded Visual C++ 开发的 SMS 应用程序。共享模拟环境的便利方法是使用诸如 EmuAsCfg 的工具。EmuAsCfg 包含在两个 2003 SDK 中,并且还作为 Windows Mobile Developer Power Toys。它能够使您通过 Microsoft ActiveSync® 连接到仿真程序,这恰好是一种在诸如 eMbedded Visual C++ 和 Visual Studio .NET 工具之间共享仿真程序的便利方法。例如,您构建 MapiRule SDK 示例来测试截获 SMS 消息,并使用 SendCallEvents 来生成测试消息。
2003 仿真程序上的 Virtual Radio(虚拟无线电)支持可以提供便利的环境来开发基于 SMS 和呼叫的应用程序。非常有创造性,而且重要的是能够让您感到愉快!
为特权签名配置您的开发环境
1. | 在 SDK\Tools 目录中找到 TestCert_Privileged.pfx,然后双击它并在所有默认导入向导提示中连续选择“下一步”。这会将特权测试证书添加到您的证书存储区中,后者由 eMbedded Visual C++ 和 Signcode 实用工具使用。默认情况下,将 SDK 安装到 C:\Program Files\Windows CE Tools\wce420\SMARTPHONE 2003 目录中。 |
2. | 要配置 Visual Studio 自动使用仿真程序的特权测试证书来签名托管二进制代码,请将 Microsoft.smartphone2003.1.0.xsl(通常可以在 \Documents and Settings\All Users\Application Data\Microsoft\visualstudio\devices\addon 目录中找到)中的引用从 TestCert_UnPrivileged 更改为 Test_CertPrivileged。如果打开了 Visual Studio,则需要重新启动并重新编译每个应用程序,只有这样才能使这些更改生效。 注 这将用一个特权证书对所有可执行文件进行签名。 |
对托管示例进行签名
1. | 对于托管桌面示例,从 SDK\Tools 目录中运行 signcode.exe 并在示例目录 Desktop\SendCallEvents\bin\debug 中签名 frildll.dll 和 frilstub.exe。使用所有默认提示,在签名证书步骤选择“Smartphone 2003 Privileged Test Signing Authority”。 |
2. | 对于托管模拟示例,从 SDK\Tools 目录运行 signcode.exe,并对 Emulation\Samples\Smartphone\Managed\SendSMS 目录中的 frildll.dll 进行签名,然后重新编译托管项目。 |
签名本机示例
对于本机 Smartphone 示例,从 SDK\Tools 目录运行 signcode.exe,并签名 Emulation\FrilDll 目录中的 frildll.dll。
在构建每个本机 eMbedded Visual C++ 项目之前,通过从项目安全设置中指定“Smartphone 2003 Privileged Test Signing Authority”证书以确保签名了应用程序,然后重新编译它。
要验证一个文件是否正确签名,请打开文件属性并检验“Digital Signatures”选项卡以验证特权证书。
注 请确保在部署项目时,您记得要选择包含 Virtual Radio 的 2003 仿真程序版本。