Click here to Skip to main content
Email Password   helpLost your password?

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:

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralRe: E_NOTIMPL when viewing Sent Items/Drafts message
rwt33
18:13 16 Jun '08  
Cool, that's useful to know too.
If only MS had written MAPI for a multithreaded environment and used semaphores etc...

Rob
Generalnative error
Shawrie
0:25 9 May '08  
Hi

ive been intergrating MAPIdotnet into a application i have been playing with. I start by simply retrieving the email accounts and displaying the name. It works fine and then moves on to the rest of my code. The problem is after a couple of minutes i get a native error pop up but no details on the error. If comment out the mapi code everything runs fine. Should i need to call a event like dispose when i have finished using the MAPI code?

Thanks Shawrie
GeneralRe: native error
rwt33
11:31 11 May '08  
These types of errors are always tricky. If the MAPI instance and any of its child instances go out of scope (ie no longer referenced and the garbage collector gets rid of them) it could mean something's not being disposed properly. The most likely thing I could think of would be events. Are you using MAPI events at all? The next step is to debug MAPIlib. This is a bit tricky but is doable.
On windows mobile you can debug native or .net code but not both at the same time (like you can in win32). To switch to native debugging detach from the .net process (Debug->Detach All) then attach to the native process: Debug->Attach to Process... then choose Transport=Smart Device, change to native code rather than .net and then select your program. This allows you to break during execution of the native lib (MAPIlib) and debug it.
Hopefully this helps.

Rob
GeneralRe: native error
Shawrie
6:15 12 May '08  
Hi

it seems to be when mapi dispose is called in the mapi.cs thati get the error. The dispose method has nothing in should it have?

It does look like that my app is running out of memory. basically i save the attachments to a folder and then import the data into a sql ce database.

Neil
GeneralRe: native error
rwt33
13:46 12 May '08  
Looking a bit closer I can see a potential for error. When MAPI is disposed the cemapi.MAPISession deconstructor first drops any references it has and then proceeds to logoff the session and then uninitialise MAPI (see cemapi.MAPISession deconstructor). At the moment I assume (what do they say about assumptions...) that although the references have been dropped to other mapi objects, the session will be logged off and uninitialised before the GC gets a chance to perform any outstanding Release (cemapi.Unknown.Release() which all MAPI objects come from).
To fix this is not quite as easy as it sounds. On one hand we could force a Dispose on any references the cemapi.MAPISession instance has but we can't be sure that 100% of MAPI objects have been released. Forcing the GC to do a collect (which can be done) would help but it's still highly possible an application could be holding a reference to MAPI object (ie a message or folder etc) which wouldn't get collected and when it tries to would give the same result. Any further ideas?

Rob
GeneralRe: native error
Shawrie
6:00 13 May '08  
Hi

ive been having a play around with the app. It runs the ~MAPISession in cemapi.IMAPISession.cs the second it finishes it gives the error. Ive noticed that it logs the session off with the SessionLogoff then clears up with the unknown release. Would it better to Unknown.Release() before SessionLogoff?

How should the MAPI be disposed is there anything i need to do in my app? Currently i dont do anything do it must be the GC that is attempting to do the clear up and failing.

Its very strange as everything is else is working great.

How do you force the gc to collect and i will give it a go

Shawrie
GeneralRe: native error
rwt33
11:39 13 May '08  
That's a very good point about the IUnknown.Release(). I had a little play last night and the simple answer was to comment out the Uninitialise in deconstructor.
Google forcing GC and there's tons of articles on the garbage collector.

Any luck with the Sourceforge repository yet? I plan to add a new application I've been working on soon.

Rob
GeneralRe: native error
Shawrie
8:34 14 May '08  
hi

ok i have had some joy. I have a vb .net program that calls a c# class that deals with reading the emails and stripping the attachments out. On the destructor of my class i put

GC.Collect();
GC.WaitForPendingFinalizers();

