Messaging-enabled Programs with Exchange

Presentation 487

 

Bob Combs, Director of Technology

PCSI, Inc.

3 University Plaza, Suite 600

Hackensack, NJ  07601

Tel. (201) 488-7222

bcombs@pcsiusa.com

 

Abstract

Exchange Server is a fairly popular email system, and many application developers would like to message-enable their applications. This paper will list the various methods for programming to Exchange, and then it will delve into code showing real working examples.

 

Introduction

Microsoft uses the term message-enabled to mean that an application can access Exchange folders to augment the application's operation.  Exchange Server is Microsoft's server-based email system that supplies folders for email, public information, and schedules.

Exchange is a server-based email system, which means all processing rules and primary storage of messages is maintained on the server.  The client itself, while sophisticated, does not contain the main storage and rules for processing messages.  The advantage of a server-base email system is that the rules and processing is maintained in one location and does not have to be distributed to every client machine that uses the email system.  This was a problem with earlier email systems such as Microsoft Mail, cc:Mail, etc.

Having a more powerful and stable email system has also meant that application programs want to use email capabilities.  Exchange provides several Application Programming Interface (API) sets to use in coding messaging access.

API Sets

The messaging API sets available for Exchange are

bv

Description

Languages supported

CMC

Common Messaging Calls

C++

Simple MAPI

Simple Messaging API

VB, C++

MAPI (Extended MAPI)

Extended Messaging API

C++

CDO

Collaborative Data Objects

VB, C++

 

The Simple Messaging Application Program Interface (simple MAPI) is the most straightforward set of library routines available for sending and receiving e-mail messages.  However, it is severely limited in what it can do.

The Common Messaging Calls (CMC) library will allow code to be transportable between Microsoft Exchange and other e-mail systems, but adds little functionality over simple MAPI.  Unfortunately, CMC can be called only from C/C++ and not from Visual Basic as simple MAPI can.

The full power and complexities of Exchange are available using Extended MAPI.  Extended MAPI looks nothing like Simple MAPI, having hundreds of routines, and is very complex to code. Extended MAPI is only ever coded in C/C++.

CDO is an object-oriented interface to e-mail.  It started out as OLE Messaging version 1.0.  As of Exchange Server 5.0, it was renamed to Active Messaging version 1.1.  It is currently named CDO for version 1.2.  CDO 1.2 is included with Exchange Server 5.5.  There is a slightly newer version of CDO, 1.21, that is shipped with Outlook 98.

Message Properties

The Exchange messaging system uses object-oriented storage.  An email message is stored in the Message object, which has a long list of base properties or attributes.  Here is a quick list of the typical properties that might be needed to send a message.

 

Property

Description

PR_BODY

The message text body.

PR_MESSAGE_CLASS

The message class type; for normal email this is IPM.Note

PR_PRIORITY

The message priority; defaults to Normal.

PR_SUBJECT

The message subject text.

PR_ATTACH_METHOD

The attached file manner.  Programs can use ATTACH_OLE for object streams.

PR_ATTACH_DATA_OBJ

The attachment data.

PR_DISPLAY_NAME

The attachment displayed filename.

 

Simple MAPI

Simple MAPI has only a handful of routines.  Those routines are used to send and receive mail, with or without attached files.  Below is an example of a simple MAPI program to send an email.

 

SendXMsg.cpp

// SendXMsg.cpp

//

// Purpose: To send a message with an attachment to an Exchange

// recipient using simple MAPI.

//

//

// 960423 original

//

/////////////////////////////////////////////////////////////////////

#include <windows.h>

#include <windowsx.h>

#include <fstream.h>

#include <mapi.h>

#include <stdio.h>

 

#define MAIN

#include "mapinit.h"

 

 

SYSTEMTIME LocalTime;

char msglog[80];

char szDate[10];

char szTime[10];

char buffer[1];

int  retcode = 0;

 

LHANDLE lplhSession;

HANDLE hLibrary;

 

void main()

