http://www.developer.com/

Back to article

Mastering CEMAPI


July 15, 2004

In our recent era of global communications, providing some e-mailing capabilities to an application is more than "nice to have." Mobile applications are no exception here. WinCE e-mailing was available ages ago, since the first versions of WIndows CE were released. Since Pocket PC 2002, Microsoft has decided to change its working message store model and has introduced a new set of interfaces. eVC help proudly states:

"Windows CE Messaging Application Programming Interface (CEMAPI) is for mobile devices, what MAPI is for computers. CEMAPI provides a set of classes, interfaces, functions, and other data types to facilitate the development of messaging applications for mobile devices"

As usual, previous MsgStore functionality was dropped and forgotten. This article's main goal is to highlight how to work with CEMAPI and related stuff.

Getting Started

So what's CEMAPI? You have about 20 interfaces to manage message stores, sessions, filtering incoming messages, and so forth. When you try to work with all this, you'll quickly realize that that is going to be as almost the same fun as the OLE DB jungle. The Pocket PC 2003 SDK has several samples for CEMAPI, so you may get an initial feeling for what it is.

A typical working scenario is as follows:

  • Call the MAPIInitialize function.
  • Call the MAPILogonEx function.
  • Get a CEMAPI interface.
  • Call some CEMAPI methods.
  • Call the IMAPISession::Logoff method.
  • Call the MAPIUninitialize function.

All sets of interfaces may be groupped by their functionality. In the next sections, we will discuss mail folder management, message-related interfaces, and synchronization. You can find more details about CEMAPI here.

CEMAPI Session

The "entry point" of CEMAPI is the IMAPISession interface. It allows you to handle different tasks, among them managing message stores. The Windows CE Inbox is logically organized as a tree with accounts as high-level nodes. There is one default account called "ActiveSync," which gives you an opportunity to synchronize e-mails via MS ActiveSync utility. You also may create your own accounts. Later in this article, we will see how to do it programmatically. In the meantime, what we are interested in is that IMAPISession exposes methods to create, open, and enumerate message stores.

The following sample code illustrates how to open some message store in the Inbox. It uses wrapper classes from the attached sample project:

BOOL CMAPISession::EnumMsgStores(CMAPIEntriesArray& entries)
{
   ASSERT(m_pSession != NULL);
   if(m_pSession == NULL)
      return FALSE;

   IMAPITable* pMsgStoresTable;
   HRESULT hr = m_pSession->GetMsgStoresTable(MAPI_UNICODE, &pMsgStoresTable);
   TRACE(_T("CMAPISession::EnumMsgStores - GetMsgStoresTable: %X\n"), hr);
   if(FAILED(hr))
      return FALSE;

   return entries.Read(pMsgStoresTable);
}


CMAPIMsgStore* CMAPISession::OpenMsgStore(const MAPIEntry& entry)
{
   IMsgStore* pMsgStore;
   HRESULT hr = m_pSession->OpenMsgStore( 0,
      entry.arrId.GetSize(),              //entry id size
      (ENTRYID*)entry.arrId.GetData(),    //entry id bytes
      NULL,
      MDB_NO_DIALOG | MAPI_BEST_ACCESS,
      &pMsgStore);

   TRACE(_T("CMAPISession::OpenMsgStore(")
      + entry.sName + _T("): %X\n"), hr);
   if(FAILED(hr))
      return NULL;

   return new CMAPIMsgStore(pMsgStore,entry);
}

CMAPIMsgStore* CMAPISession::OpenMsgStore(CString sName)
{
   CMAPIEntriesArray entriesArr;
   if(!EnumMsgStores(entriesArr))
      return NULL;
   BOOL bCmp = FALSE;
   for(int i = 0; i < entriesArr.GetSize(); ++i)
   {
      if(entriesArr[i].sName.CompareNoCase(sName) == 0)    //compare
      {
         bCmp = TRUE;
         break;
      }
   }
   if(!bCmp)
   {
      TRACE(_T("CMAPISession::OpenMsgStore(")
         + sName +_T(") has no elements with this name\n"));
      return NULL;
   }
   return OpenMsgStore(entriesArr[i]);
}

CMAPIMsgStore* CMAPISession::CreateMsgStore(CString sName)
{
   CMAPIMsgStore* pMsgStore = OpenMsgStore(sName);
   if(pMsgStore !=NULL)
      return pMsgStore;

   IMsgStore* pStore;
   HRESULT hr = ((ICEMAPISession*)m_pSession)->CreateMsgStore(sName, &pStore );
   TRACE(_T("CreateMsgStore: %X\n"), hr);
   if(FAILED(hr))
      return NULL;
   pStore->Release();
   return OpenMsgStore(sName);
}