I also commented out the dispose from the deconstructor of the mapisession.

This no longer gives me the error i was getting. Ive still got some experimenting todo but at least its a step forward.
The only the problem i have is im not sure that it is truely cleaning up.

I have registered with sourceforge under shawrie318

thanks shawrie
GeneralRe: native error
awilbourn
15:31 5 Jul '08  
I am trying to find where resources are not being cleaned up and this sound like one area I may have over looked. According to the SDK from MSFT I would think the MAPIUninitialize should be last "The MAPIUninitialize function decrements the reference count, cleans up, and deletes per-instance global data for the MAPI DLL. "

The only way I have to clean up the component on Windows Mobile 5 and 2003 is to kill the process. Mobile 6 seems to handle it better and get rid of it.
GeneralMMS attachment problem
:_Rocket_:
19:01 6 May '08  
It a nice library.

I try to get attachment from MMS message.but it return nothing.

:_Rocket_:

GeneralRe: MMS attachment problem
rwt33
19:18 6 May '08  
I'm not entirely sure about an MMS message but I did have a similar problem once when using a hotmail account. In the normal messaging application it showed the attachment with only a half icon indicating the attachment(s) hadn't been downloaded. I didn't take it any further than that or to even check if an attachment was downloaded or not but that could be the issue.
I'll have a play with non-downloaded attachments and also mms attachments and get back to you. In the mean time, do you have the latest project code on SourceForge?

Rob
GeneralRe: MMS attachment problem
:_Rocket_:
19:53 6 May '08  
yes i already have from on SourceForge.

thanks Rob

:_Rocket_:

GeneralRe: MMS attachment problem
atix-group
8:43 30 May '08  
I have similar ploblem with MMSFrown Did you check it?
GeneralRe: MMS attachment problem
rwt33
12:07 3 Jun '08  
No I have not tried reading MMS messages to be honest. Let me know if you make any interesting discoveries.

Cheers,
Robert
GeneralLPATTACH type
Shawrie
4:39 25 Apr '08  
Hi

sorry im back again with a question.
There is a type called LPATTACH ( which i think is from IAttach) which allows a pointer to the attachment so i can open the attachment. How can i declare this in a c# project?

Its probably easy but its doing my nut in now.

thanks
GeneralRe: LPATTACH type
rwt33
12:57 27 Apr '08  
Once you've looked through an attachment table (as received from IMessage::GetAttachmentTable) you need to open the attachment(s) of interesting using IMessage::OpenAttach. This will obviously need an entry in MAPIlib etc.
This would be declared in MAPIdotnet.cemapi as its own interface/implementation in MAPIdotnet.cemapi and then in MAPIdotnet to reflect the mapi interface IAttach. It's worth looking at other implementations such as MAPIdotnet.cemapi.IMessage for an example on how these are implemented.

Good luck.
Let me know how you get on. I would be interested in adding this to the SourceForge repository once done.

Robert
GeneralRe: LPATTACH type
Shawrie
4:28 28 Apr '08  
Hi

thanks for that. I haved added the OpenAttach in all the right places but it requires a variable of a type LPATTACH but i just cant find where to set this data type up.

Neil
GeneralRe: LPATTACH type
rwt33
11:54 28 Apr '08  
I had a good go at this last night and am almost ready to commit the changes made to access attachments to the SourceForge repository. The code just needs a few tidy ups at the moment but can successfully read/write file attachments. The additions also include accessing the message body.

