Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Examining Extended MAPI 1.0


Dr. Dobb's Journal January 1997: Networked Systems

Examining Extended MAPI 1.0

Les is a software engineer at Microsoft and co-author of Inside MAPI (Microsoft Press, 1996). He can be contacted at [email protected].


The success of software such as Lotus Notes, Microsoft Exchange, and Novell GroupWise suggests that messaging and workflow applications have been embraced within the enterprise. Until recently, messaging applications used proprietary interfaces for passing data between various messaging-system components, so independent software vendors (ISVs) writing messaging apps were forced to develop different versions for each messaging system and to license the technology from each messaging-system vendor. This lack of standards made it both costly and technically challenging to develop new messaging applications, and created an artificial impediment to ISVs bringing new products to market.

The Extended Messaging Application Programming Interface (MAPI) 1.0 was created to standardize the interfaces between messaging applications and the underlying messaging systems. It does this by specifying a set of OLE Component Object Model (COM) interfaces that clients can call to connect to multiple back-end messaging systems. In addition to Exchange, Notes, and GroupWise, software supporting the Extended MAPI specification includes Windows 95/NT 4.0, ISOCOR's ISOPRO Client, Deming Secure Messenger, Verity's topic family, Delrina WinFax Pro, and Microsoft Schedule+ 2.0.

The components of a MAPI messaging system -- clients, delivery agents, directory agents, and message-storage databases -- use and/or expose one or more of these prescribed interfaces. In theory, any MAPI- compliant application can run against any messaging system (if the correct MAPI "drivers" are installed), and any number of messaging systems can be accessed concurrently. Decoupling the messaging client code from the specifics of the back-end messaging system makes MAPI applications highly interoperable and easily extensible, allowing users and administrators to mix and match different vendors' software. MAPI also insulates users and vendors from changes in underlying messaging technology because the interfaces stay the same even if the low-level driver implementation changes.

This interoperability was a primary requirement of the MAPI spec (for information, see http://www.microsoft.com/win32dev/mapi/) and was driven by the goal of making all electronic communications -- e-mail, fax, voice mail, electronic forms -- uniformly accessible. MAPI makes it possible for a single application (a client) to manipulate any or all of these types of messages and to access the required back-end systems concurrently in a way that is mostly transparent to the end user.

MAPI interfaces are nonproprietary, so any vendor can develop a component without paying licensing fees. The run-time components are integrated into the operating system, making them available on Windows 95/NT 4.0 without vendors having to redistribute them.

MAPI Architecture

As Figure 1 illustrates, there are three basic components of a MAPI messaging system:

  • Clients.
  • Service providers.
  • The MAPI subsystem.

Clients are applications that are consumers of messaging-system services. Examples include e-mail clients for sending and receiving e-mail, scheduling programs that use MAPI messages to exchange scheduling information, and programs for which messaging is an added feature (apart from their primary function). A word processor that includes a Send option is a typical "mail-aware" application. MAPI clients can also be automated list servers and mailbox agents, but these are usually server based (instead of running on the end user's machine). OTTO, the program I present in this article, simulates this kind of client: a mailbox agent that monitors a university mailbox and processes incoming admissions applications. The distinguishing feature of MAPI clients is that they log on to a MAPI session to access messaging-system services.

MAPI service providers are the components that produce services consumed by MAPI clients. They are analogous to Windows print drivers, except instead of understanding how to talk to a specific printer, they understand how to talk to a specific back-end system such as a server-based post office, database, or foreign mail system like SMTP. Like print drivers, service providers are installed separately from client applications, and can easily be added or replaced as needed. They are implemented as DLLs that are installed and configured by a setup program or the Control Panel's Mail and Fax applet. (A number of commercial service providers are available, including Transend's cc:Mail postoffice store/transport, DEC's MailWorks, CompuServe's store/transport, AT&T Mail, Netscape's POP3/SMTP transport, and others.)

There are several types of service providers, but the most important of these are the transport, message-store, and address-book providers. Transport providers handle message delivery to and from a specific back-end or foreign system. A fax transport, for example, accepts MAPI messages as input, dials the recipient's fax number, and outputs a bitstream that can be rendered by the receiving fax machine. Another example would be a transport that connects to a server-based post office like Microsoft Mail. In this case, the transport connects to the server, translates each MAPI message into Mail format, uploads outbound messages to the server, and downloads incoming messages from the user's mailbox.

An address-book provider manages a database of recipient records that is used to add addressing information to messages. Address books can be server based (like the Exchange Global Address List) or local based (like the Microsoft Personal Address Book). Server-based providers access a remote database residing on a server, whereas local-based providers access a file on the end user's hard drive.

Address-book providers present a hierarchical view of the recipient database. This hierarchy is visualized as a tree in which the leaf nodes are addressable entries called "mail users" and the interior nodes are entries called "containers." A mail-user entry has addressing information about a single recipient; for example, a name, e-mail address, and so on. A container can be a distribution list, which is an addressable entry that represents or expands to multiple recipients, or a nonaddressable entry that groups other containers or mail users together.

A message-store provider also accesses a database, but where the address-book database is comprised of records about people (recipients), the message-store database records are usually documents of some kind. The typical case is a database of e-mail messages, although this is by no means true in general. Other examples include stores that access document libraries, provide news services, or implement bulletin boards where users can view and/or post messages. The message-store database is also represented hierarchically, but in this case, the interior nodes are containers called "folders" and the leaf nodes are the documents themselves, known as "messages." Like address books, store providers can be either server or local based.

Clients don't manage service providers directly, just as Windows applications don't deal directly with print drivers. Instead, a MAPI client logs on to a MAPI session by calling the MAPI subsystem, then uses the services provided by the session to pass requests to the providers. The MAPI subsystem handles the details of loading the provider DLLs, routing the request to the appropriate provider and returning the results. This is something of an oversimplification, but is nevertheless useful for visualizing the functional separation of each component in the MAPI architecture. In reality, a MAPI session returns OLE Component Object Model (COM) interfaces to the client. The client then calls methods on the interfaces to obtain new interfaces or data. The code implementing these interfaces may or may not live in the service provider DLLs, and this detail is deliberately obscured from the client. Listing One llustrates how a client logs on to a MAPI session.

The MAPI subsystem also includes a separate task, known as the "message spooler," that manages the flow of messages into and out of the messaging subsystem. Its main job is to queue outbound messages submitted by a client, and to place inbound messages into the message store where they can then be accessed by the client. When a message is submitted, it is added to the spooler's outbound queue. The spooler examines the message at the head of the queue, decides which one of the available transports will deliver it, and passes a copy of the message to the selected transport. Once a transport has accepted a message for delivery, the spooler removes the message from the outbound queue. Inbound messages are accepted from the installed transports and placed in a message-store folder; the user's inbox folder, for instance.

MAPI Interfaces

MAPI is not a library of APIs that client applications can simply call. Instead, it is a set of abstract base classes that declare OLE COM interfaces. These interfaces form the backbone of the MAPI specification, and are published in some header files that give the name of the interfaces, their declarations, and their unique numeric interface identifiers (IIDs). MAPI also specifies which interfaces are required for each component. A developer creating a service provider implements the required interfaces by defining classes derived from the abstract base classes; in effect, overriding each pure virtual method with code that does some work. MAPI specifies what kind of work each method does (its semantics) and the expected return values, but the implementation details are left to the discretion of the developer.

Clients consume services produced by service providers by obtaining an interface and calling its public methods. A client requests an interface by calling MAPI APIs or by calling methods on interfaces obtained from the MAPI subsystem. An interface is really a pointer to a COM object implemented by either MAPI or by a service provider, but where the code implemented is deliberately transparent to the client: All it knows is that it obtains an interface on which it can call methods. Some interfaces are remote, such as IMAPIStatus, and in that case the MAPI subsystem handles marshaling the interface across process boundaries.

User Profiles

Service providers are installed and configured through a configuration program like the Control Panel's Mail and Fax applet. A service provider isn't configured individually, but as part of a message service -- a group of one or more providers that share some common configuration code and a single entry point. Each message service is implemented in a DLL that contains the configuration code and exports the entry-point function for invoking it. The service providers in a message service can be implemented as a single DLL or as multiple DLLs. Usually, a message service will be comprised of a set of related providers that talk to a specific back end. For example, the Exchange message service includes address books and stores that communicate with the Exchange Server post office.

A profile is a list of one or more message services along with their stored configuration data. To users, a profile appears as a set of property pages that can be viewed or edited in the Control Panel. To the message service and its service providers, a profile appears as a database that they can save configuration data in and query for information. Access to this database, which is called the profile section, is through an interface obtained from MAPI. The data is saved in the system registry.

Important MAPI Interfaces

There are a number of important MAPI interfaces, including IMAPIProp and IMAPITable.

IMAPIProp. MAPI interfaces, like all COM interfaces, expose only public methods. All data is private to the interface and accessible only through interface methods. Often, MAPI objects are abstractions of some set of closely related data. A MAPI message, for example, is an object that represents a database record in a message store's database.

MAPI formalizes access to an object's data by defining an interface for reading and writing data to an object. This interface is called IMAPIProp, and any object that stores data that must be visible externally is derived from it. Data is accessed by reading and writing attributes or properties of the object through a method on IMAPIProp. For example, a message has attributes for the subject and for the body. A client application can read the subject and body by calling the GetProps method, requesting PR_SUBJECT and PR_BODY (see Listing Two) and can write the subject and body by calling SetProps on PR_SUBJECT and PR_BODY (see Listing Three).

Interfaces derived from IMAPIProp include: IMessage, an object that abstracts a MAPI message's database record; IMAPIFolder, a container of messages (for example, the inbox folder contains inbound messages); IMailUser, an abstraction of a single recipient's database record; IABContainer, a container of recipients; IDistList, a container that represents a distribution list; and IAttach, an attachment to a message.

IMAPITable. A folder exposes a list of messages it contains by returning a table in which each row represents a message or subfolder. Address-book containers have a similar concept, but instead of messages, the table rows represent recipients or containers residing within the parent container. Tables are also used for other purposes, for example, to list the recipients of a message, the members of a distribution list, the services in a profile, or the providers in a message service.

A table in MAPI is an interface, not a data structure. It provides a read-only view of the data backing it and supports some basic table-manipulation primitives for sorting, column selection, cursor movement, and simple filtering. A program specifies a query by calling IMAPITable methods that perform one or more of these operations and execute the query. Executing the query rearranges the rows and columns in the table to match the conditions given by the query and returns rows from the rearranged table. A query is executed by calling IMAPITable::QueryRows, which returns the rows in a data structure called an SRowSet. Table implementations apply a default query in the absence of one defined explicitly by the caller. The resultant code fragment demonstrates a query that includes column selection (SetColumns), filtering (Restrict), and sorting (SortTable) as applied to a folder's contents table; see Listing Four.

Message Path

To see how the various components interact with each other, you need to follow the path of an inbound message. The message's data stream arrives from the sender over a connection maintained by the transport provider. The link between the sender's system and the transport is typically non-MAPI, and the protocol used is dictated by the foreign system. For example, an SMTP transport would listen for incoming connection requests on a TCP/IP socket bound to port 25.

The transport buffers the inbound data stream and notifies the spooler that it has incoming mail for processing. The spooler responds by creating a "blank" MAPI-message object in the default store provider and passes it to the transport provider. Part of a transport's job is to map the foreign system's native data format into a set of MAPI properties, and to set them on the message.

When the transport finishes with the message, control passes back to the spooler, which saves the message in the store's receive folder (the inbox, for example). The processing thus far has occurred only in the spooler's context, and has involved only the spooler, the transport, and the store instance loaded in the spooler's address space. A client can ask to be notified when new mail arrives by instantiating an IAdviseSink object and passing it to the store's Advise method. When the spooler-side instance of the store receives an inbound message, it must connect to the client-side instance (using some form of IPC-like named pipes), and notify it that there is new mail. The client-side instance of the store calls the client's IAdviseSink::OnNotify method, which can be used to trigger processing of the inbound message in the client. OTTO, for example, responds to the new mail notification by moving the inbound message to a public folder and sending a reply.

Outbound Message Flow

Outbound processing also involves the client, store, spooler, and transport. First the client creates a new message in a store provider folder. By convention, the outbox folder is usually used, but a message can be created in any folder. The client populates the message with data by calling SetProps, then adds one or more recipients to the message's recipient table via a call to ModifyRecipients. The recipient information can be obtained from the MAPI address book, from a specific address-book provider, or created on the fly. OTTO only sends replies, so it gets the addressing information directly from the sender information present on the inbound message.

IMessage::SubmitMessage starts the outbound logic. The call is serviced by the store provider, which maintains an outbound message queue (a MAPI table, actually) in which each pending message is represented by a table row. The spooler has a notification link to this table, so when the client-side store adds a row to the table, the spooler is notified. The spooler responds to the notification by opening the outgoing message (obtaining an IMessage interface from the store), selecting one or more of the installed transports to deliver the message, and passing a copy of the message object to the chosen transports.

The spooler decides which transports to select by examining the message's recipient table. Each recipient exposes a property that identifies his messaging system and the type of transport that can connect to it. For example, a message with two SMTP recipients and one MS Mail recipient would be handled by two transports, an SMTP transport and an MS Mail transport. The spooler would pass one copy of the message to the SMTP transport and another copy to the MS Mail transport.

The transport handles the details of mapping MAPI properties to the foreign system's data format, and transmitting the message to its particular back end. For example, an MS Mail transport would connect to an MS Mail post office, an SMTP transport would connect to a Sendmail server, and a fax transport would dial the destination fax machine.

OTTO: An Extended MAPI Client

OTTO (which is available electronically; see "Availability," page 3) is an Extended MAPI client that simulates a mailbox agent. I wrote this with the expectation that you would not have administrator privileges on a post-office server, so OTTO runs as an ordinary Windows app on an end user's machine. Mailbox agents usually run as NT services and are server based. You can easily modify OTTO to be a true mailbox agent by making it an NT service and running it from a mailbox account instead of from a user account.

OTTO listens for incoming mail in the logged-on account's mailbox. These inbound messages represent admission applications to a fictitious university. These messages are addressed to the admissions office and delivered to the office's mailbox. (Of course, you can use your personal mailbox account.) The message subject contains the name of the college to which the applicant is applying, and the body contains any arbitrary text representing his or her application information.

When OTTO logs on, it searches for a public-folder store and for public folders for each college: Arts, Science, and Business. If the folders exist, they are opened; otherwise, they are created. If the logged-on profile doesn't contain a public-folder store, OTTO simulates the public folders by creating them in the user's default store.

OTTO runs unattended, reading each inbound message's subject line, routing the application to the correct college's public folder, and sending a reply to the applicant. An error message is returned and the message isn't routed if the subject contains an invalid string. Each public folder contains the work load for that college's admissions officer.

DDJ

Listing One

LPMAPISESSION pSession;HRESULT       hRes;
if (FAILED(hRes = MAPIInitialize(NULL)))
    return hRes;
if (FAILED(hRes = MAPILogonEx((ULONG)hWnd,NULL,NULL,LOGON_FLAGS,&pSession)))
    DoError();
// Call methods on session object to get other interfaces
hRes = pSession -> OpenEntry(...);
 . . .

Back to Article

Listing Two

HRESULT       hRes;LPSPropValue  pspv;
ULONG         ulCnt; 
LPMESSAGE     pMsg = ...// code to get message object    


</p>
SizedSPropTagArray(2,spt) = {2, {PR_SUBJECT, PR_BODY}};


</p>
hRes = pMsg -> GetProps((LPSPropTagArray) &spt, 0, &ulCnt, &pspv);
if (S_OK != hRes || !ulCnt)
{
    hRes = MAPI_E_NOT_FOUND;
    DoError();
}
cout << "Subject: " << pspv[0].Value.lpszA << endl;
cout << "Body: " << pspv[1].Value.lpszA << endl;


</p>
MAPIFreeBuffer(pspv);
 . . .

Back to Article

Listing Three

SPropValue spv[2];

</p>
spv[0].ulPropTag = PR_SUBJECT;
spv[0].Value.lpszA = "Miles Davis";
spv[1].ulPropTag = PR_BODY;
spv[1].Value.lpszA = "A tribute to Jack Johnson";


</p>
hRes = pMsg -> SetProps(2,spv,NULL);


</p>
if (FAILED(hRes))
    DoError();
hRes = pMsg -> SaveChanges(0);
pOrigMsg -> Release();
 . . .

Back to Article

Listing Four

HRESULT         hRes;LPSRowSet       pRows = NULL;
SRestriction    sres;
SPropValue      spv;
LPMAPIFOLDER    pFldr;
LPMAPITABLE     pCTbl;


</p>
SizedSPropTagArray(2,spt) = {2, PR_NORMALIZED_SUBJECT, PR_MESSAGE_SIZE};
SizedSSortOrderSet(2,sso) = {2,0,0, PR_SUBJECT,TABLE_SORT_ASCEND,
                             PR_MESSAGE_SIZE,TABLE_SORT_DESCEND};
hRes = pFldr -> GetContentsTable(0,&pCTbl);
// select columns first
hRes = pCTbl->SetColumns((LPSPropTagArray)&spt,0);    
// sort depends on result of Restrict, do first
sres.rt = RES_PROPERTY;
sres.res.resProperty.relop = RELOP_GT;
sres.res.resProperty.ulPropTag = PR_MESSAGE_SIZE;
sres.res.resProperty.lpProp = &spv;


</p>
spv.ulPropTag = PR_MESSAGE_SIZE;
spv.Value.l   = 1024;


</p>
hRes = pCTbl->Restrict(&sres,TBL_BATCH);


</p>
// sort
hRes = pCTbl->SortTable((LPSSortOrderSet) & sso,0);    


</p>
// execute query
hRes = pCTbl->QueryRows(100,0,&pRows);
{
    // do something with the rows
}
FreeProws(pRows);
pCTbl -> Release();
pFldr -> Release();
 . . .

Back to Article


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.