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,
- The program starts a new MAPI Session without acquiring a new one.
- It opens a new MAPI user dialog if any authentication details are required.
- 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;
if(FAILED(hr = lpGAL->GetContentsTable(0L, &lpContentsTable)))
{
return hr;
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.,
- Initialize MAPI using
MapiInitialize
function.
- Login to MAPI using
MapiLogonEx
with a profile.
- Use the
IMAPISession
pointer to open the address book of the session with OpenAddressBook
.
- Get the
ENTRYID
to the Global Address List using HrFindExchangeGlobalAddressList
.
- Open the GAL using the
ENTRYID
.
- Now the table can be generated using the
GetContentsTable
function and the data can be displayed after using HRQueryRows
function.
- 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.
- 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.
- 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.
Using the Source Files
- Copy the MAPIUtils.h, MAPIUtils.cpp, MapiDataListCtrl.h and MapiDataListCtrl.cpp files to the project folder and Add them to the project.
- This project uses two icons
IDI_ICONDL
and IDI_ICONMAILUSER
for differentiating a user and a distribution list. Copy them to the project.
- Place a List control on the dialog with the OWNER DATA property set.
- Include the MAPIUtils.h in the Source file of the Dialog box and declare a variable.
- 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
- A call should be made to the
StartandLogon
function of the MapiUtils
class, with the profile to be loaded.
- 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.
- Call
InsertColumn
functions of the list control. The title of the columns should be one of the following:
- Name
- Phone
- Alias
- Email
- Location
- The reason is because the function
OnGetdispinfo
related to the message LVN_GETDISPINFO
, gets the data according to the column names as specified above.
- 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.
- 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.
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);
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="";
}
}
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.