Are you still interested in becoming a developer on the SourceForge project? If you send me your sourceforge account name (if you don't have one just go to their site and register one) I'll grant access. Looking forward to having someone help with code additions!

Robert
GeneralRe: LPATTACH type
Shawrie
23:58 1 May '08  
hi

how you getting on with it? Is there any chance i can take a look at what you have so far?

Ill set my sourceforge account up this weekend and let you the details.

thanks again
Shawrie
GeneralRe: LPATTACH type
rwt33
18:22 5 May '08  
Take a look at the svn repository on the sourceforge page, I recently committed a large amount of work. Including attachments etc.

Rob
GeneralDeleting a message
DarrenD
11:09 24 Apr '08  
Hello. I am having a problem deleting an existing message in the inbox. Here is my code.

private void DeleteSMS(IMAPIMessage msg, IMAPIFolder folder)
{
folder.DeleteMessage(msg.MessageID);
}


Basically, I pass the msg and the folder that contains that message and call DeleteMessage. It doesn't seem too difficult, but every time it gets into the DeleteMessage method it gets stuck on HRESULT hr = pIMAPIFolderDeleteMessages(this.ptr, pMsg); and gets a MissingMethodException. Can't find an Entry Point 'IMAPIFolderDeleteMessages' in a PInvoke DLL 'MAPIlib.dll'. Here is the DllImport:

[DllImport("MAPIlib.dll", EntryPoint = "IMAPIFolderDeleteMessages")]
private static extern HRESULT pIMAPIFolderDeleteMessages(IntPtr folder, IntPtr lpMsgs);


I have used SortMessagesByDeliveryTime and SortMessagesBySenderName with no problem (which also use IMAPIFolder). Any suggestions?

Thanks for your time,
Darren
GeneralRe: Deleting a message
Shawrie
23:49 24 Apr '08  
Hi

Have you added a entry in the MAPIlib.h and MAPIlib.cpp? You will then need to redeploy the MAPILib.dll to your device.

For example i have added a routine to get the attachment table. So in the

MAPIlib.h i added

AFX_EXT_CLASS HRESULT IMessageGetAttachmentTable(IMessage *msg,IMAPITable **table);

MAPIlib.cpp

HRESULT IMessageGetAttachmentTable(IMessage *msg,IMAPITable **table)
{
return msg->GetAttachmentTable(0,table);
}

Hope that points you in the right direction

Shawrie
GeneralRe: Deleting a message
DarrenD
4:20 25 Apr '08  
The MAPIlib.h and the MAPIlib.cpp already contain entries for Deleting Messages.
AFX_EXT_CLASS HRESULT IMAPIFolderDeleteMessages(IMAPIFolder *folder, LPENTRYLIST lpMsgList);
and
HRESULT IMAPIFolderDeleteMessages(IMAPIFolder *folder, LPENTRYLIST lpMsgList)
{
return folder->DeleteMessages(lpMsgList, 0, NULL, 0);
}

respectively. So, I'm assuming the MAPIlib.dll was already built with that added. That's why I'm confused on why I am getting this error. Has anyone else deleted messages that were successful in doing it?

Darren
GeneralRe: Deleting a message
DarrenD
6:59 25 Apr '08  
I finally got it. I had to rebuild the dll from the given project files. I came across a few errors; specifically I had to add the cemapi.lib and the aygshell.lib files to the project (I'm not sure both files were needed, but cemapi.lib file was) and I also had to set the project properties - Linker - Command Line - Additional Properties to
/subsystem:windowsce,5.01/machine:THUMB
instead of
/subsystem:windowsce,4.20 /machine:ARM /ARMPADCODE
since I'm using CE version 5 and not 4.2 (I would assume). I hope this helps anyone else that is coming across similar issues. Excellent job on this by the way. This has been very helpful.

Darren
GeneralSOURCEFORGE PROJECT PAGE
rwt33
12:05 23 Apr '08  
Hi all.
Brilliant news, due to the popularity and the ongoing development and debugging being done by many, I've setup a Sourceforge project page for MAPIdotnet http://sourceforge.net/projects/mapidotnet/[^]. The project at time of writing supports SVN access of the current lib and the simple example application. More applications (some that I've been working on/using) coming soon.

Those interested in contributing svn commits or generally administering the website let me know.

Rob


Last Updated 28 Apr 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010