Wednesday, February 22, 2006

Opening the Address Book from .NET

The Problem
In today’s world, applications often to need to send e-mail messages. The .NET framework has a class for sending e-mail messages (System.Web.Mail.SmtpMail) that works pretty well (it has some shortcomings, but that is a topic for another article). In order to send an e-mail to someone, you need an e-mail address. Most people keep their e-mail addresses in a MAPI-compliant address book, like Outlook, Outlook Express, etc. The .NET framework, however, does not have a mechanism for opening and retrieving e-mail addresses from this address book. This is not good.

The Typical Solution
The typical solution to this problem is to use Simple MAPI’s MAPIAddress method to display the address book. This method works for most situations, but has one major drawback. It only works for ASCII text. There is not a Unicode version of Simple MAPI interface. If you display the address book and select a person with a Japanese (Chinese, Thai, etc.) name, your application will see their name as a series of questions marks (????).

The Extended MAPI Solution
The only way to support Unicode names is to use Extended MAPI. Unfortunately, Microsoft does not support calling the Extended MAPI library from .NET (see KB 813349). I have not personally tried to call the IAddrBook::Address method from .NET, but I have read enough newsgroup articles to think that it is probably not worth the effort.

My solution to this dilemma was to create an ATL/COM/Active X dll that wrapped the Extended MAPI call. I then called this dll from my .NET application using COM Interop. This solution worked very well for what I wanted, but it adds an extra two dlls to my application (oh well).

The Code
Here is the ATL code I wrote that makes the Extended MAPI calls.

// EasyMapi.cpp : Implementation of CEasyMapi

#include "stdafx.h"
#include "EasyMapi.h"

#include
#include ".\easymapi.h"

// CEasyMapi

// Constructor
CEasyMapi::CEasyMapi()
{
   MAPIInitialize(NULL);
}

STDMETHODIMP CEasyMapi::AddressBook(VARIANT_BOOL* Cancel,
   BSTR* AddressesSelected)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   // Local Variables
   HRESULT hResult = NULL;
   ULONG ulUIParam = NULL;
   ULONG ulFlags = 0;
   LPMAPISESSION lpSession = NULL;
   LPADRBOOK pAddressBook = NULL;
   ADRPARM adrparm;
   LPTSTR rglpszDestTitles[1];
   ULONG rgulDestComps[1];
   LPADRLIST pReceipients = NULL;
   CString ReceipientsList;

   // Begin
   *Cancel = VARIANT_FALSE;

   // Logon to MAPI
   ulFlags = MAPI_UNICODE | MAPI_LOGON_UI | MAPI_NO_MAIL |
      MAPI_EXTENDED;
   if(FAILED(hResult = MAPILogonEx(
      ulUIParam, NULL, NULL, ulFlags,
      (LPMAPISESSION FAR *) &lpSession )))
   {
      return hResult;
   }

   // Open AddressBook Object
   if(FAILED(hResult = lpSession->OpenAddressBook(
      ulUIParam, NULL, 0, (LPADRBOOK FAR *) &pAddressBook)))
   {
      return hResult;
   }

   // Display Dialog
   ulFlags = DIALOG_MODAL | MAPI_UNICODE;

   rglpszDestTitles[0] = (LPTSTR)("To");
   rgulDestComps[0] = MAPI_TO;

   adrparm.cbABContEntryID = 0;
   adrparm.lpABContEntryID = NULL;
   adrparm.ulFlags = ulFlags;
   adrparm.lpReserved = NULL;
   adrparm.ulHelpContext = 0;
   adrparm.lpszHelpFileName = NULL;
   adrparm.lpfnABSDI = NULL;
   adrparm.lpfnDismiss = NULL;
   adrparm.lpvDismissContext = NULL;
   adrparm.lpszCaption = (LPTSTR)"Address Book";
   adrparm.lpszNewEntryTitle = (LPTSTR)("");
   adrparm.lpszDestWellsTitle = (LPTSTR)("");
   adrparm.cDestFields = 1;
   adrparm.nDestFieldFocus = 0;
   adrparm.lppszDestTitles = rglpszDestTitles;
   adrparm.lpulDestComps = rgulDestComps;
   adrparm.lpContRestriction = NULL;
   adrparm.lpHierRestriction = NULL;

   if(FAILED(hResult = pAddressBook->Address(
      &ulUIParam, &adrparm, (LPADRLIST FAR *) &pReceipients)))
   {
      if (hResult == MAPI_E_USER_CANCEL)
      {
         *Cancel = VARIANT_TRUE;
         return S_OK;
      }
      return hResult;
   }

   // Parse Return Values
   if ((pReceipients != NULL) && (pReceipients->cEntries > 0))
   {
      for(unsigned int ReceipientIndex = 0;
         ReceipientIndex < pReceipients->cEntries;
         ReceipientIndex++)
      {
         if (pReceipients->aEntries[ReceipientIndex].cValues > 0)
         {
            if (ReceipientsList.GetLength() > 0)
            {
               ReceipientsList.Format(_T("%s, "),
                  ReceipientsList);
            }
            ReceipientsList.Format(_T("%s\"%s\" <%s>"),
ReceipientsList,
pReceipients->aEntries[ReceipientIndex].rgPropVals[1].Value.lpszW,
pReceipients->aEntries[ReceipientIndex].rgPropVals[2].Value.lpszW);
         }
      }
      *AddressesSelected = ReceipientsList.AllocSysString();
   }
   else
   {
      *AddressesSelected = NULL;
   }

   // Logoff MAPI (ignor any errors)
   lpSession->Logoff(0,0,0);

   return S_OK;
}


References
Differences between CDO, Simple MAPI, and Extended MAPI
http://support.microsoft.com/kb/q200018/

Microsoft Windows SDK - Simple MAPI
http://windowssdk.msdn.microsoft.com/library/en-us/mapi/
html/_mapi1book_simple_mapi.asp


Microsoft Windows SDK – MAPI – IAddrBook::Address
http://windowssdk.msdn.microsoft.com/library/en-us/mapi/
html/_mapi1book_iaddrbook_address.asp


Support policy for Microsoft Exchange APIs with the .NET Framework applications
http://support.microsoft.com/Default.aspx?id=813349

No comments: