先闲聊几句,说说这文章和文章里的代码是为何而产生的。小弟今年9月6日就要正式开始我的大学生活。我也算上是个短信达人,上大学的话短信费估计就更疯狂了,所以就考虑用包月的GPRS发送短信。虽然网络上已经有不少这类的软件,如飞信和做做客吧。然而这些软件都存在不太符合我个人期望的问题,比如信息记录在不同的地方以后翻查管理不方便;而做做客这类非通信营运商提供的软件,信息安全没保障,一会儿没准闹出个“艳信门”(当然,我也没那个艳福~),那我岂不是完蛋了。
自己手上用的是Windows Mobile 6.1的机子,所以就想写一个自用版的软件够无缝地结合到原有的SMS服务和客户端窗体中,就是说:当我在WM原有的短信界面进行发送操作时,程序能自动拦截这条短信,不让他发送出去,然后转由GPRS发送出去。同时还需要把接收到的短信更换格式以后保存下来。对于我而言这里存在2个技术难点:一就是如何将信息写入收件箱和已发邮件箱,使短信看起来跟通过系统发送后的情况一样;二就是如何拦截准备发送的短信。
对于写入短信到收件箱或者已发邮件箱,CSDN的无聊客采取的办法是对Sim卡进行直写操作(他的方法似乎只能写入收件箱),我个人不太喜欢这种方式。这样看起来跟默认情况下收发的短信是不同的(其中最让人心痛的就是不能使用SMS-Chat模式),此外sim卡有一定的容量限制且不方便进行日后的统一管理。这几天查阅Windows Mobile 6.0 SDK和读取系统发送和接收的短信的各项字段,这一个问题已经较好地被我解决。
但是对于如何拦截准备发送的短信,我就没办法了。网上虽然很多关于“拦截短信”这一主题的文章,然而讨论的都是拦截接收到的短信(用于过滤垃圾短信等),至今我似乎还没有看到讨论拦截准备发送的短信的文章(也许这是因为我这样的需求比较罕见吧)。我尝试通过在MAPI中注册Folder和Message类的事件,等待系统发来通知时,就把发件箱、草稿箱的短信删除 —— 可是这样并没有作用,短信还是会发送出去。
所以我这个文章就叫做抛砖引玉,真的希望抛出一块砖没把大家砸死,还能引一块玉回来——找到一个能拦截准备发送出去的短信的方法。好了,废话说多了,下面放上代码,然后我提出几点大家需要注意的地方。
(虽然是C++ Native的代码,不过大家把它封装一下就可以在.net里面用了)

核心函数代码
1
// Author: U2U
2
// Date: 2008-08-29
3
// HomePage: Http://U2USoft.CNBlogs.com
4
5
HRESULT CreateMessage_Recv(IMAPIFolder *folder, LPTSTR lpszSenderFullAdress, LPTSTR lpszMessageBody, IMessage **newMessage)
6

{
7
8
IMessage *spMessage;
9
folder->CreateMessage(NULL, 0 ,&spMessage);
10
11
// 增加 Unicode 支持
12
SPropValue propVals[1];
13
14
MAPINAMEID mapiNameId;
15
ZeroMemory(&mapiNameId, sizeof(mapiNameId));
16
GUID PS_MAPI =
{0x00020328, 0, 0, 0xC0,0,0,0,0,0,0,0x46};
17
mapiNameId.lpguid = (LPGUID)&PS_MAPI;
18
mapiNameId.ulKind = MNID_STRING;
19
mapiNameId.Kind.lpwstrName = L"SMS:Unicode";
20
LPMAPINAMEID pmapiNameId = &mapiNameId;
21
LPSPropTagArray psPropArr = NULL;
22
23
spMessage->GetIDsFromNames(1, &pmapiNameId, MAPI_CREATE, &psPropArr);
24
25
propVals[0].ulPropTag = CHANGE_PROP_TYPE(psPropArr[0].aulPropTag[0], PT_BOOLEAN);
26
propVals[0].Value.b = TRUE;
27
28
spMessage->SetProps(1, propVals, NULL);
29
MAPIFreeBuffer(psPropArr);
30
31
32
// 设置基本属性
33
SPropValue props[5];
34
ULONG cProps = 0;
35
36
ZeroMemory(&props, sizeof(props));
37
38
// 设置显示窗体类型为“短信”
39
props[cProps].ulPropTag = PR_MESSAGE_CLASS;
40
props[cProps].Value.lpszW = TEXT("IPM.SMStext");
41
++cProps;
42
43
// 短信内容
44
props[cProps].ulPropTag = PR_SUBJECT;
45
props[cProps].Value.lpszW = (LPWSTR)lpszMessageBody;
46
++cProps;
47
48
// 发件人号码
49
props[cProps].ulPropTag = PR_SENDER_EMAIL_ADDRESS;
50
props[cProps].Value.lpszW = (LPWSTR)lpszSenderFullAdress;
51
++cProps;
52
53
// 短信状态标志
54
props[cProps].ulPropTag = PR_MSG_STATUS;
55
props[cProps].Value.ul = 0;
56
++cProps;
57
58
// 短信状态标志
59
props[cProps].ulPropTag = PR_MESSAGE_FLAGS;
60
props[cProps].Value.ul = 0;
61
++cProps;
62
63
spMessage->SetProps(sizeof(props) / sizeof(props[0]), (LPSPropValue)&props, NULL);
64
65
newMessage = &spMessage;
66
67
return TRUE;
68
}
69
70
HRESULT CreateMessage_Sent(IMAPIFolder *folder,LPTSTR lpszRecipientName, LPTSTR lpszRecipientAdress, LPTSTR lpszMessageBody,IMessage **newMessage)
71