{

       ULONG hr;

       char sCurrentTime[40];

      

//     Open log file

       ofstream ofLogFile("c:\\temp\\SendXMsg.log",ios::out);

 

//     date & time

       GetLocalTime(&LocalTime);

       GetDateFormat(NULL,0,&LocalTime,"MM'-'dd'-'yy",szDate,10);

       GetTimeFormat(NULL,0,&LocalTime,"HH':'mm':'ss",szTime,10);

       strncpy(msglog,"SendXMsg - Begin date/time: ",79);

       strcat(msglog,szDate);

       strncat(msglog," ",1);

       strcat(msglog,szTime);

       strcat(msglog,"\n");

       ofLogFile.write(msglog,strlen(msglog));

 

//     declare variables

       MapiMessage Message, *lpMessage=&Message;

       MapiRecipDesc Recip, *lpRecip=&Recip;

 

// Load MAPI.DLL

       if (InitMAPI()) {

              strncpy(msglog,"Initialization to MAPI.DLL failed.\n",79);

              ofLogFile.write(msglog,strlen(msglog));

              retcode = 1;

       }

       else {

 

              strcpy(sCurrentTime, "1996/05/23 14:55");

 

              lpMessage->ulReserved = 0;

              lpMessage->lpszSubject = "NOTEBOOK PRODUCTION UPLOAD";

              lpMessage->lpszNoteText =

"Notebook upload to production failed - see attached log.";

              lpMessage->lpszMessageType = NULL;

              lpMessage->lpszDateReceived = sCurrentTime;

              lpMessage->lpszConversationID = "";

              lpMessage->flFlags = 0;

              lpMessage->lpOriginator = NULL;

              lpMessage->nRecipCount = 1;

              lpMessage->lpRecips = lpRecip;

              lpMessage->nFileCount = 0;

              lpMessage->lpFiles = NULL;

 

              lpRecip->ulReserved = 0;

              lpRecip->ulRecipClass = MAPI_TO;

              lpRecip->lpszName = "Bob Combs";

              lpRecip->lpszAddress = NULL;

              lpRecip->ulEIDSize = 0;

              lpRecip->lpEntryID = NULL;

 

              hr = (*lpfnMAPILogon) (0L,"MyProfile",0L,

MAPI_NEW_SESSION | MAPI_LOGON_UI,0L,&lplhSession);

              if (hr != SUCCESS_SUCCESS) {

                     strncpy(msglog,"Logon to Exchange failed.\n",79);

                     ofLogFile.write(msglog,strlen(msglog));

                     retcode = 2;

              }

              else {

                     hr = (*lpfnMAPISendMail)

(lplhSession,0L,lpMessage,MAPI_LOGON_UI,0L);

                     if (hr != SUCCESS_SUCCESS) {

                           strncpy(msglog,"SendMail to Exchange failed.\n",79);

                           ofLogFile.write(msglog,strlen(msglog));

                           retcode = 3;

                     }

                     if ((*lpfnMAPILogoff)

   (lplhSession,0L,0L,0L) != SUCCESS_SUCCESS) {

                           strncpy(msglog,"Logoff from Exchange failed.\n",79);

                           ofLogFile.write(msglog,strlen(msglog));

                           retcode = 4;

                     }

              }

              DeInitMAPI();

              strncpy(msglog,"Message sent to Exchange.\n",79);

              ofLogFile.write(msglog,strlen(msglog));

       }

 

 

//     termination message and timestamp

//     date & time

       GetLocalTime(&LocalTime);

       GetDateFormat(NULL,0,&LocalTime,"MM'-'dd'-'yy",szDate,10);

       GetTimeFormat(NULL,0,&LocalTime,"HH':'mm':'ss",szTime,10);

       strncpy(msglog,"End date/time: ",16);

       strcat(msglog,szDate);

       strncat(msglog," ",1);

       strcat(msglog,szTime);

       strcat(msglog,"\n");

       ofLogFile.write(msglog,strlen(msglog));

 

//     close log file

       ofLogFile.close();

 

//     exit with return code

//     return retcode;

}

 

Mapinit.cpp

The Mapinit module defines the entry points to allow SendXMsg to load the Simple MAPI DLL (MAPI32.DLL).

 

// Mapinit.cpp

//

// Purpose: To load MAPI.DLL and acquire an entry point to the simple

