.Net Compact Framework高级篇(1)

Author:冯峰
Date: 2008-09-21

本篇文章主要讲述短信拦截的一些方法,虽然该方法不是最理想的方法,但仍然能够达到相同的功效。
作为高级篇的第一篇文章,希望文中的一些 API的使用方法,能够给更多人予以帮助。

在.net 2.0中,MS就提供了 Microsoft.WindowsMobile.PocketOutlook.MessageInterception命名空间,通过使用该命名空间,可以截获短信和邮件的到达消息。
该命名空间中的一些类及属性:

  1. MessageInterceptor:监控对象类,一旦添加监控事件后,就会对所有消息进行监控。
  2. MessageCondition:监控的过滤筛选条件类,通过设置需要过滤某个字段。
  3. MessageInterceptorEventHandler:监控事件,一旦截获某消息,就立即执行该方法。
  4. I nterceptionAction(枚举):
    Notify(提示,但此时系统仍会第一时间处理,自定义处理虽然有效,但肯定不是大家想要的。)
    NotifyAndDelete(系统不会做任何处理,直接交给用户自己处理,同时达到的消息,如果用户不处理,则会删除。)
  5. MessageProperty(枚举):按照某个属性进行对比
  6. MessagePropertyComparisonType(枚举):对比方法

运行程序效果如下:

在 Menu菜单下,点击 Message Intercepter事件,开始监听。在此,我设置了,当发短消息的用户为 Test Man时,将短消息过滤,不会在短消息的收件箱中出现,截获的内容这会在界面上显示。

测试:运行 Cellular Emulator程序,输入电话号码及短信内容,发送到模拟器上。

模拟器效果如下:

可以看到消息已经被截获了,且消息内容一致。

如果发送号码为 123456788,则不是添加的联系人 (Test Man)。则不会截获。

代码如下:

1         private void menuItem4_Click(object sender, EventArgs e)
 2          {
 3             MessageInterceptor msgInterceptor = new MessageInterceptor();
 4             msgInterceptor.InterceptionAction = InterceptionAction.NotifyAndDelete;
 5 
 6             MessageCondition msgCondition = new MessageCondition();
 7             msgCondition.ComparisonType = MessagePropertyComparisonType.Contains;
 8             msgCondition.Property = MessageProperty.Sender;
 9             msgCondition.ComparisonValue = "Test Man";
10 
11             msgInterceptor.MessageCondition = msgCondition;
12 
13             msgInterceptor.MessageReceived += new MessageInterceptorEventHandler(msgInterceptor_MessageReceived);
14         }
15 
16         void msgInterceptor_MessageReceived(object sender, MessageInterceptorEventArgs e)
17          {
18             this.listBox1.Items.Add("Type Name: " + e.Message.GetType().Name);
19 
20             if (e.Message.GetType() == typeof(SmsMessage))
21              {
22                 SmsMessage sms = (SmsMessage)e.Message;
23                 this.listBox1.Items.Add("From: " + sms.From.Name);
24                 this.listBox1.Items.Add("Body: " + sms.Body);
25                 this.listBox1.Items.Add("Received Tiem: " + sms.Received.ToString("yyyy-MM-dd"));
26             }
27             if (e.Message.GetType() == typeof(EmailMessage))
28              {
29                 EmailMessage mail = (EmailMessage)e.Message;
30 
31                 this.listBox1.Items.Add("ItemId: " + mail.ItemId.ToString());
32             }
33         }

Tips:

  1. 使用 MessageInterceptor能监视的只是一个属性,如果根据不同用户,指定不同过滤操作的话,就无法实现。
  2. 一旦将 InterceptionAction设为 NotifyAndDelete,则系统就不做处理了,接收到的消息,就不会在收件箱中出现,需要自己添加进去 (下文会介绍如何实现 )
  3. 可以不设置 MessageInter ceptor的 MessageCondition,这样使得所有消息都截获,然后自己处理,这时,就可以设置更广泛的过滤。

