MAPI

The Messaging API (MAPI) is at the heart of many of today's Internet-enabled applications. Using MAPI, both the client and the server in a client/server system can make people or even other systems aware of information needed to get the job done. Of course our emphasis will be on server-based applications using MAPI, but normally the operation of MAPI is the same for both clients and servers.

One significant difference between clients and servers is that servers need to have UI-free interactions with MAPI. Fortunately, allowances are made for interacting with MAPI without the UI, and some of the techniques can even be used for client applications that need to send a message without user intervention.

Flavors of MAPI

MAPI comes in several flavors, which adds to the confusion, of course. What exactly do I mean when I say MAPI? When we discuss MAPI, we are talking about the following four families of client interfaces:

  • Common Messaging Calls (CMC) A cross-platform client interface for C/C++ programs, supported in Windows and some UNIX implementations
  • Simple MAPI A Windows-only client interface with a function-call API suitable for both C/C++ and Microsoft Visual Basic
  • MAPI A COM Windows-only client interface for use by C/C++ programs
  • Collaboration Data Objects (CDO) An automation client interface supporting only Windows clients

Some additional distinctions exist between the client interfaces, but they are not important for server-based applications. For instance, MAPI and CDO both support forms processing, something a server-based application does not need. We will focus on Simple MAPI and MAPI, with a special emphasis on the steps required to allow MAPI to work in a Windows 2000 server-based application. For a Windows 2000 server-based application, the multiplatform advantage offered by CMC is hardly worth using this lesser-known interface. CDO is overkill for what most server-based applications need to do. So, the choice narrows down to Simple MAPI or MAPI.

Selecting the Correct Client Interface

Given the above descriptions, which interface is best? That requires knowing exactly what the application must do. Do you want your server-based application to send and receive e-mail? Both Simple MAPI and MAPI do that. Do you need to interact with the address book on the server? Here things become a bit more difficult. While Simple MAPI does provide some ability to interact with address book entries, doing so using MAPI might be better.

Of course, MAPI takes its own toll. As with TAPI 3, special care must be taken when using COM functions that create hidden windows that require message pumps, and special care must be taken to allow MAPI to work in a server-based application. On the other hand, some of the concern with using MAPI is reduced by explicit support in the API for use within a Windows 2000 server-based application.

NOTE
Of course, if your service application will be integrated with Microsoft SQL Server, you have an alternative to doing your own MAPI application. SQL Server contains extended stored procedures that allow e-mail to be sent from Transact-SQL. Chapter 13 contains an example that uses extended stored procedures to send e-mail. Of course this solution results in really simple MAPI.

Simple MAPI?

The name Simple MAPI is somewhat misleading. While it certainly is the simplest of the messaging APIs, it is by no means simple. For instance, rather than calling the functions via an import library, the library is called after an explicit call to LoadLibrary and GetProcAddress . While this is not terribly difficult, it does not always seem simple.

