본문 바로가기

Windows/MFC

Receiving SMS Messages Inside a Managed Application

NET Compact Framework
Receiving SMS Messages Inside a Managed Application
 

Maarten Struys
PTS Software

September 2004

Applies to:
   Microsoft? .NET Compact Framework 1.0
   Microsoft? Visual Studio? .NET 2003

Download Receiving SMS Sample.

Summary: In this article we describe a way to receive SMS messages in a managed application without the SMS message appearing in the Inbox and without showing a bubble notification when the SMS message arrives. The sample code will run on a Pocket PC 2003 Phone Edition device. With small modifications, mainly in the MMI part, it should run on a Smartphone 2003 as well. (10 Printed Pages)

Contents

Introduction
Using and Expanding the Existing MAPI Rule Example
Registering the MailRuleClient object with Inbox
Unregistering the MailRuleClient object
Communication between a client application and the MAPI Rule Client
Client Side Processing to Capture SMS Messages
Building the Sample Code
Testing the Sample Code
Shortcomings
ConclusionAbout the Author

Introduction

Even thought the .NET Compact Framework version 1.0 offers great functionality, it does not have much support for phone functionality. Using P/Invoke it is reasonably simple to access the Phone APIs and the SIM APIs as described in the article "Accessing Phone APIs from the Microsoft .NET Compact Framework". With Windows Mobile 2003 software there is no direct API available to immediately process received SMS messages inside a managed application without the SMS messages appearing in the Inbox. The SDK documentation refers to the IMailRuleClient Interface to process incoming messages inside an application. A MAPI Rule Client is a COM object that implements the IMailRuleClient Interface. When registered, the MAPI Rule Client will be loaded by the Inbox application. Once registered, Incoming SMS messages are passed to the MAPI Rule Client that can decide what to do with the incoming message.

Figure 1 - Relation between Inbox and IMailRuleClient

Both the Smartphone 2003 SDK and the Pocket PC 2003 SDK ship with many code samples. One of the samples implements a simple mail rule client. It can be found in the following subfolder of the Pocket PC 2003 SDK: \POCKET PC 2003\Samples\Win32\Mapirule. After registering on the device this sample MAPI Rule Client receives all SMS messages and passes them to the Inbox except for messages that contain the string "zzz". Those messages are handled in the MAPI Rule Client itself, showing the message and the sender's phone number in a MessageBox and deleting the SMS message afterwards.

Using and Expanding the Existing MAPI Rule Example

The mapirule sample is a great starting point for creating our own MAPI Rule Client. If there would be a way to create an interface between the mapirule sample and a managed application it would even be possible to easily have access to SMS messages from within a managed application. Since the mapirule sample contains complete functionality to receive SMS messages and to delete them after processing, the only thing we need to do is pass the SMS messages from the mapirule sample to a managed application. To do so, it is possible to extend the mapirule client sample with a number of simple native functions to pass SMS messages on to a calling client. For that purpose, the downloadable source code for this article contains a modified version of the mapirule sample. In the mapirule sample code, the method CMailRuleClient::ProcessMessage needs modifications. This method is the heart of the MAPI Rule Client; it receives SMS messages and handles them or passes them on to the Inbox. The following code snippet shows the original ProcessMessage method that can be found in mapirule.cpp.

