During my previous life as a MAPI hack, I regularly heard a number of the same questions about MAPI application development, both from folks who wanted to write an add-in like Internet Idioms, and from MAPI developers wishing for more documentation and sample code. Here I've recorded my answers to the most common questions I received. (For Microsoft Exchange mail client usage issues, please see the parallel FAQ on that topic.)
Microsoft Press published Developing Applications for Microsoft® Exchange with C++ (ISBN 1-57231-500-8) in late 1996, allowing it to fall out of print in the middle of 1998. You may be able to find a copy at one of the technical book remaindering services on the WWW, such as Book Archives. I have retained full copyright over certain portions of the book, such as the example source code, and have republished those portions here.
Special limited offer! While cleaning out my workshop, I uncovered a small cache of promotional copies of my book. If you're in Seattle, I'd be happy to give you one of these copies in exchange for, say, a good bottle of wine, or some flowers, or, I don't know, maybe a Colt Woodsman .22 with the six inch barrel (hey, I can dream). (As I do not have time for the overhead of packaging and shipping, you'll need to fetch the book in person, unless you want to engage a courier to retrieve the book on your behalf.)
(Note: the following refers to full, so-called Extended MAPI, not the Simple MAPI supported in VB and MFC. Simple MAPI has copious documentation, having first appeared in Microsoft Mail 3.0.)
Since MAPI not a product of the IETF, no RFC exists for it. It theoretically is part of Microsoft's Win32 API.
The MAPI SDK is part of every Win32 SDK released since July 1995. As such, every version of Microsoft Visual C++ since 2.2 has contained MAPI documentation. These documents also appear in MSDN I, while the sundry samples, header files, and tools that constitute the MAPI PDK all appear in MSDN II. Since Microsoft now releases most of the content of the Platform SDK on its web site, you can freely access all of this information, including the MAPI SDK code samples, via the WWW, as long as you use a Web browser that accepts cookies and supports (ugh) Java.
Irving de la Cruz and Les Thayer have written Inside MAPI, originally published by Microsoft Press, and now republished on CD-ROM. This is the authoritative work for writing MAPI service providers, as well as an excellent guide for clients.
I have written Developing Applications for Microsoft Exchange with C++, also published by Microsoft Press. Where De la Cruz and Thayer focused primarily on service providers, I focused on client applications, particularly Exchange client extensions, form servers, and folder applications - i.e. applied MAPI. My book is out of print, and mostly obsolete in the wake of multiple releases of the Outlook mail client.
An Internet mailing list of MAPI developers exists. Send a message to LISTSERV@PEACH.EASE.LSOFT.COM with the single line SUBSCRIBE MAPI-L. This mailing list maintains archives of previous messages.
On public Usenet, the newsgroup comp.os.ms-windows.programmer.win32 contains occasional MAPI discussion. You will have better luck in the Microsoft-sponsored MAPI newsgroup, microsoft.public.win32.programmer.messaging, available on Microsoft's NNTP server msnews.microsoft.com (if you can't get it elsewhere). Additional applications of MAPI appear in microsoft.public.exchange.applications, microsoft.public.outlook.program_addins, and microsoft.public.outlook.program_forms, though these all tend to concentrate on CDO scripting solutions.
(See also the notes on Exchange Server programming information below.)
You have four options, each addressing a different sort of application.
You can use a toolkit, such as the Application Design Environment included in Microsoft Exchange Server, or other third party solutions. Different toolkits will offer different development facilities. Some toolkits, such as the Forms Designer component of the Exchange Application Design Environment, emit source code which the developer may subsequently alter or amend. (The Exchange Forms Designer emits Visual Basic.)
You can write in Visual Basic or another ActiveX Automation (nee OLE Automation) client to manipulate Collaboration Data Objects (nee Active/Messaging, nee OLE/Messaging). Collaboration Data Objects is an ActiveX Automation server that offers a set of message and calendar objects to its clients. It appears in the Win32 and Back Office SDKs, and as part of the Microsoft Exchange Server Forms Designer. Learn more about it by reading this whitepaper. For further detail, reference the chapter OLE Messaging Library under Win32 Messaging (MAPI) in the Win32 SDK.
You can write in C or C++ to the Simple MAPI or CMC 1.0 APIs. (Simple MAPI is also accessible to Visual Basic clients.) These APIs are suitable for implementing a File - Send... command on an application's menu, or similarly bolting a couple of messaging-related functions onto an application primarily concerned with some other domain. Learn more about them by reading Introduction to MAPI Programming, Programming with CMC, and Programming with Simple MAPI in the Guide under Win32 Messaging (MAPI) in the Win32 SDK.
Finally, you can write in C or C++ to the full Win32 Messaging API. Start by familiarizing yourself with the Microsoft Component Object Model (COM). Next open the Win32 SDK and read most of the Guide under Win32 Messaging (MAPI). Skip anything that talks about developing providers. See above for further sources of information on MAPI.
Search the Microsoft TechNet IT Home for samples of several kinds of Exchange application.
Anything capable of generating an in-process component object server. While C or C++ are the usual choices, Delphi can work, too.
Note that Microsoft Visual C++ contains all necessary header files and libraries for MAPI development in its releases subsequent to the July 1995 Win32 SDK, i.e. 2.2 and 4.0, as I imagine do all Win32-targeting compilers from other vendors. If you have MSVC 2.2, you still will need documentation, either from MSDN I or from the release points detailed above; versions 4.0 and thereafter contain MAPI documentation as part of their Win32 documentation. (It still wouldn't hurt to obtain the most recent version.)
The precise interfaces are set forth in Extending the Microsoft Exchange Client under Win32 Messaging (MAPI) in the Win32 SDK.
Quite a lot, actually. You can write applications using Collaboration Data Objects, or the object models exported by Schedule+ and Microsoft Outlook 97. You can also modify the e-form applications emitted by the Exchange Server Forms Designer. Where these fall short, you can use more capable third-party controls such as Sax mPower.
The Microsoft Technet IT Home offers many examples of all of these, using the VB-centric Exchange Application Design Environment and Outlook 97 Design Environment. Peter Krebs has written two books on this topic, Building Microsoft Exchange Applications, (ISBN 157231334X) and Building Microsoft Outlook 97 Applications, (ISBN 1572315369) both published by Microsoft Press.
You can write an Inbox Assistant custom action in VB by virtue of a proxy program that launches the desired VB application. One such proxy is the LAUNCHER utility, offered on the TechNet IT Home. The proxy acts as the actual a custom rule action, invoking your specified external program to process the targeted message.
However, you cannot presently write an Exchange client extension (such as Internet Idioms) in VB. The best you can do is write a old-style Microsoft Mail 3.0 extension, as documented in the Microsoft Mail 3.0 Technical Reference, then hope that Exchange emulates the Microsoft Mail interfaces sufficiently closely to run your application.
These paragraphs comprise everything that I know about VB. I do not use it myself.
Generally speaking: if you can call an entry point in a DLL, and pass a data record conforming to an externally defined structure, then you can use Simple MAPI. If you can implement or manipulate arbitrary objects conforming to the Microsoft Component Object Model (COM), then you can use full MAPI.
Beyond that I haven't a clue. I don't use VB, Delphi, Java, TCL, or indeed any language other than C, C++, and a couple of dialects of Lisp/Scheme.
Paul Qualls might be willing to answer Delphi/MAPI questions. Delphi users may also find this tutorial helpful.
As you might expect, Microsoft offers plenty of information on its own web site and discussion groups.
On public Usenet, the newsgroup comp.os.ms-windows.nt.software.backoffice contains occasional Exchange Server discussion. As with MAPI, you will have better luck in the Microsoft-sponsored newsgroups, microsoft.public.exchange.*, available on Microsoft's NNTP server msnews.microsoft.com (if you can't get them elsewhere). Software developers may find microsoft.public.exchange.applications particularly pertinent. See also the groups on the private NNTP server community1.microsoft.com, part of Microsoft's peer-support BackOffice Community program.
Don Adams keeps a very interesting Exchange page, demonstrating Exchange Server clients using Citrix WinFrame. Don also maintains a collection of pages on developing for the Microsoft Outlook 97 client.
Syscom Consulting Inc. maintains an indexed, searchable archive of the Microsoft Exchange Server mailing list, a list consisting of Exchange Server administrators and (apparently) some confused Windows 95 "Windows Messaging" end users who wandered onto the list by mistake. Stephen A. Gutknecht compiled his Exchange Server FAQ from the traffic on this list and elsewhere.
Nik Okuntseff makes public the working draft of his book on server-side (EDK/GDK) programming, the aptly named MS Exchange Server Programming.
(See also the notes on MAPI programming information above.)
To create a custom rule, implement a library similar to that for Exchange client extensions, but instead offering objects that support IExchangeRuleExt, then install that library on every workstation that will support the custom rule. This interface appears in the BackOffice 2.0 SDK header file exchcli.h, which also contains documentation for the interface and its environment.
Releases of the BackOffice 2.0 SDK since July 1996 contain the CRARUN sample, implementing this interface.
You may also want to visit the Microsoft TechNet IT Home to review the LAUNCHER utility: a custom rule action that invokes an external program to process the targeted message. Such an external program may be written in Visual Basic, as is Launcher's accompanying EXPRINT sample.
It's not on the object.
Remember that IMAPIProp::GetProps will return MAPI_W_ERRORS_RETURNED if any of the specified properties are absent on the object. Furthermore, this code is considered a success value. Hence the following sequence succeeds:
HRESULT hr = MAPI_W_ERRORS_RETURNED;
ASSERT(!FAILED(hr));
If the call returns this code, you cannot assume the presence or validity of any of the values returned in the output array. Instead, you must test each entry in the output array to see if it was in fact found. A missing entry will have type PT_ERROR, with a value of MAPI_E_NOT_FOUND. Hence in the example given, the caller was attempting to interpret a value of type PT_ERROR as some other type (PT_TSTRING or PT_BINARY being two particularly catastrophic examples).
For more details, RTFM.
The Exchange Server Internet Mail Service renders the RFC822 headers
(Received:, Message-Id:, et al.) on an incoming message
into the property PR_TRANSPORT_HEADERS,
as does the version of the MAPI transport provider minet32.dll
released in the Outlook 97 Internet Mail Enhancement Patch (IMEP).
Previous versions of minet32.dll, however,
including the versions included in the Windows 95 Plus! Pack and Windows NT 4.0,
saved these headers into the transport-defined custom property
PROP_TAG(PT_TSTRING, 0x5901),
prefixed by the four-character cookie MSIM.
A client extension that wishes to process RFC822 headers on an arbitrary incoming message should check both locations on the message, unless the extension limits itself to operating with a particular transport.
I have no experience with this, since I don't use MFC.
You may want to review the MFC Form Wizard for Microsoft Visual C++, available on the Microsoft TechNet IT Home.
You have two problems here: first, bootstrapping a build environment for 16-bit MAPI applications, and then emulating enough of the Win32 SDK environment to build a client extension.
Start a project without any of the client extension definition files. Concentrate on getting a program that calls a few MAPI calls to compile and link successfully. Make this a simple program (e.g. msess from D.A.M.E.), limiting itself to little more than the sequence MAPIInitialize, MAPILogonEx, IMAPISession::Logoff, MAPIUnintialize.
Once you've figured out your basic build environment,
then your project needs definitions for the Win32-specific structures
referenced by client extensions.
Essentially you're porting a subset of prsht.h and commctrl.h
to 16-bit Windows.
Take these header files from the Win32 SDK,
liberally applying #if 0 preprocessor directives
to remove all dependencies on 32-bit RPC definitions, et cetera.
You may have to insert simple structure or type definitions from other files.
In the end, you'll have definitions of necessary structures such as PROPSHEETA,
plus the sundry manifest constants and private window message definitions such as TB_CHECKED.
See D.A.M.E. page 179.
There's no such thing. The closest approximation to this that you'll find is the Developer's Edition of Microsoft Office 97, which includes documentation of the Microsoft Outlook 97 object model for Automation clients.
For all other SDK clients, you still use the MAPI portion of the Win32 SDK (renamed the Platform SDK in the latest releases).
Any well-behaved Exchange client extension is an Outlook 97 client extension. Outlook 97 emulates the environment of the Microsoft Exchange (aka Windows Messaging) clients, in order to host as many Exchange client extensions as is possible.
Microsoft published some notes on Outlook client extensions in this MSDN article.
Most likely, you are making assumptions about your runtime environment that fall outside of the guarantees of the IExchExt contract.
Some obvious differences between Outlook 97 and Exchange include:
Here are some possible reasons for a client extension failure:
Outlook 97 apparently goes to great lengths to compensate for ill-behaved Exchange client extensions. It lies in its IExchExtCallback::GetVersion callback, often returns S_OK instead of S_FALSE to a IExchExtCallback::GetWindow that should fail, and may even try to unroll menu changes in an extension's faulty implementation of IExchExtCommands::InstallCommands. You may find these changes frustrating when debugging your own Outlook-specific development.
(Within my book, inetxidm is rife with Exchange client specific assumptions, both in its assumptions about callback order and its work within the controls on a loaded form window. Mtwb and rtfguard are properly generic. Fwdasatt works, except where it uses an Exchange-client specific link format.)
Since Outlook lies in the IExchExtCallback::GetVersion callback, and may even load your extension before an IExchExtCallback::GetWindow would succeed (thus preventing walking the window tree), you must resort to magic.
Fortunately, every Outlook client extension callback supports another interface in addition to IExchExtCallback. Hence the following incantation will return TRUE from within Outlook.
const IID IID_IOnlyInOutlook = {0x0006720D,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
BOOL IsExchExtWithinOutlook(IExchExtCallback* peecb)
{
IUnknown* punk = NULL;
HRESULT hr = peecb->QueryInterface(IID_IOnlyInOutlook, (void**)&punk);
if (punk)
punk->Release();
return (SUCCEEDED(hr));
}
Every Outlook 97 client extension callback contains a hook by which a client extension can access the current object. E.g., if the client extension is running in what Exchange would call a "read note form", that extension can reach the current Outlook MailItem.
const IID IID_IOutlookGetObjectForExchExtCallback = {0x0006720D,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
DECLARE_INTERFACE_(IOutlookGetObjectForExchExtCallback, IUnknown)
{
// *** IUnknown methods ***
STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvObj) PURE;
STDMETHOD_(ULONG,AddRef) (THIS) PURE;
STDMETHOD_(ULONG,Release) (THIS) PURE;
// *** IOutlookGetObjectForExchExtCallback methods ***
STDMETHOD(GetObject) (THIS_ IUnknown **ppunk) PURE;
STDMETHOD(DontCallThis)(THIS_ void **ppv) PURE;
};
IDispatch* GetDispatch(IExchExtCallback* peecb)
{
IOutlookGetObjectForExchExtCallback* po = NULL;
IUnknown* punk = NULL;
IDispatch* pdisp = NULL;
HRESULT hr = peecb->QueryInterface(IID_IOutlookGetObjectForExchExtCallback, (void**)&po);
if (SUCCEEDED(hr))
hr = po->GetObject(&punk);
if (SUCCEEDED(hr))
hr = punk->QueryInterface(IID_IDispatch, (void**)&pdisp);
if (punk)
punk->Release();
if (po)
po->Release();
return pdisp;
}
As you can see from comparing IIDs, this hook is the same interface that we used to differentiate Outlook 97 from Exchange. The hook finds the item corresponding to the current client extension callback. With an automation interface to this item, our client extension can get and set properties or invoke methods as exported by the Outlook object model.
The following fragment uses the Outlook object model to fetch the subject of the current MailItem.
{
HRESULT hr = NOERROR;
DISPID dispid;
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
VARIANT var;
VariantInit(&var);
IDispatch* pdisp = GetDispatch(/* pass peecb */);
OLECHAR* pszMember = L"Subject";
hr = pdisp->GetIDsOfNames(IID_NULL, &pszMember, 1, LOCALE_USER_DEFAULT, &dispid);
Assert(SUCCEEDED(hr));
hr = pdisp->Invoke(
dispid,
IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &var, NULL, NULL);
Assert(SUCCEEDED(hr));
Assert(VT_BSTR == var.vt);
/* var.bstrVal contains subject */
VariantClear(&var);
pdisp->Release();
}
A client extension running within the Exchange client doesn't need to detect WordMail, since Exchange WordMail does not load client extensions. (Somewhat like asking yourself, "Am I asleep?") However, due to the much tighter integration between Outlook 97 and Outlook WordMail, a client extension running within Outlook may end up running within WordMail - particularly if the client extension has an ECF.
To detect WordMail within Outlook, we depend on the Outlook object model, as the following fragment demonstrates.
{
EXCEPINFO xi;
unsigned int errArg;
HRESULT hr = NOERROR;
DISPID dispid;
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
OLECHAR* pszMember = NULL;
VARIANT var;
VariantInit(&var);
IDispatch* pdisp = GetDispatch(/* pass peecb */);
pszMember = L"GetInspector";
hr = pdisp->GetIDsOfNames(IID_NULL, &pszMember, 1, LOCALE_USER_DEFAULT, &dispid);
Assert(SUCCEEDED(hr));
hr = pdisp->Invoke(
dispid,
IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &var, &xi, &errArg);
Assert(SUCCEEDED(hr));
Assert(VT_DISPATCH == var.vt && NULL != var.pdispVal);
IDispatch* pdispInsp = var.pdispVal;
pdispInsp->AddRef();
VariantClear(&var);
pszMember = L"IsWordMail";
hr = pdispInsp->GetIDsOfNames(IID_NULL, &pszMember, 1, LOCALE_USER_DEFAULT, &dispid);
Assert(SUCCEEDED(hr));
hr = pdispInsp->Invoke(
dispid,
IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,
&dispparamsNoArgs, &var, &xi, &errArg);
Assert(SUCCEEDED(hr));
Assert(VT_BOOL == var.vt);
/* var.boolVal contains answer */
VariantClear(&var);
pdispInsp->Release();
pdisp->Release();
}
When Outlook fires an event, it looks for every connectable object registered with the current item, then invokes a particular dispid on each of those objects. Hence your client extension must implement enough of an automation item to support this invocation, then register itself in the appropriate place.
The automation item handling the event needs only a skeleton of the usual support. Most methods it can stub: E_NOTIMPL to IDispatch::GetIDsOfNames and IDispatch::GetTypeInfo, S_OK IDispatch::GetTypeInfoCount with count zero. IDispatch::Invoke is the method of interest, and it needs respond only to dispids corresponding to the events of interest. To find the values of those dispids, go fishing in the debugger with a stub Invoke implementation.
Once you have implemented an automation item, you must register it with the Outlook MailItem in order for Outlook to call it. Get the item, query it for the connection point container, and enumerate the single connection point sub-object. IConnectionPoint::Advise on that connection point to give it the IDispatch of your own automation object.
Running within the Outlook host's address space, a client extension can easily damage Outlook via careless use of the facilities exposed by IOutlookGetObjectForExchExtCallback. Beware: here be dragons.
An ECF (Extension Configuration File) allows Outlook to optimize or otherwise change the way in which it loads and runs Exchange client extensions. By deferring loading and initializing an extension until the extension is needed, Outlook itself can load and run more quickly.
Microsoft documents some of the ECF format in Appendix A of the Office 97 Resource Kit, available both on the MSDN library and on the Microsoft web site.
In the absence of an ECF file, Outlook will adhere as closely as possible to the behavior of the old Exchange client. Otherwise, all guarantees are off. I therefore recommend that you eschew ECF files if you can, particularly given the incomplete nature of the documentation.
An ECF file overrides any registry entry for an extension. To install an extension that uses an ECF file, stop Outlook, copy the ECF file to the Office Addins directory, then restart Outlook. To remove an extension that uses an ECF file, stop Outlook, delete the ECF file from the Office Addins directory, delete the extend.dat file, then restart Outlook. (Caveat: removing an extension in this manner will re-enable any extensions previously disabled by the user.) See also Microsoft's published notes on Outlook client extensions.
MAPI forms that run correctly under the Exchange client may encounter one or more of the following incompatibilities when running under the Outlook 97 client:
Rumor has it that more recent versions of Outlook correct some or all of these.
Last modified: 17 June 2000
Copyright 1996-2000, Ben Goetter. All rights reserved.