// MAPI functions.

//

// 960418 original

//

/////////////////////////////////////////////////////////////////////

 

#include <windows.h>

#include <mapi.h>

#include "mapinit.h"

 

extern HANDLE hLibrary;

 

int FAR PASCAL InitMAPI()

{

// load the dll file MAPI.DLL

       if ((hLibrary = LoadLibrary("MAPI32.DLL")) < (HANDLE)32)

              return (ERR_LOAD_LIB);

 

// establish entry points to dll functions

 

// pointer to MAPILOGON - begin simple MAPI session function

       if ((lpfnMAPILogon = (PFNMAPILOGON) GetProcAddress

    (hLibrary,SZ_MAPILOGON)) == NULL)

              return (ERR_LOAD_FUNC);

 

// pointer to MAPILOGOFF - end simple MAPI session function

       if ((lpfnMAPILogoff = (PFNMAPILOGOFF) GetProcAddress

    (hLibrary,SZ_MAPILOGOFF)) == NULL)

              return (ERR_LOAD_FUNC);

 

// pointer to MAPISENDMAIL - send message function

       if ((lpfnMAPISendMail = (PFNMAPISENDMAIL) GetProcAddress

    (hLibrary,SZ_MAPISENDMAIL)) == NULL)

              return (ERR_LOAD_FUNC);

 

       return (0);

}

 

int FAR PASCAL DeInitMAPI()

{

       lpfnMAPILogon = NULL;

       lpfnMAPILogoff = NULL;

       lpfnMAPISendMail = NULL;

 

       FreeLibrary(hLibrary);

 

       return (0);

}

 

Mapinit.h

Mapinit.h is the include file.

 

// Program: Mapinit.h

//

// Purpose: Header definitions file for Mapinit

//

// 960418 original

//

/////////////////////////////////////////////////////////////////////

 

#define SZ_MAPILOGON "MAPILogon"

#define SZ_MAPILOGOFF "MAPILogoff"

#define SZ_MAPISENDMAIL "MAPISendMail"

 

#define ERR_LOAD_LIB 0x02

#define ERR_LOAD_FUNC 0x04

 

typedef ULONG (FAR PASCAL *PFNMAPILOGON) (ULONG, LPTSTR, LPTSTR, FLAGS, ULONG, LPLHANDLE);

typedef ULONG (FAR PASCAL *PFNMAPILOGOFF) (LHANDLE, ULONG, FLAGS, ULONG);

typedef ULONG (FAR PASCAL *PFNMAPISENDMAIL) (LHANDLE, ULONG, lpMapiMessage, FLAGS, ULONG);

 

#ifdef MAIN

 

PFNMAPILOGON lpfnMAPILogon;

PFNMAPILOGOFF lpfnMAPILogoff;

PFNMAPISENDMAIL lpfnMAPISendMail;

 

#else

 

extern PFNMAPILOGON lpfnMAPILogon;

extern PFNMAPILOGOFF lpfnMAPILogoff;

extern PFNMAPISENDMAIL lpfnMAPISendMail;

 

#endif

 

int FAR PASCAL InitMAPI(void);

int FAR PASCAL DeInitMAPI(void);

 

CDO

The CDO libraries are actually two libraries; CDO.DLL and CDOHTML.DLL.  The CDO library is used for direct client programming.  The CDOHTML library is used for rendering to HTML.  There is an invaluable file included with CDO named CDO.HLP.  This is the programmer's help file, containing details of each object and all methods and properties available.

CDO does not install automatically from the Exchange Server CD-ROM.  You must explicitly copy the CDO files onto your hard drive.  Those files are CDO.DLL, CDOHTML.DLL, and CDO.HLP.  Copy them to \winnt\system32 and then run regsvr32 cdo.dll to register CDO.  If you're not rendering to HTML you don't need to copy the CDOHTML.DLL file. You should note that when CDO is invoked it calls MAPI32.DLL routines.  Therefore, the Windows Messaging Subsystem should be installed on your client.

 


 

 


The base object is the Session object.  All CDO objects are derived from the Session object.  The first thing any program must do is create the Session object and log onto Exchange using the profile name.  If a profile name is not supplied to logon then either the default profile will be used or a pop-up dialog will allow the user to select one.  Since we want this to happen programmatically, we should always supply the profile name.  Many of the next level objects down from Session are collections of objects, for example a folder of messages such as the Inbox. 

After the Session object is created and Logon has been performed, the Message object should be instantiated.  The Message object has a Recipients list object which must include the person(s) to send the e-mail to.  It could additionally include CC and BC recipients as well, but a single TO recipient is minimally required.  CDO supports sending attached files in the message.  The message has an Attachments object that consists of one or more attachment objects.

The following table describes the basic CDO objects.

Object

Use

Session

Logon session

   Address Entries

Address book entries

   Address Lists

Distribution lists

   Attachments

Attachments to a message

   Fields

Fields in a message

   Messages

Messages

   Recipients

Message recipients to send to

 

CDO is used in VB projects by selecting Project, References from the VB menu and checking the line that reads Microsoft CDO 1.2 Library.

 

SendMsg

The SendMsg example is a simple example of sending a message.  It accepts a subject line, message body, recipients, and a filename to attach.  Both the recipient and filename fields use helper controls to browse for the desired reference.

The form used is:

 

The source code for SendMsg is:

 

' references CDO and Common Dialog Control 6.0

Dim objSession As MAPI.Session

Dim objOutbox As Folder

Dim objMessages As Messages

Dim objMessage As Message

Dim objRecipients As MAPI.Recipients

Dim sAttachmentFileName As String

 

Private Sub CommandExit_Click()

    On Error Resume Next

    FormSend.MousePointer = vbHourglass

    Set objMessage = Nothing

    Set objMessages = Nothing

    On Error GoTo 0

    Set objOutbox = Nothing

    objSession.Logoff

    Set objSession = Nothing

    FormSend.MousePointer = vbDefault

    End

End Sub

 

Private Sub CommandFilename_Click()

    CommonDialogFilename.ShowOpen

    TextFilename = CommonDialogFilename.FileName

    TextSubject = TextFilename

End Sub

 

Private Sub CommandSend_Click()

    Set objMessage = objMessages.Add

    If objMessage Is Nothing Then

        Exit Sub

    End If

    objMessage.Subject = TextSubject.Text

    objMessage.Text = TextBody.Text

   

    objMessage.Recipients = objRecipients

   

    With objMessage.Attachments

        If TextFilename <> "" Then

            sAttachmentFileName = TextFilename

            Call .Add(TextFilename, , , sAttachmentFileName)

        End If

    End With

    Call objMessage.Send(False, False)

    Set objMessage = Nothing

    Call CommandExit_Click

End Sub

 

Private Sub Form_Load()

    Set objSession = CreateObject("MAPI.Session")

'   objSession.Logon ("Bob Combs")

    objSession.Logon

    Set objOutbox = objSession.Outbox

    If objOutbox Is Nothing Then

        Exit Sub

    End If

    Set objMessages = objOutbox.Messages

    If objMessages Is Nothing Then

        Exit Sub

    End If

    TextClass = "myapp.file"

    TextBody = "Your message here ..."

End Sub

 

Private Sub TextRecipient_Click()

    Dim i, iCount As Integer

    Set objRecipients = objSession.AddressBook(objRecipients, _

                        Title:="Select Recipients", _

                        forceResolution:=True, _

                        recipLists:=2)

    TextRecipient.Text = ""

    iCount = objRecipients.Count

    For i = 1 To iCount Step 1

        TextRecipient = TextRecipient & objRecipients.Item(i) & ";"

    Next i

End Sub

 

CDO from C++

It is possible to call CDO objects from C++, as can be seen in this example.  This is not normally recommend since it is easier to use from VB.  One usually uses C++ when additional functionality is needed, which results in coding in C++ for extended MAPI.

 

// CdoSendMsg.cpp

//

// Example of sending a message using CDO instead of MAPI.

//

///////////////////////////////////////////////////////////////

#include <windows.h>

 

int main()

