Click here to Skip to main content
15,868,016 members
Articles / Desktop Programming / MFC
Article

Large Address book in MAPI

Rate me:
Please Sign up or sign in to vote.
4.31/5 (8 votes)
7 Dec 20048 min read 120.1K   1.1K   38   22
Explains how to display the list of users in a Global Address Book in a large organization using a Virtual List Control.

Working with MAPI will be difficult for any one until one learns some basics of it. This article is going to explain some different methods of accessing the address book data using Extended MAPI.

Extracting the data from a smaller address list will never give any problems. But if the address book of the organization is very large (more than 5000), then we need some special handling of this address book. In my case, I had to handle an address list of more than a lakh entries. This article explains how to extract info from a small address list and also a large address list.

Requirements

This article explains the development of Address book apps for Exchange Server 5.5 using Exchange Development Kit (Exchange SDK) and with Microsoft Visual C++ 6.0. The Exchange SDK can be downloaded from Microsoft Exchange home page.

Basics of Working with MAPI

Any MAPI application and Exchange related programs first need a profile to be created. The MAPI profile can be created through the Control Panel --> Mail option. To do this one has to have an Exchange mail-box account created with one of the Exchange servers in the organization.

The next step in a MAPI Program is to initialize the MAPI libraries with MAPIInitialize function. When the program exits, it should call the MAPIUninitialize function. Very frequent calls to these two functions should be avoided as this could result in a memory overhead.

Logging into MAPI

This is the second step in a MAPI program. The MAPILogonEx function can be used (supply the profile name) with the MAPI_EXTENDED| MAPI_NEW_SESSION| MAPI_LOGON_UI| MAPI_EXPLICIT_PROFILE flags set. These flags ensure that,

  1. The program starts a new MAPI Session without acquiring a new one.
  2. It opens a new MAPI user dialog if any authentication details are required.
  3. It does not use the default profile.

Once the session is established, the session pointer can be used throughout the program to do a lot of jobs the outlook can do. In fact, it can also be used to create/modify the Distribution lists (provided, these are manipulated under the same Exchange site as the user is present), Viewing/Modifying/Creating public folders (if the user has access) etc.

Understanding IMAPITable

This IMAPITable is the standardised interface through which all the MAPI related data are extracted. Some of the places where this can be used are Address Book, Folders list, Mail Messages, Appointments etc. The samples in this article uses this tables for displaying Address Book data. This IMAPITable should be used along with structure like SRowSet and functions like IMAPITable->QueryRows or IMAPITable- >HrQueryAllRows etc., These two functions will retrieve the data from the table and populate the data as an SRowSet array. Though it may not be clear at this point, the following code snippet may be of help.

LPSRowSet pRow;
LPMAPITABLE lpContentsTable = NULL;
LPABCONT lpGAL = NULL;
/*.. Write the code to get a pointer to the Global Address List(GAL)
....
*/
//Use the GAL pointer to retreive the IMapitable
if(FAILED(hr = lpGAL->GetContentsTable(0L, &lpContentsTable)))
{
    return hr;
}
//Get the data from the table into the pRow - SRowSet pointer
if ( FAILED ( hr = HrQueryAllRows (lpContentsTable,
            (SPropTagArray*) &sptCols, NULL,     NULL, 0,&pRow)))
{
    return hr;
}

Understanding ENTRYID

Exchange Server stores each of the objects with an entry ID. Every object created inside Exchange Server including Mailboxes, Distribution Lists, Folders, Messages etc., will be assigned an entry id. An interface to the IMailBox, IDistList, IMapiFolder, IMessage etc., can be obtained by calling the OpenEntry function with the ENTRYID of the corresponding object.

Property Tags in MAPI

Exchange keeps the properties of the objects in reference to Property Tags. The property tags are Hexa decimal values defined with Programmer understandable tags starting with PR_ . For example PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ENTRY_ID are used to store the display name, email address and entry id of the objects. This can be roughly equated with the Column names of a database table.

Working with a Smaller Address List

This is quite an easier operation. It is enough if the basic steps of MAPI are done. i.e.,

  1. Initialize MAPI using MapiInitialize function.
  2. Login to MAPI using MapiLogonEx with a profile.
  3. Use the IMAPISession pointer to open the address book of the session with OpenAddressBook.
  4. Get the ENTRYID to the Global Address List using HrFindExchangeGlobalAddressList.
  5. Open the GAL using the ENTRYID.
  6. Now the table can be generated using the GetContentsTable function and the data can be displayed after using HRQueryRows function.
  7. End of all jobs, free all the used memory and Logoff from MAPI session. The attached sample project MAPIAddressList_demo.zip can be used as a reference. Though the given demo app is a console application, it can very easily be ported to a MFC GUI application. This is attached as a separate ZIP file within the Demo project download.

Working with Larger Address Lists

This is when some care should be taken while writing GUIs. If the application is a small console application which is run in batch mode, the same code as Mapi_small_AddressList_demo.zip will be acceptable and the speed issues can be compromised.

But while writing GUIs which involve lakhs of records of mailboxes and distribution lists, the following points should be considered.

  1. The data cannot be kept in memory in any data structure as it have a huge impact on the performance of the application and the computer.
  2. This data cannot also be loaded on to a grid or list box or list control as these controls will also experience some performance issues.