All of the above is simple enough, so we will move forward to message folders.

Managing Inbox Folders

CEMAPI dedicates a specific interface that allows the programmer work with mail folders—IMAPIFolder. It's some analogue of the file system folder; in other words, you're able to enumerate its contents, manage messages inside it, and so on. There are several standard folders: Inbox, Outbox, Deleted, Drafts, and Sent. Below is a tiny example of opening a folder via the IMsgStore interface:

...
//somewhere in the code
m_pMsgStore->OpenFolder(m_eBuiltInID));
...

CMAPIFolder* CMAPIMsgStore::OpenFolder(CMAPIFolder::BUILTIN_ID nFolderID)
{
   ULONG propIds[] =
   {
      PR_IPM_WASTEBASKET_ENTRYID,
      PR_CE_IPM_DRAFTS_ENTRYID,
      PR_CE_IPM_INBOX_ENTRYID,
      PR_IPM_OUTBOX_ENTRYID,
      PR_IPM_SENTMAIL_ENTRYID
   };

   return GetMessageFolder(propIds[nFolderID]);
}

CMAPIFolder* CMAPIMsgStore::GetMessageFolder(ULONG propTag)
{
   HRESULT hr;
   IMAPIFolder* pFolder;

   LPSPropValue rgprops = MAPIGetProperty(m_pStore,propTag);
   if(rgprops == NULL)
              return NULL;

   hr = m_pStore->OpenEntry(rgprops[0].Value.bin.cb,
       (LPENTRYID)rgprops[0].Value.bin.lpb, NULL, MAPI_MODIFY,
        NULL, (LPUNKNOWN*)&pFolder);
   if(FAILED(hr))
   {
      TRACE(_T("CMAPIMsgStore::GetMessageFolder OpenEntry: %X\n"),hr);
      MAPIFreeBuffer(rgprops);
      return NULL;
   }

   MAPIEntry entry;
   entry.SetId(rgprops[0].Value.bin);

   MAPIFreeBuffer(rgprops);
   rgprops = MAPIGetProperty(pFolder, PR_DISPLAY_NAME);

   if(rgprops == NULL)
   {
      pFolder->Release();
      return NULL;
   }

   entry.sName = rgprops->Value.lpszW;
   MAPIFreeBuffer(rgprops);

   return new CMAPIFolder(pFolder,entry);
}

As a common base, most interfaces in CEMAPI can be achieved via several other ones. IMAPIFolder is just one good example. It also supports folder enumeration:

BOOL CMAPIFolder::EnumFolders(CMAPIEntriesArray& entryArr)
{
   LPMAPITABLE pTable;
   HRESULT hr = m_pFolder->GetHierarchyTable(0,&pTable);
   if(FAILED(hr))
      return FALSE;

   return entryArr.Read(pTable);
}

Working with Messages

Well, now that we have successfully opened the desired folder in some message store, we are ready to figure out how to deal with the messages themselves. The IMAPIFolder interface has a full set of methods for message management. You may create, open, delete, copy, or move selected messages together with messages enumeration. To make your life easier, IMAPIFolder returns a IMessage interface as an e-mail message representation. Let's take a look at how it happens in the simplest examples:

CMAPIMessage* CMAPIFolder::CreateMessage()
{
   IMessage* pMessage;
   HRESULT hr = m_pFolder->CreateMessage(NULL, 0, &pMessage);

   if(FAILED(hr))
      return NULL;

   return new CMAPIMessage(pMessage);
}

BOOL CMAPIFolder::DeleteMessage(const MAPIEntry& entry)
{
   SBinary id = { entry.arrId.GetSize(), (BYTE*)entry.arrId.GetData() };
  ENTRYLIST ls = { 1, &id };

   HRESULT hr = m_pFolder->DeleteMessages(&ls, 0, NULL, 0);

   return !FAILED(hr);
}

With IMessage, we actually arrived to the "workhorse" of CEMAPI, the IMAPIProp interface. Via this interface, you may get a lot of message properties such as sender, body, recipients, and the like. You may find a long list of available properties in the mapitags.h header file. Below, you will find the sample implementation of how to retrieve a required message property:

