Click here to Skip to main content
Click here to Skip to main content

Pocket PC and SmartPhone 99% .NET MAPI

, 28 Apr 2008
Rate this:
Please Sign up or sign in to vote.
A wrapper for using MAPI on the Pocket PC and SmartPhone, written 99% in .NET, resulting in a fast and easy-to-maintain library.

Screenshot - MAPIdotnet1.jpg

Introduction

My introduction to MAPI was to make what I thought would be a simple application to sort through the Inbox and Sent items folder on my PocketPC phone. I also wanted to display messages that had a common sender and recipient, a similar concept to Gmail conversations, MSN conversations, etc. C# was the obvious language of choice, as it makes coding on the PocketPC (well, all platforms in general, really) a piece of cake. This article is the first in a series documenting several projects that complete what turned out to be a huge solution to make the application essentially replace the standard mail viewing program that comes with PocketPCs and Smartphones. This article provides and documents the .NET wrapper for MAPI, covering how and why its 99% .NET form came about.

Those of you who have been looking at using MAPI for PocketPC projects will have seen that there are several other projects around. Of these, I have experimented with a couple extensively, which also helped get me have a greater understanding of MAPI when learning. However, in the end, they failed me mainly for two reasons: They usually didn't compile out of the box (minor issue really) and, most importantly, they were always heavily C++ orientated.

Being heavily C++ orientated was a downfall, as it meant that any extensions required for the MAPI library would have to be done in C. As it turned out, compared to my final 99% .NET product, this had a performance hit. I attribute the performance hit to the fact that the C++ libraries were primarily intended to be used from a C++ project (which makes sense), with the .NET wrapper being another abstraction on top of that.

Library Overview

Available Messaging Technologies

In Win32 (PC and PocketPC), there are predominantly two data models used by Microsoft to store information about messages, contacts, tasks, appointments, etc. (Outlook items): Pocket Outlook Object Model (POOM) and Messaging Application Program Interface (MAPI). The latter only supports messages.

POOM is the more "modern" of the two, while MAPI has been around for years. Pocket devices (PocketPC, Smartphone, etc.) have supported a subset of the full MAPI for years, while POOM is only just starting to come around.

Choosing a Messaging Technology

The first part of my ambitious project was to be able to access messages in C# on my phone. So, starting with a blank slate, I did what I always do when I want to do something I haven't done before in .NET: I headed straight to the class libraries. When I saw the Microsoft.WindowsMobile.PocketOutlook namespace, I thought I'd hit gold, until I looked a little closer. This namespace is Microsoft's .NET wrapper for POOM objects. While a powerful little namespace for accessing contacts, tasks, appointments, etc., it does not support messages. So, POOM was out of the question.

I, therefore, had to resort to looking at the native (Win32) APIs for pocket devices to access messages, which led me to find MAPI. A quick squish over MAPI made me realise I had hit the right thing: MsgStores, Folders, and Messages.

My first implementation -- which I am only going to mention briefly -- was similar to other people's implementations of having a large C++ library. This resulted in a large C++ library and a large C# wrapper. As the project grew and the required functionality increased, the number of .NET wrapper functions ballooned beyond excess. The library also ran rather slowly, as huge numbers of strings were marshalled between C++ and C#. To add insult to injury, after prolonged use, there was an apparent memory leak, which meant having to close the program regularly.

The entire project was ditched for the more favoured and obvious approach: to do everything in the .NET world, including creating and destroying objects and marshalling data to .NET.

How MAPI Works

MAPI on Win32 and Pocket devices provides wrapper functions around some database that, in the background, stores all the MAPI items. For interest's sake, on PocketPC devices, mail is still stored in the old CE database (the name escapes me right now...), with talk of it being migrated to the CE SQL database soon. Because of the database back-end, MAPI has a database-ish feel to it in that, to access data for a given object, table columns are first populated and sorted. Then the required number of rows are requested. To really get a good understanding of how MAPI works, I recommend having a look over the API, which is rather confusing unless you bear in mind that it's a database wrapper.

Doing Everything in .NET

In deciding to do everything in C# (.NET), there was still the low-level native interaction with the MAPI. As it turns out, MAPI only has a minimal amount of individual functions (such as initiating the first transaction, MAPILogonEx, which gets an IMAPISession) and has interfaces for each message item such as a MsgStore, Folder, Message, etc. After exhaustively trying to call native interface functions from C#, I had to succumb to the fact that I couldn't make a 100% .NET MAPI wrapper. The result is a C++ wrapper that wraps the individual MAPI interface member functions. For example, for the IMAPISession member function HRESULT GetMsgStoresTable(ULONG ulFlags, IMAPITable ** lppTable), the following function is produced:

HRESULT IMAPISessionGetMsgStoresTable(IMAPISession *pSession,
        IMAPITable ** lppTable)
{
    return pSession->GetMsgStoresTable(0, lppTable);
}

MAPIdotnet Library Structure

The C# MAPI library has two levels of abstraction:

Internal MAPI Wrapper Classes

For each of the native MAPI interfaces, there is a corresponding .NET interface and class that is responsible for holding onto and disposing off the native MAPI pointer. Whenever a native interface is returned (e.g., opening a folder returns a IMAPIFolder pointer, or requesting a MAPI data table), the corresponding C# wrapper class is created, which holds onto that pointer to ensure it isn't lost. When a C# wrapper class loses its references (ready for garbage collection), the corresponding MAPI pointer is released to ensure that there are no memory leaks. These wrapper classes and interfaces are in the MAPIdotnet.cemapi namespace. The following code shows how the base interface for all items, IMAPIUnknown, keeps track of the interface pointer:

public interface IMAPIUnknown
{
    void Release();

    IntPtr Ptr { get; }
}
private abstract class MAPIUnknown : IMAPIUnknown
{
    [DllImport("MAPILib.dll", EntryPoint = "Release")]
    public static extern uint pRelease(IntPtr iUnknown);

    protected IntPtr ptr = IntPtr.Zero;

    public void Release()
    {
        if (this.ptr != IntPtr.Zero)
        {
            pRelease(this.ptr);
            this.ptr = IntPtr.Zero;
        }
    }

    public IntPtr Ptr { get { return this.ptr; } }

    ~MAPIUnknown() { Release(); }
}