{
72
73
IMessage *spMessage;
74
folder->CreateMessage(NULL, 0 ,&spMessage);
75
76
// 增加 Unicode 支持
77
SPropValue propVals[1];
78
79
MAPINAMEID mapiNameId;
80
ZeroMemory(&mapiNameId, sizeof(mapiNameId));
81
GUID PS_MAPI =
{0x00020328, 0, 0, 0xC0,0,0,0,0,0,0,0x46};
82
mapiNameId.lpguid = (LPGUID)&PS_MAPI;
83
mapiNameId.ulKind = MNID_STRING;
84
mapiNameId.Kind.lpwstrName = L"SMS:Unicode";
85
LPMAPINAMEID pmapiNameId = &mapiNameId;
86
LPSPropTagArray psPropArr = NULL;
87
88
spMessage->GetIDsFromNames(1, &pmapiNameId, MAPI_CREATE, &psPropArr);
89
90
propVals[0].ulPropTag = CHANGE_PROP_TYPE(psPropArr[0].aulPropTag[0], PT_BOOLEAN);
91
propVals[0].Value.b = TRUE;
92
93
spMessage->SetProps(1, propVals, NULL);
94
MAPIFreeBuffer(psPropArr);
95
96
// 设置收件人属性
97
SPropValue propRecipient[4];
98
ULONG cRecipientProps = 0;
99
100
ZeroMemory(&propRecipient, sizeof(propRecipient));
101
102
// 收件人类型
103
propRecipient[cRecipientProps].ulPropTag = PR_RECIPIENT_TYPE;
104
propRecipient[cRecipientProps].Value.l = MAPI_TO;
105
++cRecipientProps;
106
107
// 收件人地址类型
108
propRecipient[cRecipientProps].ulPropTag = PR_ADDRTYPE;
109
propRecipient[cRecipientProps].Value.lpszW = _T("SMS");
110
++cRecipientProps;
111
112
// 收件人姓名
113
propRecipient[cRecipientProps].ulPropTag = PR_DISPLAY_NAME;
114
propRecipient[cRecipientProps].Value.lpszW = (LPWSTR)lpszRecipientName;
115
++cRecipientProps;
116
117
// 收件人号码
118
propRecipient[cRecipientProps].ulPropTag = PR_EMAIL_ADDRESS;
119
propRecipient[cRecipientProps].Value.lpszW = (LPWSTR)lpszRecipientAdress;
120
121
// 创建地址列表
122
ADRLIST adrlist;
123
adrlist.cEntries = 1;
124
adrlist.aEntries[0].cValues = sizeof(propRecipient) / sizeof(propRecipient[0]);
125
adrlist.aEntries[0].rgPropVals = (LPSPropValue)(&propRecipient);
126
127
spMessage->ModifyRecipients(MODRECIP_ADD, &adrlist);
128
129
130
// 设置基本属性
131
SPropValue props[6];
132
ULONG cProps = 0;
133
134
ZeroMemory(&props, sizeof(props));
135
136
// 设置显示窗体类型为“短信”
137
props[cProps].ulPropTag = PR_MESSAGE_CLASS;
138
props[cProps].Value.lpszW = TEXT("IPM.SMStext");
139
++cProps;
140
141
// 短信内容
142
props[cProps].ulPropTag = PR_SUBJECT_W;
143
props[cProps].Value.lpszW = (LPWSTR)lpszMessageBody;
144
++cProps;
145
146
// 告诉 MAPI 这是一封需要投递的短信
147
props[cProps].ulPropTag = PR_MSG_STATUS;
148
props[cProps].Value.ul = MSGSTATUS_RECTYPE_SMS;
149
++cProps;
150
151
// 表明这是发出的短信
152
props[cProps].ulPropTag = PR_MESSAGE_FLAGS;
153
props[cProps].Value.ul = MSGFLAG_FROMME | MSGFLAG_READ;
154
155
spMessage->SetProps(sizeof(props) / sizeof(props[0]), (LPSPropValue)&props, NULL);
156
157
newMessage = &spMessage;
158
159
return TRUE;
160
}
说明:
-
为了让代码显得更清晰,使大家能把注意力集中在关键的属性设置上,我把对HRESULT的判断去掉。
-
CEMPAI对于SPropValue是比较敏感的,所以一定要调用ZeroMemory
-
对于收件箱内的短信,虽然读取时可以得到PR_DISPLAY_NAME这个属性,然而在CreateMessage后似乎设置该字段并不能起效果,要让发送者的姓名显示出来,可以使用一个小技巧,在PR_EMAIL_ADRESS属性值中使用FullAdress格式,即 /"姓名" <号码>/ (不包括斜杠"/")
-
CEMAPI对于属性设置的顺序似乎也是敏感的,好比对于已发送的短信(包括需要真正发送出去的短信),都需要先设置发送目标(即Recipient),然后才能设置其他属性
最后 ,希望哪位做WM开发的高手能指点一二,告诉我怎样能够拦截用户通过原始方法发送出去的短信(就是不让短信发送出去,而被程序拦截并且销毁)的方法,谢谢!Windows Mobile的SDK文档还不够丰富,起码很多东西在MSDN就根本搜索不到,所以大家有什么问题也不妨提出,让我们一起交流交流。呵呵。