{

       BSTR bstrProfile;

       CLSID clsid;

       DISPPARAMS dispArgs;

       DISPID dispid;

       HRESULT hr;

       IDispatch* pSession=NULL;

       IUnknown* pUnk=NULL;

       VARIANT varResult;

       VARIANTARG varg;

       OLECHAR* logon=L"Logon";

       wchar_t wszProfile[]=L"MyProfile";

       wchar_t wszProgID[]=L"MAPI.Session";

 

       // get the class ID for MAPI.Session

       hr=CLSIDFromProgID(wszProgID, &clsid);

       // initialize COM

       hr=CoInitialize(NULL);

       if(FAILED(hr)) {

              MessageBox(NULL, "Unable to find CDO object", "Error", MB_OK);

              exit(1);

       }

       // create object

       hr=CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown,

(void**)&pUnk);

       hr=pUnk->QueryInterface(IID_IDispatch, (void**)&pSession);

pUnk->Release();

       // find the Logon method's dispid

       hr=pSession->GetIDsOfNames(IID_NULL, &logon, 1, GetUserDefaultLCID(),

&dispid);

       // build variant variable structure for passing parameter(s)

       bstrProfile=SysAllocString(wszProfile);

       VariantInit(&varg);

       varg.vt=VT_BSTR;

       varg.bstrVal=bstrProfile;

       dispArgs.cArgs=1;

       dispArgs.rgvarg=&varg;

       dispArgs.cNamedArgs=0;

       dispArgs.rgdispidNamedArgs = NULL;

       // use the IDispatch interface to invoke Logon routine

       pSession->Invoke(dispid,

                                  IID_NULL,

                                  LOCALE_USER_DEFAULT,

                                  DISPATCH_METHOD,

                                  &dispArgs,

                                  &varResult,

                                  NULL,

                                  NULL);

       SysFreeString(bstrProfile);

 

       //  Place other code here

 

       // terminate session object & COM

       pSession->Release();

       CoUninitialize();

       return 0;

}

 

Extended MAPI

While we can't cover extended MAPI in a this paper, suffice it to say that extended MAPI is by far the most complex of the methods to manipulate Exchange.  Below is a snippet of code that should give the general flavor of extended MAPI coding.

 

<< code deleted for brevity >>

 

       // Initialize the MAPI libraries before calling ANY MAPI function.

       hResult=MAPIInitialize(NULL);

       if (FAILED(hResult)) {

              LogErr("Failed to initialize MAPI.", hResult);

              goto error_exit;

       }

 

       // Logon to the message subsystem.  We are going to ask the user to

       // select a profile to log into.  The UI for this will be provided by

       // MAPI.

       hResult=MAPILogonEx(0, szProfile, NULL, 0, &pSession);

       if (FAILED(hResult)) {

              LogErr("Failed to logon.", hResult);

              goto error_exit;

       }

 

<< code deleted for brevity >>

 

       // Obtain new message object (IMessage interface)

       hResult=lpInbox->CreateMessage(NULL,0,&lpMessage);

       if (FAILED(hResult)) {

              LogErr("Create message failed.",hResult);

              goto error_exit;

       }

      

       // Allocate memory for property array

       cProps = 6;

       if (MAPIAllocateBuffer(cProps * sizeof(SPropValue),

(LPVOID *) &lpProps) != S_OK) {

LogErr("Unable to allocate memory for properties.", nError);

              goto error_exit;

       }

 

       // Setup the properties

       i=0;

       lpProps[i].ulPropTag     = PR_MESSAGE_CLASS;

       lpProps[i++].Value.lpszA = "IPM.Note";

       lpProps[i].ulPropTag     = PR_PRIORITY;

       lpProps[i++].Value.l     = 0;

       lpProps[i].ulPropTag     = PR_SUBJECT;

       lpProps[i++].Value.lpszA = szSubject;

       lpProps[i].ulPropTag     = PR_COMMENT;

       lpProps[i++].Value.lpszA = szMsgID;

 

       // Set the message submission time

       GetSystemTime(&SysTime);

 

       // Convert to file time

       SystemTimeToFileTime(&SysTime, &SubmitTime);

 

       // Set up this property

       lpProps[i].ulPropTag     = PR_CLIENT_SUBMIT_TIME;

       lpProps[i++].Value.ft    = SubmitTime;

 

       // Build the recipients list

       hResult=GetRecipient(lpAdrBook, szTo, &lpAdrList);

       if (FAILED(hResult)) {

           LogErr("GetRecipient() failed.", hResult);

           goto error_exit;

       }

 

       // Set the properties in the message object

       hResult=lpMessage->SetProps(i, lpProps, &lpPropProblems);

       if (FAILED(hResult)) {

              LogErr("Set Props failed.",hResult);

              goto error_exit;

       }

       if(hResult == S_OK)

              if(lpPropProblems) {

                     MAPIFreeBuffer(lpPropProblems);

                     lpPropProblems=NULL;

              }

 

       // Set the recipients to the message

       hResult=lpMessage->ModifyRecipients(MODRECIP_ADD, lpAdrList);

       if (FAILED(hResult)) {

              LogErr("Modify Recipients failed.",hResult);

              goto error_exit;

       }

 

       // Open stream to file containing the attachment

       strcpy(szFQN, szPath);

       strcat(szFQN, szFilename);

      

       hResult=OpenStreamOnFile(MAPIAllocateBuffer,

                                                MAPIFreeBuffer,

                                                STGM_READ | STGM_DIRECT,

                                                szFQN,

                                                NULL,

                                         &lpSrcStream);

       if (FAILED(hResult)) {

              LogErr("Open stream on file failed.",hResult);

              goto error_exit;

       }

 

       // Create an attachment object

       hResult=lpMessage->CreateAttach(NULL,0,&lAttachNum,&lpAttach);

       if (FAILED(hResult)) {

              LogErr("Create attach failed.",hResult);

              goto error_exit;

       }

 

       // Open stream to attachment

       hResult=lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ,

                                           &(IID_IStream),

                                              0,

                                              MAPI_MODIFY | MAPI_CREATE,

                                                (LPUNKNOWN*) &lpDstStream);

       if (FAILED(hResult)) {

              LogErr("Open Property on attach failed.",hResult);

              goto error_exit;

       }

 

       // Get size of input stream file

       lpSrcStream->Stat(&StatInfo, STATFLAG_NONAME);

       // Copy file into attachment

       hResult=lpSrcStream->CopyTo(lpDstStream,StatInfo.cbSize,NULL,NULL);

       if (FAILED(hResult)) {

              LogErr("Copy To stream failed.",hResult);

              goto error_exit;

       }

 

       // Setup the properties for the attachment (re-use same memory area)

       i=0;

       // Rendering position must be set so that an icon displays in the message

       // text area

       lpProps[i].ulPropTag     = PR_RENDERING_POSITION;

       lpProps[i++].Value.l     = 2;

       lpProps[i].ulPropTag     = PR_ATTACH_METHOD;

       lpProps[i++].Value.l     = ATTACH_OLE;

 

       // Set the remaining properties for the attachment

       hResult=lpAttach->SetProps(i, lpProps, &lpPropProblems);

       if (FAILED(hResult)) {

              LogErr("Set Props failed.",hResult);

              goto error_exit;

       }

       if(hResult == S_OK)

              if(lpPropProblems) {

                     MAPIFreeBuffer(lpPropProblems);

                     lpPropProblems=NULL;

              }

 

       // Save attachment

       hResult=lpAttach->SaveChanges(KEEP_OPEN_READWRITE);

       if (FAILED(hResult)) {

              LogErr("Failure to save changes on attach.",hResult);

              goto error_exit;

       }

 

       // Save message

       hResult=lpMessage->SaveChanges(KEEP_OPEN_READWRITE);

       if (FAILED(hResult)) {

              LogErr("Failure to save changes on message.",hResult);

              goto error_exit;

       }

 

       // Send the message

       hResult=lpMessage->SubmitMessage(FORCE_SUBMIT);

       if (FAILED(hResult)) {

              LogErr("Submit Message failed.",hResult);

              goto error_exit;

       }

 

 

Author | Title | Track | Home

Send email to Interex or to the Webmaster
©Copyright 1999 Interex. All rights reserved.