LPSPropValue MAPIGetPropertyImpl(IMAPIProp* object, ULONG propTag,
                                 CString sPropName, CString sFile, int nLine)
{
   LPSPropValue pValue;
   ULONG count;
   SPropTagArray tags = { 1, { propTag } };
   HRESULT hr = object->GetProps(&tags, MAPI_UNICODE, &count, &pValue);
   if(FAILED(hr) || count != 1 || pValue->ulPropTag != propTag)
   {
      CString sLog;
      sLog.Format(_T("FAIL: MAPIGetProperty(%s=0x%08x) at %s:%d\r\n\t\
         HRESULT 0x%08x count %d ulPropTag = 0x%08x err = 0x%08x\n"),
         sPropName, propTag, sFile, nLine, hr, count, pValue->ulPropTag,
         pValue->ulPropTag == PT_ERROR ? pValue->Value.err : S_OK);
      TRACE(sLog);
      return NULL;
   }
   return pValue;
}

The sample project implements some of useful classes; for example, handling message recipients. You will find many examples of properties retrieving throughout the project, so we may just skip odd explanations here.

Managing Accounts

Another important point of CEMAPI is e-mail accounts maintenance. Until PocketPC 2003, there was no documented way to handle all needed stuff. Fortunately, in PocketPC 2003, the SDK Config Manager API was introduced. This API is presented by only one function:

HRESULT DMProcessConfigXML(LPCWSTR pszWXMLin, DWORD dwFlags,
                           LPWSTR *ppszwXMLout);

But it uses general XML input/output, thus allowing a wide range of configuration tasks. We will deal with EMAIL2 for our purposes. By the way, EMAIL2 is also a new challenge for the Pocket PC 2003 line. Now, the configuration becomes an almost trivial task; you just need to provide some XML file, and that's all. In MSDN, you may find the following sample:

<wap-provisioningdoc>
   <characteristic type="EMAIL2">
      <characteristic type="Some GUID should be placed here">
         <parm name="SERVICENAME" value="MyPOP"/>
         <parm name="SERVICETYPE" value="POP3"/>
         <parm name="INSERVER"    value="popserver"/>
         <parm name="OUTSERVER"   value="smtpserver"/>
         <parm name="AUTHNAME"    value="alias"/>
         <parm name="AUTHSECRET"  value="password"/>
         <parm name="DOMAIN"      value="domain"/>
         <parm name="REPLYADDR"   value="emailAddress"/>
      </characteristic>
   </characteristic>
</wap-provisioningdoc>

This example shows how to create a new e-mail account named "MyPOP". A full list of existing parameters may be found here. I'll just note that you must generate a new GUID to create each account. All the rest of the parameters are intuitive enough. Thus, you're set up with accounts.

Synchronization

The last thing we will speak about in this acticle is mail box synchronization. CEMAPI has IMailSyncHandler, an interface to synchronize message boxes. To actually get it working, you should do some tricks. Deep inside cemapi.h, you can discover the following definition:

typedef HRESULT (WINAPI *ONESTOPFACTORYFUNC)(LPCWSTR pszType,
                                             IMailSyncHandler** ppObj);

It declares a pointer to the function that returns an IMailSyncHandler instance according to service type—POP3 or IMAP4. The "magic secret" is to know that this function is located in MailTrns.dll. Thus, the required workaround is something like this:

HINSTANCE hMailTrns = LoadLibrary( _T("MailTrns.dll") );
if ( hMailTrns == NULL )
{
   // Load failed...
}
else
{
   ONESTOPFACTORYFUNC pOneStopFactoryFunc = (ONESTOPFACTORYFUNC)
                      GetProcAddress( hMailTrns, _T("OneStopFactory") );
   if ( pOneStopFactoryFunc == NULL )
   {
      // Error GetProcAddress OneStopFactory...
   }
   else
   {
      if ( pIMailSyncHandler == NULL )
         hr = (*pOneStopFactoryFunc)(_T("POP3"), &pIMailSyncHandler);
   }
}

Once you have an instance of the IMailSyncHandler interface, you are able to synchronize messages. The first step along this way is to call the Initialize method. It takes three parameters: pointer to callback interface, account name, and pointer to mail storage to be synchronized. The brilliant thing is that you should develop some implementation of the IMailSyncCallBack interface. It's not so awful as it may seem at first glance, but you have no chance to avoid additional development. A simplified example is presented below:

HRESULT CMailSyncCallBack::Progress( LPCWSTR pszProfile, SYNCPROGRESSITEM* pinfo)
{
   CString msg;

   m_pMainView->LogStatus( _T("CMailSyncCallBack::Progress(...)"));

   switch (pinfo->mask)
   {
   case SYNCPROGRESSITEM_STATUSTEXT:

      msg.Format( _T("Mail status: %s"), pinfo->pwszStatusText);
      m_pMainView->MessageNotify(msg);
      break;

   case SYNCPROGRESSITEM_STATUSTYPE:

      msg.Format(L"Mail status");
      m_pMainView->LogStatus(msg);
      break;

   case SYNCPROGRESSITEM_PROGVALUE:

      nCurrentMail = pinfo->ulProgValue; 
      msg.Format(L"mail number %d from %d",nCurrentMail,nTotalNewMail);
      m_pMainView->MessageNotify(msg);

      m_pMainView->ProgressNotify(100 * nCurrentMail/nTotalNewMail);
      break;

   case SYNCPROGRESSITEM_MAXVALUE:

      nTotalNewMail = pinfo->ulMaxValue;
      msg.Format(L"Number of mail's %d",nTotalNewMail);
      break;

   case SYNCPROGRESSITEM_DISCONNECTED:

      msg = _T("Mail disconnected");
      m_pMainView->LogStatus(msg);
      break;

   case SYNCPROGRESSITEM_TOTAL_NEW_MAIL:

      nTotalNewMail = pinfo->ulTotalNewMail;
      msg.Format(L"Total New mail %d",nTotalNewMail);
      m_pMainView->LogStatus(msg);
      break;

   case SYNCPROGRESSITEM_NEW_MESSAGE:

      msg.Format(L"Sync new message");
      m_pMainView->LogStatus(msg);
      break;
  }

   return S_OK;
}

HRESULT CMailSyncCallBack::LogEvent( TRANSPORTEVENT* pevt )
{
   CString msg;
   m_pMainView->LogStatus(_T("CMailSyncCallBack::LogEvent
                          (TRANSPORTEVENT* pevt)"));
   msg.Format(_T("  pevt->pszSourceDLL : %s"), pevt->pszSourceDLL);
   m_pMainView->LogStatus(msg);
   msg.Format(_T("  pevt->pszSourceProfile  : %s"),
              pevt->pszSourceProfile );
   m_pMainView->LogStatus(msg);
   msg.Format(_T("  pevt->hr : 0x%08x "), pevt->hr);
   m_pMainView->LogStatus(msg);

   if ( m_pIMailSyncHandler != NULL )
   {
      LPWSTR eventstr;
      m_pIMailSyncHandler->DecodeEvent( pevt, &eventstr );
      m_pMainView->LogStatus(eventstr);
   }
   return S_OK;
}

With some implementation at hand, you then may initialize IMailSyncHandler:

hr = pIMailSyncHandler->Initialize(pIMailSyncCallBack,
                                   _T("POP3"),pmsgStore);

The next step is to define the sync credentials and connect:

SYNCCREDENTIALS sc;
sc.cbSize      = sizeof(SYNCCREDENTIALS);
sc.cbBufSize   = 0;
sc.pszUsername = L"user"
sc.pszPassword = L"password";
sc.pszDomain   = L"domain";

hr = pIMailSyncHandler->Connect( 0, &sc );

After a successful connection, it's time to execute the sync and then disconnect:

MAILSYNCREQUEST msr;
msr.cbSize    = sizeof(MAILSYNCREQUEST);
msr.cbBufSize = 0;
msr.cbCookie  = 0;
msr.pbCookie  = NULL;
msr.ffFlags   = SYNC_NORMAL;
msr.objType   = 0;
msr.pid       = NULL;
msr.cbId      = 0;
msr.pval      = NULL;

m_pMainView->LogStatus(_T("Calling pIMailSyncHandler->Synchronize()\r\n"));
hr = pIMailSyncHandler->Synchronize( &msr );

m_pMainView->LogStatus(_T("Calling pIMailSyncHandler->Disconnect
                      (...)\r\n"));
hr = pIMailSyncHandler->Disconnect( 0 );

At this time, all messages will be received or sent. You may browse the message store to handle them accordingly.

Going Forward

In this article, we obviously did not touch a lot of CEMAPI capabilities. As usual, it's left as homework. But, cemapi.h will help you to investigate all the rest: Message Forms, Form Providers, Transports ... and more.

Download the sample file here.

About the Author

Alex Gusev started to play with mainframes in the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date