The same tight control on pointers is used for getting property data from an item. Like all Win32 Microsoft APIs, MAPI has a heavy use of structures for getting and setting properties. Rather than trusting the .NET Marshaller to marshal an entire structure (which I have had problems with when the structure's content gets complicated), the pointers are used directly with individual marshalling calls made to reassemble the structure in C#. For example, a common MAPI structure is the SPropValue structure whose form is:

struct {
    ULONG ulPropTag;
    ULONG dwAlignPad;
    union _PV Value;
} SPropValue, *LPSPropValue;

When getting and setting the properties of an object (e.g., message, folder, etc.), you either receive or pass in an array of SPropValues. So, rather than trusting the C# Marshaller, the pointer is used directly. For an array of structures, the first structure is marshaled, and then the pointer is incremented and so on. The pointer is finally released as specified by MAPI. A snippet from the cemapi.MAPIProp wrapper class is given below as an example:

public IPropValue[] GetProps(PropTags[] tags)
{
    // Populate the tags
    uint[] t = new uint[tags.Length + 1];
    t[0] = (uint)tags.Length;
    for (int i = 0; i < tags.Length; i++)
        t[i + 1] = (uint)tags[i];

    IntPtr propVals = IntPtr.Zero;
    uint count = 0;

    // Call the native MAPI wrapper interface member wrapper:
    HRESULT hr = pIMAPIPropGetProps(this.ptr, t, out count, ref propVals);
    if (hr != HRESULT.S_OK)
        throw new Exception("GetProps failed: " + hr.ToString());

    IPropValue[] props = new IPropValue[count];
    uint pProps = (uint)propVals;

    // Iterate over the received property array, using the pointer offset
    for (int i = 0; i < count; i++)
    {
        pSPropValue lpProp =
            (pSPropValue)Marshal.PtrToStructure((IntPtr)(
            pProps + i * cemapi.SizeOfSPropValue), typeof(pSPropValue));
        props[i] = new SPropValue(lpProp);
    }
    // Free the pointer
    cemapi.MAPIFreeBuffer(propVals);
    return props;
}

Exposed Interfaces

The library exposes a single MAPI class and a series of interfaces that correspond to MAPI items. Each implementation of an exposed interface, in most cases, has a corresponding cemapi wrapper interface. The self-explanatory list is as follows:

  • MAPI
  • IMAPIEntryID
  • IMAPIContact
  • IMAPIFolderID
  • IMAPIFolder
  • IMAPIMessageID
  • IMAPIMessage
  • IMAPIMsgStore
  • IMAPIProp

Folder, Message, and MsgStore each inherit from Prop. Prop exposes common properties such as DisplayName, EntryID (session-specific IDs publicly used for comparing objects), and display Icon (although Icons don't exist for at least SMS messages in my experience). IMAPIMessageID and IMAPIFolderID extend from IEntryID which contain specific IDs for messages and folders, respectively. IDs can then be used to open folders and messages. Each of the exposed interface implementations provides more "human" ways of getting and setting information. For example, rather than having to do a database request for a message subject or time, the IMAPIMessage interface has string Subject { get; } and DateTime LocalDeliveryTime { get; } properties, which do all the necessary conversions.

Using the Code

The solution provided here has three projects: The minimal C++ MAPI wrapper (MAPIlib), the C# MAPI wrapper (MAPIdotnet), and a sample project for use on PocketPCs and Smartphones to display messages and some of their properties (PocketMail).

Screenshot - MAPIdotnet2.jpg

If you get Exceptions when it tries to access the native Win32 MAPIlib.dll, it may be due to it not being copied into the directory when you deploy the solution. To do this, add an "Existing Item" to the PocketMail project and navigate to MAPIlib/bin/[Release or Debug]/MAPIlib.dll. Don't click "Open". Click on the little down arrow next to the "Open" button and select "Open As Link." Then, in the properties box for that file, select "Build Action = Content" and "Copy to Output Directory = Always".

Warning: I am currently in the process of making item events that occur when something changes (e.g., a new message arrives, a message is deleted, a message subject is changed, etc.) more "elegant", so I recommend ignoring cemapi.IMAPIAdviseSink.

Message Store Events

An IMAPIMsgStore can now register for events such as new messages arriving, message/folder changes/moves/copies/deletes. IMAPIMsgStore exposes three events. As well as subscribing to the exposed events for folders and messages, the different event types must be masked by setting EventNotifyMask. A little note, you'll quickly realise that registering for every possible event is a nightmare as about half a dozen are risen every time something happens!

Points of Interest

Performance

As I was writing the library, I was constantly worried that doing practically everything in C# would mean it would run like a dog. As it turns out though, I was wrong. On my phone, I have over 1500 messages in my inbox, and to fetch each of these (which involves creating at least two classes for each one and getting all the information for them) takes just over a second. In comparison, to build the tree of nodes (not even display) for the PocketMail program provided takes about ten seconds. Then, to add the populated nodes to the TreeView (TreeView.Nodes.AddRange(TreeNode[] nodes)) takes over thirty seconds!

Ongoing Development

MAPIdotnet now has a dedicated project on SourceForge here. The project page gives access to an SVN repository with latest additions and new projects. If you're also interested, feel free to contact me to become a developer with commit access.

History

  • 31/10/2007 - Initial release of MAPIdotnet.
  • 13/02/2008 - Update including addition of Message and Folder IDs, deleting messages, composing new messages, and message/folder/store events.
  • 28/04/2008 - Added details regarding SourceForge project details.

License

This article, along with any associated source code and files, is licensed under The BSD License

Share

About the Author

rwt33

New Zealand New Zealand
I have predominantly worked in C for embedded systems but in the last few years have been working with C# for the PC and Pocket devices.

Comments and Discussions

 
QuestionGet message from microSD PinmemberChincoCodus21-Nov-11 23:57 
AnswerRe: Get message from microSD Pinmemberrwt3322-Nov-11 9:01 
QuestionAdding sms Pinmemberquaitex12-Jul-11 21:27 
AnswerRe: Adding sms Pinmemberrwt3315-Jul-11 5:18 
QuestionDo you intend to add send-sms functionality? Pinmemberinamgul10-Feb-11 6:25 
AnswerRe: Do you intend to add send-sms functionality? Pinmemberrwt3314-Feb-11 8:38 
GeneralRe: Do you intend to add send-sms functionality? Pinmemberinamgul14-Feb-11 20:37 
QuestionIdentify SMS Folders for Dutch Mobile PinmemberQuipmentN31-Jan-11 0:16 
GeneralEmail body Pinmembertaximania15-Jan-11 22:58 
QuestionMAPI on Windows phone 7 PinmemberChincoCodus2-Jan-11 21:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140827.1 | Last Updated 28 Apr 2008
Article Copyright 2007 by rwt33
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid