Working with MAPI Objects and Data Types

In order to become familiar with how MAPI works, it is important to first understand some MAPI-specific data types and interfaces that are consistently used when working with an e-mail message store. This section focuses on MAPI entry identifiers, MAPI tables, MAPI object properties, and MAPI memory allocation. Regardless of what you do with MAPI, understanding how data is stored and accessed is the key to writing a good, e-mail-friendly application.

Entry Identifiers

As you might have already guessed by its name, a MAPI entry ID is a unique identifier assigned by a message store to identify an individual object. Almost every item in MAPI has a unique identifier associated with it, and is specified by using the ENTRYID structure. Some objects that have an entry identifier include message stores, folders, messages, attachments, and distribution lists.

The ENTRYID structure, which will typically be read-only for a client application, is defined as follows:

typedef struct {
   BYTE abFlags[4];

The first field, abFlags, is a four-byte flag that is used to describe the type of entry identifier. The first byte, abFlags[0], can be set to one of the following values:

  • MAPI_NOTRECIP indicates that the entry cannot be used as a recipient of a message.

  • MAPI_NOTRESERVED indicates that the entry identifier can be reused by other sessions.

  • MAPI_NOW indicates that the entry identifier can be used only now.

  • MAPI_SHORTTERM indicates that the entry identifier is guaranteed to be unique only during the current session.

  • MAPI_THISSESSION indicates that the entry identifier cannot be used by other sessions.

The remaining three bytes of the abFlags field are reserved. The other field, ab, is an array of binary data that represents the unique ID for the entry.

As you will see in the section "Accessing MAPI Objects," you use the ENTRYID structure to reference particular entries in a message store. For example, it is used as a parameter to the ::OpenEntry() method that many of the MAPI objects implement, enabling you to get a pointer to the interface for the particular object to which the ENTRYID refers.

Remember that the binary data inside an ENTRYID structure is used only internally by a message store, and should be considered only a reference to an object (the data has no meaning outside the message store). This being the case, you cannot determine if two ENTRYID structures represent the same object just by simply comparing the binary data?you need to use the IMAPISession::CompareEntryIDs() function, described later in this chapter.

Table 11.4 describes the macros that MAPI provides to help you more efficiently work with the ENTRYID structure.

Table 11.4. ENTRYID Macro Functions




Calculates the number of bytes for a new ENTRYID of a specified size

SizedENTRYID(_cb, _name)

Creates a new ENTRYID of a specified size

MAPI Object Properties

Almost every object found within MAPI uses a set of attributes that describe the object and provide more information about it (such as the subject of a message, or an object identifier). These attributes are also known as properties.

Every property has a unique property tag that can be used to identify it (for example, the PR_ENTRYID tag is used for the entry identifier of an object). A property tag is a 32-bit number that consists of two parts: a property type and a property identifier. Every property tag is prefixed with the letters PR_. As you will see in the next section, properties are also used by MAPI tables to represent individual columns of data.

The property type is specified in the lower 16 bits of the property tag, and is used to identify the data type of the property. Table 11.5 describes the valid property types.

Table 11.5. MAPI Property Data Types

Property Type



16-bit Boolean


Signed 32-bit value


Embedded object in a property


Application time value


Counted byte array




Signed 64-bit integer value


Floating point value


32-bit error value


4-byte floating point value


8-byte signed integer value


NULL value


Signed 16-bit value


8-bit character string


FILETIME 64-bit integer


Unicode string



The remaining 16 bits of a property tag are used for the property identifier, and fall within the range of 0x0001?0xFFFF.

Table 11.6 describes the macros that MAPI provides to help you efficiently work with object properties.

Table 11.6. Property Macro Functions




Gets the property identifier for a property tag

PROP_TAG(ulPropType, ulPropID)

Gets the property tag for a specific property identifier and type


Gets the property type for a property tag

For example, if you wanted to retrieve the property identifier and type for a value that was returned from SRowSet (more on SRowSet later in this chapter), you could do the following:

TCHAR tchProperties[10] = TEXT("\0");
DWORD dwType = 0, dwID = 0;

dwType = PROP_TYPE (pRowSet->aRow[0].lpProps[nVal].
dwID = PROP_ID(pRowSet->aRow[0].lpProps[nVal].ulPropTag);
wsprintf(tchProperties, TEXT("0x%08x Type:%04x ID:%04x\r\n"),
   pRowSet->aRow[0].lpProps[nVal].ulPropTag, dwType, dwID);
Accessing Object Properties

On Pocket PC, the IAttach, IMAPIContainer, IMessage, and IMsgStore interfaces are derived from the IMAPIProp interface. The IMAPIProp interface provides the implementation details for getting and setting property values, as well as deleting them. It supports the methods described in Table 11.7.

Table 11.7. IMAPIProp Methods




Deletes one or more properties


Gets the property identifiers for specific property names


Gets specific property values


Gets a pointer to an interface to access property values, instead of using the IMAPIProp::GetProps() and IMAPIProp::SetProps() interfaces


Sets specific property values

Two functions are used to retrieve property values from an object. To simply return one or more property values from a MAPI object to your application, you can use the IMAPIProp::GetProps() function:

HRESULT IMAPIProp::GetProps(LPSPropTagArray lpPropTagArray,
  ULONG ulFlags, ULONG *lpcValues, LPSPropValue

The first parameter, lpPropTagArray, is a pointer to an SPropTagArray structure that contains an array of property tags that you are interested in retrieving from the object. This is followed by ulFlags, which should be set to the MAPI_UNICODE flag. The lpcValues parameter should point to a ULONG value that will be filled in with the number of properties returned. Finally, the lppPropArray parameter points to an SPropValue array that contains the returned property values. Note that property values are returned in the same order that is specified in the lpPropTagArray structure.

The SPropTagArray structure that is used to list the properties you are interested in is defined as follows:

typedef struct _SPropTagArray {
   ULONG cValues;
   ULONG aulPropTag[MAPI_DIM];
} SPropTagArray, FAR *LPSPropTagArray;

The structure contains only two fields: an array of property tags specified by the aulPropTag field, and the number of items in the array, which should be specified by the cValues member.

The SPropValue structure that is filled in with the property values is defined as follows:

typedef struct _SPropValue {
   ULONG ulPropTag;
   ULONG dwAlignPad;
   union _PV Value;
} SPropValue, FAR *LPSPropValue;

The first field, ulPropTag, specifies the property tag for the property. The Value field is filled in with the specific value based on the data type for the property. The dwAlignPad field is used by MAPI to ensure that the structure has proper memory alignment, and should not be tampered with.

Table 11.8 describes the macros that MAPI provides to help you build SPropTagArray structures.

Table 11.8. SPropTagArray Macro Functions




Calculates the number of bytes for a new SPropTagArray structure


Calculates the number of bytes of an existing SPropTagArray structure

SizedSPropTagArray(_ctag, _name)

Creates a named SPropTagArray structure for a specific number of property tags

The following example shows how you can use the IMAPIProp::GetProps() function to return the ENTRYID and OBJECT_TYPE properties for a MAPI folder:

LPSPropValue rgprops = NULL;
LPSPropValue lppPropArray = NULL;
ULONG cValues = 0;
IMAPIFolder *pPOPInboxFolder = NULL;
SizedSPropTagArray(2, rgTags) =

// Now get the Inbox folder
hr = pPop3Store->GetProps((LPSPropTagArray)&rgTags,
   MAPI_UNICODE, &cValues, &rgprops);

If you require the capability to open a large property, such as a message attachment or message body, you need to use the IMAPIProp::OpenProperty() method to access it. This function opens a property value and uses an IStream interface for reading the property data in large blocks.

   lpiid, ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN

The first parameter, ulPropTag, should specify the property tag that you are interested in. The lpiid and ulInterfaceOptions parameters are not supported on Pocket PC, and are ignored. The ulFlags parameter determines the access mode of the property value you are opening. The default value opens as read-only. If you need to open it for reading or writing, you should use the MAPI_MODIFY flag. The last parameter, lppUnk, receives a pointer to the interface for the IStream object when the function returns.

Modifying and Deleting Property Values

To delete a property value, you can call the IMAPIProp::DeleteProps() function:

HRESULT IMAPIProp::DeleteProps(LPSPropTagArray
  lpPropTagArray, LPSPropProblemArray *lppProblems);

The first parameter should point to an SPropTagArray that specifies the property tags you want to delete. The lppProblems parameter, which is optional, can point to an SPropProblemArray, which will provide you with detailed information regarding any errors that occur when the function returns. If you are not interested in the problem list, set this value to NULL.

The SPropProblemArray structure contains an array of SPropProblem structures. It is defined as follows:

typedef struct _SPropProblemArray {
   ULONG cProblem;
   SPropProblem aProblem[MAPI_DIM];
} SPropProblemArray, FAR *LPSPropProblemArray;

The cProblem field contains the number of SPropProblem structures that are in the array specified by the aProblem field.

An SPropProblem structure contains information about a specific problem that occurred. The structure has the following definition:

typedef struct _SPropProblem {
   ULONG ulIndex;
   ULONG ulPropTag;
   SCODE scode;
} SPropProblem, FAR *LPSPropProblem;

The first field, ulIndex, indicates the index of the item in the lpPropTagArray that was passed into the IMAPIProp::DeleteProps() or IMAPIProp::SetProps() function containing the error. The ulPropTag field indicates the property tag that caused the error, and the scode field indicates the error value.

Table 11.9 describes the macros that MAPI provides to help you work with SPropProblemArray structures.

Table 11.9. SPropProblemArray Macro Functions




Calculates the number of bytes for a new SPropProblemArray structure


Calculates the number of bytes of an existing SPropProblemArray structure

SizedSPropProblemArray(_cprob, _name)

Creates a named SPropProblemArray structure for a specific number of SPropProblem structures

Use the following to change or set a property value:

HRESULT IMAPIProp::SetProps(ULONG cValues, LPSPropValue
  lpPropArray, LPSPropProblemArray *lppProblems);

The first parameter, cValues, is the number of property values in lpPropArray (and cannot be 0). The lpPropArray parameter should point to an array of SPropValue structures that contain the new or modified property values. Finally, lppProblems can optionally point to an SPropProblemArray structure if you are interested in more detailed error reporting, as shown in the following example:

SPropValue sMsgProps[2];
SPropProblemArray *psPropProblems = NULL;
TCHAR tchSubject[128] = TEXT("\0");

memset(&sMsgProps, 0, sizeof(sMsgProps));
wsprintf(tchSubject, TEXT("Test Message Subject"));

sMsgProps[0].ulPropTag = PR_MESSAGE_FLAGS;
sMsgProps[0].Value.ul = MSGFLAG_UNSENT;
sMsgProps[1].ulPropTag = PR_SUBJECT;
sMsgProps[1].Value.lpszW = tchSubject;

hr = pNewMessage->SetProps(2, sMsgProps, psPropProblems);

One last note: If a property value you are writing to is too large, you will usually receive the MAPI_E_NOT_ENOUGH_MEMORY error. In order to handle a large property value (such as the body of an e-mail message), you need to use the IMAPIProp::OpenProp() function and write to the IStream interface directly, instead of using the IMAPIProp::SetProps() function.

MAPI Tables

One interface that MAPI makes extensive use of is the IMapiTable object. The main use of a MAPI table is to provide you with a read-only view of a collection of properties that are owned by a parent MAPI object. For example, the MAPI Session object (IMAPISession) owns a table of the ENTRYIDs of the message stores currently installed and in use on the device. Each message store, in turn, has another collection of MAPI object properties that are represented by a different table, and that specify the folders that the individual store contains.

An easy way to visualize a MAPI table is to think of a spreadsheet. Each row in the table represents a different object that is contained by the parent object. Every column represents a different property of the object. Every object in a MAPI table contains at least a column for its unique ENTRYID, which is specified by the PR_ENTRYID property.

Figure 11.4 shows how a collection of MAPI message store objects would be represented by a MAPI table.

Figure 11.4. Layout of a MAPI table


Table 11.10 describes the different types of tables that you use with MAPI.

Table 11.10. MAPI Table Types

MAPI Table

Parent Object




A list of attachments for a message



A list of root folders in a message store



A list of child folders in a folder

Message Stores


A list of message store providers that are available to the MAPI session



A list of messages in a folder



A list of message recipients

You can access the data within a table through the IMAPITable interface. Getting a pointer to a specific table ultimately depends on the parent interface (for example, to get the table of message store providers, you would call the IMAPISession::GetMsgStoresTable() function).

The IMAPITable interface supports the methods described in Table 11.11.

Table 11.11. IMAPITable Methods




Returns the current table row position of the cursor. Also returns the cursor's relative position to the end of the table.


Returns row data.


Restrictions are not implemented on Pocket PC.


Moves the cursor row position.


Sets the properties and column order for returned data rows.


Sorting is not implemented on Pocket PC.

Just by looking at the methods that are supported with the IMAPITable interface, you can probably already tell that it is similar to using a database table. There are functions to sort table data, apply filter criteria for restrictive results, query a table for an arbitrary amount of data, and even keep track of your current position in a table by using a cursor.

Table Structures and Macros

Whenever a called method accesses the table and returns data, MAPI represents an individual row (or object) by using the SRow structure. It is defined as follows:

typedef struct _SRow {
   ULONG ulAdrEntryPad;
   ULONG cValues;
   LPSPropValue lpProps;
} SRow, FAR *LPSRow;

The first field, ulAdrEntryPad, is a set of bytes that are used to properly align the structure in memory. This field is reserved, and should not be tampered with.

The other fields are used to describe the individual properties (or columns) for the entry. The cValues field contains the number of items that are in the array of SPropValue structures to which lpProps points. Each entry in the array represents a column in the table.

When you make a typical query on a table for a set of objects, you receive more than one row back. For this, MAPI provides the SRowSet structure, which is defined as follows:

typedef struct _SRowSet {
   ULONG cRows;
   SRow aRow[MAPI_DIM];
} SRowSet, FAR *LPSRowSet;

The structure simply represents an array of SRow structures that are specified by the aRow member. The number of SRow structures in the array can be found in the cRows field.

Remember that whenever you are returned an SRow or SRowSet structure, you also need to call the FreeProws() and MAPIFreeBuffer() functions accordingly to properly free any memory that has been allocated. More information about MAPI memory allocation can be found later in this section.

Table 11.12 describes the macros that MAPI provides to help you efficiently work with the SRowSet structure.

Table 11.12. SRowSet Macro Functions




Calculates the number of bytes for a new SRowSet structure


Calculates the number of bytes of an existing SRowSet structure

SizedSRowSet(_crow, _name)

Creates a named SRowSet structure for a specific number of rows

Getting Data from a Table

To retrieve information about the objects that are contained in a table, you use the IMAPITable::QueryRows() function. The function is defined as follows:

HRESULT IMAPITable::QueryRows(LONG lRowCount, ULONG ulFlags,
   LPSRowSet *lppRows);

The first parameter, lRowCount, is the number of rows that you want to return from the table, starting at the current cursor position. This can be set to a maximum of 10 rows at a time, and must be a positive number (you cannot retrieve retral rows). The next parameter, ulFlags, is not supported, and can be set to 0. The lppRows parameter will point to an SRowSet structure that contains the row data.

As you will soon learn, you can also filter and sort the data returned to you in the lppRows structure by using the IMAPITable::Restrict(), IMAPITable::SortTable(), and IMAPITable::SetColumns() functions.

The following example shows how you can use the IMAPITable::QueryRows() function to return all of the rows in the message store table:

IMAPITable *pIMapiStoresTable = NULL;
hr = pIMapi->GetMsgStoresTable(0, &pIMapiStoresTable);
while(1) {
   SRowSet *pRowSet = NULL;

   hr = pIMapiStoresTable->QueryRows(1, 0, &pRowSet);
   if(pRowSet->cRows != 1)

   // Do something with the entry
   // Free up the row memory

When making a query to a MAPI table, you will be returned the default properties (columns) appropriate for the table type. If you want to specify any additional columns, or apply a specific sort order to them, you can call the IMAPITable::SetColumns() function before calling IMAPITable::QueryRows(). The function is defined as follows:

HRESULT IMAPITable::SetColumns(LPSPropTagArray
  lpPropTagArray, ULONG ulFlags);

The first parameter is a pointer to an array of the property tags (columns) that you want to return when you call into IMAPITable::QueryRows(), and cannot be set to a NULL value. The order of the properties that you are returned in SRowSet will reflect the order specified in lpPropTagArray.

The ulFlags parameter is not used and must be set to 0.

For example, if you wanted to retrieve both the display name and the entry identifier columns from the message store table, you would do the following:

IMAPITable *pIMapiStoresTable = NULL;
hr = pIMapi->GetMsgStoresTable(0, &pIMapiStoresTable);
while(1) {
   SRowSet *pRowSet = NULL;
   SizedSPropTagArray(2, tblColumns) = {2,
       &tblColumns, 0);

   hr = pIMapiStoresTable->QueryRows(1, 0, &pRowSet);
   if(pRowSet->cRows != 1)

   // Do something with the entry (such as print out the
   // name)

   // Free up the row memory
Table Cursor Position

As previously mentioned, MAPI tables use a cursor to track the current position within a table. To determine your current location in the table at any given time, you can use the IMAPITable::QueryPosition() function:

HRESULT IMAPITable::QueryPosition(ULONG *lpulRow, ULONG
  *lpulNumerator, ULONG *lpulDenominator);

The first parameter, lpulRow, points to the number of the current row (tables begin at 0). The lpulNumerator and lpulDenominator parameters are used to calculate a fractional position in the table that can be used to set scroll bar positions. For example, if the lpulNumerator parameter returns a value of 5 and the lpulDenominator parameter returns a value of 25, you know you are 5/25ths or one-fifth of the way through the table.

To move the cursor to a specific location in the table, you can use the IMAPITable::SeekRow() function, which is defined as follows:

   lRowCount, LONG *lplRowsSought);

The first parameter, bkOrigin, must be set to BOOKMARK_BEGINNING, which will start moving the cursor from the beginning of the table. The lRowCount parameter specifies the number of rows to move the cursor ahead. The last parameter, lplRowsSought, is unused and should be set to 0.

MAPI Object Memory Allocation

When writing applications that use MAPI, it is important to use some new memory functions that MAPI provides to allocate and free memory that is used by MAPI and your application. The primary reason that MAPI provides new memory allocation functions is to eliminate the issue of "who" releases memory?the client application or the MAPI session. For example, when you request an SRowSet from a MAPI Table, MAPI automatically allocates the necessary memory to hold the row data. To free it properly, the client needs to call the MAPIFreeBuffer() function. If you are passing data to MAPI, if it has been allocated by using the MAPIAllocateBuffer() function, MAPI can automatically allocate more memory or release it when it is finished using it.

Use the following to allocate a buffer that MAPI can use:

SCODE MAPIAllocateBuffer(ULONG cbSize, LPVOID *lppBuffer);

The first parameter, cbSize, specifies the size of the memory allocation. The lppBuffer parameter will point to a newly allocated buffer when the function returns.

To free a memory buffer that was allocated by MAPI (by a call to MAPIAllocateBuffer()), you can use the following function:

ULONG MAPIFreeBuffer(LPVOID lpBuffer);

The only parameter the function requires is a pointer to the buffer to be freed.

If a buffer is not large enough, and you need to allocate additional memory, you can use the MAPIAllocateMore() function. It has the following prototype:

SCODE MAPIAllocateMore(ULONG cbSize, LPVOID lpObject,
   LPVOID *lppBuffer);

The first parameter, cbSize, specifies the size, in bytes, of the new buffer. The lpObject parameter should point to a buffer that was previously allocated by the MAPIAllocateBuffer() function. Finally, the lppBuffer parameter will point to a newly allocated buffer when the function returns.

When freeing the memory for an object that has used the MAPIAllocateMore() function, you should use the pointer that was passed into the lpObject parameter when calling MAPIFreeBuffer(), as the buffers are automatically linked together by MAPI.

Finally, when working with MAPI tables, you can use the FreeProws() function to free the memory that was allocated for an SRowSet structure. Because MAPI will internally use MAPIAllocateBuffer() for each SRow structure, the FreeProws() structure will automatically walk through each SRow and call MAPIFreeBuffer() on it:

void FreeProws(LPSRowSet prows);

The only parameter the function takes is a pointer to an SRowSet structure.