In my previous
post I have explained the problem with
MessageInterceptor on new HTC Phones and how to temporary fix it it by installing the
patch. Although this allows existing applications to work without any code changes, user will have to use Pocket Outlook, which has an inferior user interface compared to HTC Messages, to send SMS. MMS are not supported until the
Arcsoft MMS client is installed. HTC Sense will not be able to show new incoming text messages, which is a major limitation.
Luckily there is a way to intercept incoming text messages without installing any patch. However, you will have to make some code changes to your application. The idea is that, although the .NET MessageInterceptor class does not work, the SDK
MapiRule sample (C:\Program Files\Windows Mobile 6.5.3 DTK\Samples\Common\CPP\Win32\MapiRule) works just fine. This post shows you how to modify the MAPIRule sample code to integrate it with your .NET project
Modifying MAPIRule.dll
You will not have to touch most of the MAPIRule code, except for modifying the GUID (important!) and the ProcessMessage() function (the sample just displays the incoming text message received in a MessageBox). Although you can parse the incoming message and process it directly inside ProcessMessage(), I decided on the following to make things clearer:
- When MapiRule.dll received a new SMS message in ProcessMessage(), it simply send a Win32 message via SendMessage to the host application.
- Upon received the specific Win32 message, host application, which is a .NET application, will decide on whether or not to intercept the new SMS message (it will not appear in phone Inbox) or let the default messaging application handle it.
- Based on the result of SendMessage(), MapiRule.dll will act accordingly.
This interprocess communication can be easily done by using the Win32 API SendMessage() and the .NET Compact Framework's MessageWindow class. Although you can specify your own message ID, I have chosen WM_COPYDATA since MapiRule.dll needs to send the SMS text and sender number to the host application, otherwise string pointers to message text and sender numbers cannot be shared. The sample code is below. Notice that
newMsgWin is the handle to the host application's window. The sender number and message text is concatenated into a single string to be sent out.
HRESULT CMailRuleClient::ProcessMessage(IMsgStore *pMsgStore, ULONG cbMsg, LPENTRYID lpMsg, ULONG cbDestFolder, LPENTRYID lpDestFolder, ULONG *pulEventType, MRCHANDLED *pHandled)
{
............
TCHAR msgInfo[500];
COPYDATASTRUCT cds;
// we concatenate the sender number and the message text, to be sent to the host application
memset(msgInfo, 0, sizeof(msgInfo));
wcscpy(msgInfo, pspvEmail->Value.lpszW);
wcscat(msgInfo, L"\n");
wcscat(msgInfo, pspvSubject->Value.lpszW);
//length for the recipient to know
cds.dwData = wcslen(pspvEmail->Value.lpszW) + wcslen(pspvSubject->Value.lpszW) + 1;
cds.cbData = sizeof(msgInfo); //msg size in bytes
cds.lpData = &msgInfo; //pointer to the information to be sent
// tell the main app about the text message
if (SendMessage(newMsgWin, WM_COPYDATA, 0, (LPARAM) &cds) == (LRESULT) 1)
{
// a LRESULT of 1 means that the message was processed by parent application
// so we delete the message and mark it as handled so it won't show up in Inbox
hr = DeleteMessage(pMsgStore, pMsg, cbMsg, lpMsg, cbDestFolder, lpDestFolder, pulEventType, pHandled);
}
else
{
// other LRESULT means message not handled by main app, pass it on
*pHandled = MRC_NOT_HANDLED;
}
............
}
Everything is straightforward, except that the lpData member of COPYDATASTRUCT structure has to be part of the structure memory area. This explains why an array of TCHAR, an not LPWSTR is used. If this is not followed, application may work or crash randomly without any indication why.
UPDATE (26 May 2012): As suggested by a reader, the above code may
have problem with Unicode messages since sizeof(wchar_t) is 2 on
Windows, resulting in incorrect value for the data length, dwData. If
you have problems with truncated messages, try this:
cds.dwData = 2*wcslen(pspvEmail->Value.lpszW) + 2*wcslen(pspvSubject->Value.lpszW) + 1;
The updated MAPI DLL with the fix can be downloaded from
here
The .NET code is as follows:
[StructLayout(LayoutKind.Sequential)]
private struct CopyDataStruct
{
public int IntData;
public int Length;
[MarshalAs(UnmanagedType.LPWStr)]
public string Data;
}
public class NewMsgWindow : MessageWindow
{
..........
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_COPYDATA) //we received a message telling us that there is an incoming SMS
{
CopyDataStruct cs = (CopyDataStruct)Marshal.PtrToStructure(m.LParam, typeof(CopyDataStruct));
if (cs.Data.Contains(delim))
{
string sender = cs.Data.Split(delim)[0];
string messageText = cs.Data.Split(delim)[1];
if (true)
{
// a LRESULT of 1 marks message as processed.
// and native msg app will not show msg in Inbox
m.Result = new IntPtr(1);
}
else
// Other LRESULT indicates we did not intercept the message
// and the message will be passed on to native messaging application.
m.Result = new IntPtr(0);
}
}
.......
}
The challenge is to receive the WM_COPYDATA message and marshal it back to a string. Most sample codes use GetLParam(), but as this is not supported by .NET CF, we have to use Marshal.PtrToStructure(). As commented, the .NET code will response with a result of either 1 or 0.
Source code
The sample code is
here. Some part of the code was taken from the
Remote Tracker open source project which also intercepts incoming text messages. Remote Tracker source code, however, parses the text message directly inside MapiRule.dll.
Setting up
Some registry keys need to be modified for MapiRule.dll to be used. This can either be done via a CAB file, or via the CreateInterceptorMethod2() method in the code. A reboot is needed for changes to take effect. If you need to change MapiRule.dll, you will have to remove the registry keys via RemoveInterceptorMethod2(), reboot the device, update the dll, call CreateInterceptorMethod2() and reboot again! Terminating poutlook.exe and tmail.exe may also help to reduce the number of restarts on some devices. This makes the development process very time consuming. Debugging MapiRule.dll is possible by attaching the debugger to tmail.exe.
Issues
There are still some minor issues yet to be solved. On HTC HD2, for some reasons, all text messages received will have the string " - GSM" appended at the end (refer to
this discussion). Also, the total length of the sender name and the message text cannot exceed 500 since the TCHAR array length is hard coded. If you need to receive longer text message, the interprocess communication algorithm has to be modified to send and receive the text message part by part. You cannot simply increase the array length, as it will cause a stack fault.
Last but not least, the approach described here works on all devices, including HTC Devices. So it can be used as a replacement for the MessageInterceptor class. In a sense, it's better as you can decide, at the point of receiving, which message to be processed, instead of pre-creating a set of MessageInterceptor conditions and re-creating them should the interception conditions change.