一旦将消息全部截获后,必不可少的会遇到一些不需要过滤的消息,此时,还要将消息重新放入收件箱中。很遗憾,暂时在托管环境中没有相应的操作类,相信 MS会在以后的 CF版本中会对此进行扩展的。

下面将介绍如何 SIM卡的短消息的 API。
涉及的 API:
SimInitialize方法:初始化 SIM卡的对象指针。
SimDeinitialize方法:释放 SIM卡指针,类似于 Ado里的连接对象,操作完后,关闭对象。
SimGetSmsStorageStatus方法:得到 SIM卡里的可以存放的最大消息个数及已存放的消息个数。
SimReadMessage方法:从 SIM卡中读取短信 (如果 SIM卡有短信 )。
SimWriteMessage方法:向 SIM卡中写入或修改短信。
SimDeleteMessage方法:删除 SIM卡中某条短信。
涉及的结构体:
SimMessageTag:定义某条消息中的所有信息。
SystemTime:自定义的一个时间结构体。

在运行的程序中点击 Test事件,则会读取 SIM卡中所有短信,并且向 SIM卡中添加一条短信。

运行该事件前,短信收件箱。

运行结果,读取 SIM卡中的短信内容 (只有 1条 )。

查看短信收件箱,该短信已写入。

通过这些 API,我们可以很方便的将自己需要的短信写入 SIM,而在过滤事件中过滤不需要的。

代码如下:

1         [DllImport("cellcore.dll", SetLastError=true)]
  2         private static extern int SimInitialize(int dwFlags, int lpfnCallBack, int dwParam, out int lphSim);
  3 
  4         [DllImport("cellcore.dll", SetLastError = true)]
  5         private static extern int SimDeinitialize(int hSim);
  6 
  7         [DllImport("cellcore.dll", SetLastError = true)]
  8         public static extern int SimGetSmsStorageStatus(int hSim, int dwStorage, ref int lpdwUsed, ref int lpdwTotal);
  9 
 10         [DllImport("cellcore.dll", SetLastError = true)]
 11         private static extern int SimWriteMessage(int hSim, int dwStorage, ref int lpdwIndex, ref SimMessageTag SmsStructType);
 12 
 13         [DllImport("cellcore.dll", SetLastError = true)]
 14         private static extern int SimReadMessage(int hSim, int dwStorage, int lpdwIndex, ref SimMessageTag SmsStructType);
 15 
 16         [DllImport("cellcore.dll", SetLastError = true)]
 17         private static extern int SimDeleteMessage(int hSim, int dwStorage, ref int lpdwIndex);
 18 
 19         private void menuItem6_Click(object sender, EventArgs e)
 20          {
 21             int hSim = 0, res = 0;
 22 
 23             try
 24              {
 25                 res = SimInitialize((int)SIM_INIT_NONE, 0, (int)SIM_PARAM_MSG_ALL, out hSim);
 26 
 27                 if (res != 0)
 28                     throw new Exception("Could not initialize SIM");
 29 
 30                 int used = 0, total = 0;
 31 
 32                 res = SimGetSmsStorageStatus(hSim, (int)SIM_NUMSMSSTORAGES, ref used, ref total);
 33 
 34                 if (res == 0)
 35                  {
 36                     this.listBox1.Items.Add("Used: " + used.ToString());
 37                     this.listBox1.Items.Add("Total: " + total.ToString());
 38                 }
 39                 else
 40                  {
 41                     this.listBox1.Items.Add("Last Error: " + Marshal.GetLastWin32Error().ToString());
 42                 }
 43 
 44                 SimMessageTag message = new SimMessageTag();
 45 
 46                 int index = 1;
 47 
 48                 //res = SimDeleteMessage(hSim, (int)SIM_SMSSTORAGE_BROADCAST, ref index);
 49 
 50                 //if (res != 0)
 51                 //{
 52                 //    this.listBox1.Items.Add("Last Error: " + Marshal.GetLastWin32Error().ToString());
 53                 //}
 54                 //this.listBox1.Items.Add("Index: " + index.ToString());
 55 
 56                 for (int j = 1; j <= used; j++)
 57                  {
 58                     res = SimReadMessage(hSim, (int)SIM_NUMSMSSTORAGES, j, ref message);
 59                     if (res == 0)
 60                      {
 61                         this.listBox1.Items.Add("From: " + message.lpszAddress);
 62                         this.listBox1.Items.Add("CBSize: " + message.cbSize.ToString());
 63                         this.listBox1.Items.Add("Address Type: " + message.dwAddressType.ToString());
 64                         this.listBox1.Items.Add("NumPlan: " + message.dwNumPlan.ToString());
 65                         this.listBox1.Items.Add("Params: " + message.dwParams.ToString());
 66                         char[] header = new char[message.rgbHeader.Length];
 67                         string msg = "";
 68                         for (int i = 0; i < message.rgbHeader.Length; i++)
 69                          {
 70                             header[i] = (char)message.rgbHeader[i];
 71                             msg += header[i].ToString();
 72                         }
 73                         this.listBox1.Items.Add("Header: " + msg);
 74                         this.listBox1.Items.Add("Receive Time: " + message.stReceiveTime.ToString());
 75                         this.listBox1.Items.Add("Message: " + message.lpszMessage);
 76                         this.listBox1.Items.Add("");
 77                     }
 78                     else
 79                      {
 80                         this.listBox1.Items.Add("Last Error: " + Marshal.GetLastWin32Error().ToString());
 81                     }
 82                 }
 83 
 84                 SimMessageTag msg1 = new SimMessageTag();
 85                 msg1.cbSize = message.cbSize;
 86                 msg1.dwAddressType =1;
 87                 msg1.dwNumPlan = 1;
 88                 msg1.dwParams = 111;
 89                 msg1.lpszAddress = "123456789";
 90                 msg1.stReceiveTime = new global::SystemTime(System.DateTime.Now);
 91                 msg1.lpszMessage = "It is a test mail!";
 92                 msg1.cbHdrLength = 0;
 93                 msg1.rgbHeader = new byte[256];
 94 
 95                 index = used + 1;
 96 
 97                 res = SimWriteMessage(hSim, (int)SIM_NUMSMSSTORAGES, ref index, ref msg1);
 98                 if (res != 0)
 99                  {
100                     this.listBox1.Items.Add("Last Error: " + Marshal.GetLastWin32Error().ToString());
101                 }
102             }
103             catch (Exception ex)
104              {
105                 MessageBox.Show(ex.Message);
106             }
107             finally
108              {
109                 this.listBox1.Items.Add("Result: " + res.ToString());
110                 SimDeinitialize(hSim);
111             }
112         }

在 DllImport中定义 SetLastError,这样在调用 API出错时,可以通过 Marshal.GetLastWin32Error()来取得 ErrorCode。
SimInitialize方法的第一个参数,网上很多直接写 0,为什么是 0,因为常量 SIM_INIT_NONE定义为 0。
在操作 SIM卡消息时的所有方法都需要 dwStorage参数,该参数常量定义为:
SIM_NUMSMSSTORAGES = 2,从 SIM卡里读取
SIM_SMSSTORAGE_BROADCAST = 1,从设备本地读取
添加一条消息时,该消息结构体的赋值中
dwAddressType = 1 ( SIM_ADDRTYPE_INTERNATIONAL )
dwNumPlan = 1 ( SIM_NUMPLAN_TELEPHONE )
dwParams = 111 必须设为 111,否则添加不了,该数字是属性累加起来的
cbHdrLength = 0 (短信头的长度通常为 0)

