Jim is a business manager for Chevron Chemical and the author of numerous programming books, including the recently published Windows API New Testament (The Waite Group, 1994). Jim can be contacted on CompuServe at 73220,324.
Electronic mail has become a mainstream application. For many of us, the electronic-mail system is the primary application we use every day, with word processors and spreadsheets filling lesser roles. For the most part, e-mail systems have been used to simply send and receive text messages which would otherwise have been sent on a printed page. Many developers, however, have discovered that e-mail is capable of doing much more than simply transmitting text. Lotus Notes has popularized the concept of a textual database, which allows groups to collect and organize more-structured documents. The integration of OLE into several mail systems has made file transfer simple enough for most users to master with very limited training. A logical extension to e-mail is to build custom applications that mirror a company's existing work procedures. This can vary from simple input forms, to complex applications that lead the user through a complete work process. Figure 1 is the main window of an application which structures requests for technical information. This application can be run from within Microsoft Mail or as a stand-alone program. In the latter case, the application starts a background mail session to transmit the data as a specially identified MS Mail message. The application also "pops up" whenever an incoming message of this type is selected from within the MS Mail in-box.
There are two keys to extending MS Mail beyond simple messaging. The first is the MAPI.DLL library, which exports 12 functions that you can call within your own program. Microsoft calls this the "simple mail API" since it intends to add to the function list in future releases of Mail. Table 1 is a list of the MAPI functions, which are documented in the Microsoft Mail Technical Reference and the Microsoft Development Library (MSDN) CD-ROM. You can call these functions within Access, Excel, Word Basic, Visual Basic, or just about any other development platform. The MAPI functions are easy to use, and provide a lot of horsepower for a minimum effort.
The second key to extending MS Mail is Microsoft's APPEXEC.DLL, which passes information about a message received by MS Mail to another application. The information is transferred by passing the handle of a global-memory block containing a message identifier. The handle is passed using a clever trick that I have not seen elsewhere. If APPEXEC.DLL is called with a command line containing the string <PARAMBLK>, that string is replaced in the command-line arguments with the hexadecimal handle of the global-memory block containing the message information. In other words, if APPEXEC.DLL is called with the command-line arguments APPEXEC.DLL <PARAMBLK>, the DLL will overwrite the <PARAMBLK> string with a handle to APPEXEC.DLL B046. The program wishing to read the message can then use this handle to read the memory block. Example 1 shows how the memory block is accessed. The application takes ownership of the memory block by calling GlobalRealloc() using the GMEM_ MODIFY| GMEM_MOVEABLE|GMEM_SHARE flags, and then works directly with the data in memory.
The memory block will contain information about the message (or messages) that the user has selected from within MSMAIL.EXE. The data is organized as a PARAMBLK structure; see Example 2. The key member of this structure is lpMessageIDList, which contains one or more unique message identifiers. The message identifier is used to read the actual message data using MAPIReadMail().
The combination of the mail functions in MAPI.DLL and the APPEXEC.DLL library gives you the flexibility to build your own application right into the fabric of MS Mail. Figure 2 is a conceptual model of how these tools work together.
Mail supports the concept of different message types. Normal text messages are the default type, but you can create your own specialized message types to handle transmittal of more-structured data. The message identifier allows Mail to route specific types of messages to different applications, instead of always displaying the message data as text. Mail (MSMAIL.EXE) is made aware of the specialized message types by adding an entry to the MSMAIL.INI file in the user's Windows subdirectory. Example 3 is a typical entry that creates a custom message type "MailDemo.Demo"; the prefix IPM stands for "interprocess message." The MSMAIL.INI entry specifies a custom menu item in the Mail program window titled "Mail Demo_"; selecting this menu item will load the library APPEXEC.DLL and pass the command line \MAILDEMO\MAILDEMO.EXE --m <PARAMBLK> to that DLL. The binary 1s and 0s specify that this custom message type be recognized for composing, reading, replying, forwarding, printing, and saving of messages marked as having the type "MailDemo.Demo." The MSMAIL.INI entry must all be on one continuous line of text.
The MS Mail system uses several data structures to encapsulate message information. Example 4 shows the definitions of the MapiMessage and MapiRecipDesc structures, which are defined in the MAPI.H header file supplied by Microsoft. The MapiMessage structure defines how an individual Mail message is organized. For example, the MapiMessage structure contains a pointer to the subject string (lpszSubject), a pointer to the contents of the message (lpszNoteText), and a pointer to a string used to identify unique message types (lpszMessageType). Messages are transmitted including information about who sent the message and everyone on the distribution list. Address information is organized using the MapiRecipDesc structure (Example 4). The MapiMessage structure contains pointers to two sets of MapiRecipDesc data. lpOriginator points to MapiRecipDesc data for the individual that sent the message, while lpRecips points to an array of one or more MapiRecipDesc entries for the people copied on the message-distribution list, including the originator. The long-integer value nRecipCount is used to store the number of entries in the MapiRecipDesc array pointed to by lpRecips. Messages can also include embedded files and OLE objects. At the bottom of the MapiMessage structure definition are the member variables nFileCount and lpFiles, which are used to organize embedded data. MAPI.H also includes the definition of the MapiFileDesc structure, which is used to store individual file and OLE entries.
MAPIUTIL.H and MAPIUTIL.CPP (Listings One and Two) show how the MAPI functions exported by MAPI.DLL can be organized into a C++ class named CMapiUtil. CMapiUtil does not use every possible mail feature, but is sufficient for most projects. Table 2 summarizes the member functions of the CMapiUtil class.
Listing Three shows how the CMapiUtil class can be used to send a mail message. The Logon() member function either starts a new mail session or establishes a link to an existing session if MS Mail is already running. GetAddressList() displays the standard MS Mail address-list dialog box for the user to make selections. The SendMessage() function then uses the address list to distribute the message. FreeMessage() clears memory buffers used by the CMapiUtil class. Finally, Logoff() exits the mail session or simply severs the link if MS Mail was already running.
The complete MAILDEMO program, including executables and programmer notes, is available electronically; see "Availability," page 3. This program sends and receives both standard text messages and special message types. The application was written using Visual C++ 1.5 and the MFC library, plus the CMapiUtil class presented here.
Figure 1 Typical customized mail application. Figure 2 Inserting your own application into the MS Mail system.
int CTsrApp::ReadMessageFromMemory (char * psCmdLine)
{ // convert the handle from ASCII hex digits to a number
HGLOBAL handle = (HGLOBAL) atol_hex (psCmdLine) ;
if (handle == 0)
return HANDLE_ERROR ;
// get ownership of block from APPEXEC.DLL
HGLOBAL hMem = GlobalReAlloc (handle, 0,
GMEM_MODIFY | GMEM_MOVEABLE | GMEM_SHARE) ;
if (hMem == NULL)
return HANDLE_ERROR ;
// and lock the block to access it's members
PARAMBLK * pParamBlk = (PARAMBLK *) GlobalLock (hMem) ;
// use the PARAMBLK data in the memory block here...
GlobalUnlock (hMem) ;
GlobalFree (hMem) ;
return 0 ;
}
typedef struct tagPARAMBLK
{
WORD wVersion; // eg. 0x0300 for ver 3.0
WORD wCommand; // eg. wcommandOpen (def. MAILEXTS.H)
LPSTR lpDllCmdLine; // command line string for msg. type
LPSTR lpMessageIDList; // array of message identifiers
WORD wMessageIDCount; // no. items in lpmessageIDList
HWND hwndMail; // the mail window handle
HANDLE hinstMail; // the mail instance handle
LPSTR lpHelpPath; // the help file name (if any)
DWORD hlpID; // the help file context ID
} PARAMBLK;
[Custom Messages]
IPM.MailDemo.Demo=3.0;Mail;&Mail
Demo...;2;\maildemo\APPEXEC.DLL;\maildemo\maildemo.EXE -m
<PARAMBLK>;1111111000000000;Mail demo application;;;
typedef struct
{
ULONG ulReserved; // Reserved (Must be 0)
LPSTR lpszSubject; // Message Subject
LPSTR lpszNoteText; // Message Text
LPSTR lpszMessageType; // Message Class
LPSTR lpszDateReceived; // in YYYY/MM/DD HH:MM format
LPSTR lpszConversationID; // conversation thread ID
FLAGS flFlags; // unread,return receipt
lpMapiRecipDesc lpOriginator; // Originator descriptor
ULONG nRecipCount; // Number of recipients
lpMapiRecipDesc lpRecips; // Recipient descriptors
ULONG nFileCount; // # of file attachments
lpMapiFileDesc lpFiles; // Attachment descriptors
} MapiMessage, FAR * lpMapiMessage;
#define MAPI_UNREAD 0x00000001 // flFlags values
#define MAPI_RECEIPT_REQUESTED 0x00000002
#define MAPI_SENT 0x00000004
typedef struct
{
ULONG ulReserved; // Reserved for future use
ULONG ulRecipClass; // Recipient class
// MAPI_TO, MAPI_CC, MAPI_BCC, MAPI_ORIG
LPSTR lpszName; // Recipient name
LPSTR lpszAddress; // Recipient address (optional)
ULONG ulEIDSize; // Count in bytes of size of pEntryID
LPVOID lpEntryID; // System-specific recipient reference
} MapiRecipDesc, FAR * lpMapiRecipDesc;
#define MAPI_ORIG 0 // Recipient is message originator
#define MAPI_TO 1 // Recipient is a primary recipient
#define MAPI_CC 2 // Recipient is a copy recipient
#define MAPI_BCC 3 // Recipient is blind copy recipient
Function Description
MAPIAddress Uses the Mail addressing features.
MAPIDeleteMail Deletes a message.
MAPIDetails Provides details of an address-list entry.
MAPIFindNext Locates a message in the message queue.
MAPIFreeBuffer Deletes a buffer used by another MAPI function.
MAPILogoff Ends a Mail session.
MAPILogon Starts a new Mail session.
MAPIReadMail Accesses the data in a message.
MAPIResolveName Gets a unique address for a recipient.
MAPISaveMail Saves a message.
MAPISendDocuments Transmits files.
MAPISendMail Transmits a message.
Function Description
Logon() Loads MAPI.DLL into memory, obtains
pointers to the MAPI functions,
and starts a mail session.
GetAddressList() Displays the standard Mail dialog box
for creating a distribution list.
The list is initially empty.
GetUpdatedAddressList() Displays the standard Mail dialog box
for creating a distribution list.
The list is initialized with the names
saved by SaveAddressList().
FreeAddressList() Frees the buffers used
by the GetUpdatedAddressList function.
SendMessage() Sends a message using the current address list.
ReadMessage() Reads a message from the Mail system.
SaveAddressList() Saves the distribution list from the last message.
FreeMessage() Frees memory tied up with the last message.
Logoff() Logs off the mail session and unloads MAPI.DLL.
NoteText() Returns a pointer to the contents of a message.
NoteLong() Returns the number of bytes in the contents of a message.
// mapiutil.h header file for mapiutil.cpp, jim conger, 1994
#ifndef MAPIUTIL_H
#define MAPIUTIL_H
// MAPI function types (from mapi.h prototypes)
typedef ULONG (FAR PASCAL *LPMAPILOGON)(ULONG, LPSTR, LPSTR, FLAGS,
ULONG, LPLHANDLE);
typedef ULONG (FAR PASCAL *LPMAPILOGOFF)(LHANDLE, ULONG, FLAGS, ULONG);
typedef ULONG (FAR PASCAL *LPMAPISENDMAIL)(LHANDLE, ULONG,
lpMapiMessage, FLAGS, ULONG);
typedef ULONG (FAR PASCAL *LPMAPIADDRESS)(LHANDLE, ULONG, LPSTR, ULONG,
LPSTR, ULONG, lpMapiRecipDesc, FLAGS, ULONG, LPULONG,
lpMapiRecipDesc FAR *);
typedef ULONG (FAR PASCAL *LPMAPIREADMAIL)(LHANDLE, ULONG, LPSTR, FLAGS,
ULONG, lpMapiMessage FAR *);
typedef ULONG (FAR PASCAL *LPMAPIRESOLVENAME)(LHANDLE, ULONG, LPSTR,
FLAGS, ULONG, lpMapiRecipDesc FAR *);
typedef ULONG (FAR PASCAL *LPMAPIFREEBUFFER)(LPVOID);
class CMapiUtil
{
public:
CMapiUtil () ;
~CMapiUtil () ;
private:
HINSTANCE m_hMAPI ; // handle of MAPI.DLL
unsigned long m_MailHandle ; // mail session handle
MapiRecipDesc m_MapiRecipDesc ; // defined in MAPI.H
ULONG m_lRecipients ; // number of recipients
lpMapiRecipDesc m_lpRecipList ; // pointer to list of recips
ULONG m_lOldRecipients ; // number of recipients
lpMapiRecipDesc m_lpOldRecipList ; // last list of recips
lpMapiMessage m_lpMessage ; // message data in memory
CStringList m_NameList ; // string list of recipients
LPMAPILOGON lpMAPILogon ; // pointers to functions in
LPMAPISENDMAIL lpMAPISendMail ; // mapi.dll
LPMAPIFREEBUFFER lpMAPIFreeBuffer ;
LPMAPILOGOFF lpMAPILogoff ;
LPMAPIADDRESS lpMAPIAddress ;
LPMAPIREADMAIL lpMAPIReadMail ;
LPMAPIRESOLVENAME lpMAPIResolveName ;
public:
int Logon (CWnd * pWnd) ;
int GetAddressList (CWnd * pWnd) ;
int GetUpdatedAddressList (CWnd * pWnd) ;
void FreeAddressList () ;
int SendMessage (CWnd * pWnd, MapiMessage * lpMessage) ;
int ReadMessage (CWnd * pWnd, LPSTR lpMessageID) ;
void SaveAddressList () ;
void FreeMessage () ;
void Logoff (CWnd * pWnd) ;
char * NoteText () ;
int NoteLong () ;
} ;
#endif // MAPIUTIL_H
// mapiutil.cpp utility functions for working with mail api, jim conger, 1994
#include "stdafx.h" // used by VC++ for standard MFC includes
#include "mapi.h"
#include "mailexts.h"
#include "mapiutil.h"
#include <stdlib.h>
CMapiUtil::CMapiUtil ()
{
TRACE ("\nCMapiUtil constructor") ;
m_hMAPI = NULL ;
m_MailHandle = NULL ;
m_lRecipients = 0 ;
m_lpRecipList = NULL ;
m_lOldRecipients = 0 ;
m_lpOldRecipList = NULL ;
m_lpMessage = NULL ;
}
CMapiUtil::~CMapiUtil () // allows mindless shutdown of mail
{
TRACE ("\nCMapiUtil destructor") ;
if (m_lpOldRecipList != NULL)
delete m_lpOldRecipList ;
if (m_lpMessage != NULL)
(*lpMAPIFreeBuffer) (m_lpMessage) ;
if (m_hMAPI != NULL)
::FreeLibrary (m_hMAPI) ;
}
// Load mapi.dll into memory, get function addresses, and logon
int CMapiUtil::Logon (CWnd * pWnd)
{
TRACE ("\nCMapiUtil::Logon ()") ;
ULONG lHwnd = (ULONG)(LPSTR) pWnd->GetSafeHwnd () ;
m_hMAPI = ::LoadLibrary ("MAPI.DLL") ;// explicitly load MAPI.DLL
if (m_hMAPI < HINSTANCE_ERROR)
return 0 ;
// get address of MAPILogon()
lpMAPILogon = (LPMAPILOGON) ::GetProcAddress (m_hMAPI,"MAPILogon") ;
if (lpMAPILogon == NULL)
return 0 ;
// if MAPILogon() was found, assume the other functions can be found
lpMAPISendMail = (LPMAPISENDMAIL)
::GetProcAddress (m_hMAPI, "MAPISendMail") ;
lpMAPIFreeBuffer = (LPMAPIFREEBUFFER)
::GetProcAddress (m_hMAPI, "MAPIFreeBuffer") ;
lpMAPILogoff = (LPMAPILOGOFF)
::GetProcAddress (m_hMAPI, "MAPILogoff") ;
lpMAPIAddress = (LPMAPIADDRESS)
::GetProcAddress (m_hMAPI, "MAPIAddress") ;
lpMAPIReadMail = (LPMAPIREADMAIL)
::GetProcAddress (m_hMAPI, "MAPIReadMail") ;
lpMAPIResolveName = (LPMAPIRESOLVENAME)
::GetProcAddress (m_hMAPI, "MAPIResolveName") ;
// open a mail session - does nothing if a session is running
ULONG lStatus = (*lpMAPILogon) (lHwnd, NULL, NULL,
MAPI_LOGON_UI, 0, (LPLHANDLE) &m_MailHandle) ;
if (lStatus != SUCCESS_SUCCESS)
return 0 ;
else
return TRUE ;
}
// Show empty address dialog box, and get selections from user
int CMapiUtil::GetAddressList (CWnd * pWnd)
{
TRACE ("\nCMapiUtil::GetAddressList ()") ;
ULONG lHwnd = (ULONG)(LPSTR) pWnd->GetSafeHwnd () ;
long lStatus = (*lpMAPIAddress) ((LHANDLE) m_MailHandle, lHwnd,
NULL, 2,NULL, 0, NULL, 0, 0, &m_lRecipients, &m_lpRecipList) ;
return (int) lStatus ;
}
// Show initialized address dialog box, get final selections
int CMapiUtil::GetUpdatedAddressList (CWnd * pWnd)
{
TRACE ("\nCMapiUtil::GetUpdatedAddressList ()") ;
ULONG lHwnd = (ULONG)(LPSTR) pWnd->GetSafeHwnd () ;
lpMapiRecipDesc lpRD ;
if (m_lOldRecipients > 0) // if there is a list of names
{
if (m_lpOldRecipList != NULL)
delete m_lpOldRecipList ;
m_lpOldRecipList = new MapiRecipDesc [m_lOldRecipients] ;
ASSERT (m_lpOldRecipList) ;
lpMapiRecipDesc lpMRD = m_lpOldRecipList ;
for (int i = 0 ; i < (int) m_lOldRecipients ; i++)
{
POSITION pos = m_NameList.FindIndex (i) ;
ULONG lStatus = (*lpMAPIResolveName) (
(LHANDLE) m_MailHandle, lHwnd,
(LPSTR) (const char *) m_NameList.GetAt (pos),
MAPI_DIALOG, 0, &lpRD) ;
if (lStatus == SUCCESS_SUCCESS)
lpMRD [i] = * lpRD ; // copy recipient data to array
else
{
delete m_lpOldRecipList ;
m_lpOldRecipList = NULL ;
m_lOldRecipients = 0 ;
break ;
}
}
}
long lStatus = (*lpMAPIAddress) ((LHANDLE) m_MailHandle, lHwnd,
NULL, 2, NULL, m_lOldRecipients, m_lpOldRecipList, 0, 0,
&m_lRecipients, &m_lpRecipList) ;
return (int) lStatus ;
}
// free the buffers tied up by the GetUpdatedAddressList() function
void CMapiUtil::FreeAddressList ()
{
TRACE ("\nCMapiUtil::FreeAddressList ()") ;
if (m_lpOldRecipList != NULL)
{
delete m_lpOldRecipList ; // delete array holding copies
m_lpOldRecipList = NULL ;
m_lOldRecipients = 0 ;
}
}
// Send the message, using addresses from GetAddressList()
int CMapiUtil::SendMessage (CWnd * pWnd, MapiMessage * lpMessage)
{
TRACE ("\nCMapiUtil::SendMessage ()") ;
ULONG lHwnd = (ULONG)(LPSTR) pWnd->GetSafeHwnd () ;
m_MapiRecipDesc.ulReserved = 0 ;
m_MapiRecipDesc.ulRecipClass = MAPI_ORIG ;
m_MapiRecipDesc.lpszName = "TSR" ;
m_MapiRecipDesc.lpszAddress = NULL ;
lpMessage->lpOriginator = &m_MapiRecipDesc ;
lpMessage->nRecipCount = m_lRecipients ;
lpMessage->lpRecips = m_lpRecipList ;
long lStatus = (*lpMAPISendMail) ((LHANDLE) m_MailHandle, lHwnd,
lpMessage, 0L, 0L) ;
return (int) lStatus ;
}
// Read message identified by lpMessageID, save pointer to message
// Returns TRUE if message was read OK, FALSE on error
int CMapiUtil::ReadMessage (CWnd * pWnd, LPSTR lpMessageID)
{
TRACE ("\nCMapiUtil::ReadMessage ()") ;
static lpMapiMessage lpMessage ;
ULONG lHwnd = (ULONG)(LPSTR) pWnd->GetSafeHwnd () ;
if (m_lpMessage != NULL) // make sure last message is purged
(*lpMAPIFreeBuffer) (m_lpMessage) ;
if (m_lpRecipList != NULL) // and clean up old address list
{
(*lpMAPIFreeBuffer) (m_lpRecipList) ;
m_lpRecipList = NULL ;
}
m_lRecipients = 0 ;
// read the message
ULONG lStatus = (*lpMAPIReadMail) (m_MailHandle, lHwnd,
lpMessageID, 0L, 0, &lpMessage) ;
if (lStatus == SUCCESS_SUCCESS)
{
m_lpMessage = lpMessage ; // save pointer to message if OK
return TRUE ; // and return TRUE
}
else
{
m_lpMessage = NULL ; // otherwise, quit and return FALSE
return FALSE ;
}
}
// save the names of all recipients in a message for use
// in initializing the address dialog box in next mail send
void CMapiUtil::SaveAddressList ()
{
TRACE ("\nCMapiUtil::SaveAddressList ()") ;
if (m_lpMessage == NULL)
return ;
m_NameList.RemoveAll () ; // empty the last list (if any)
m_lOldRecipients = (long) m_lpMessage->nRecipCount ;
// add all of the cc's to the end of the list
for (int i = 0 ; i < (int) m_lpMessage->nRecipCount ; i++)
{
m_NameList.AddTail (m_lpMessage->lpRecips [i].lpszName) ;
}
}
// Free all buffers associated with a message
void CMapiUtil::FreeMessage ()
{
TRACE ("\nCMapiUtil::FreeMessage ()") ;
if (m_lpMessage != NULL)
{
(*lpMAPIFreeBuffer) (m_lpMessage) ;
m_lpMessage = NULL ;
}
if (m_lpRecipList != NULL)
{
(*lpMAPIFreeBuffer) (m_lpRecipList) ;
m_lpRecipList = NULL ;
m_lRecipients = 0 ;
}
}
// Log off from mail, and release MAPI.DLL
void CMapiUtil::Logoff (CWnd * pWnd)
{
TRACE ("\nCMapiUtil::Logoff ()") ;
ULONG lHwnd = (ULONG)(LPSTR) pWnd->GetSafeHwnd () ;
if (m_MailHandle != 0)
{
(*lpMAPILogoff) (m_MailHandle, lHwnd, 0L, 0L) ;
m_MailHandle = 0 ;
}
if (m_hMAPI != NULL)
{
::FreeLibrary (m_hMAPI) ;
m_hMAPI = NULL ;
}
}
// Returns a pointer to the text of the message, NULL if no message
char * CMapiUtil::NoteText ()
{
TRACE ("\nCMapiUtil::NoteText ()") ;
if (m_lpMessage != NULL)
return m_lpMessage->lpszNoteText ;
else
return NULL ;
}
// Returns number of chars in the text of the message, 0 if none
int CMapiUtil::NoteLong ()
{
TRACE ("\nCMapiUtil::NoteLong ()") ;
if (m_lpMessage != NULL)
return ::lstrlen (m_lpMessage->lpszNoteText) ;
else
return 0 ;
}
void CMainFrame::OnMailSendText()
{
CMapiUtil MapiUtil ;
if (MapiUtil.Logon (this))
{
if (MapiUtil.GetAddressList (this) == SUCCESS_SUCCESS)
{
MapiMessage Message ;
Message.ulReserved = 0 ;
Message.lpszSubject = "Message subject" ;
Message.lpszNoteText = "Text of the message." ;
Message.lpszMessageType = NULL ; // standard msg.
Message.lpszDateReceived = "1994/01/01 12:00" ;
Message.lpszConversationID = NULL ;
Message.flFlags = MAPI_UNREAD | MAPI_DIALOG ;
Message.lpOriginator = NULL;
Message.nRecipCount = 0 ;
Message.lpRecips = NULL ;
Message.nFileCount = 0 ;
Message.lpFiles = NULL ;
MapiUtil.SendMessage (this, &Message) ;
MapiUtil.FreeMessage () ;
}
MapiUtil.Logoff (this) ;
}
else
MessageBox ("Could not log on to MS Mail.", "Error",
MB_ICONEXCLAMATION | MB_OK) ;
}
Copyright © 1994, Dr. Dobb's Journal