The best alternative will be to query the Exchange Server for data in small packets of 1 row or 10 rows. Though this will have impact on the network traffic, the impact will not be large.

So in cases like this, we will have to use the Owner drawn controls of a list box or a Virtual List control using OWNER DATA.

Using a Owner Draw ListBox for showing Data

There is already a sample provided by Microsoft on this regard. The sample name is called "MAPI Address Book" or "ABVIEW". There is a small bug in this code. It uses an integer variable to get and use the Row numbers. As an integer cannot handle a more than 32K odd numeric value, I ran into trouble while using it on my code. I had an address book which is more than 1 lakh in size. But this can be solved very easily. Just replace these integers with long data type variables. This class will serve the purpose if only the name has to be displayed in the application as one column is enough.

Using virtual List controls for showing Multiple columns

A list control (CListCtrl) is used when there is a need to show Multiple data like the User Display Name, email address, House Phone numbers, Office Phone Numbers etc., The performance gain using this kind of list control is visible only if we work with larger address lists. I could see a huge performance gain with our production servers where we have a lakh records and the servers keep a replicated GAL at each site. But if the servers are placed remotely, refreshing each row by getting the data from the servers can be very very slow.

Image 1

Using the Source Files

  1. Copy the MAPIUtils.h, MAPIUtils.cpp, MapiDataListCtrl.h and MapiDataListCtrl.cpp files to the project folder and Add them to the project.
  2. This project uses two icons IDI_ICONDL and IDI_ICONMAILUSER for differentiating a user and a distribution list. Copy them to the project.
  3. Place a List control on the dialog with the OWNER DATA property set.
  4. Include the MAPIUtils.h in the Source file of the Dialog box and declare a variable.
  5. Add a call to MAPIInitialize once at the beginning of the application. While exiting the application, call MAPIUninitialize() function.

Getting the Profiles installed in the machine

A normal allocation to the MapiUtils class will load the profiles to the CArray variable MapiUtils::m_ProfileList. This array can be iterated to retrieve the list of Profiles installed in the system.

Loading the List Control

  1. A call should be made to the StartandLogon function of the MapiUtils class, with the profile to be loaded.
  2. Declare a Pointer variable to the MapiDataListCtrl class and allocate memory to it. Pass the MapiUtils:: m_pContentsTable pointer to the constructor and call the SubclassDlgItem function to assign the control to the class.
  3. Call InsertColumn functions of the list control. The title of the columns should be one of the following:
    • Name
    • Phone
    • Alias
    • Email
    • Location
  4. The reason is because the function OnGetdispinfo related to the message LVN_GETDISPINFO, gets the data according to the column names as specified above.
  5. If adding the column is being done for the first time, call the InitListBox function. Call ChangeCount() function of the list control, any time after this. This will start loading the data automatically.
  6. Do not forget to call stopandlogoff while closing the application.

Note: The project uses a PropTagArray as follows. This is used in the MAPIUtils.cpp file.

SizedSPropTagArray ( 8, sptCols ) = { 8,
                PR_ENTRYID,PR_DISPLAY_NAME,PR_ACCOUNT,
                PR_OBJECT_TYPE,PR_OFFICE_LOCATION,PR_COMPANY_NAME,
                PR_EMAIL_ADDRESS,PR_OFFICE_TELEPHONE_NUMBER};

These property tags are the ones which are treated similar to column names in the Database tables. So the Display name property is accessible from PR_DISPLAY_NAME tag, Alias name is from PR_ACCOUNT tag and so on. If more data is needed, the property tags supported are available at MSDN Property Tags link.

After adding the property tags, add a column to the list control and add the code inside the OnGetdispinfo function as follows.

//Do the list need text information?
if (pItem->mask & LVIF_TEXT)
{
        CString l_strText;

    int l_iRet;
    LVCOLUMN pColumn;
    char strBuffer[100];

    pColumn.mask=LVCF_TEXT;
    pColumn.pszText=strBuffer;
    pColumn.cchTextMax=sizeof(strBuffer);

    l_iRet = GetColumn(pItem->iSubItem,&pColumn);
    //... Retain the code as it is till this point
    //Add this code snippet
    else if(strcmpi(pColumn.pszText,"NewColumnName")==0 && ( lpRows->cRows > 0))
    {
        LPSPropValue lpDN = PpropFindProp(lpRows->aRow[0].lpProps,lpRows->aRow[0].

                 cValues,PR_NEW_PROPERTY);
        lpDN != NULL ? l_strText = lpDN->Value.lpszA : l_strText="";
    }
    //..Add the rest of the code as in the examples
}

Include Files and Libraries

Ensure that the edk.h is included in the project.

Link to the following libraries:

  • Edkguid.lib
  • Edkutils.lib
  • Edkmapi.lib
  • Addrlkup.lib
  • Edkdebug.lib
  • Mblogon.lib
  • mapi32.lib
  • Msvcrt.lib
  • Version.lib

Visit the CoderSource.net site for some more articles.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalhelp me to compile the project Pin
rshakirov1-Apr-09 3:08
rshakirov1-Apr-09 3:08 
AnswerRe: help me to compile the project Pin
elf_cash12-Jun-09 5:26
elf_cash12-Jun-09 5:26 

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

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