HRESULT CMailRuleClient::ProcessMessage(IMsgStore *pMsgStore,
                                        ULONG cbMsg,
                                        LPENTRYID lpMsg, 
                                        ULONG cbDestFolder,
                                        LPENTRYID lpDestFolder,
                                        ULONG *pulEventType,
                                        MRCHANDLED *pHandled)
{
    HRESULT hr = S_OK;
    SizedSPropTagArray(1, sptaSubject) = { 1, PR_SUBJECT}; 
    SizedSPropTagArray(1, sptaEmail)   = { 1, PR_SENDER_EMAIL_ADDRESS}; 
    ULONG       cValues     = 0;
    SPropValue *pspvSubject = NULL;
    SPropValue *pspvEmail   = NULL;
    IMessage   *pMsg        = NULL;
    HRESULT     hrRet       = S_OK;

    // Get the message from the entry ID
    hr = pMsgStore->OpenEntry(cbMsg,
                              lpMsg,
                              NULL, 
                              0,
                              NULL, 
                              (LPUNKNOWN *) &pMsg);
    if (FAILED(hr)) {
        RETAILMSG(TRUE, (TEXT("Unable to get the message!\r\n")));
        goto Exit;
    }
    
    // For SMS, the subject is also the message body
    hr = pMsg->GetProps((SPropTagArray *) &sptaSubject,
                        MAPI_UNICODE, 
                        &cValues, 
                        &pspvSubject);
    if (FAILED(hr)) {
        RETAILMSG(TRUE, (TEXT("Unable to get the message body!\r\n")));
        goto Exit;
    }

    // get the sender's address or phone number
    hr = pMsg->GetProps((SPropTagArray *) &sptaEmail, 
                        MAPI_UNICODE, 
                        &cValues, 
                        &pspvEmail);

    if (FAILED(hr)) {
        RETAILMSG(TRUE, (TEXT("Couldn't get the sender's address!\r\n")));
        goto Exit;
    }
    //
    // ***** START REPLACE WITH CODE FROM LISTING 2 *****
    //
    // Here we filter the message on some predetermined string.
    // For sample purposes here we use "zzz". What happens when the 
    // filter condition(s) are met is up to you. You can send 
    // WM_COPYDATA messages to other app windows for light IPC,
    // send an SMS message in response, or whatever you need to do.
    // Here, we just play a sound and show the message in a 
    // standard message box.
    if (wcsstr(pspvSubject->Value.lpszW, L"zzz") != NULL)
    {
        MessageBeep(MB_ICONASTERISK);
        MessageBox(NULL, 
                   pspvSubject->Value.lpszW, 
                   pspvEmail->Value.lpszW, 
                   MB_OK);
      
        // 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 {
        // a 'normal' message, pass it on
        *pHandled = MRC_NOT_HANDLED;   
    }
    //
    // ***** END REPLACE CODE *****
    //

// Clean up
Exit:
    if (pspvEmail) {
        MAPIFreeBuffer(pspvEmail);
    }
    if (pspvSubject) {
        MAPIFreeBuffer(pspvSubject);
    }
    if (pMsg) {
        pMsg->Release();
    }
    
    return hr;
}

Listing 1: Original CMailRuleClient::ProcessMessage sample code.

It is beyond the scope of this article to explain every function call and all that is going on inside ProcessMessage, but the main thing to understand is that whenever an SMS message is received, the Inbox application will call into ProcessMessage with a message entry that contains all information for the received SMS message. All methods that are called inside ProcessMessage are explained in the Pocket PC 2003 help file that comes with the SDK. By replacing the highlighted lines with the next code snippet we can modify the example in such a way that it passes all received SMS messages with a special prefix (zzz) to a client application.

The next code snippet shows the code that replaces the marked code of listing 1.