What can Simple MAPI do? A list of the supported functions is fairly short, but does include the bulk of what many server-based applications will need. The Simple MAPI functions are

  • MAPILogon
  • MAPISaveMail
  • MAPILogoff
  • MAPIDeleteMail
  • MAPISendMail
  • MAPIFreeBuffer
  • MAPISendDocuments
  • MAPIAddress
  • MAPIFindNext
  • MAPIDetails
  • MAPIReadMail
  • MAPIResolveName
  • Given these 12 functions, you can send e-mail with and without file attachments, read e-mail with and without file attachments, peek at e-mail, get information on addresses, and look through a filtered list of messages. The bad news for server-based applications is that some of this functionality is clearly aimed at client applications. For instance, MAPIAddress and MAPIDetails both create dialogs suitable for a user sitting at the machine but don't expose a suitable interface for a server. A common requirement for e-mail applications is the ability to take incoming e-mail and place it in certain folders, depending upon the content of the message, rather than placing it all in the Inbox. This is not possible with Simple MAPI.

    If you have bare-bones requirements, do consider Simple MAPI. While 12 Simple MAPI functions are supported, we'll use only 5 of them in the Send Mail example in the following section. For limited applications in which we know the addressees and can limit our requirements to sending and receiving e-mail, Simple MAPI can do the trick.

    Simple MAPI send/read mail example

    Listings 7-3 and 7-4 contain the header file and the source, respectively, for a Simple MAPI program to send and read e-mail. It is, of course, simple.

    Listing 7-3

    MAPIInit.h

     #defineSZ_MAPILOGON"MAPILogon" #defineSZ_MAPILOGOFF"MAPILogoff" #defineSZ_MAPISENDMAIL"MAPISendMail" #defineSZ_MAPISENDDOC"MAPISendDocuments" #defineSZ_MAPIFINDNEXT"MAPIFindNext" #defineSZ_MAPIREADMAIL"MAPIReadMail" #defineSZ_MAPISAVEMAIL"MAPISaveMail" #defineSZ_MAPIDELMAIL"MAPIDeleteMail" #defineSZ_MAPIFREEBUFFER"MAPIFreeBuffer" #defineSZ_MAPIADDRESS"MAPIAddress" #defineSZ_MAPIDETAILS"MAPIDetails" #defineSZ_MAPIRESOLVENAME"MAPIResolveName" #defineMAPIDLL"MAPI32.DLL" #defineERR_LOAD_LIB0x02 #defineERR_LOAD_FUNC0x04 typedefULONG(FARPASCAL*PFNMAPILOGON)(HWND,LPSTR,LPSTR, FLAGS,ULONG,LPLHANDLE); typedefULONG(FARPASCAL*PFNMAPILOGOFF)(LHANDLE,HWND, FLAGS,ULONG); typedefULONG(FARPASCAL*PFNMAPISENDMAIL)(LHANDLE,HWND, lpMapiMessage,FLAGS,ULONG); typedefULONG(FARPASCAL*PFNMAPISENDDOCUMENTS)(HWND,LPSTR, LPSTR,LPSTR,ULONG); typedefULONG(FARPASCAL*PFNMAPIFINDNEXT)(LHANDLE,HWND, LPSTR,LPSTR,FLAGS,ULONG,LPSTR); typedefULONG(FARPASCAL*PFNMAPIREADMAIL)(LHANDLE,HWND, LPSTR,FLAGS,ULONG,lpMapiMessageFAR*); typedefULONG(FARPASCAL*PFNMAPISAVEMAIL)(LHANDLE,HWND, lpMapiMessage,FLAGS,ULONG,LPSTR); typedefULONG(FARPASCAL*PFNMAPIDELETEMAIL)(LHANDLE,HWND,LPSTR, FLAGS,ULONG); typedefULONG(FARPASCAL*PFNMAPIFREEBUFFER)(LPVOID); typedefULONG(FARPASCAL*PFNMAPIADDRESS)(LHANDLE,HWND,LPSTR, ULONG,LPSTR,ULONG,lpMapiRecipDesc,FLAGS,ULONG, LPULONG,lpMapiRecipDescFAR*); typedefULONG(FARPASCAL*PFNMAPIDETAILS)(LHANDLE,HWND, lpMapiRecipDesc,FLAGS,ULONG); typedefULONG(FARPASCAL*PFNMAPIRESOLVENAME)(LHANDLE,HWND, LPSTR,FLAGS,ULONG,lpMapiRecipDescFAR*); #ifdefTHE_MAIN PFNMAPILOGONlpfnMAPILogon; PFNMAPILOGOFFlpfnMAPILogoff; PFNMAPISENDMAILlpfnMAPISendMail; PFNMAPISENDDOCUMENTSlpfnMAPISendDocuments; PFNMAPIFINDNEXTlpfnMAPIFindNext; PFNMAPIREADMAILlpfnMAPIReadMail; PFNMAPISAVEMAILlpfnMAPISaveMail; PFNMAPIDELETEMAILlpfnMAPIDeleteMail; PFNMAPIFREEBUFFERlpfnMAPIFreeBuffer; PFNMAPIADDRESSlpfnMAPIAddress; PFNMAPIDETAILSlpfnMAPIDetails; PFNMAPIRESOLVENAMElpfnMAPIResolveName; #else externPFNMAPILOGONlpfnMAPILogon; externPFNMAPILOGOFFlpfnMAPILogoff; externPFNMAPISENDMAILlpfnMAPISendMail; externPFNMAPISENDDOCUMENTSlpfnMAPISendDocuments; externPFNMAPIFINDNEXTlpfnMAPIFindNext; externPFNMAPIREADMAILlpfnMAPIReadMail; externPFNMAPISAVEMAILlpfnMAPISaveMail; externPFNMAPIDELETEMAILlpfnMAPIDeleteMail; externPFNMAPIFREEBUFFERlpfnMAPIFreeBuffer; externPFNMAPIADDRESSlpfnMAPIAddress; externPFNMAPIDETAILSlpfnMAPIDetails; externPFNMAPIRESOLVENAMElpfnMAPIResolveName; #endif intPASCALInitMAPI(void); intDeInitMAPI(void); voidFormatSimpleMAPIError(ULONGret,char*str); 

    Listing 7-4

    SimpleMAPI.cpp

     //SimpleMAPI.cpp:MainroutineofSimpleMAPIsend/read //mailexample // #defineTHE_MAIN #include"stdafx.h" HMODULEhLibrary; PFNMAPILOGONlpfnMAPILogon; PFNMAPILOGOFFlpfnMAPILogoff; PFNMAPISENDMAILlpfnMAPISendMail; PFNMAPISENDDOCUMENTSlpfnMAPISendDocuments; PFNMAPIFINDNEXTlpfnMAPIFindNext; PFNMAPIREADMAILlpfnMAPIReadMail; PFNMAPISAVEMAILlpfnMAPISaveMail; PFNMAPIDELETEMAILlpfnMAPIDeleteMail; PFNMAPIFREEBUFFERlpfnMAPIFreeBuffer; PFNMAPIADDRESSlpfnMAPIAddress; PFNMAPIDETAILSlpfnMAPIDetails; PFNMAPIRESOLVENAMElpfnMAPIResolveName; LHANDLElHandle; intmain(intargc,char*argv[]) { MapiMessageMessage; MapiMessage*pMessage; MapiRecipDescTo; MapiRecipDescOriginator; charszMessageID[512]; ULONGret; if(InitMAPI()!=0) { printf("\nCan'tinitializeSimpleMAPI"); return(1); } if(ret=lpfnMAPILogon(0,NULL,NULL,0, 0,&lHandle)!=SUCCESS_SUCCESS) { charbuffer[255]; FormatSimpleMAPIError(ret,buffer); printf("\nMAPILogon:%s",buffer); } memset(&Message,'\0',sizeof(Message)); memset(&To,'\0',sizeof(To)); memset((void*)&Originator,'\0',sizeof(Originator)); Originator.ulRecipClass=MAPI_ORIG; Originator.lpszName="test"; Originator.lpszAddress="test"; memset((void*)&To,'\0',sizeof(To)); To.ulRecipClass=MAPI_TO; To.lpszName="DougReilly"; To.lpszAddress="SMTP:74040.607@compuserve.com"; memset((void*)&Message,'\0',sizeof(Message)); Message.lpszSubject="Test"; Message.lpszNoteText="Thisisatest"; Message.lpOriginator=&Originator; Message.lpRecips=&To; Message.nRecipCount=1; Message.nFileCount=0; Message.lpFiles=0; if(ret=lpfnMAPISendMail(lHandle,0,&Message, 0,0)!=SUCCESS_SUCCESS) { charbuffer[255]; FormatSimpleMAPIError(ret,buffer); printf("\nMAPISendMail:%s",buffer); } //Nowlet'sreadtheoldestmessage. if(ret=lpfnMAPIFindNext(lHandle,0,NULL,NULL, MAPI_GUARANTEE_FIFOMAPI_LONG_MSGID,0, szMessageID)!=SUCCESS_SUCCESS) { if(ret!=MAPI_E_NO_MESSAGES) { charbuffer[255]; FormatSimpleMAPIError(ret,buffer); printf("\nMAPIFindNext:%s",buffer); } } else { //Loopuntilwefindanattachment. boolfoundFile=false; do{ if(ret=lpfnMAPIReadMail(lHandle,0,szMessageID, 0,0,&pMessage)!=SUCCESS_SUCCESS) { charbuffer[255]; FormatSimpleMAPIError(ret,buffer); printf("\nMAPIFindNext:%s",buffer); } else { printf("\nOriginator:%s",pMessage->lpOriginator); printf("\nReceived:%s", pMessage->lpszDateReceived); printf("\nSubject:%s",pMessage->lpszSubject); if(pMessage->nFileCount>0) { printf("\nAttachmentin%s", pMessage->lpFiles->lpszPathName); foundFile=true; } lpfnMAPIFreeBuffer(pMessage); } }while(!(foundFile)&& (ret=lpfnMAPIFindNext(lHandle,0,NULL,szMessageID, MAPI_GUARANTEE_FIFOMAPI_LONG_MSGID,0, szMessageID))==SUCCESS_SUCCESS); } if(ret=lpfnMAPILogoff(lHandle,0,0,0)!=SUCCESS_SUCCESS) { charbuffer[255]; FormatSimpleMAPIError(ret,buffer); printf("\nMAPISendMail:%s",buffer); } DeInitMAPI(); return0; } voidFormatSimpleMAPIError(ULONGret,char*str) { switch(ret) { caseMAPI_USER_ABORT: sprintf(str,"MAPI_USER_ABORT"); break; caseMAPI_E_FAILURE: sprintf(str,"MAPI_E_FAILURE"); break; caseMAPI_E_LOGIN_FAILURE: sprintf(str,"MAPI_E_LOGIN_FAILURE"); break; caseMAPI_E_DISK_FULL: sprintf(str,"MAPI_E_DISK_FULL"); break; caseMAPI_E_INSUFFICIENT_MEMORY: sprintf(str,"MAPI_E_INSUFFICIENT_MEMORY"); break; caseMAPI_E_ACCESS_DENIED: sprintf(str,"MAPI_E_ACCESS_DENIED"); break; caseMAPI_E_TOO_MANY_SESSIONS: sprintf(str,"MAPI_E_TOO_MANY_SESSIONS"); break; caseMAPI_E_TOO_MANY_FILES: sprintf(str,"MAPI_E_TOO_MANY_FILES"); break; caseMAPI_E_TOO_MANY_RECIPIENTS: sprintf(str,"MAPI_E_TOO_MANY_RECIPIENTS"); break; caseMAPI_E_ATTACHMENT_NOT_FOUND: sprintf(str,"MAPI_E_ATTACHMENT_NOT_FOUND"); break; caseMAPI_E_ATTACHMENT_OPEN_FAILURE: sprintf(str,"MAPI_E_ATTACHMENT_OPEN_FAILURE"); break; caseMAPI_E_ATTACHMENT_WRITE_FAILURE: sprintf(str,"MAPI_E_ATTACHMENT_WRITE_FAILURE"); break; caseMAPI_E_UNKNOWN_RECIPIENT: sprintf(str,"MAPI_E_UNKNOWN_RECIPIENT"); break; caseMAPI_E_BAD_RECIPTYPE: sprintf(str,"MAPI_E_BAD_RECIPTYPE"); break; caseMAPI_E_NO_MESSAGES: sprintf(str,"MAPI_E_NO_MESSAGES"); break; caseMAPI_E_INVALID_MESSAGE: sprintf(str,"MAPI_E_INVALID_MESSAGE"); break; caseMAPI_E_TEXT_TOO_LARGE: sprintf(str,"MAPI_E_TEXT_TOO_LARGE"); break; caseMAPI_E_INVALID_SESSION: sprintf(str,"MAPI_E_INVALID_SESSION"); break; caseMAPI_E_TYPE_NOT_SUPPORTED: sprintf(str,"MAPI_E_TYPE_NOT_SUPPORTED"); break; caseMAPI_E_AMBIGUOUS_RECIPIENT: sprintf(str,"MAPI_E_AMBIGUOUS_RECIPIENT"); break; caseMAPI_E_MESSAGE_IN_USE: sprintf(str,"MAPI_E_MESSAGE_IN_USE"); break; caseMAPI_E_NETWORK_FAILURE: sprintf(str,"MAPI_E_NETWORK_FAILURE"); break; caseMAPI_E_INVALID_EDITFIELDS: sprintf(str,"MAPI_E_INVALID_EDITFIELDS"); break; caseMAPI_E_INVALID_RECIPS: sprintf(str,"MAPI_E_INVALID_RECIPS"); break; caseMAPI_E_NOT_SUPPORTED: sprintf(str,"MAPI_E_NOT_SUPPORTED"); break; } } intPASCALInitMAPI() { if((hLibrary=LoadLibrary(MAPIDLL))<(HANDLE)32) { return(ERR_LOAD_LIB); } if((lpfnMAPILogon=(PFNMAPILOGON)GetProcAddress(hLibrary,SZ_MAPILOGON))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPILogoff=(PFNMAPILOGOFF)GetProcAddress(hLibrary,SZ_MAPILOGOFF))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPISendMail=(PFNMAPISENDMAIL)GetProcAddress(hLibrary,SZ_MAPISENDMAIL))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPISendDocuments= (PFNMAPISENDDOCUMENTS)GetProcAddress(hLibrary,SZ_MAPISENDDOC))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIFindNext=(PFNMAPIFINDNEXT)GetProcAddress(hLibrary,SZ_MAPIFINDNEXT))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIReadMail=(PFNMAPIREADMAIL)GetProcAddress(hLibrary,SZ_MAPIREADMAIL))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPISaveMail=(PFNMAPISAVEMAIL)GetProcAddress(hLibrary,SZ_MAPISAVEMAIL))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIDeleteMail=(PFNMAPIDELETEMAIL)GetProcAddress(hLibrary,SZ_MAPIDELMAIL))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIFreeBuffer=(PFNMAPIFREEBUFFER)GetProcAddress(hLibrary,SZ_MAPIFREEBUFFER))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIAddress=(PFNMAPIADDRESS)GetProcAddress(hLibrary,SZ_MAPIADDRESS))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIDetails=(PFNMAPIDETAILS)GetProcAddress(hLibrary,SZ_MAPIDETAILS))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIResolveName=(PFNMAPIRESOLVENAME)GetProcAddress(hLibrary,SZ_MAPIRESOLVENAME))==NULL) { return(ERR_LOAD_FUNC); } return(0); } intPASCALDeInitMAPI() { lpfnMAPILogon=NULL; lpfnMAPILogoff=NULL; lpfnMAPISendMail=NULL; lpfnMAPISendDocuments=NULL; lpfnMAPIFindNext=NULL; lpfnMAPIReadMail=NULL; lpfnMAPISaveMail=NULL; lpfnMAPIDeleteMail=NULL; lpfnMAPIFreeBuffer=NULL; lpfnMAPIAddress=NULL; lpfnMAPIDetails=NULL; lpfnMAPIResolveName=NULL; FreeLibrary(hLibrary); return(0); } 

    Listing 7-3 is MAPIInit.h, a header file containing function type declarations and manifest constant declarations required to dynamically load the Simple MAPI functions. The first group of #define statements is used to create constants for the literal names of the Simple MAPI functions. These are used later for getting the address of a function in the loaded DLL. Following the #define statements are some constant declarations used to signal particular types of errors.

    Next comes a series of type definitions for the function pointers, required when dynamically loading DLLs and obtaining pointers to exported functions. Finally variables of the function types just created with the typedefs are declared.

    Listing 7-4 is the source for SimpleMAPI.cpp. The main function first calls InitMAPI , a function defined later in the module. InitMAPI loads MAPI32.dll using LoadLibrary and then calls the Win32 API function GetProcAddress to obtain the address of each of the Simple MAPI functions. If this operation succeeds, MAPILogon is called through the lpfnMAPILogon function pointer. Like the functions they point to, all function pointers are named with an lpfn prefix. The prototype for MAPILogon is as follows:

     ULONGFARPASCALMAPILogon(ULONGulUIParam, LPTSTRlpszProfileName, LPTSTRlpszPassword, FLAGSflFlags, ULONGulReserved, LPLHANDLElplhSession) 

    The first parameter is the parent window handle, or 0. For service applications, 0 is always appropriate since the goal is not to use the user interface Simple MAPI provides. This parameter is included in many of the Simple MAPI calls, and we will always pass 0 as the parameter.

    The profile is the key to MAPI of any flavor. A profile is used to store information about the service providers and message services that are available. Whenever a user logs on to MAPI, she must specify a profile or use the default. The lpszProfileName parameter can point to a string containing the name of the profile to use, point to an empty string, or point to NULL. MAPI service providers can require a password, and lpszPassword should be set to the password if one is required. It is important to set this if needed, but if it is set to NULL or an empty string, MAPI will not present a dialog if flFlags does not contain the MAPI_LOGON_UI bit flag. MAPI_PASSWORD_UI is another bit flag that can be set to force the entry of only a password, while not allowing the user to change the profile name. These two flags are mutually exclusive.

    MAPI_FORCE_DOWNLOAD tells MAPILogon to attempt to download messages before the function returns. If this flag is not set, messages can be downloaded in the background after the return from the function call. MAPI_NEW_SESSION causes MAPILogon to attempt to create a new session rather than to acquire the environment's shared session. The fifth parameter is reserved and must be 0. The final parameter is a pointer to a Simple MAPI session handle that will be set upon a successful call. If the call is not successful, the return code indicates which error has occurred. This error code, along with a pointer to a string, is passed to FormatSimpleMAPIError , a function defined later in the module that is a simple switch statement controlled by the return value from a Simple MAPI function.

    To send a message with Simple MAPI, a MapiMessage structure must be filled in:

     typedefstruct{ ULONGulReserved; LPTSTRlpszSubject; LPTSTRlpszNoteText; LPTSTRlpszMessageType; LPTSTRlpszDateReceived; LPTSTRlpszConversationID; FLAGSflFlags; lpMapiRecipDesclpOriginator; ULONGnRecipCount; lpMapiRecipDesclpRecips; ULONGnFileCount; lpMapiFileDesclpFiles; }MapiMessage; 

    The first data element of the MapiMessage structure is reserved and must be set to 0. The subject and text of the message are passed in the next two parameters, lpszSubject and lpszNoteText . The message type can be passed in the lpszMessageType element of the structure. A NULL is commonly passed for this parameter. The default message type is interpersonal messages (IPM), and if the client does not support any type but interpersonal messages, the parameter is ignored. Another type of message supported by some clients is interprocess communications (IPC). The fifth and sixth members of the structure, lpszDateReceived and lpszConversationID , are pointers to buffers to accept the date received and a conversation identifier, respectively. The date received is in the format YYYY-MM-DD HH:MMfor example, 1999-08-25 10:08. The conversation ID can point to a string identifying the thread to which the message belongs. Some messaging systems ignore this member.

    The next element of the MapiMessage structure is flFlags . The defined flags for this bit flag are MAPI_RECEIPT_REQUESTED to request a receipt (set when sending a message), MAPI_SENT to indicate the message has been sent, and MAPI_UNREAD to indicate that the message has not been read. Note that when we go to read a MAPI message, we can peek at the message without setting this bit.

    There are two instances of an embedded MapiRecipDesc structure within the MapiMessage structure. The originator is described in lpOriginator , and the recipients are described in lpRecips . The count of the number of MapiRecipDesc structures pointed to by lpRecips is contained in nRecipCount . The prototype for the MapiRecipDesc structure is as follows:

     typedefstruct{ ULONGulReserved; ULONGulRecipClass; LPTSTRlpszName; LPTSTRlpszAddress; ULONGulEIDSize; LPVOIDlpEntryID; }MapiRecipDesc; 

    The first member is reserved and must be set to 0. The second parameter, ulRecipClass , is one of the following values:

    • MAPI_ORIG The originator of the message
    • MAPI_TO A primary message recipient
    • MAPI_CC A receiver of a copy of the message
    • MAPI_BCC A receiver of a blind copy of the message

    The display name of the person referred to in this structure is pointed to by lpszName . Like TAPI, MAPI has its own concept of what an address is. The address of the person referred to in this structure is pointed to by lpszAddress . This address has a specific format: [AddressType]:[Address]. Valid MAPI addresses include SMTP:74040.607@compuserve.com and FAX:732-555-2683 . For outbound messages, the address can also point to the name of someone in the address book rather than a custom address. The final two members of the structure, ulEIDSize and lpEntryID , identify the recipient to the messaging system service. These are not decipherable by the client application, and the lpEntryID should be considered a black box not to be opened.

    The final two members of the MapiMessage structure are related to file attachments. An array of MapiFileDesc structures is pointed to by lpFiles , and nFileCount contains the number of file description structures.

     typedefstruct{ ULONGulReserved; ULONGflFlags; ULONGnPosition; LPTSTRlpszPathName; LPTSTRlpszFileName; LPVOIDlpFileType; }MapiFileDesc; 

    The first member of the structure is reserved and must be set to 0. The second member, flFlags , is a bit-mapped field that can contain one or both of the following flags:

    • MAPI_OLE The attachment is an OLE object. If MAPI_OLE_STATIC is also set, the attachment is a static OLE object. Otherwise the attachment is an embedded OLE object.
    • MAPI_OLE_STATIC The attachment is a static OLE object.

    The position of the file within the note text is returned in nPosition . This positioning allows the e-mail client to properly position an icon or other visual cue for the placement of the file in the note text. Server-based applications commonly do not display the note text, so this member is generally ignored. The full path of the attached file and the filename are placed in lpszPathName and lpszFileName , respectively. The path name is a fully qualified path name with drive and directory. The filename is the name as seen by the recipient and generally can be ignored by server-based applications. The type of the file is pointed to by the lpFileType member, which can point to a structure of type MapiFileTagExt . This structure contains information about the name of the file type ( lpTag ) as well as the encoding scheme used ( lpEncoding ), for example MacBinary , UUENCODE , or binary.

     typedefstruct{ ULONGulReserved; ULONGcbTag; LPBYTElpTag; ULONGcbEncoding; LPBYTElpEncoding; }MapiFileTagExt; 

    Once the MapiMessage structure is filled in, we call MapiSendMail to actually send the message. There is also a MapiSendDocuments function that, while less flexible, is significantly less complex. MapiSendMail , which has the following prototype, is virtually always worth the additional effort.

     ULONGFARPASCALMAPISendMail(LHANDLElhSession, ULONGulUIParam, lpMapiMessagelpMessage, FLAGSflFlags, ULONGulReserved) 

    The first parameter, lhSession , is the session handle returned from the call to MAPILogon , and ulUIParam is the parent window handle, or 0. Once again, 0 is used in this example because we do not expect to generate any UI. The address of the message structure just described is sent as lpMessage . The bit flag parameter, flFlags , can include:

    • MAPI_DIALOG Used to cause a dialog to appear to allow the user to select recipientssomething our server-based applications will never want to do.
    • MAPI_LOGON_UI Used to cause a logon dialog to appear if neededanother option that server-based applications will not use.
    • MAPI_NEW_SESSION Used to request that a new MAPI session be started. If this flag is not set, the function uses an existing session. No examples presented in this chapter use this flag.

    The final parameter to MapiSendMail ulReserved must be 0. As with all the Simple MAPI functions, a return code other than SUCCESS_SUCCESS indicates an error, and the SimpleMAPI example application in Listing 7-4 will call a routine to create an error string describing the problem and then display the string. Depending upon the state of the current connection, the message will either be immediately sent, or in the case of a machine connected through a dial-up Internet connection, the message will be placed in the Outbox and sent when a connection is established.

    A Simple MAPI read mail example

    Once we have sent e-mail, we might want to read e-mail. For example, if we had requested a return receipt in the previous portion of Listing 7-4, we might want the server application to next await a reply indicating that the remote user or system has received the e-mail. Doing this with Simple MAPI is a two-step operation. First we must find the message, and then we can read it.

    MapiFindNext is the somewhat misnamed function used to find a message so that it can be read. While many APIs have FindFirst and FindNext pairs of functions, Simple MAPI uses MapiFindNext to do both:

     ULONGFARPASCALMAPIFindNext(LHANDLElhSession, ULONGulUIParam, LPTSTRlpszMessageType, LPTSTRlpszSeedMessageID, FLAGSflFlags, ULONGulReserved, LPTSTRlpszMessageID) 

    The first two parameters, lhSession and ulUIParam , are used exactly as previous Simple MAPI functions. The next parameter, lpszMessageType , is used the same way as the member of the same name in the MapiMessage structure, with NULL passed for IPM messages. The fourth parameter, lpszSeedMessageID , is used to find a specific message. If this is NULL or points to an empty string, the first message that matches the type specified is returned.

    The three flags that can be sent in flFlags are

    • MAPI_GUARANTEE_FIFO Messages should be returned in the order they were received.
    • MAPI_LONG_MSGID The returned message ID (pointed to by lpszMessageID ) can be as long as 512 characters. If you don't set this flag, operations that attempt to read messages with longer message IDs might fail. Early versions of MAPI only supported 64-character message IDs.
    • MAPI_UNREAD_ONLY Only unread messages should be read.

    The next parameter to MapiFindNext ulReserved must be 0. The last parameter is lpszMessageID . This should point to a buffer allocated by the application at least 64 bytes in size unless MAPI_LONG_MESID is specified in flFlags , in which case it must be 512 bytes.

    In Listing 7-4, MapiFindNext is first called with NULL passed for the lpszMessageType and lpszSeedMessageID , MAPI_GUARANTEE_FIFO, and MAPI_LONG_MSGID in flFlags. A second call to MapiFindNext is made in a loop with lpszSeedMessageID set to the message ID of the previously read message.

    If MapiFindNext succeeds, MapiReadMail is called to actually read the message.

     ULONGFARPASCALMAPIReadMail(LHANDLElhSession, ULONGulUIParam, LPTSTRlpszMessageID, FLAGSflFlags, ULONGulReserved, lpMapiMessageFAR*lppMessage) 

    The first two parameters, lhSession and ulUIParam , are used exactly as we've used in previous Simple MAPI functions. The lpszMessageID points to a message ID, which is generally discovered through an earlier call to MapiFindNext . Four values can be sent in the flFlags parameter:

    • MAPI_BODY_AS_FILE MapiReadMail should save the note text of the message to a file and save it as the first attachment.
    • MAPI_ENVELOPE_ONLY MapiReadMail should only read the header. This can significantly improve performance.
    • MAPI_PEEK If the messaging system supports it, the message is read, but the message is not marked as read. If the messaging system does not support this flag, it marks the message as read.
    • MAPI_SUPPRESS_ATTACH MapiReadMail should not copy attachments to temporary filesthus enhancing performance.

    The final parameter to MapiReadMessage is a pointer to a pointer to contain a MapiMessage structure. This structure is allocated by MAPI and must be freed later with a call to MapiFreeBuffer . This function takes as its sole parameter the pointer to the MapiMessage structure.

    Listing 7-4 contains this code fragment to loop through messages, reporting on the subject and date. If an attachment is found, information on that is displayed. The loop continues until it finds a file with an attachment and then exits the loop.

     //Loopuntilwefindanattachment. boolfoundFile=false; do{ if(ret=lpfnMAPIReadMail(lHandle,0,szMessageID, 0,0,&pMessage)!=SUCCESS_SUCCESS) { charbuffer[255]; FormatSimpleMAPIError(ret,buffer); printf("\nMAPIFindNext:%s",buffer); } else { printf("\nOriginator:%s",pMessage->lpOriginator); printf("\nReceived:%s",pMessage->lpszDateReceived); printf("\nSubject:%s",pMessage->lpszSubject); if(pMessage->nFileCount>0) { printf("\nAttachmentin%s", pMessage->lpFiles->lpszPathName); foundFile=true; } lpfnMAPIFreeBuffer(pMessage); } }while(!(foundFile)&& (ret=lpfnMAPIFindNext(lHandle,0,NULL,szMessageID, MAPI_GUARANTEE_FIFOMAPI_LONG_MSGID,0, szMessageID))==SUCCESS_SUCCESS); 

    The significant part of this code is that the second call to MapiFindNext is made with the message ID from the previous read. Failing to do this will cause the program to simply loop, displaying information about the same message over and over again.

    Closing Out MAPI

    The last MAPI function used in Listing 7-4 is MapiLogoff .

     ULONGFARPASCALMAPILogoff(LHANDLElhSession, ULONGulUIParam, FLAGSflFlags, ULONGulReserved) 

    The only parameter that is significant is lhSession , which is the session handle obtained through a call to MapiLogon . After MapiLogoff is called, the DeInitMapi function is called, which sets all the function pointers to NULL and frees the MAPI library.

    Full MAPI

    Now that you've had a thorough introduction to Simple MAPI, what more should you know to use full MAPI? You'll recognize many of the full MAPI functions. The way that the functions are calledthrough function pointers obtained by a call to LoadLibrary and GetProcAddress is also similar. In fact, some of the initial calls in a full MAPI application (which I'll call MAPI for the balance of the book) look similar to the calls used in Simple MAPI.

    The differences, however, are quite striking when you begin to manipulate messages. Rather than calling a function such as MAPISendMail , you create a message in the Outbox using a COM interface. This can be quite a complicated process, involving several interfaces and lots of structures, but it also offers flexibility that can be important for a server-based application.

    For instance, suppose that you are writing a monitoring application that will send out e-mail messages in the event of problems, or perhaps messages at scheduled intervals to let you know the application is still running. Just as if you were entering the text of the message in Microsoft Outlook, when you create a message using Simple MAPI the message briefly appears in the Outbox and then moves to the Sent Items folder. For messages created manually, this might not be a problem. In fact, this is a feature that allows you to retain copies of messages you have sent and thus decide when these messages should be purged. The situation might be different for messages automatically created. Rather than a couple dozen messages a week, a server-based application operating robotically might create hundreds or even thousands of messages per week. OK, you say, I'll just have the program delete them periodically. MAPIDeleteMessage sounds like it might do the trick; however, it only deletes messages in the Inbox. But you need a MAPI program at least for the deletion of sent messages. MAPI allows you to go one step farther, eliminating the messages from the Outbox and not sending them to the Sent Items folder. If the details of the sent messages are not important, or if those details are stored elsewhere in the application, this might be an even better solution.

    A server-based application might also want to read incoming messages and sort them according to criteria such as the sender, the message, or the existence of attachments. Doing this requires MAPI. Simple MAPI does not provide the ability to enumerate folders, let alone move messages from the Inbox to other folders.

    MAPI's flexibility comes at the cost of added complexity in a normal application. This complexity increases even more for services. As I've mentioned, the application will need to have a message pump operational for messages MAPI will send to the hidden window it will create. Furthermore, other special settings must be present for a MAPI application to run within a service.

    Full MAPI example

    Let's take a look at a basic MAPI application that enumerates all folders and then places a message in the Outbox. Listing 7-5 shows the headers for this application (MAPIInit.h and StdAfx.h) and Listing 7-6 shows the code for the program itself, FullMAPI.cpp.

    Listing 7-5

    MAPIInit.h

     #defineSZ_MAPIINITIALIZE"MAPIInitialize" #defineSZ_MAPIUNINITIALIZE"MAPIUninitialize" #defineSZ_MAPILOGONEX"MAPILogonEx" #defineSZ_MAPIFREEBUFFER"MAPIFreeBuffer" #defineSZ_MAPIALLOCATEBUFFER"MAPIAllocateBuffer" #defineSZ_HRGETONEPROP"HrGetOneProp@12" #defineSZ_HRQUERYALLROWS"HrQueryAllRows@24" #defineSZ_FREEPROWS"FreeProws@4" #defineSZ_FREEPADRLIST"FreePadrlist@4" #defineMAPIDLL"MAPI32.DLL" #defineERR_LOAD_LIB0x02 #defineERR_LOAD_FUNC0x04 typedefVOID(FARPASCAL*PFNFREEPADRLIST) (LPADRLISTpAdrList); typedefVOID(FARPASCAL*PFNFREEPROWS) (LPSRowSetprows); typedefHRESULT(FARPASCAL*PFNHRGETONEPROP)(LPMAPIPROP, ULONG,LPSPropValueFAR*); typedefHRESULT(FARPASCAL*PFNMAPIINITIALIZE)(LPVOID); typedefHRESULT(FARPASCAL*PFNHRQUERYALLROWS)(LPMAPITABLE, LPSPropTagArray,LPSRestriction,LPSSortOrderSet,LONG, LPSRowSetFAR*); typedefVOID(FARPASCAL*PFNMAPIUNINITIALIZE)(void); typedefHRESULT(FARPASCAL*PFNMAPILOGONEX)(ULONG, LPTSTR,LPTSTR,FLAGS,LPMAPISESSIONFAR*); typedefULONG(FARPASCAL*PFNMAPIFREEBUFFER)(LPVOID); typedefSCODE(FARPASCAL*PFNMAPIALLOCATEBUFFER)(ULONG, LPVOIDFAR*); intPASCALInitMAPI(void); intPASCALDeInitMAPI(void); 

    StdAfx.h

     //stdafx.h:includefileforstandardsystemincludefiles, //orproject-specificincludefilesthatareusedfrequentlybut //arechangedinfrequently // #if!defined(AFX_STDAFX_H__289B1463_5BB0_11D3_B4AD_00C04F79B510__ INCLUDED_) #defineAFX_STDAFX_H__289B1463_5BB0_11D3_B4AD_00C04F79B510__INCLUDED_ #if_MSC_VER>1000 #pragmaonce #endif//_MSC_VER>1000 #defineWIN32_LEAN_AND_MEAN #include<stdio.h> #include<windows.h> #include<mapi.h> #include<mapix.h> #include"mapiinit.h" #include<initguid.h> #include<mapitags.h> //{{AFX_INSERT_LOCATION}} //MicrosoftVisualC++willinsertadditionaldeclarations //immediatelybeforethepreviousline. #endif//!defined(AFX_STDAFX_H__289B1463_5BB0_11D3_B4AD_00C04F79B510__ INCLUDED_) 

    Listing 7-6

    FullMAPI.cpp

     //FullMAPI.cpp:Definestheentrypointfortheconsoleapplication. // #include"stdafx.h" #include<mapiguid.h> PFNHRQUERYALLROWSlpfnHrQueryAllRows; PFNHRGETONEPROPlpfnHrGetOneProp; PFNMAPIALLOCATEBUFFERlpfnMAPIAllocateBuffer; PFNMAPIFREEBUFFERlpfnMAPIFreeBuffer; PFNMAPIINITIALIZElpfnMAPIInitialize; PFNMAPIUNINITIALIZElpfnMAPIUninitialize; PFNMAPILOGONEXlpfnMAPILogonEx; PFNFREEPROWSlpfnFreeProws; PFNFREEPADRLISTlpfnFreePadrlist; HMODULEhLibrary; IMsgStore*pmdb=NULL; IMAPISession*psess=NULL; IMAPIFolder*prootfolder=NULL; IAddrBook*pAddrBook=NULL; ULONGnfolders=0; typedefstruct{ shortintk; char*name; shortintdepth; SBinary*pbv; SRowSet*prs; IMAPIFolder*pfolder; IMAPITable*pcontentstable; }FOLDER; FOLDERfolder[200]; intinboxidx=-1; intdeletedidx=-1; intsentidx=-1; intoutboxidx=-1; char*pInboxName="Inbox"; char*pDeletedName="DeletedItems"; char*pSentItemsName="SentItems"; char*pOutboxName="Outbox"; charename[512]; boolGetRootFolder(void); boolGetFoldersHierarchy(char*name,IMAPIFolder*pfolder, shortintdepth); boolAssignInOutSentDel(); #defineFREEBUFFSif(pMessage)pMessage->Release();\ if(lpPropProblems) (lpfnMAPIFreeBuffer)(lpPropProblems);\ if(pr)(lpfnMAPIFreeBuffer)(pr);\ if(pAdrList)(lpfnFreePadrlist)(pAdrList); intmain(intargc,char*argv[]) { MAPIINIT_0MapiInitInfo; LPSPropValuepr=NULL; LPADRLISTpAdrList=NULL; LPSPropProblemArraylpPropProblems=0; IMessage*pMessage=NULL; FLAGSflags=MAPI_EXTENDEDMAPI_NEW_SESSION MAPI_USE_DEFAULT; InitMAPI(); MapiInitInfo.ulFlags=MAPI_MULTITHREAD_NOTIFICATIONS; MapiInitInfo.ulVersion=0; lpfnMAPIInitialize(&MapiInitInfo); HRESULThr=lpfnMAPILogonEx(0,NULL,NULL,flags,&psess); if(FAILED(hr)) { DWORDdRet=GetLastError(); return(-1); } if((GetRootFolder())==false) { return(-1); } if((GetFoldersHierarchy(ename,prootfolder,0))==false) { return(-1); } AssignInOutSentDel(); for(unsignedloop=0;loop<nfolders;loop++) { printf("Folder%d\t%s\n",loop,folder[loop].name); } hr=psess->OpenAddressBook(0,NULL,AB_NO_DIALOG,&pAddrBook); if(FAILED(hr)) { printf("OpenAddressBook"); FREEBUFFS; return(-1); } hr=lpfnMAPIAllocateBuffer(CbNewADRLIST(1), (LPVOID*)&pAdrList); if(FAILED(hr)) { printf("AllocAdrList"); FREEBUFFS; return(-1); } ZeroMemory(pAdrList,CbNewADRLIST(1)); hr=lpfnMAPIAllocateBuffer(sizeof(SPropValue)*3, (LPVOID*)&(pAdrList->aEntries[0].rgPropVals)); if(FAILED(hr)) { printf("AllocSPropValue2"); FREEBUFFS; return(-1); } ZeroMemory(pAdrList->aEntries[0].rgPropVals, sizeof(SPropValue)*2); pAdrList->cEntries=1; pAdrList->aEntries[0].cValues=2; pAdrList->aEntries[0].rgPropVals[0].ulPropTag= PR_DISPLAY_NAME; pAdrList->aEntries[0].rgPropVals[0].Value.lpszA= "DouglasReilly"; pAdrList->aEntries[0].rgPropVals[1].ulPropTag= PR_RECIPIENT_TYPE; pAdrList->aEntries[0].rgPropVals[1].Value.l= MAPI_TO; hr=pAddrBook->ResolveName(NULL,0,NULL,pAdrList); if(FAILED(hr)) { printf("ResolveName"); FREEBUFFS; return(-1); } hr=lpfnMAPIAllocateBuffer(sizeof(SPropValue)*5,(LPVOID*)&pr); if(FAILED(hr)) { printf("AllocSPropValue5"); FREEBUFFS; return(-1); } ZeroMemory(pr,sizeof(SPropValue)*5); char*pStamp="MesssageSubject"; pr[0].ulPropTag=PR_PRIORITY; pr[0].Value.ul=PRIO_NORMAL; pr[1].ulPropTag=PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED; pr[1].Value.b=TRUE; pr[2].ulPropTag=PR_SUBJECT; pr[2].Value.lpszA=pStamp; pr[3].ulPropTag=PR_BODY; pr[3].Value.lpszA="Thisthebodyofthemessage."; pr[4].ulPropTag=PR_DELETE_AFTER_SUBMIT; pr[4].Value.b=TRUE; hr=folder[outboxidx].pfolder->CreateMessage(NULL,0, &pMessage); if(FAILED(hr)) { printf("SetProps"); FREEBUFFS; return(-1); } hr=pMessage->ModifyRecipients(MODRECIP_ADD,pAdrList); if(FAILED(hr)) { printf("ModifyRecipients"); FREEBUFFS; return(-1); } hr=pMessage->SetProps(5,pr,&lpPropProblems); if(FAILED(hr)) { printf("SetProps"); FREEBUFFS; return(-1); } hr=pMessage->SubmitMessage(0); if(FAILED(hr)) { printf("SubmitMessage%d",hr); FREEBUFFS; return(-1); } FREEBUFFS; psess->Logoff(0,0,0); psess->Release(); psess=NULL; pAddrBook->Release(); pAddrBook=NULL; lpfnMAPIUninitialize(); DeInitMAPI(); return0; } boolAssignInOutSentDel() { for(inti=0;i<(int)nfolders;i++) { if(strcmp(folder[i].name,pInboxName)==0) inboxidx=i; if(strcmp(folder[i].name,pDeletedName)==0) deletedidx=i; if(strcmp(folder[i].name,pSentItemsName)==0) sentidx=i; if(strcmp(folder[i].name,pOutboxName)==0) outboxidx=i; } returntrue; } boolGetFoldersHierarchy(char*name,IMAPIFolder*pfolder, shortintdepth) { IMAPITable*ptable=NULL; ULONGulRowCount=0; HRESULThr; SPropTagArray*ptag=NULL; hr=pfolder->GetHierarchyTable(0,&ptable); if(FAILED(hr)) { returnfalse; } hr=ptable->GetRowCount(0,&ulRowCount); if(ulRowCount) { hr=ptable->QueryColumns(TBL_ALL_COLUMNS,&ptag); hr=ptable->SetColumns(ptag,0); hr=ptable->SeekRow(BOOKMARK_BEGINNING,0,NULL); for(unsignedi=0;i<ulRowCount;i++) { ++nfolders; hr=ptable->QueryRows(1,0,&folder[nfolders-1].prs); if(FAILED(hr)) { returnfalse; } SPropValue*p=folder[nfolders-1].prs->aRow->lpProps; folder[nfolders-1].depth=depth; for(intj=0;j<(int)ptag->cValues;j++) { if(p[j].ulPropTag==PR_DISPLAY_NAME) folder[nfolders-1].name=p[j].Value.lpszA; if(p[j].ulPropTag==PR_ENTRYID) folder[nfolders-1].pbv=&p[j].Value.bin; } ULONGul; hr=pmdb->OpenEntry(folder[nfolders-1].pbv->cb, (LPENTRYID)folder[nfolders-1].pbv->lpb, (LPCIID)&IID_IMAPIFolder, MAPI_MODIFYMAPI_BEST_ACCESS,&ul, (IUnknown**)&folder[nfolders-1].pfolder); if(hr==S_OK) GetFoldersHierarchy(folder[nfolders-1].name, folder[nfolders-1].pfolder,depth+1); } } lpfnMAPIFreeBuffer(ptag); ptable->Release(); returntrue; } boolGetRootFolder(void) { ULONGulRowCount; HRESULThr; IMAPITable*ptable=NULL; hr=psess->GetMsgStoresTable(0L,&ptable); SPropTagArray*ptag=NULL; hr=ptable->GetRowCount(0,&ulRowCount); hr=ptable->QueryColumns(TBL_ALL_COLUMNS,&ptag); hr=ptable->SetColumns(ptag,0); for(unsignedi=0;i<ulRowCount;i++) { char*provname="",*storname=""; SRowSet*prs=NULL; hr=ptable->SeekRow(BOOKMARK_BEGINNING,i,NULL); hr=ptable->QueryRows(1,TBL_NOADVANCE,&prs); SPropValue*p=prs->aRow->lpProps; SBinarybv; for(intk=0;k<(int)ptag->cValues;k++) { if(p[k].ulPropTag==PR_DEFAULT_STORE&& p[k].Value.b==1) { for(intj=0;j<(int)ptag->cValues;j++) { if(p[j].ulPropTag==PR_DISPLAY_NAME) storname=p[j].Value.lpszA; if(p[j].ulPropTag==PR_PROVIDER_DISPLAY) provname=p[j].Value.lpszA; if(p[j].ulPropTag==PR_ENTRYID) memcpy(&bv,&p[j].Value.bin, sizeof(SBinary)); } hr=psess->OpenMsgStore(0,bv.cb, (LPENTRYID)bv.lpb,NULL, MAPI_BEST_ACCESSMDB_NO_DIALOG,&pmdb); lpfnMAPIFreeBuffer(prs); if(FAILED(hr)) { printf("\nErrorwhenopening" "defaultmessagestore."); returnfalse; } ULONGulObjType; hr=pmdb->OpenEntry(0,NULL, /*(LPCIID)&IID_IMAPIFolder,*/NULL, MAPI_MODIFYMAPI_BEST_ACCESS, &ulObjType, (IUnknown**)&prootfolder); if(FAILED(hr)) { returnfalse; } strcpy(ename,storname); break; } } lpfnMAPIFreeBuffer(prs); if(prootfolder) break; } lpfnMAPIFreeBuffer(ptag); ptable->Release(); if(!pmdb) { printf("Unabletofinddefaultmessagestorerecord."); returnfalse; } returntrue; } intPASCALInitMAPI() { if((hLibrary=LoadLibrary(MAPIDLL))<(HANDLE)32) { return(ERR_LOAD_LIB); } if((lpfnHrQueryAllRows= (PFNHRQUERYALLROWS)GetProcAddress(hLibrary, SZ_HRQUERYALLROWS))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnHrGetOneProp= (PFNHRGETONEPROP)GetProcAddress(hLibrary, SZ_HRGETONEPROP))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIAllocateBuffer= (PFNMAPIALLOCATEBUFFER)GetProcAddress(hLibrary, SZ_MAPIALLOCATEBUFFER))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIFreeBuffer= (PFNMAPIFREEBUFFER)GetProcAddress(hLibrary, SZ_MAPIFREEBUFFER))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIInitialize= (PFNMAPIINITIALIZE)GetProcAddress(hLibrary, SZ_MAPIINITIALIZE))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIUninitialize= (PFNMAPIUNINITIALIZE)GetProcAddress(hLibrary, SZ_MAPIUNINITIALIZE))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPILogonEx= (PFNMAPILOGONEX)GetProcAddress(hLibrary, SZ_MAPILOGONEX))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnFreeProws= (PFNFREEPROWS)GetProcAddress(hLibrary, SZ_FREEPROWS))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnFreePadrlist= (PFNFREEPADRLIST)GetProcAddress(hLibrary, SZ_FREEPADRLIST))==NULL) { return(ERR_LOAD_FUNC); } return(0); } intPASCALDeInitMAPI() { lpfnHrQueryAllRows=NULL; lpfnHrGetOneProp=NULL; lpfnMAPIAllocateBuffer=NULL; lpfnMAPIFreeBuffer=NULL; lpfnMAPIInitialize=NULL; lpfnMAPIUninitialize=NULL; lpfnMAPILogonEx=NULL; lpfnFreeProws=NULL; lpfnFreePadrlist=NULL; FreeLibrary(hLibrary); return(0); } 

    MAPIInit.h is similar to the Simple MAPI header of the same name, but the function pointers declared are slightly different. There are also fewer function pointers; in fact, only six of the nine function pointers are actually used in this example. One thing that is important about StdAfx.h is the #include statement for Mapix.h in addition to Mapi.h. There are several other files included to support the COM side of MAPI.

    FullMAPI.cpp first declares the function pointers defined in FullMAPI.h. Next you'll see some other declarations, including a structure used to store information about folders. This structure has a couple of members that are important to understand:

     typedefstruct{ shortintk; char*name; shortintdepth; SBinary*pbv; SRowSet*prs; IMAPIFolder*pfolder; IMAPITable*pcontentstable; }FOLDER; 

    The name member holds the actual name of the folder, such as Inbox or Sent. The interface pointer, pfolder , is used to create a message using the CreateMessage method of the IMAPIFolder interface. Following the definition of the FOLDER structure, there are declarations to give special support to the four common folders: Inbox, Outbox, Sent Items, and Deleted Items. Next comes some function declarations, and the #define statement that defines a FREEBUFFS macro used to clean up memory allocations.

    After a call to MapiInit , which is similar to the call to MapiInit in the Simple MAPI example, MAPIInitialize is called through a pointer, lpfnMAPIInitialize . (All function pointers are named like the functions they point to, with an lpfn prefix.) MAPIInitialize takes as its single parameter a pointer to a MAPIINIT_0 structure.

     typedefstruct { ULONGulVersion; ULONGulFlags; }MAPIINIT_0; 

    The only member that needs to be set is ulFlags . This bit flag has two possible values:

    • MAPI_MULTITHREAD_NOTIFICATIONS MAPI should generate notifications using a thread dedicated to notification handling, rather than the first thread that calls MAPIInitialize .
    • MAPI_NT_SERVICE This must be set for Windows 2000 and Windows NT service applications and must not be set for nonservice applications.

    MAPILogonEx is similar in function to MAPILogon (which we used with Simple MAPI), but it acts as the gateway to MAPI's COM interface by returning an IMAPISession interface.

     HRESULTMAPILogonEx(ULONGulUIParam, LPTSTRlpszProfileName, LPTSTRlpszPassword, FLAGSflFlags, LPMAPISESSIONFAR*lppSession); 

    The first three parameters are similar to the same parameters in MAPILogon . The flFlags parameter accepts 13 different values. Several of the important ones for server-based applications are

    • MAPI_EXTENDED Not an optionthis must be set.
    • MAPI_FORCE_DOWNLOAD Forces a synchronous download of all messages for the user.
    • MAPI_NEW_SESSION MAPI tries to create a new session rather than sharing an existing session.
    • MAPI_NT_SERVICE Must be set for services and must not be set for other applications.
    • MAPI_TIMEOUT_SHORT Logon should fail if blocked for more than a few seconds.
    • MAPI_USE_DEFAULT MAPI default profile should be used. This is not likely to work in a service if the service is operating under the System account.

    The final parameter is a pointer to a pointer to an IMAPISession interface. The return from MAPILogon can be checked using the FAILED macro. This interface is used extensively, and in this example it is declared as a global variable.

    Getting the root folder If the logon succeeds, three functions defined within FullMAPI.cpp are called. GetRootFolder gets the root folder that allows other folders to be found. GetRootFolder first obtains an IMAPITable interface by calling GetMsgStoresTable to access the message stores table. Using pTable , the interface returned by GetMsgStoresTable , the rows and columns of the message stores table are gathered. Next it loops through the rows checking properties until it finds the default store. Properties have tags and values. The tag identifies the type of property; for instance, PR_DISPLAY_NAME is the display name. The value of the display name of a message store provider might be "Personal Message Store". Looping through the tags and values of a row is handled in the following code fragment from GetRootFolder in FullMAPI.cpp.

     for(intj=0;j<(int)ptag->cValues;j++) { if(p[j].ulPropTag==PR_DISPLAY_NAME) storname=p[j].Value.lpszA; if(p[j].ulPropTag==PR_PROVIDER_DISPLAY) provname=p[j].Value.lpszA; if(p[j].ulPropTag==PR_ENTRYID) memcpy(&bv,&p[j].Value.bin,sizeof(SBinary)); } 

    This example might include any number of properties, but our interest is only in the display name, the display name of the service provider, and the entry ID. The entry ID is then used to open the appropriate message store, getting an IMsgStore interface pointer through a call to the OpenMsgStore method of the IMAPISession interface.

     HRESULTOpenMsgStore(ULONGulUIParam, ULONGcbEntryID, LPENTRYIDlpEntryID, LPCIIDlpInterface, ULONGulFlags, LPMDBFAR*lppMDB); 

    The values for these parameters are documented in the MAPI documentation, but server-based applications should always include the MDB_NO_DIALOG flag in ulFlags . Generally, all bit flags in MAPI also accept the MAPI_BEST_ACCESS flag. This flag tries to get the best possible access to the object being returned, given the rights of the client. The default interface can be selected by passing NULL to lpInterface . The interface pointer returned in lppMDB is then used to call the OpenEntry method of the IMsgStore interface:

     HRESULTOpenEntry(ULONGcbEntryID, LPENTRYIDlpEntryID, LPCIIDlpInterface, ULONGulFlags, ULONGFAR*lpulObjType, LPUNKNOWNFAR*lppUnk) 

    OpenEntry returns the root folder in lppUnk . NULL passed as the lpInterface means that the default interface will be returned, in this case IMAPIFolder .

    Getting the folder hierarchy Once we have the root folder, GetFoldersHierarchy is called with the name of the root folder and a pointer to the IMAPIFolder interface pointing to the root. GetFoldersHierarchy operates like GetRootFolder in that it obtains an IMAPITable interface pointer by calling the GetHierarchyTable method of the IMAPIContainer interface pointer, using the root folder interface pointer passed as a parameter.

     HRESULTGetHierarchyTable(ULONGulFlags, LPMAPITABLEFAR*lppTable); 

    One value that can be included in ulFlags worth noting is MAPI_DEFERRED_ERRORS. This should never be called in a server-based application because the method can returnindicating success quicklybut in fact might fail. If this happens, making subsequent calls using the interface pointer returned will result in an error. This is another case in which the possibility of errors occurring that no one will see outweighs any possible optimization advantage. This flag is available in many areas of MAPI, but I don't use it in any of this book's examples.

    Once the interface pointer to the hierarchy table is obtained, the balance of the logic is similar, though in this case rather than looking for a particular entry, the program writes all entries into the FOLDERS structure array, which makes them available elsewhere in the program.

    Assigning common folders Once the FOLDERS structure array is filled, some offsets in the array are set as pointers to the special folders used most often: Inbox, Outbox, Sent Items, and Deleted Items. AssignInOutSentDel is a straightforward function:

     voidAssignInOutSentDel() { for(inti=0;i<(int)nfolders;i++) { if(strcmp(folder[i].name,pInboxName)==0) inboxidx=i; if(strcmp(folder[i].name,pDeletedName)==0) deletedidx=i; if(strcmp(folder[i].name,pSentItemsName)==0) sentidx=i; if(strcmp(folder[i].name,pOutboxName)==0) outboxidx=i; } } 

    After we get the folders, we simply print a list of them. In real-world applications, other activities are performed using the folder information. We use the Outbox folder next to send a message.

    Sending a message In MAPI, sending a message requires that we create a message and place it in the Outbox. One of the other advantages of MAPI over Simple MAPI is the ability to easily resolve addresses using the address book. To do so, the address book must be accessed through an IaddrBook interface pointer obtained by calling the OpenAddressBook method of the IMAPISession interface.

     HRESULTOpenAddressBook(ULONGulUIParam, LPCIIDlpInterface, ULONGulFlags, LPADRBOOKFAR*lppAdrBook); 

    Again, ulUIParam is set to 0 because our goal is to not use any user interface. We want the default interface, so lpInterface can be passed as NULL. The only value defined for the ulFlags bit flag is AB_NO_DIALOG, which suppresses the display of dialogs, a feature that server-based applications require.

    Next, several buffers are allocated using MAPIAllocateBuffer , a simple function that takes the size of the buffer and the pointer to a pointer to the allocated memory. An address list for a single address and an array of three property values are allocated in separate calls.

    Setting the recipients is a bit different than in the Simple MAPI example but similar in principal. With MAPI, a number of properties can be set. In FullMAPI.cpp, two are set.

     pAdrList->cEntries=1; pAdrList->aEntries[0].cValues=2; pAdrList->aEntries[0].rgPropVals[0].ulPropTag= PR_DISPLAY_NAME; pAdrList->aEntries[0].rgPropVals[0].Value.lpszA= "DouglasReilly"; pAdrList->aEntries[0].rgPropVals[1].ulPropTag= PR_RECIPIENT_TYPE; pAdrList->aEntries[0].rgPropVals[1].Value.l= MAPI_TO; hr=pAddrBook->ResolveName(0,0,NULL,pAdrList); 

    Once again we have property tags and values. First the display name of the recipient is set, and the recipient is identified as a MAPI_TO recipient. Finally, since we have set only a display name, we need to call the ResolveName method of the IMAPIAddrBook interface to properly identify this recipient.

     HRESULTResolveName(ULONGulUIParam, ULONGulFlags, LPTSTRlpszNewEntryTitle, LPADRLISTlpAdrList); 

    The first two parameters are passed as 0, and lpszNewEntryTitle is passed as NULL, since this parameter, used as a caption for a dialog box, should not be used. The parameter lpAdrList is an in-out pointer, and in FullMAPI.cpp we pass pAddrList as this parameter, allowing it to be modified to contain the proper address.

    Next we allocate a buffer for five property values to be used for properties of the message. The properties are set as follows:

     pr[0].ulPropTag=PR_PRIORITY; pr[0].Value.ul=PRIO_NORMAL; pr[1].ulPropTag=PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED; pr[1].Value.b=TRUE; pr[2].ulPropTag=PR_SUBJECT; pr[2].Value.lpszA=pStamp; pr[3].ulPropTag=PR_BODY; pr[3].Value.lpszA="Thisthebodyofthemessage."; pr[4].ulPropTag=PR_DELETE_AFTER_SUBMIT; pr[4].Value.b=TRUE; 

    We set the priority to normal, request delivery status reports, set the subject and the body, and request that the message is deleted after submission. The details of what is happening in MAPI are hidden from normal users because MAPI is virtually always accessed through a UI with behaviors such as moving messages from the Outbox to Sent Items when the message is sent. This is not native MAPI behavior, and if we don't specify that the message should be deleted after delivery, the message will remain in the Outbox after being sent.

    Finally we call four methods of the IMAPISession interface to create the message, add the recipient, set the properties that have just been filled in, and actually submit the message. You can see this set of actions in the following code fragment from FullMAPI.cpp:

     hr=folder[outboxidx].pfolder->CreateMessage(NULL,0,&pMessage); if(FAILED(hr)) { printf("CreateMessage"); FREEBUFFS; return(-1); } hr=pMessage->ModifyRecipients(MODRECIP_ADD,pAdrList); if(FAILED(hr)) { printf("ModifyRecipients"); FREEBUFFS; return(-1); } hr=pMessage->SetProps(5,pr,&lpPropProblems); if(FAILED(hr)) { printf("SetProps"); FREEBUFFS; return(-1); } hr=pMessage->SubmitMessage(0); if(FAILED(hr)) { printf("SubmitMessage%d",hr); FREEBUFFS; return(-1); } 

    First we create the message in the Outbox (to indicate it should be sent) by calling the CreateMessage method of the IMAPIFolder interface. We pass NULL for the first parameter to accept the default interface type, 0 for the second parameter, ulFlags , because there aren't any appropriate flags to set, and the address of our IMAPIMessage interface pointer, pMessage , as the final parameter. Next we call the ModifyRecipients method of the IMAPIMessage interface, adding the address list previously filled in. We set the properties using the SetProperties method of the IMAPIMessage interface, to which we pass the number of properties, the pointer to the properties, and a pointer to an LPSPropProblemArray . In FullMAPI.cpp, the pointer is set to NULL on entry and so is not updated. If the pointer were non-null, detailed information about setting properties would be filled in. At this point the message does not exist except in the memory system running this program. The message is actually sent when we call the SubmitMessage method of the IMAPIMessage interface.

    Cleaning up When the session is done, we call the Logoff method of the IMAPISession interface:

     HRESULTLogoff(ULONGulUIParam, ULONGulFlags, ULONGulReserved); 

    We pass all zeros as the parameters in this example because the UI should not appear, no appropriate flags are available, and the last parameter is reserved and must be 0. After we have logged off the session, we call Release on the session interface and set the pointer to the interface to NULL. We repeat this process for the address book, and then we call MAPIUninitialize . Once all MAPI operations are completed, we call DeInitMAPI to unload the MAPI DLL.

    Putting It All Together: Using MAPI and TAPI in a Service

    You might want to put both TAPI and MAPI together in a Windows 2000 service. If the server encounters a problem, sending an e-mail first with a follow-up beep if the problem is not resolved can allow the server to run without constant monitoring. There are several areas to consider when adding support for TAPI and MAPI. MAPI requires a change to the C++ Windows 2000 CPPService class first introduced in Chapter 3 and updated for event logging in Chapter 5. Changes have been made to support MAPI in particular and COM in general. Listing 7-7 contains the updated CPPService.h. Listing 7-8 contains the updated CPPService.cpp. In both cases, I've noted changes to the code in the comments.

    Listing 7-7

    CPPService.h

     #defineSERVICE_CONTROL_USER128 VOIDStartThisService(char*serviceName); classCPPService{ public: //AddedforChapter7 staticunsignedlong_stdcallMessagePumpThread(void*StillRunning); //AddedforChapter5 BOOLLogEvent(WORDwType,DWORDdwEventID,constchar*ps1=0, constchar*ps2=0, constchar*ps3=0, constchar*ps4=0, constchar*ps5=0); //CreateMsgPumpAddedforChapter7 CPPService(char*tsvcName,char*tdispName, DWORDtsvcStart=SERVICE_DEMAND_START, boolCreateMsgPump=false); virtual~CPPService(); BOOLIsInstalled(); BOOLInstall(); BOOLUnInstall(); BOOLStartService(); voidSetStatus(DWORDdwControl); BOOLInitialize(); BOOLParseArguments(intargc,char*argv[]); char*ServiceName() { returnserviceName; } voidSetUserAndPassword(char*user=NULL,char*pwd=NULL); virtualvoidOnInstall(); virtualvoidOnUninstall(); virtualBOOLOnInit(DWORD*status); virtualvoidRun(); virtualvoidOnStop(); virtualvoidOnPause(); virtualvoidOnContinue(); virtualBOOLOnUserControl(DWORDdwControl); virtualvoidOnParamChange(); virtualvoidOnNetBindAdd(); virtualvoidOnNetBindRemove(); virtualvoidOnNetBindEnable(); virtualvoidOnNetBindDisable(); #ifdef_WIN2K_COMPAT virtualvoidOnDeviceEvent(DWORDdwEventType,LPVOIDpvEventData); virtualvoidOnPowerEvent(DWORDdwEventType,LPVOIDpvEventData); virtualvoidOnHardwareProfileChange(DWORDdwEventType); #endif protected: SC_HANDLEm_scmHandle; SC_HANDLEm_scHandle; //AddedforChapter5 HANDLEm_EventHandle; SERVICE_STATUSServiceStatus; SERVICE_STATUS_HANDLEServiceStatusHandle; staticCPPService*m_this; charserviceName[128]; charserviceDisplayName[128]; VOIDlogError(char*msg); FILE*log; charm_diag[255]; DWORDm_svcStartType; DWORDm_status; BOOLm_isRunning; BOOLm_isPaused; //AddedforChapter7 BOOLm_CreateMsgPump; #ifdef_WIN2K_COMPAT staticDWORDWINAPIHandlerEx(DWORDdwControl,DWORDdwEventType, LPVOIDlpEventData,LPVOIDlpContext); #else staticVOIDWINAPIHandler(DWORDdwControl); #endif staticVOIDWINAPIServiceMain(DWORDdwArgc,LPTSTR*lpszArgv); }; 

    Listing 7-8

    CPPService.cpp

     #include"Stdafx.h" CPPService*CPPService::m_this=NULL; VOIDStartThisService(char*serviceName) { SERVICE_STATUSssStatus; DWORDdwOldCheckPoint; SC_HANDLEschSCManager= OpenSCManager(NULL,//Localmachine NULL,//ServicesActivedatabase SC_MANAGER_ALL_ACCESS);//Fullaccess printf("\nStartingService...\n"); SC_HANDLEschService= OpenService(schSCManager,//SCMdatabase TEXT(serviceName),//Servicename SERVICE_ALL_ACCESS); if(schService==NULL) { printf("\nCan'tOpenService...LastError=%d", GetLastError()); return; } if(!StartService(schService,//Handletoservice 0,//Numberofarguments NULL))//Noarguments { printf("\nStartServicefailure..."); return; } else { printf("Servicestartpending\n"); } //Checkthestatusuntiltheserviceisnolongerstartpending. if(!QueryServiceStatus(schService,//Handletoservice &ssStatus))//Addressofstatusinformation { printf("\nErrorinQueryServiceStatus"); return; } //Someservicestakealongtimetostart,sothereis //anelaboratemechanismtohandlethat.Thisisnot //veryusefulwiththeexampleservicesprovidedbut //couldbemoresoformorecomplexservices. while(ssStatus.dwCurrentState==SERVICE_START_PENDING) { //Savethecurrentcheckpoint. dwOldCheckPoint=ssStatus.dwCheckPoint; //Waitforthespecifiedinterval. Sleep(ssStatus.dwWaitHint);//Checkthestatusagain. if(!QueryServiceStatus(schService,//Handletoservice &ssStatus))//Addressofstatusinformation { break; } //Breakifthecheckpointhasnotbeenincremented. if(dwOldCheckPoint>=ssStatus.dwCheckPoint) { break; } } if(ssStatus.dwCurrentState==SERVICE_RUNNING) { printf("\nServiceStarted\n"); } else { printf("CurrentState:%d\n", ssStatus.dwCurrentState); printf("ExitCode:%d\n",ssStatus.dwWin32ExitCode); printf("ServiceSpecificExitCode:%d\n", ssStatus.dwServiceSpecificExitCode); printf("CheckPoint:%d\n",ssStatus.dwCheckPoint); printf("WaitHint:%d\n",ssStatus.dwWaitHint); } CloseServiceHandle(schService); CloseServiceHandle(schSCManager); } //CreateMsgPumpAddedforChapter7 CPPService::CPPService(char*tsvcName,char*tdispName, DWORDtsvcStartType, boolCreateMsgPump) { strncpy(serviceName,tsvcName,128); strncpy(serviceDisplayName,tdispName,128); m_svcStartType=tsvcStartType; m_scmHandle=OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS); if(m_scmHandle==NULL) { printf("\nOpenSCManagererror%d", GetLastError()); } m_scHandle=0; m_this=this; m_isRunning=false; m_isPaused=false; //AddedforChapter5 m_EventHandle=0; //AddedforChapter7 m_CreateMsgPump=CreateMsgPump; ServiceStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS; ServiceStatus.dwCurrentState=SERVICE_START_PENDING; ServiceStatus.dwControlsAccepted= SERVICE_ACCEPT_STOPSERVICE_ACCEPT_PAUSE_CONTINUE; ServiceStatus.dwWin32ExitCode=0; ServiceStatus.dwServiceSpecificExitCode=0; ServiceStatus.dwCheckPoint=0; ServiceStatus.dwWaitHint=0; } CPPService::~CPPService() { if(m_scmHandle!=0) { ::CloseHandle(m_scmHandle); } if(m_scHandle!=0) { ::CloseHandle(m_scHandle); } } BOOLCPPService::ParseArguments(intargc,char**argv) { BOOLresult=false; if(argc>1&&!(_strnicmp(argv[1],"-i",2))) { if((Install())) { printf("\n%sinstalled!",ServiceName()); } else { printf("\n%sNOTinstalled!Lasterror%d", ServiceName(),GetLastError()); } result=true; } elseif(argc>1&&!(_strnicmp(argv[1],"-u",2))) { if((UnInstall())) { printf("\n%suninstalled!",ServiceName()); } else { printf("\n%sNOTuninstalled!Lasterror%d", ServiceName(),GetLastError()); } result=true; } returnresult; } VOIDCPPService::logError(char*text) { //AddedforChapter5 LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG, (constchar*)text); //RemovedforChapter5 //log=fopen("c:\\service.log","a+t"); //if(log) //{ //fprintf(log,"%s\n",text); //fclose(log); //} return; } BOOLCPPService::Install() { charszPath[MAX_PATH]; charszBuffer[MAX_PATH+2]; //AddedforChapter5 charszKey[256]; HKEYhKey=NULL; //EndAddedforChapter5 BOOLresult=true;//Defaulttosuccess GetModuleFileName(GetModuleHandle(NULL),szPath,MAX_PATH); //Whydowedothis?Thepathmightcontainspaces. //Ifitdoes,thepathmustbequoted. strcpy(szBuffer,"\""); strcat(szBuffer,szPath); strcat(szBuffer,"\""); printf("\nInstallingservice%s",szPath); m_scHandle=CreateService(m_scmHandle,serviceName, serviceDisplayName,SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, m_svcStartType, SERVICE_ERROR_NORMAL,szBuffer, NULL,NULL,NULL,NULL,NULL); if(m_scHandle==NULL)//processerror { DWORDlastError; lastError=GetLastError(); if(lastError==1073)//Servicealreadyinstalled { wsprintf(m_diag,"CreateServiceerror=" "ServiceAlreadyInstalled(1073)\n"); } else { wsprintf(m_diag,"CreateServiceerror=%d\n", lastError); } printf("%s\n",m_diag); logError(m_diag); result=false; } //AddedforChapter5 strcpy(szKey,"SYSTEM\\CurrentControlSet\\Services\\" "EventLog\\Application\\"); strcat(szKey,ServiceName()); if(::RegCreateKey(HKEY_LOCAL_MACHINE, szKey,&hKey)!=ERROR_SUCCESS) { returnFALSE; } //AddtheEventIDMessagefile(inthiscase,the.EXEfile). ::RegSetValueEx(hKey, "EventMessageFile", 0, REG_EXPAND_SZ, (CONSTBYTE*)szPath, strlen(szPath)+1); //Setthesupportedtypesflags. DWORDdwData=EVENTLOG_ERROR_TYPEEVENTLOG_WARNING_TYPE EVENTLOG_INFORMATION_TYPE; ::RegSetValueEx(hKey, "TypesSupported", 0, REG_DWORD, (CONSTBYTE*)&dwData, sizeof(DWORD)); ::RegCloseKey(hKey); //EndAddedforChapter5 OnInstall(); returnresult; } BOOLCPPService::StartService() { SERVICE_TABLE_ENTRYDispatchTable[]={ {serviceName,CPPService::ServiceMain}, {NULL,NULL} }; BOOLb=::StartServiceCtrlDispatcher(DispatchTable); //AddedforChapter5 if(b) { //Nostringsneededforthismessage. LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_STARTED); } //EndAddedforChapter5 returnb; } BOOLCPPService::UnInstall() { m_scHandle=OpenService(m_scmHandle,serviceName, SERVICE_ALL_ACCESS); if(m_scHandle!=NULL) { DeleteService(m_scHandle); printf("\n%suninstalled...",serviceName); //CloseHandle(m_scHandle); m_scHandle=0; //AddedforChapter5 LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_REMOVED,ServiceName()); //EndAddedforChapter5 OnUninstall(); return(true); } printf("\nOpenServiceerror:%d",GetLastError()); return(false); } voidCPPService::SetStatus(DWORDdwControl) { ServiceStatus.dwCurrentState=dwControl; ServiceStatus.dwCheckPoint=0; ServiceStatus.dwWaitHint=0; if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus)) { m_status=GetLastError(); wsprintf(m_diag, "%sSetServiceStatuserror%lddwControl=%d\n", serviceName,m_status,dwControl); logError(m_diag); } } VOIDWINAPICPPService::ServiceMain(DWORDargc,LPTSTR*argv) { BOOLinitStat; DWORDspecificError; CPPService*thisPtr; thisPtr=m_this; #ifdef_WIN2K_COMPAT charszContext[255]; strcpy(szContext,thisPtr->ServiceName()); thisPtr->ServiceStatusHandle=RegisterServiceCtrlHandlerEx(thisPtr->ServiceName, HandlerEx, &szContext); #else thisPtr->ServiceStatusHandle=RegisterServiceCtrlHandler(thisPtr->serviceName, Handler); #endif if(thisPtr->ServiceStatusHandle==(SERVICE_STATUS_HANDLE)0) { wsprintf(thisPtr->m_diag, "RegisterServiceCtrlHandlerfailed%d", GetLastError()); thisPtr->logError(thisPtr->m_diag); return; } thisPtr->SetStatus(SERVICE_START_PENDING); //Initializationcodegoeshere. initStat=thisPtr->OnInit(&specificError); //Handleerrorcondition if(initStat==false) { thisPtr->SetStatus(SERVICE_STOPPED); wsprintf(thisPtr->m_diag, "OnInitfailedinitStat=%d...",initStat); thisPtr->logError(thisPtr->m_diag); return; } thisPtr->SetStatus(SERVICE_RUNNING); thisPtr->m_isRunning=true; //AddedforChapter7 if(thisPtr->m_CreateMsgPump) { wsprintf(thisPtr->m_diag,"StartingMessagePump" "Thread...\n"); DWORDdwCreationFlags=0; DWORDdwThreadID=0; CreateThread(NULL,0,MessagePumpThread, &thisPtr->m_isRunning, dwCreationFlags, &dwThreadID); } wsprintf(thisPtr->m_diag,"Justbeforeprocessingloop...\n"); thisPtr->logError(thisPtr->m_diag); //Thisiswheretheservicedoesitswork. thisPtr->Run(); //Sendcurrentstatus. thisPtr->SetStatus(SERVICE_STOPPED); return; } #ifdef_WIN2K_COMPAT DWORDWINAPICPPService::HandlerEx(DWORDdwControl,DWORDdwEventType, LPVOIDlpEventData,LPVOIDlpContext) { DWORDstatus=SERVICE_RUNNING; CPPService*thisPtr; thisPtr=m_this; wsprintf(thisPtr->m_diag,"INtoserviceControl...\n"); thisPtr->logError(thisPtr->m_diag); switch(dwControl) { caseSERVICE_CONTROL_PAUSE: //Dowhateverittakestopausehere. wsprintf(thisPtr->m_diag,"Pausing...\n"); thisPtr->logError(thisPtr->m_diag); if(!(thisPtr->m_isPaused)) { thisPtr->m_isPaused=true; thisPtr->OnPause(); status=SERVICE_PAUSED; } break; caseSERVICE_CONTROL_CONTINUE: //Dowhateverittakestocontinuehere. sprintf(thisPtr->m_diag,"Continuing...\n"); thisPtr->logError(thisPtr->m_diag); if(thisPtr->m_isPaused) { thisPtr->OnContinue(); thisPtr->m_isPaused=false; status=SERVICE_RUNNING; } break; caseSERVICE_CONTROL_STOP: //Dowhateverittakestostophere. wsprintf(thisPtr->m_diag,"Stopping...\n"); thisPtr->logError(thisPtr->m_diag); if(thisPtr->m_isRunning) { thisPtr->OnStop(); status=SERVICE_STOP_PENDING; thisPtr->m_isRunning=false; } thisPtr->SetStatus(status); wsprintf(thisPtr->m_diag, "ServiceLeavingHandler\n",0); thisPtr->logError(thisPtr->m_diag); return; caseSERVICE_CONTROL_PARAMCHANGE: thisPtr->OnParamChange(); break; caseSERVICE_CONTROL_NETBINDADD: thisPtr->OnNetBindAdd(); break; caseSERVICE_CONTROL_NETBINDDELETE: thisPtr->OnNetBindDelete(); break; caseSERVICE_CONTROL_NETBINDREMOVE: thisPtr->OnNetBindRemove(); break; caseSERVICE_CONTROL_NETBINDENABLE: thisPtr->OnNetBindEnable(); break; caseSERVICE_CONTROL_NETBINDDISABLE: thisPtr->OnNetBindDisable(); break; caseSERVICE_CONTROL_DEVICEEVENT: thisPtr->OnDeviceEvent(dwEventType,lpEventData); break; caseSERVICE_CONTROL_POWEREVENT: thisPtr->OnPowerEvent(dwEventType,lpEventData); break; caseSERVICE_CONTROL_HARDWAREPROFILECHANGE: thisPtr->OnHardwareProfileChange(dwEventType); break; caseSERVICE_CONTROL_INTERROGATE: //Fallthroughtosendcurrentstatus. break; default: if(dwControl>=SERVICE_CONTROL_USER) { if(!thisPtr->OnUserControl(dwControl)) { wsprintf(thisPtr->m_diag, "%sUnhandledcontrolcode%ld\n", thisPtr->ServiceName(), dwControl); thisPtr->logError(thisPtr->m_diag); } } else { wsprintf(thisPtr->m_diag, "%sUnrecognizedcontrolcode%ld\n", thisPtr->ServiceName(), dwControl); thisPtr->logError(thisPtr->m_diag); } break; } //Sendcurrentstatus. thisPtr->SetStatus(status); return0; } #else VOIDWINAPICPPService::Handler(DWORDdwControl) { DWORDstatus=SERVICE_RUNNING; CPPService*thisPtr; thisPtr=m_this; wsprintf(thisPtr->m_diag,"INtoserviceControl...\n"); thisPtr->logError(thisPtr->m_diag); switch(dwControl) { caseSERVICE_CONTROL_PAUSE: //Dowhateverittakestopausehere. wsprintf(thisPtr->m_diag,"Pausing...\n"); thisPtr->logError(thisPtr->m_diag); if(!(thisPtr->m_isPaused)) { thisPtr->m_isPaused=true; thisPtr->OnPause(); status=SERVICE_PAUSED; } break; caseSERVICE_CONTROL_CONTINUE: //Dowhateverittakestocontinuehere. wsprintf(thisPtr->m_diag,"Continuing...\n"); thisPtr->logError(thisPtr->m_diag); if(thisPtr->m_isPaused) { thisPtr->OnContinue(); thisPtr->m_isPaused=false; status=SERVICE_RUNNING; } break; caseSERVICE_CONTROL_STOP: //Dowhateverittakestostophere. //RemovedforChapter5 //wsprintf(thisPtr->m_diag,"Stopping...\n"); //thisPtr->logError(thisPtr->m_diag); if(thisPtr->m_isRunning) { thisPtr->OnStop(); //AddedforChapter5 thisPtr->LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_STOPPED, thisPtr->ServiceName()); //EndAddedforChapter5 status=SERVICE_STOP_PENDING; thisPtr->m_isRunning=false; } thisPtr->SetStatus(status); wsprintf(thisPtr->m_diag, "BeeperServiceLeavingHandler\n",0); thisPtr->logError(thisPtr->m_diag); return; caseSERVICE_CONTROL_PARAMCHANGE: thisPtr->OnParamChange(); break; caseSERVICE_CONTROL_NETBINDADD: thisPtr->OnNetBindAdd(); break; caseSERVICE_CONTROL_NETBINDDISABLE: thisPtr->OnNetBindDisable(); break; caseSERVICE_CONTROL_NETBINDREMOVE: thisPtr->OnNetBindRemove(); break; caseSERVICE_CONTROL_NETBINDENABLE: thisPtr->OnNetBindEnable(); break; caseSERVICE_CONTROL_INTERROGATE: //Fallthroughtosendcurrentstatus. break; default: if(dwControl>=SERVICE_CONTROL_USER) { if(!thisPtr->OnUserControl(dwControl)) { wsprintf(thisPtr->m_diag, "%sUnhandledcontrolcode%ld\n", thisPtr->ServiceName(), dwControl); thisPtr->logError(thisPtr->m_diag); } } else { wsprintf(thisPtr->m_diag, "%sUnrecognizedcontrolcode%ld\n", thisPtr->ServiceName(), dwControl); thisPtr->logError(thisPtr->m_diag); } break; } //Sendcurrentstatus. thisPtr->SetStatus(status); return; } #endif //Virtualfunctions,generallyoverriddenifneeded. BOOLCPPService::OnUserControl(DWORDdwControl) { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG,"OnContinue"); returnfalse; } voidCPPService::OnContinue() { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG,"OnContinue"); return; } voidCPPService::OnPause() { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG,"OnPause"); return; } voidCPPService::OnStop() { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG,"OnStop"); return; } voidCPPService::OnInstall() { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG,"OnInstall"); return; } voidCPPService::OnUninstall() { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG,"OnUninstall"); return; } BOOLCPPService::OnInit(DWORD*status) { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG,"OnInit"); *status=0; returntrue; } voidCPPService::OnParamChange() { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG,"OnParamChange"); return; } voidCPPService::OnNetBindAdd() { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG,"OnNetBindAdd"); return; } voidCPPService::OnNetBindRemove() { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG, "OnNetBindRemove"); return; } voidCPPService::OnNetBindEnable() { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG, "OnNetBindEnable"); return; } voidCPPService::OnNetBindDisable() { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG, "OnNetBindDisable"); return; } voidCPPService::Run() { while(m_isRunning) { Sleep(1000); } return; } #ifdef_WIN2K_COMPAT voidOnDeviceEvent(DWORDdwEventType,LPVOIDpvEventData) { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG, "OnDeviceEvent"); return; } voidOnPowerEvent(DWORDdwEventType,LPVOIDpvEventData) { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG,"OnPowerEvent"); return; } voidOnHardwareProfileChange(DWORDdwEventType) { LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG, "OnHardwareProfileChange"); return; } #endif //AddedforChapterFive BOOLCPPService::LogEvent(WORDwType,DWORDdwEventID, constchar*ps1, constchar*ps2, constchar*ps3, constchar*ps4, constchar*ps5) { WORDwNumStrings; constchar*pStrings[5]; if(m_EventHandle==0) { m_EventHandle=::RegisterEventSource(NULL,ServiceName()); } if(m_EventHandle!=0) { wNumStrings=0; if(ps1!=0) { pStrings[wNumStrings]=ps1; wNumStrings++; if(ps2!=0) { pStrings[wNumStrings]=ps2; wNumStrings++; if(ps3!=0) { pStrings[wNumStrings]=ps3; wNumStrings++; if(ps4!=0) { pStrings[wNumStrings]=ps4; wNumStrings++; if(ps5!=0) { pStrings[wNumStrings]=ps5; wNumStrings++; } } } } } ::ReportEvent(m_EventHandle,wType,0,dwEventID,NULL, wNumStrings,0,pStrings,NULL); returntrue; } returnfalse; } //EndaddedforChapter5 //AddedforChapter7 unsignedlong_stdcallCPPService::MessagePumpThread(void*StillRunning) { MSGmsg; HACCELhAccelTable=0; CPPService*thisPtr; thisPtr=m_this; while(GetMessage(&msg,NULL,0,0)&&thisPtr->m_isRunning) { if(!TranslateAccelerator(msg.hwnd,hAccelTable,&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return(0); } //EndAddedforChapter7 

    All the changes to CPPService are related to adding support for a message pump, which is required for MAPI. The message pump is placed in a separate thread, and the thread monitors the m_isRunning flag. I added a flag to the CPPService constructor to determine whether a message pump should be created. The default value for the flag is false . If this flag is true , the following code is executed during the ServiceMain method:

     //AddedforChapter7 if(thisPtr->m_CreateMsgPump) { wsprintf(thisPtr->m_diag,"StartingMessagePumpThread...\n"); DWORDdwCreationFlags=0; DWORDdwThreadID=0; CreateThread(NULL,0,MessagePumpThread, &thisPtr->m_isRunning, dwCreationFlags, &dwThreadID); } 

    A message is written to the event log if the flag is set as an aid to diagnostics. The thread function MessagePumpThread is a static member function declared as stdcall. The pointer to the m_isRunning member variable is sent as the argument to the thread function. MessagePumpThread is a simple message pumpit calls GetMessage , checks the value of m_isRunning , and breaks when GetMessage returns 0 or m_isRunning is false .

    CTAPIMAPIService

    Inheriting from CPPService, I created a new class, CTAPIMAPIService . This class adds functions based upon the TAPI and MAPI code from Listings 7-2 and 7-6, respectively. I modified most of the code from these previous examples simply to change from displaying messages on the screen to writing the messages to the event log. Using the code from the previous examples, the CallBeeper method is called during the Run method, and a new function based upon the main function in Listing 7-6, named SendMapiMessage , is called. Obviously the phone numbers and names used for the e-mail should be modified for your applications. A more useful example in Chapter 14 will call a beeper or send e-mail as the result of a specific event.

    Listing 7-9 contains the headers for CTAPIMAPIService and Listing 7-10 contains the source.

    Listing 7-9

    MapiInit.h

     #defineSZ_MAPIINITIALIZE"MAPIInitialize" #defineSZ_MAPIUNINITIALIZE"MAPIUninitialize" #defineSZ_MAPILOGONEX"MAPILogonEx" #defineSZ_MAPIFREEBUFFER"MAPIFreeBuffer" #defineSZ_MAPIALLOCATEBUFFER"MAPIAllocateBuffer" #defineSZ_HRGETONEPROP"HrGetOneProp@12" #defineSZ_HRQUERYALLROWS"HrQueryAllRows@24" #defineSZ_FREEPROWS"FreeProws@4" #defineSZ_FREEPADRLIST"FreePadrlist@4" #defineMAPIDLL"MAPI32.DLL" #defineERR_LOAD_LIB0x02 #defineERR_LOAD_FUNC0x04 typedefVOID(FARPASCAL*PFNFREEPADRLIST) (LPADRLISTpAdrList); typedefVOID(FARPASCAL*PFNFREEPROWS)(LPSRowSetprows); typedefHRESULT(FARPASCAL*PFNHRGETONEPROP)(LPMAPIPROP, ULONG,LPSPropValueFAR*); typedefHRESULT(FARPASCAL*PFNMAPIINITIALIZE)(LPVOID); typedefHRESULT(FARPASCAL*PFNHRQUERYALLROWS)(LPMAPITABLE, LPSPropTagArray,LPSRestriction,LPSSortOrderSet,LONG, LPSRowSetFAR*); typedefVOID(FARPASCAL*PFNMAPIUNINITIALIZE)(void); typedefHRESULT(FARPASCAL*PFNMAPILOGONEX) (ULONG,LPTSTR,LPTSTR,FLAGS,LPMAPISESSIONFAR*); typedefULONG(FARPASCAL*PFNMAPIFREEBUFFER) (LPVOID); typedefSCODE(FARPASCAL*PFNMAPIALLOCATEBUFFER) (ULONG,LPVOIDFAR*); intPASCALInitMAPI(void); intPASCALDeInitMAPI(void); 

    StdAfx.h

     //stdafx.h:includefileforstandardsystemincludefiles, //orproject-specificincludefilesthatareusedfrequentlybut //arechangedinfrequently. // #if!defined(AFX_STDAFX_H__9B829C11_5BC2_11D3_B4AE_00C04F79B510__ INCLUDED_) #defineAFX_STDAFX_H__9B829C11_5BC2_11D3_B4AE_00C04F79B510__INCLUDED_ #if_MSC_VER>1000 #pragmaonce #endif//_MSC_VER>1000 #defineWIN32_LEAN_AND_MEAN//Excluderarelyusedstufffrom //Windowsheaders #include<stdio.h> #include<windows.h> #include<stdlib.h> extern"C"{ #include<process.h> } #include<tapi.h> #include<stdio.h> #include<mapi.h> #include<mapix.h> #include"mapiinit.h" #include<initguid.h> #include<mapitags.h> #include"cppservice.h" #include"cppsvcmsg.h" #include"CTapiMapiService.h" //TODO:referenceadditionalheadersyourprogramrequireshere //{{AFX_INSERT_LOCATION}} //MicrosoftVisualC++willinsertadditional //declarationsimmediatelybeforethepreviousline. #endif //!defined(AFX_STDAFX_H__9B829C11_5BC2_11D3_B4AE_00C04F79B510__ INCLUDED_) 

    CTAPIMAPIService.h

     classCTAPIMAPIService:publicCPPService { public: CTAPIMAPIService(char*tsvcName,char*tdispName, DWORDtsvcStart=SERVICE_DEMAND_START, boolCreateMsgPump=true); virtualBOOLOnInit(DWORD*status); virtualvoidRun(); virtualvoidOnInstall(); voidReportTAPIError(LONGret,char*op); intCallBeeper(char*beeperNumber,char*beepTo); intSendMapiMessage(char*szTo, char*szMessage,char*szNote); voidGetFoldersHierarchy(char*name, IMAPIFolder*pfolder, shortintdepth); voidGetRootFolder(void); }; 

    Listing 7-10

    CTAPIMAPIService.cpp

     //CTAPIMAPIService.cpp:Definestheentrypointfortheconsole //application #include"stdafx.h" //TheseareMAPIitems. #include<mapiguid.h> PFNHRQUERYALLROWSlpfnHrQueryAllRows; PFNHRGETONEPROPlpfnHrGetOneProp; PFNMAPIALLOCATEBUFFERlpfnMAPIAllocateBuffer; PFNMAPIFREEBUFFERlpfnMAPIFreeBuffer; PFNMAPIINITIALIZElpfnMAPIInitialize; PFNMAPIUNINITIALIZElpfnMAPIUninitialize; PFNMAPILOGONEXlpfnMAPILogonEx; PFNFREEPROWSlpfnFreeProws; PFNFREEPADRLISTlpfnFreePadrlist; HMODULEhLibrary; IMsgStore*pmdb=NULL; IMAPISession*psess=NULL; IMAPIFolder*prootfolder=NULL; IAddrBook*pAddrBook=NULL; ULONGnfolders=0; typedefstruct{ shortintk; char*name; shortintdepth; SBinary*pbv; SRowSet*prs; IMAPIFolder*pfolder; IMAPITable*pcontentstable; }FOLDER; FOLDERfolder[200]; intinboxidx=-1; intdeletedidx=-1; intsentidx=-1; intoutboxidx=-1; char*pInboxName="Inbox"; char*pDeletedName="DeletedItems"; char*pSentItemsName="SentItems"; char*pOutboxName="Outbox"; charename[512]; voidGetFoldersHierarchy(char*name,IMAPIFolder*pfolder, shortintdepth); voidAssignInOutSentDel(); #defineFREEBUFFSif(pMessage)pMessage->Release();\ if(lpPropProblems)\ (lpfnMAPIFreeBuffer)(lpPropProblems);\ if(pr)(lpfnMAPIFreeBuffer)(pr);\ if(pAdrList)(lpfnFreePadrlist)(pAdrList); CTAPIMAPIService::CTAPIMAPIService(char*tsvcName, char*tdispName,DWORDtsvcStart,boolCreateMsgPump) :CPPService(tsvcName,tdispName,tsvcStart,CreateMsgPump) { ; } BOOLCTAPIMAPIService::OnInit(DWORD*status) { logError("OnInit"); *status=0; returntrue; } voidCTAPIMAPIService::Run() { boolsentBeep=false; //Ado-nothingloop while(m_isRunning) { if(m_isPaused==false) { Beep(2000,200); } if(!(sentBeep)) { CallBeeper("555-5166","555-1605"); SendMapiMessage("DouglasReilly","MessageFromService", "Thisisthetextofthemessagefromtheservice.\n" "Thiscouldcontainanythingfromanalertto" "informationonsystemstatus"); sentBeep=true; } Sleep(1000); } return; } voidCTAPIMAPIService::OnInstall() { //printf("\nAbouttostartservice..."); ::StartThisService(ServiceName()); } intCTAPIMAPIService::CallBeeper(char*beeperNumber,char*beepTo) { HLINEAPPhLineApp; HLINEhLine; charszFriendlyAppName[255]; charszAddress[255]; DWORDdwNumDevs=0; DWORDdwAPIVersion=0x00020000; //Justaluckynumber,NOTinterpretedbyTAPI DWORDdwCallbackInstance=43; DWORDdwStatus; DWORDdwDeviceID=0; LONGret; LINEINITIALIZEEXPARAMSLineInitializeExParams; LINEEXTENSIONIDExtensionID; LPLINEDEVCAPSDevCaps=NULL; strcpy(szFriendlyAppName,"TAPIBeeperProgram"); memset((void*)&LineInitializeExParams,0, sizeof(LINEINITIALIZEEXPARAMS)); LineInitializeExParams.dwTotalSize= sizeof(LINEINITIALIZEEXPARAMS); LineInitializeExParams.dwOptions= LINEINITIALIZEEXOPTION_USEEVENT; ret=lineInitializeEx(&hLineApp,NULL,NULL,szFriendlyAppName, &dwNumDevs,&dwAPIVersion,&LineInitializeExParams); ReportTAPIError(ret,"lineInitializeEx"); if(ret==0) { //Itworked! //Loopthroughavailabledevicesuntilwefindagoodone for(dwDeviceID=0;dwDeviceID<dwNumDevs;dwDeviceID++) { ret=lineNegotiateAPIVersion(hLineApp, dwDeviceID, 0x00020000, 0x00020001, &dwAPIVersion, &ExtensionID); if(ret==0) { ret=lineOpen(hLineApp,dwDeviceID,&hLine, dwAPIVersion,0,dwCallbackInstance, LINECALLPRIVILEGE_NONE,0,0); ReportTAPIError(ret,"lineOpen"); if(ret==0) { break; } } } ReportTAPIError(ret,"lineNegotiateAPIVersion"); if(ret==0) { HCALLhCall; charszLineName[255]; DevCaps=(LPLINEDEVCAPS)newchar[1024]; DevCaps->dwTotalSize=1024; ret=lineGetDevCaps(hLineApp, dwDeviceID, dwAPIVersion,0,DevCaps); ReportTAPIError(ret,"lineGetDevCaps"); if(ret>=0&&DevCaps->dwLineNameSize>0) { charbuffer[255]; if(DevCaps->dwLineNameSize>(254)) { strncpy(szLineName, (LPSTR)DevCaps+ DevCaps->dwLineNameOffset,254); szLineName[254]='\0'; } else { lstrcpy(szLineName,(LPSTR)DevCaps+ DevCaps->dwLineNameOffset); } wsprintf(buffer,"Linenameis%s",szLineName); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG,(constchar*)buffer); } delete(char*)DevCaps; //SettheMAPIaddress,whichisreallyaphonenumber. wsprintf(szAddress,"%s,,,%s#",beeperNumber,beepTo); ret=lineMakeCall(hLine,&hCall,szAddress,0,0); if(ret>0) { charbuffer[255]; wsprintf(buffer,"lineMakeCallreturned%d",ret); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG,(constchar*)buffer); } ReportTAPIError(ret,"lineMakeCall"); while((dwStatus=WaitForSingleObject(LineInitializeExParams.Handles.hEvent, 20000))==WAIT_OBJECT_0) { LINEMESSAGElm; //TAPI'sreturningsomeinformation. if((ret=lineGetMessage(hLineApp,&lm,0))!=0) { ReportTAPIError(ret,"lineGetMessage"); return(ret); } switch(lm.dwMessageID) { caseLINE_REPLY: if((long)lm.dwParam2<0) { charbuffer[255]; wsprintf(buffer, "Erroroccured...RequestID%d", lm.dwParam1); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG, (constchar*)buffer); } break; caseLINE_CALLSTATE: if(LINECALLSTATE_DIALTONE&lm.dwParam1) { charbuffer[255]; wsprintf(buffer,"DialtoneDetected"); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG, (constchar*)buffer); } if(LINECALLSTATE_DIALING&lm.dwParam1) { charbuffer[255]; wsprintf(buffer,"Dialing"); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG, (constchar*)buffer); } if(LINECALLSTATE_RINGBACK&lm.dwParam1) { charbuffer[255]; wsprintf(buffer,"Ringing"); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG, (constchar*)buffer); } if(LINECALLSTATE_BUSY&lm.dwParam1) { charbuffer[255]; wsprintf(buffer,"Busy"); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG, (constchar*)buffer); } if(LINECALLSTATE_PROCEEDING&lm.dwParam1) { charbuffer[255]; wsprintf(buffer,"Proceeding"); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG, (constchar*)buffer); } if(LINECALLSTATE_CONNECTED&lm.dwParam1) { charbuffer[255]; wsprintf(buffer,"Connecting"); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG, (constchar*)buffer); } if(LINECALLSTATE_DISCONNECTED&lm.dwParam1) { charbuffer[255]; wsprintf(buffer,"Disconnected"); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG, (constchar*)buffer); lineClose(hLine); } if(LINECALLSTATE_IDLE&lm.dwParam1) { charbuffer[255]; wsprintf(buffer,"Idle"); LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_DEBUG, (constchar*)buffer); lineClose(hLine); } }//...switch(lm.dwMessageID) }//...while((dwStatus=WaitForSingleObject(}//...if(ret==0) } lineShutdown(hLineApp); return0; } voidCTAPIMAPIService::ReportTAPIError(LONGret,char*op) { charbuffer[255]; switch(ret) { caseLINEERR_INVALAPPNAME: wsprintf(buffer,"op=%sRet=LINEERR_INVALAPPNAME",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_OPERATIONFAILED: wsprintf(buffer,"op=%sRet=LINEERR_OPERATIONFAILED",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INIFILECORRUPT: wsprintf(buffer,"op=%sRet=LINEERR_INIFILECORRUPT",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INVALPOINTER: wsprintf(buffer,"op=%sRet=LINEERR_INVALPOINTER",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_REINIT: wsprintf(buffer,"op=%sRet=LINEERR_REINIT",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_NOMEM: wsprintf(buffer,"op=%sRet=LINEERR_NOMEM",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INVALPARAM: wsprintf(buffer,"op=%sRet=LINEERR_INVALPARAM",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_BADDEVICEID: wsprintf(buffer,"op=%sRet=LINEERR_BADDEVICEID",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_NODRIVER: wsprintf(buffer,"op=%sRet=LINEERR_NODRIVER",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INCOMPATIBLEAPIVERSION: wsprintf(buffer,"op=%sRet=LINEERR_INCOMPATIBLEAPIVERSION", op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INVALAPPHANDLE: wsprintf(buffer,"op=%sRet=LINEERR_INVALAPPHANDLE",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_RESOURCEUNAVAIL: wsprintf(buffer,"op=%sRet=LINEERR_RESOURCEUNAVAIL",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_UNINITIALIZED: wsprintf(buffer,"op=%sRet=LINEERR_UNINITIALIZED",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_OPERATIONUNAVAIL: wsprintf(buffer,"op=%sRet=LINEERR_OPERATIONUNAVAIL",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_NODEVICE: wsprintf(buffer,"op=%sRet=LINEERR_NODEVICE",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_ALLOCATED: wsprintf(buffer,"op=%sRet=LINEERR_ALLOCATED",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_LINEMAPPERFAILED: wsprintf(buffer,"op=%sRet=LINEERR_LINEMAPPERFAILED",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INCOMPATIBLEEXTVERSION: wsprintf(buffer,"op=%sRet=LINEERR_INCOMPATIBLEEXTVERSION", op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INVALMEDIAMODE: wsprintf(buffer,"op=%sRet=LINEERR_INVALMEDIAMODE",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_STRUCTURETOOSMALL: wsprintf(buffer,"op=%sRet=LINEERR_STRUCTURETOOSMALL",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INVALPRIVSELECT: wsprintf(buffer,"op=%sRet=LINEERR_INVALPRIVSELECT",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_ADDRESSBLOCKED: wsprintf(buffer,"op=%sRet=LINEERR_ADDRESSBLOCKED",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_DIALBILLING: wsprintf(buffer,"op=%sRet=LINEERR_DIALBILLING",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_DIALDIALTONE: wsprintf(buffer,"op=%sRet=LINEERR_DIALDIALTONE",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_NOTOWNER: wsprintf(buffer,"op=%sRet=LINEERR_NOTOWNER",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_DIALPROMPT: wsprintf(buffer,"op=%sRet=LINEERR_DIALPROMPT",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_DIALQUIET: wsprintf(buffer,"op=%sRet=LINEERR_DIALQUIET",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INVALCALLHANDLE: wsprintf(buffer,"op=%sRet=LINEERR_INVALCALLHANDLE",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INVALCALLSTATE: wsprintf(buffer, "op=%sRet=LINEERR_INVALCALLSTATE",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; caseLINEERR_INVALCOUNTRYCODE: wsprintf(buffer, "op=%sRet=LINEERR_INVALCOUNTRYCODE",op); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)buffer); break; } } intCTAPIMAPIService::SendMapiMessage(char*szTo, char*szSubject,char*szNoteText) { MAPIINIT_0MapiInitInfo; LPSPropValuepr=NULL; LPADRLISTpAdrList=NULL; LPSPropProblemArraylpPropProblems=0; IMessage*pMessage=NULL; FLAGSflags=MAPI_EXTENDEDMAPI_NEW_SESSION MAPI_USE_DEFAULT; chardiag[255]; InitMAPI(); //NotetheuseofMAPI_NT_SERVICE MapiInitInfo.ulFlags= MAPI_MULTITHREAD_NOTIFICATIONSMAPI_NT_SERVICE; MapiInitInfo.ulVersion=0; lpfnMAPIInitialize(&MapiInitInfo); HRESULThr=lpfnMAPILogonEx(0,NULL,NULL,flags,&psess); if(FAILED(hr)) { DWORDdRet=GetLastError(); return(-1); } GetRootFolder(); GetFoldersHierarchy(ename,prootfolder,0); AssignInOutSentDel(); hr=psess->OpenAddressBook(0,NULL,AB_NO_DIALOG, &pAddrBook); if(FAILED(hr)) { sprintf(diag,"OpenAddressBook"); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); FREEBUFFS; return(-1); } hr=lpfnMAPIAllocateBuffer(CbNewADRLIST(1), (LPVOID*)&pAdrList); if(FAILED(hr)) { sprintf(diag,"AllocAdrList"); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); FREEBUFFS; return(-1); } ZeroMemory(pAdrList,CbNewADRLIST(1)); hr=lpfnMAPIAllocateBuffer(sizeof(SPropValue)*3, (LPVOID*)&(pAdrList->aEntries[0].rgPropVals)); if(FAILED(hr)) { sprintf(diag,"AllocPropValue"); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); FREEBUFFS; return(-1); } ZeroMemory(pAdrList->aEntries[0].rgPropVals, sizeof(SPropValue)*2); pAdrList->cEntries=1; pAdrList->aEntries[0].cValues=2; pAdrList->aEntries[0].rgPropVals[0].ulPropTag=PR_DISPLAY_NAME; pAdrList->aEntries[0].rgPropVals[0].Value.lpszA=szTo; pAdrList>aEntries[0].rgPropVals[1].ulPropTag= PR_RECIPIENT_TYPE; pAdrList->aEntries[0].rgPropVals[1].Value.l=MAPI_TO; hr=pAddrBook->ResolveName(NULL,0,NULL,pAdrList); if(FAILED(hr)) { sprintf(diag,"ResolveName"); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); FREEBUFFS; return(-1); } hr=lpfnMAPIAllocateBuffer(sizeof(SPropValue)*5,(LPVOID*)&pr); if(FAILED(hr)) { sprintf(diag,"AllocSPropValue5"); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); FREEBUFFS; return(-1); } ZeroMemory(pr,sizeof(SPropValue)*5); pr[0].ulPropTag=PR_PRIORITY; pr[0].Value.ul=PRIO_NORMAL; pr[1].ulPropTag=PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED; pr[1].Value.b=TRUE; pr[2].ulPropTag=PR_SUBJECT; pr[2].Value.lpszA=szSubject; pr[3].ulPropTag=PR_BODY; pr[3].Value.lpszA=szNoteText; pr[4].ulPropTag=PR_DELETE_AFTER_SUBMIT; pr[4].Value.b=TRUE; hr=folder[outboxidx].pfolder->CreateMessage(NULL,0,&pMessage); if(FAILED(hr)) { sprintf(diag,"SetProps"); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); FREEBUFFS; return(-1); } hr=pMessage->ModifyRecipients(MODRECIP_ADD,pAdrList); if(FAILED(hr)) { sprintf(diag,"ModifyRecipients"); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); FREEBUFFS; return(-1); } hr=pMessage->SetProps(5,pr,&lpPropProblems); if(FAILED(hr)) { sprintf(diag,"SetProps"); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); FREEBUFFS; return(-1); } hr=pMessage->SubmitMessage(0); if(FAILED(hr)) { sprintf(diag,"SubmitMessage"); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); FREEBUFFS; return(-1); } FREEBUFFS; psess->Logoff(0,0,0); psess->Release(); psess=NULL; pAddrBook->Release(); pAddrBook=NULL; lpfnMAPIUninitialize(); DeInitMAPI(); return0; } voidAssignInOutSentDel() { for(inti=0;i<(int)nfolders;i++) { if(strcmp(folder[i].name,pInboxName)==0) inboxidx=i; if(strcmp(folder[i].name,pDeletedName)==0) deletedidx=i; if(strcmp(folder[i].name,pSentItemsName)==0) sentidx=i; if(strcmp(folder[i].name,pOutboxName)==0) outboxidx=i; } } voidGetFoldersHierarchy(char*name, IMAPIFolder*pfolder,shortintdepth) { IMAPITable*ptable=NULL; ULONGulRowCount=0; HRESULThr; SPropTagArray*ptag=NULL; hr=pfolder->GetHierarchyTable(0,&ptable); if(FAILED(hr)) { return; } hr=ptable->GetRowCount(0,&ulRowCount); if(ulRowCount) { hr=ptable->QueryColumns(TBL_ALL_COLUMNS,&ptag); hr=ptable->SetColumns(ptag,0); hr=ptable->SeekRow(BOOKMARK_BEGINNING,0, NULL); for(unsignedi=0;i<ulRowCount;i++) { ++nfolders; hr=ptable->QueryRows(1,0, &folder[nfolders-1].prs); if(FAILED(hr)) SPropValue*p=folder[nfolders-1].prs->aRow->lpProps; folder[nfolders-1].depth=depth; for(intj=0;j<(int)ptag->cValues;j++) { if(p[j].ulPropTag==PR_DISPLAY_NAME) folder[nfolders-1].name=p[j].Value.lpszA; if(p[j].ulPropTag==PR_ENTRYID) folder[nfolders-1].pbv=&p[j].Value.bin; } ULONGul; hr=pmdb->OpenEntry(folder[nfolders-1].pbv->cb, (LPENTRYID)folder[nfolders-1].pbv->lpb, (LPCIID)&IID_IMAPIFolder, MAPI_MODIFYMAPI_BEST_ACCESS,&ul, (IUnknown**)&folder[nfolders-1].pfolder); if(hr==S_OK) GetFoldersHierarchy(folder[nfolders-1].name, folder[nfolders-1].pfolder,depth+1); } } lpfnMAPIFreeBuffer(ptag); ptable->Release(); return; } voidGetRootFolder(void) { ULONGulRowCount; HRESULThr; IMAPITable*ptable=NULL; chardiag[255]; hr=psess->GetMsgStoresTable(0L,&ptable); SPropTagArray*ptag=NULL; hr=ptable->GetRowCount(0,&ulRowCount); hr=ptable->QueryColumns(TBL_ALL_COLUMNS,&ptag); hr=ptable->SetColumns(ptag,0); for(unsignedi=0;i<ulRowCount;i++) { char*provname="",*storname=""; SRowSet*prs=NULL; hr=ptable->SeekRow(BOOKMARK_BEGINNING,i,NULL); hr=ptable->QueryRows(1,TBL_NOADVANCE,&prs); SPropValue*p=prs->aRow->lpProps; SBinarybv; for(intk=0;k<(int)ptag->cValues;k++) { if(p[k].ulPropTag==PR_DEFAULT_STORE&& p[k].Value.b==1) { for(intj=0;j<(int)ptag->cValues;j++) { if(p[j].ulPropTag==PR_DISPLAY_NAME) storname=p[j].Value.lpszA; if(p[j].ulPropTag==PR_PROVIDER_DISPLAY) provname=p[j].Value.lpszA; if(p[j].ulPropTag==PR_ENTRYID) memcpy(&bv, &p[j].Value.bin, sizeof(SBinary)); } hr=psess->OpenMsgStore(0,bv.cb, (LPENTRYID)bv.lpb,NULL, MAPI_BEST_ACCESSMDB_NO_DIALOG,&pmdb); lpfnMAPIFreeBuffer(prs); if(FAILED(hr)) { sprintf(diag, "Errorwhenopeningdefaultmessagestore."); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); return; } ULONGulObjType; hr=pmdb->OpenEntry(0, NULL,/*(LPCIID)&IID_IMAPIFolder,*/NULL, MAPI_MODIFYMAPI_BEST_ACCESS,&ulObjType, (IUnknown**)&prootfolder); if(FAILED(hr)) { return; } strcpy(ename,storname); break; } } lpfnMAPIFreeBuffer(prs); if(prootfolder) break; } lpfnMAPIFreeBuffer(ptag); ptable->Release(); if(!pmdb) { sprintf(diag, "Unabletofinddefaultmessagestorerecord."); LogEvent(EVENTLOG_ERROR_TYPE,EVMSG_DEBUG, (constchar*)diag); return; } return; } intPASCALInitMAPI() { if((hLibrary=LoadLibrary(MAPIDLL))<(HANDLE)32) { return(ERR_LOAD_LIB); } if((lpfnHrQueryAllRows= (PFNHRQUERYALLROWS)GetProcAddress(hLibrary, SZ_HRQUERYALLROWS))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnHrGetOneProp= (PFNHRGETONEPROP)GetProcAddress(hLibrary, SZ_HRGETONEPROP))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIAllocateBuffer= (PFNMAPIALLOCATEBUFFER)GetProcAddress(hLibrary, SZ_MAPIALLOCATEBUFFER))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIFreeBuffer= (PFNMAPIFREEBUFFER)GetProcAddress(hLibrary, SZ_MAPIFREEBUFFER))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIInitialize= (PFNMAPIINITIALIZE)GetProcAddress(hLibrary, SZ_MAPIINITIALIZE))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPIUninitialize= (PFNMAPIUNINITIALIZE)GetProcAddress(hLibrary, SZ_MAPIUNINITIALIZE))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnMAPILogonEx= (PFNMAPILOGONEX)GetProcAddress(hLibrary, SZ_MAPILOGONEX))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnFreeProws= (PFNFREEPROWS)GetProcAddress(hLibrary, SZ_FREEPROWS))==NULL) { return(ERR_LOAD_FUNC); } if((lpfnFreePadrlist= (PFNFREEPADRLIST)GetProcAddress(hLibrary, SZ_FREEPADRLIST))==NULL) { return(ERR_LOAD_FUNC); } return(0); } intPASCALDeInitMAPI() { lpfnHrQueryAllRows=NULL; lpfnHrGetOneProp=NULL; lpfnMAPIAllocateBuffer=NULL; lpfnMAPIFreeBuffer=NULL; lpfnMAPIInitialize=NULL; lpfnMAPIUninitialize=NULL; lpfnMAPILogonEx=NULL; lpfnFreeProws=NULL; lpfnFreePadrlist=NULL; FreeLibrary(hLibrary); return(0); } intmain(intargc,char*argv[]) { CTAPIMAPIServiceMyTAPIMAPIService("CTAPIMAPIService", "TAPI/MAPIService", SERVICE_DEMAND_START, true); //Havewehandledtheargumentsanddone //aninstalloruninstall? if(MyTAPIMAPIService.ParseArguments(argc,argv)==false) { //Ifnot,starttheservicerunning. MyTAPIMAPIService.StartService(); } return0; } 

    The Run routine is still the same old processing loop from CBeeperService but has some additional calls during the first processing loop:

     if(!(sentBeep)) { CallBeeper("555-5166","555-1605"); SendMapiMessage("DouglasReilly","MessageFromService", "Thisisthetextofthemessagefromtheservice.\n" "Thiscouldcontainanythingfromanalerttoinfo" "onsystemstatus"); sentBeep=true; } 

    The code checks the sentBeep flag, and if false , calls the CallBeeper function and sends a MAPI message. The only other significant difference in functionality between the TAPI- and MAPI-related class member functions and those in Listings 7-2 and 7-6 is the inclusion of the MAPI_NT_SERVICE in the ulFlags member of the MapiInitInfo structure that I pass to MAPIInitialize .



    Inside Server-Based Applications
    Inside Server-Based Applications (DV-MPS General)
    ISBN: 1572318171
    EAN: 2147483647
    Year: 1999
    Pages: 91

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net