MIME -> MAPI Conversion
Nicolas Galler | July 13, 2007Last project we had a requirement to read an email (as a regular rfc822 email message) and save it to a msg file so it could be readily opened with Outlook.
There is no interface to import a message in the Outlook Object Model so the only way appears to be using MAPI. There is some documentation on how to save a message object to a .msg file here: http://support.microsoft.com/?kbid=171907. To parse the rfc822 message was a bit trickier to find out, though – we need to use the IConverterSession api, and the documentation is not top.
Well this was also my first Managed C++ project. Not that it is that different from regular C++. Should be possible to do this with P/Invoke too, I reckon, but why bother? (Edit: as it turns out, there is a decent reason to bother, because the VC++ program requires an additional redistributable install. Now why is there a redist needed in addition to the .NET redist, I don’t know, but it really seems silly)
Here is the code:
// This is the main DLL file.
#include "stdafx.h"
#include "MimeToMapi.h"
#include "IConverterSession.h"
#ifndef MAPI_NO_COINIT
# define MAPI_NO_COINIT 8
#endif
using namespace System;
using namespace SSSWorld::MimeToMapi;
using namespace System::Runtime::InteropServices;
MimeToMapi::MimeToMapi()
{
String^ dirBefore = System::Environment::CurrentDirectory;
// the MAPI_NO_COINIT is needed when calling from the CLR otherwise
// MAPIInitialize randomly fails with "Unknown Flag" error
MAPIINIT_0 MAPIINIT= { MAPI_INIT_VERSION, MAPI_MULTITHREAD_NOTIFICATIONS | MAPI_NO_COINIT } ;
if(FAILED((res = MAPIInitialize(&MAPIINIT))))
{
throw gcnew ApplicationException(String::Format("Failed to initialize MAPI: {0}", res));
}
System::Environment::CurrentDirectory = dirBefore;
}
MimeToMapi::~MimeToMapi()
{
MAPIUninitialize();
}
void MimeToMapi::SaveAsMsg(System::String ^emlFilePath, System::String ^outputFilePath)
{
LPMESSAGE pIMsg = NULL;
LPSTORAGE pStorage = NULL;
LPMSGSESS pMsgSession = NULL;
LPSTREAM stream = NULL;
LPTSTR szDestPath = NULL;
LPMAPISESSION session = NULL;
HRESULT res = 0;
// get memory allocation function
LPMALLOC pMalloc = MAPIGetDefaultMalloc();
IConverterSession * converter = NULL;
stream = MimeToMapi::OpenStream(emlFilePath);
//converter->SetEncoding(IET_RFC1522);
try {
CoCreateInstance(CLSID_IConverterSession, NULL, CLSCTX_INPROC_SERVER, IID_IConverterSession, (LPVOID*)&converter);
szDestPath = static_cast(Marshal::StringToHGlobalUni(outputFilePath).ToPointer());
// create compound file
res = ::StgCreateDocfile(szDestPath,
STGM_READWRITE |
STGM_TRANSACTED |
STGM_CREATE, 0, &pStorage);
if(FAILED(res))
throw gcnew ApplicationException(String::Format("Error creating output file store {0}: {1}", outputFilePath, res));
// Open an IMessage session.
res = ::OpenIMsgSession(pMalloc, 0, &pMsgSession);
if(FAILED(res))
throw gcnew ApplicationException(String::Format("Error opening msgSession: {0}", res));
// Open an IMessage interface on an IStorage object
res = ::OpenIMsgOnIStg(pMsgSession,
MAPIAllocateBuffer,
MAPIAllocateMore,
MAPIFreeBuffer,
pMalloc,
NULL,
pStorage,
NULL, 0, 0, &pIMsg);
if(FAILED(res))
throw gcnew ApplicationException(String::Format("Error creating message on store: {0}", res));
converter->SetEncoding(IET_QP);
converter->SetSaveFormat(SAVE_RFC822);
converter->MIMEToMAPI(stream, pIMsg, NULL, 0);
pIMsg->SaveChanges(0);
} finally {
stream->Release();
if(pIMsg)
pIMsg->Release();
if(pStorage)
pStorage->Release();
if(converter)
converter->Release();
if(szDestPath)
Marshal::FreeHGlobal((IntPtr)szDestPath);
if(pMsgSession)
::CloseIMsgSession(pMsgSession);
}
}
LPSTREAM MimeToMapi::OpenStream(String^ source)
{
LPTSTR szSourcePath = NULL;
LPSTREAM stream;
HRESULT res;
try {
// NOTE - although the documentation states that OpenStreamOnFile accepts an LPTSTR string,
// it does not seem to accept the StringToHGlobalUni results
szSourcePath = static_cast(Marshal::StringToHGlobalAnsi(source).ToPointer());
if(FAILED(res = OpenStreamOnFile(MAPIAllocateBuffer,
MAPIFreeBuffer,
STGM_READ,
szSourcePath,
NULL,
&stream))){
throw gcnew ApplicationException(String::Format("Error opening file {0}: Error code {1}",
source, res));
}
return stream;
} finally {
if(szSourcePath)
Marshal::FreeHGlobal((IntPtr)szSourcePath);
}
}