// Here we filter the message on some predetermined string. For sample 
// purposes here we use "zzz". What happens when the filter condition(s)
// are met is up to you. You can send WM_COPYDATA messages to other 
// app windows for light IPC, send an SMS message in response, 
// or whatever you need to do.
if (wcsstr(pspvSubject->Value.lpszW, L"zzz") != NULL)
{
    if (g_hSmsAvailableEvent != NULL)
    {
        // We have received an SMS message that needs to be send to our client.
        // Since we run in the process space of Inbox, we are using a memory
        // mapped file to pass the message and phone number to our client, 
        // that typically runs in another process space (therefor we can not
        // simply copy strings). We protect the memory mapped file with a Mutex
        // to make sure that we are not writing new SMS data while the reading
        // client is still processing a previous SMS message.
        WaitForSingleObject(g_hMutex, INFINITE);
        lstrcpy(g_pSmsBuffer->g_szPhoneNr, pspvEmail->Value.lpszW);
        lstrcpy(g_pSmsBuffer->g_szMessageBody, pspvSubject->Value.lpszW);
        ReleaseMutex(g_hMutex);
        SetEvent(g_hSmsAvailableEvent);
    }
      
    // 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 
{
    // a 'normal' message, pass it on
    *pHandled = MRC_NOT_HANDLED;   
}

Listing 2: Replacement code to pass all SMS messages to a client process.

Registering the MailRuleClient object with Inbox

Before we are able to use our MAPI Rule Client we need to register it as a COM object, adding its class identifier to the following registry key: HKEY_CLASSES_ROOT\CLSID\. To make Inbox aware of the MAPI Rule Client, we must write its class identifier into the following registry key as well: HKEY_LOCAL_MACHINE\Software\Microsoft\Inbox\Svc\SMS\Rules.

To set both registry keys, thus fully registering our COM object so it is ready for use, the mapirule sample contains a function DllRegisterServer. This function can be called from any client application that wants to make use of the MAPI Rule Client. Since we will make use of a managed client application in this article, we use P/Invoke from inside our managed application to call into the function DllRegisterServer. After executing this function, all received SMS messages will be routed through the MAPI Rule Client as shown in figure 1. In the sample code provided by this article we pass all received SMS messages containing "zzz" to a client process, but it is possible to filter out particular SMS messages on any particular trigger you want (e.g. filter them on phone number, on particular message content or simply pass all messages).

Unregistering the MailRuleClient object

Unregistering the MAPI Rule Client after the client application is done capturing SMS messages is at least as important as registering the object. If we omit this step, SMS messages will not appear in the Inbox once we are through with our own application. To unregister the MAPI Rule Client, we call the function DllUnregisterServer. This function removes the registry keys that were set on calling the function DllRegisterServer. From within a managed client application this can be done using P/Invoke. Since the Inbox is not dynamically notified of the changes in the registry the device must also be soft reset. See also the shortcomings section at the end of this article.

Communication between a client application and the MAPI Rule Client

There are several ways to setup communication between a client application and the MAPI Rule Client that runs in the process space of the Inbox. Independent of the communication mechanism it is important to realize that we are talking about inter-process communication here. This immediately has some consequences. Because of the fact that Windows CE processes each have their own memory space it is not possible to simply pass pointers to memory from one process to another. To be able to pass the SMS message content to a client application we make use of memory mapped files. The MAPI Rule Client creates a memory mapped file in the constructor and destroys it in the destructor. Also an event is created that will be used to indicate that an SMS message is available for the client application.

CMailRuleClient::CMailRuleClient()
{
    m_cRef = 1;

    // Setup a memory mapped file so we can pass SMS messages between this
    // server and a listening client.
    g_hMMObj = CreateFileMapping((HANDLE)-1, 
                                  NULL, 
                                  PAGE_READWRITE, 
                                  0, 
                                  MMBUFSIZE, 
                                  TEXT("SmsBuffer"));
    assert(g_hMMObj != NULL);

    g_pSmsBuffer = (PSMS_BUFFER)MapViewOfFile(g_hMMObj, 
                                              FILE_MAP_WRITE, 0, 0, 0);
    if (g_pSmsBuffer == NULL) {
        CloseHandle(g_hMMObj);
    }
    assert(g_pSmsBuffer != NULL);

    // Create an event to inform the client about a pending SMS message
    g_hSmsAvailableEvent = CreateEvent(NULL, 
                                       FALSE, 
                                       FALSE, 
                                       _T("SMSAvailableEvent"));
    assert(g_hSmsAvailableEvent != NULL);
}

Listing 3: Setting up resources for communication to a client.

It is also important to realize that all handles are local to individual processes as well. For instance, it is not possible to share the handle to the SMSAvailableEvent between the MAPI Rule Client and the client application, even though parts of the client application are implemented inside the DLL that hosts the MAPI Rule Client functionality as well.

Client Side Processing to Capture SMS Messages

To be able to capture all send SMS messages in our managed application we create a worker thread inside the managed application that simply P/Invokes to a native function that waits for an event to be set. This mechanism to asynchronously call back into managed code is described in more detail in this MSDN article, titled "Asynchronous callbacks from native Win32 code".

When an SMS message is received by CMailRuleClient::ProcessMessage it copies the message contents to a memory mapped file and it sets an event for the waiting thread to become active. We are using memory mapped files as a means to transfer data between two separate processes. Once the event is set the message contents are copied once again to managed data types after which the managed application can process the received message. As soon as the managed worker thread is done processing, it calls back into the native function to wait for another SMS message to become available. Right now the example is simplified, because we can only handle one SMS message at a time. To make sure that simultaneously received SMS messages will be handled property we would have to implement some sort of a queuing mechanism. To keep the example simple and understandable we decided to ignore queuing SMS messages in the provided code. However, the provided example is a great starting point to capture SMS messages and can be easily modified to be able to handle simultaneously received SMS messages.

The following function exists in the MAPI Rule Client DLL. This function blocks until another SMS message is available. It then passes the SMS message contents on to the managed application that can further process them.

//////////////////////////////////////////////////////////////////////////////
// SMSMessageAvailable is used to pass data to the caller, whenever the 
// worker thread has data available.
//
// SMSMessageAvailable can be called from unmanaged code or via P/Invoke 
// from managed code.
//////////////////////////////////////////////////////////////////////////////
EXTERN_C BOOL SMSMessageAvailable (wchar_t *lpDestination, wchar_t *lpPhoneNr)
{
   WaitForSingleObject(g_hClientEvent, INFINITE);

   if (g_pClientSmsBuffer != NULL) {
      WaitForSingleObject(g_hClientMutex, INFINITE);
      lstrcpy(lpPhoneNr, g_pClientSmsBuffer->g_szPhoneNr);
      lstrcpy(lpDestination, g_pClientSmsBuffer->g_szMessageBody);
      ReleaseMutex(g_hClientMutex);
   } else {
      *lpPhoneNr = '\0';
      *lpDestination = '\0';
   }
   return *lpPhoneNr != '\0';
}

Listing 4: Client application, waiting for SMS messages inside the MAPI Rule Client DLL.

The managed client application creates a worker thread that simply signs up to receive SMS messages and enters a while loop. Inside the while loop the function SMSMessageAvailable is called that exists in native code, inside the MAPI Rule Client DLL. In this simple example, the received SMS message is displayed in a listbox on the hosting form. The following code snippet shows the managed worker thread inside the CaptureSMS client application.

/// 
/// This is the worker thread of the SMSListener class. After construction
/// of the object this class runs continuously, until SMSListener is disposed.
/// The worker thread P/Invokes into unmanaged code to SMSMessageAvailable.
/// Inside this function, execution is blocked until new (asynchronous) data
/// (an SMS message) is available. If data is it is simply passed to a
/// listbox, using Control.Invoke.
/// 
private void CheckForData()
{
    StringBuilder sbSms = new StringBuilder(255);
    StringBuilder sbPhoneNr = new StringBuilder(255);

    UnmanagedAPI.SMSMessageAvailable();
    while (! bDone) 
    {
        // P/Invoke into a native function that actually blocks until an 
        // SMS message is available
        if (UnmanagedAPI.GetSMSData(sbSms, sbPhoneNr)
        {
            MessageBox.Show(sbSms.ToString(), sbPhoneNr.ToString());
            if (sbSms.Length != 0) 
            {
                parentForm.ReceivedSMSMessage = sbPhoneNr.ToString() + 
                    " - " + sbSms.ToString();
                parentForm.Invoke(new EventHandler(parentForm.UpdateListBox));
            }
        }
    }
}

Listing 5: Managed worker thread that receives SMS messages.

There is one particular thing to notice in this worker thread. When an SMS messages is received the worker thread wants to display it in a listbox that is owned by the parentForm. Updating controls inside threads that do now own the particular control might lead to unexpected problems. Therefore a worker thread should never update user interface controls directly. Instead, it should use the form's Invoke method, passing a delegate to let the thread that owns the form update the UI control on behalf of the worker thread.

Building the Sample Code

The provided sample code consists of two separate projects. The mapirule project contains all native code, including the MAPI Rule Client plugin for the Inbox that captures SMS messages. This project must be built using embedded Visual C++ 4.0 which is available as a free download. Since native code is processor specific make sure to compile mapirule for the correct platform. The CaptureSMS project contains managed code only. To be able to build CaptureSMS you need to use Visual Studio.NET 2003. Since managed code is processor independent it is not necessary to specify a particular processor type when building SMSClient. However, to make sure that the mapirule client DLL is deployed together with CaptureSMS we add the mapirule.dll as content to CaptureSMS using the solution explorer inside Visual Studio.NET 2003. Since we now add processor dependent code to a processor independent solution you have to be aware of the target on which the sample code will be running.

Testing the Sample Code

Once both mapirule and CaptureSMS have been build successfully, there are two different ways to actually test the application. The first is to deploy the application to an actual SmartPhone 2003 device or a PocketPC 2003 Phone Edition device and use another cell phone to actually send SMS messages to the target device. Right before shipping this test should actually be executed. During initial stages of development there is a cheaper and easier way to test the code. Simply select an emulator with virtual radio support as target system. To be able to send SMS messages to the emulator target, a separate application called Call Events can be used. The application comes as sample code as part of this article titled "Creating call events in an emulation environment". Once you have installed the accompanying software and start the Call Events application, you can use this application to send SMS messages from the desktop to one of the Windows Mobile Emulators. Make sure you use an emulator with a virtual radio as target for the SMSClient client application. After the CaptureSMS application is started you can use Call Events to send SMS messages to it. Figure 2 shows both the managed application and the Call Events application in action.

Figure 2 - Sending SMS messages to a PocketPC 2003 Phone Edition emulator

Shortcomings

The documentation for the mapirule sample lists that the device has to be soft reset after uninstalling the mapirule.dll. This probably has to do with the fact that certain registry entries are only read during initialization of the device. The same turns out to be true when installing the mapirule.dll. As long as no SMS messages are received prior to installing mapirule.dll, the sample application shown in this article will work fine. However, if SMS messages have been received on the device prior to running the application, the mapirule client will not capture the SMS messages until the device has been soft reset.

Conclusion

Using the provided sample code it is fairly easy to capture SMS messages and process them in a client application, even if the client application is written in managed code. Most likely it will become a lot easier to handle SMS messages in applications when version 2 of the .NET Compact Framework is released, but given this example it is not necessary to wait for v2 to be released to already take advantage of SMS messages. The usage of SMS messages inside enterprise applications can be quite interesting. For instance it could be possible to maintain inventories by defining a particular message protocol, letting applications on mobile devices take action on specific SMS messages. It is beyond the scope of this article to describe such a scenario, but the given sample code is a good starting point for incorporating SMS messages inside managed applications on smart devices with phone capabilities.

About the Author

Maarten Struys works at PTS Software where he is responsible for the real-time and embedded competence centre. Maarten is an experienced Windows (CE) developer, having worked with Windows CE since its introduction. Since 2000, Maarten has worked with managed code in .NET environments. He is also a freelance journalist for the two leading embedded systems development magazines in the Netherlands. He recently opened a website with information about .NET in the embedded world at www.dotnetfordevices.com. Maarten has professionally evaluated the real-time behavior of the .NET Compact Framework, using a mix of managed and unmanaged code in combination with Windows CE .NET 4.1. The whitepaper on this subject was awarded by Microsoft with the WinHEC 2003 Whitepaper Award earlier this year.

'Windows > MFC' 카테고리의 다른 글

GetPhoneNumber  (0) 2013.10.02
GetDeviceID  (0) 2013.10.02
Need to intercept sms to launch another program  (0) 2013.10.02
Intercept SMS Messages In Native Code  (0) 2013.10.02
Sending mail using the Microsoft Windows CE Mail API  (0) 2013.10.02