1 [StructLayout(LayoutKind.Sequential)]
 2 public struct SystemTime
 3  {
 4     public short wYear;
 5     public short wMonth;
 6     public short wDayOfWeek;
 7     public short wDay;
 8     public short wHour;
 9     public short wMinute;
10     public short wSecond;
11     public short wMilliseconds;
12 
13     public SystemTime(System.DateTime now)
14      {
15         wYear = (short)now.Year;
16         wMonth = (short)now.Month;
17         wDayOfWeek = (short)now.DayOfWeek;
18         wDay = (short)now.Day;
19         wHour = (short)now.Hour;
20         wMinute = (short)now.Minute;
21         wSecond = (short)now.Second;
22         wMilliseconds = (short)now.Millisecond;
23     }
24     public override string ToString()
25      {
26         return string.Format("{0}/{1}/{2}", wYear, wMonth, wDay);
27     }
28 }
29 
30 [StructLayout(LayoutKind.Sequential)]
31 public struct SimMessageTag
32  {
33 public int cbSize; // Size of the structure in bytes
34 public int dwParams; //Indicates valid parameter values
35 
36 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
37 public string lpszAddress; //An array that contains the actual phone number
38 
39 public int dwAddressType; //A SIM_ADDRTYPE constant
40  /*
41 SIM_ADDRTYPE_UNKNOWN = Unknown.
42 SIM_ADDRTYPE_INTERNATIONAL = International number.
43 SIM_ADDRTYPE_NATIONAL 0ne National = number.
44 SIM_ADDRTYPE_NETWKSPECIFIC Network = specific number.
45 SIM_ADDRTYPE_SUBSCRIBER Subscriber = number
46 (protocol-specific).
47 SIM_ADDRTYPE_ALPHANUM Alphanumeric = address.
48 SIM_ADDRTYPE_ABBREV Abbreviated = number.
49 */
50 
51 public int dwNumPlan; //A SIM_NUMPLAN constant
52  /*
53 SIM_NUMPLAN_UNKNOWN = Unknown.
54 SIM_NUMPLAN_TELEPHONE = ISDN/telephone numbering plan
55 (E.164/E.163).
56 SIM_NUMPLAN_DATA = Data numbering plan (X.121).
57 SIM_NUMPLAN_TELEX = Telex numbering plan.
58 SIM_NUMPLAN_NATIONAL = National numbering plan.
59 SIM_NUMPLAN_PRIVATE = Private numbering plan.
60 SIM_NUMPLAN_ERMES ERMES = numbering plan (ETSI DE/PS 3 01-3).
61 */
62 
63 public SystemTime stReceiveTime; //Timestamp for the incoming message
64 
65 public int cbHdrLength; //Header length in bytes
66 
67 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
68 public byte[] rgbHeader; //An array containing the actual header data
69 
70 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
71 public string lpszMessage; //An array containing the actual message data
72 }

字段长度设置,根据参考该 dll的头文件定义的,为 256

Tips:
1.在调查 dll的某些 API的过程确实很复杂,需要不断尝试,这里推荐大家,一旦调用出错,可以通过 Marshal.GetLastWin32Error()来取得 ErrorCode,这样可以知道错误类型。在调用 API时,尽量多参考其头文件,这样可以很方便了解这些方法中的一些结构体及一些常量。
2.如果运行模拟器的话,向 SIM卡里添加短信后,在短信收件箱中不会显示出来,但实际是添加进去了。必须通过关闭电话,然后重新设置电话网络,这样 SIM卡会重新加载。实际的设备应该不会这样。

总结:
本文提供给大家在 .net cf下如何实现短信拦截的一个思路,该方法确实是可行的,希望大家能够掌握。短信拦截的方法不是唯一的,以后会介绍别的方法。

代码下载 (附头文件以便大家能够理解 ):SmartDeviceOutlookDemo_2008_03_28.rar

 

 

下一篇:(冯峰).Net Compact Framework 高级篇(2)-- 扩展SOAP应用