![]() |
|
Published 1999-03-01 Printer-friendly version
With computers and the Internet becoming more and more prevalent in society, people are becoming more accustomed to using email, surfing the 'Web etc. One of the things that you as a developer can do (and that I've been asked for more than once) is to add email support to your application.
As with most things in life there are many means to obtain the same goal. In the case of email support the easiest is to use one of the 3rd party tools that already exist. However, being the sort of person I am, I'd rather implement my own interface - it may take more time than I'd like, but I have exact control over everything and get to understand the underlying philosophy as well.
Unfortunately, as with most things, there can be quite a lot to learn. Once you understand the basic principles it's easy, but getting there can be tough, so this month I'm just going to provide a quick overview on the basic API calls you'll need to use to implement email support.
MAPI (Mail API)
The base mail system upon which the other 3 options are based. It offers very high control over all aspects of the mail system, but is also fully a object-orientated COM-compliant implementation. What this means to us as developers (bearing in mind that Clarion currently isn't COM-compliant) is that basically we can't use it. If there are features that you want that the other options can't address, then there's probably a VBX or OCX that can.
CMC (Common Messaging Calls)
Simple MAPI (Simple Mail API)
CMC and SMAPI provide a simple set of about a dozen high-level API calls that Clarion developers can use. The features provided by SMAPI and CMC are nearly identical, but CMC is slightly more compact, combining multiple features in a single function. Although the Microsoft implementation is based on MAPI, the CMC API itself was developed in conjunction with the X.400 API Association standards organization, and is supported on Windows, MS-DOS, OS/2, Macintosh, and UNIX platforms.
AML (Active Message Library)
AML is an OLE automation programming interface that is a component of the MAPI interface. The AML furnishes programmable objects, like Microsoft Excel objects and Microsoft Access objects, that make available properties and methods that can then be managed by Visual Basic (VB) and Visual Basic for Applications (VBA) programs. Again, because Clarion is COM and OLE challenged (I'm being PC here), it means we can't use it.
As far as I can tell, that leaves us with the choice of CMC or SMAPI. As I described above, they are basically identical except that CMC is cross-platform and SMAPI isn't. Seeing as how we can only create Windows applications anyway, choosing CMC for it's cross platform support isn't high on my decision tree.
With that in mind, I'm going to describe the basics of using
Simple MAPI (SMAPI) from a Clarion program. There are 12 Simple
MAPI functions - I'm going to use 5 of them to send and receive
mail. They're all basically the same, so if you want to use the
ones I don't, they'll take similar parameters to the ones you'll
see listed below.
|
Simple MAPI
function
|
Description
|
| MAPIAddress | Addresses a message. |
| MAPIDeleteMail | Deletes a message. |
| MAPIDetails | Displays a recipient-details dialog box. |
| MAPIFindNext | Returns the identifier of the first or next message of a specified type. |
| MAPIFreeBuffer | Frees memory allocated by the messaging system. |
| MAPILogoff | Ends a session with the messaging system. |
| MAPILogon | Establishes a messaging session. |
| MAPIReadMail | Reads a message. |
| MAPIResolveName | Displays a dialog box to resolve an ambiguous recipient name. |
| MAPISaveMail | Saves a message. |
| MAPISendDocuments | Sends a standard message using a dialog box. |
| MAPISendMail | Sends a message, allowing greater flexibility than MAPISendDocuments() in message generation. |
[Mail] ;MAPI MAPIX=0 or 1 ;CMC CMC=0 or 1 ;Simple MAPI MAPI=0 or 1 ;AML OLEMessaging=0 or 1What our application should really do is test for the presence of the desired interface and, once detected, call the Windows API functions LoadLibrary() and GetProcAddress() on the relevant DLL's. Once we've done that, we can call the procedures we need. On the other hand, it's a lot easier as a developer if we link in the MAPI import library to our application. Of course, if we do that and MAPI isn't installed, the application will fail to run. So, what do we do? Morally we should not link against the MAPI DLL (in case it's not installed), but technically it's a lot easier to.
My theory on this is two-fold. Firstly, virtually every new PC comes pre-installed with some flavour of Windows, and email support is virtually guaranteed. Secondly, if you are developing email support in your application at the request of a client, then he should have some email system installed anyway.
As you can probably tell from my reasoning, I link my application against an import library. Don't go looking for one on your machine, because you probably won't find it. It's not shipped with Clarion because Clarion doesn't offer SMAPI support, and it's not in your \WINDOWS directory because Microsoft isn't in the habit of shipping import libraries for it's DLLs. To get the .LIB file we need, you'll have to run LibMaker. This handy utility ships with nearly every version of Clarion in one form or another, and simply creates a .LIB so that you can link against the original .DLL. To make your .LIB, run LibMaker and go hunt for MAPI32.DLL (or MAPI.DLL if you're coding 16-bit). It'll probably be in \WINDOWS\SYSTEM (or \WINNT\SYSTEM if you're running Windows NT).
Clients can specify one of two types of sessions to be established in the logon call: an individual session or a shared session. Individual sessions are private connections; there is a one-to-one relationship between a client application and the session it is using. As a consequence, client applications sharing a session also share a profile. Shared sessions are established once but can be "used" by other client applications who need to use them.
ULONG FAR PASCAL MAPILogon( ULONG ulUIParam, LPTSTR lpszProfileName, LPTSTR lpszPassword, FLAGS flFlags, ULONG ulReserved, LPLHANDLE lplhSession )
Its Clarion prototypes are:MAPILogon(ULONG,ULONG,ULONG,ULONG,ULONG,*ULONG),ULONG,RAW,PASCAL
The parameters take some explaining:
| MAPI_FORCE_DOWNLOAD | Attempt to download all of the user's messages before returning. If the MAPI_FORCE_DOWNLOAD flag is not set, messages can be downloaded in the background after the function call returns. |
| MAPI_NEW_SESSION | Attempt to create a new session rather than acquire the environment's shared session. If the MAPI_NEW_SESSION flag is not set, MAPILogon uses an existing shared session. |
| MAPI_LOGON_UI | A logon dialog box should be displayed to prompt the user for logon information. If the user needs to provide a password and profile name to enable a successful logon, MAPI_LOGON_UI must be set. |
| MAPI_PASSWORD_UI | MAPILogon should only prompt for a password and not allow the user to change the profile name. Either MAPI_PASSWORD_UI or MAPI_LOGON_UI should not be set, since the intent is to select between two different dialog boxes for logon. |
LogOn FUNCTION (Prm:UserName,Prm:Password)
LocalVars Group,Pre(Loc)
ulMapiStatus ULong
ulMapiSession ULong
End
CODE
Loc:ulMapiStatus = MAPILogon( 0,|
Address(Prm:Username),|
Address(Prm:Password),|
MAPI_LOGON_UI + MAPI_NEW_SESSION,|
0,|
Loc:ulMapiSession)
IF Loc:ulMapiStatus = SUCCESS_SUCCESS Then
Return(Loc:ulMapiSession)
Else
Return(0)
End
This has the effect of logging you on to MAPI. It always creates a
new session and always displaying a dialog box to the user. If the
logon is successful, you'll return the session handle back to the
calling procedure.
If you always know what the username and password are, you can change the flFlags parameter to not display a UI, but to simply create a new session. However, in my experience it's always easier (at least while you're still developing the application) to simply show a dialog box and let the user fill it in. That way you don't have to worry about the user incorrectly specifying the username and password in your setup information, and the problems that you'll get from that.
ULONG FAR PASCAL MAPISendMail( LHANDLE lhSession, ULONG ulUIParam, lpMapiMessage lpMessage, FLAGS flFlags, ULONG ulReserved )It's Clarion prototype is:
MAPISendMail(ULONG,ULONG,*GROUP,ULONG,ULONG),ULONG,RAW,PASCAL
Again, the parameters need some explaining:
lpMessge is a group, whose structure is
MapiMessageType GROUP,TYPE
ulReserved ULONG ! Reserved, must be 0.
lpszSubject ULONG ! Pointer to subject, CSTRING max 256 chars.
lpszNoteText ULONG ! Pointer to text, CSTRING, CR at paragraphs.
lpszMessageType ULONG ! Pointer to message type, null is default.
lpszDateReceived ULONG ! Pointer to string in YYY/MM/DD format.
lpszConversationID ULONG ! Ignored.
flFlags ULONG ! 1 - MAPI_UNREAD
! 2 - MAPI_RECEIPT_REQUESTED
! 3 - MAPI_SENT
lpOriginator ULONG ! Pointer to MapiRecipDesc group.
nRecipCount ULONG ! Count of groups pointed to by lpRecips.
lpRecips ULONG ! Pointer to array of recipient descriptor groups.
nFileCount ULONG ! Count of file attachment groups pointed to by lpFiles.
lpFiles ULONG ! Pointer to array of file attachment groups.
END
When sending email, you must first determine if your application is
responsible for prompting the user for subject, destination address
and mail text , or whether you want MAPI to display it's own
window. If the former, you must fill in the respective fields of
the group and call MAPISendMail() without theMAPI_DIALOGflag set.
If you want MAPI to do all the work for you (and why not?) then
simply set theMAPI_DIALOGflag. That will display the standard
Microsoft email window, and frees you from the burden of writing
yet another window. One thing I've found in the past is that even
if you do set the MAPI_DIALOG flag, you still have to provide one
destination address (described below) otherwise SendMail() returns
an 'invalid recipients' error.
If you don't want MAPI to do all the work, it gets rather messy rather quickly. You have to prime the lpszSubject and lpszNoteText fields with the ADDRESS() of the respective CSTRING, and you have to manage the destination addresses manually. They work on the principle of having a count (the number of people the mail is going to), and a pointer to an array of groups (one for each person). So, to send a mail message to someone, you have to set nRecipCount to 1, and set lpRecips to the ADDRESS() of structure holding the address of the person you are sending it to. That structure looks like this:
MapiRecipDescType GROUP,TYPE ! RECIPIENT DESCRIPTOR
ulReserved ULONG ! Reserved, must be 0.
ulRecipClass ULONG ! Recipients role in message
! 0 - MAPI_ORIG
! 1 - MAPI_TO
! 2 - MAPI_CC
! 3 - MAPI_BCC
lpszName ULONG ! Pointer to diplayed name of recipient.
lpszAddress ULONG ! Pointer to recipient's mail address.
ulEIDSize ULONG ! Size (bytes) of binary data in lpEntryID.
lpEntryID ULONG ! Pointer to binary data representing the recipient.
END
In practice I've found the only fields you need to set in this
structure are ulRecipClass (to MAPI_TO) and lpszName as the
ADDRESS() of the CSTRING holding their name. If you also want to CC
or BCC someone else, you'll have to increment the nRecipCount in
the original group, and set the ulRecipClass to MAPI_CC or MAPI_BCC
in the nth copy of the group. Everything else you can leave
as 0 or NULL.
If you are sending attachments, it gets messier still. Attachments work on the same principle as destination addresses - you have a count (the number of attachments), and a pointer to an array of groups (one for each attachment). The attachment group is as follows:
MapiFileDescType GROUP,TYPE ! FILE DESCRIPTOR
ulReserved ULONG(0) ! Reserved, must be 0.
flFlags ULONG(0) ! Ignored.
nPosition ULONG ! where in the message text to place the file
lpszPathName ULONG ! Pointer to full pathname of attached file.
lpszFileName ULONG ! Pointer to file name seen by user.
lpFileType ULONG(0) ! Reserved, must be NULL.
END
As you can see, you can leave virtually all the fields blank,
except for lpszPathName, nPosition and lpszFileName. I have found
in the past that it's a good idea not to pass fully qualified
pathnames in lpszPathName, because you get problems on the
receiving end if the path doesn't exist. Instead, I'd try using
PATH() to save the current path, SETPATH() to the path where the
file is, then SETPATH() back again once you've called
MAPISendMail().
The only other problem with attachments is in setting the nPosition field. This field determines where the attachment is placed, so if you set it to 1, the attachment overwrites the first character of the text. What I've done in the past is add an extra character to the end of the text, then set the nPosition field to the position of that character.
This lack of features shows itself in the fact that you can only check for mail in the main incoming mail directory. If you're a power user who uses Inbox Assistant to redirect your mail to different folders, Simple MAPI won't be able to read the mail in those folders, only the main inbox.
However, if that's all you want to do, then this is how you do it. This time, thought, you'll need three functions:
ULONG FAR PASCAL MAPIFindNext( LHANDLE lhSession, ULONG ulUIParam, LPTSTR lpszMessageType, LPTSTR lpszSeedMessageID, FLAGS flFlags, ULONG ulReserved, LPTSTR lpszMessageID ) ULONG FAR PASCAL MAPIReadMail( LHANDLE lhSession, ULONG ulUIParam, LPTSTR lpszMessageID, FLAGS flFlags, ULONG ulReserved, lpMapiMessage FAR * lppMessage ) ULONG FAR PASCAL MAPIFreeBuffer( LPVOID pv )Their respective Clarion prototypes are:
MAPIFindNext(ULONG,ULONG,ULONG,ULONG,ULONG,ULONG,ULONG),ULONG,RAW,PASCAL MAPIReadMail(ULONG,ULONG,ULONG,ULONG,ULONG,ULONG),ULONG,RAW,PASCAL MAPIFreeBuffer(ULONG),ULONG,RAW,PASCALMAPIFindNext() finds the next unread email, and passes back a message identifier. MAPIReadMail() takes that identifier and actually gets the mail (marking it as read in the process). MAPIFreeBuffer() frees the buffer that was allocated for the mail. Not freeing the buffer leads to memory leaks, which over time can bring the machine to it's knees. Always remember to free the buffer!
We start the process by calling MAPIFindNext():
|
The message identifiers returned should be in the order of time received. MAPIFindNext calls can take longer if this flag is set. Some implementations cannot honor this request and return the MAPI_E_NO_SUPPORT value. |
| MAPI_LONG_MSGID | The returned message identifier can be
as long as 512 characters. If this flag is set, the
lpszMessageID parameter must be large enough to accomodate
512 characters.
Older versions of MAPI supported smaller message identifiers (64 bytes) and did not include this flag. MAPIFindNext will succeed without this flag set as long as lpszMessageID is large enough to hold the message identifier. If lpszMessageID cannot hold the message identifier, MAPIFindNext will fail. |
| MAPI_UNREAD_ONLY | Only unread messages of the specified type should be enumerated. If this flag is not set, MAPIFindNext can return any message of the specified type. |
FindNextUnread FUNCTION
LocalVars Group,Pre(Loc)
ulMapiStatus ULong
MessageType CString(10)
MessageID CString(515)
End
CODE
Loc:MessageType = ''
Loc:ulMapiStatus = MapiFindNext(SMAPI:Session,|
0,|
0,|
0,|
MAPI_UNREAD_ONLY+MAP
0,|
Address(Loc:MessageI
If Loc:ulMapiStatus <> SUCCESS_SUCCESS Then
Clear(Loc:MessageID)
End
Return(Clip(Loc:MessageID))
|
|
|
|
|
|
|
|
lppMessage is a pointer to a message structure (the same structure we used when sending the email). The structure can be freed with a single call to the MAPIFreeBuffer() function. When MAPI_ENVELOPE_ONLY and MAPI_SUPPRESS_ATTACH are not set, attachments are written to temporary files pointed to by the lpFiles member of the MapiMessage structure. It is the caller's responsibility to delete these files when they are no longer needed.
The code looks something similar to:
ReadMail FUNCTION (Prm:MessageID)
LocalVars Group,Pre(Loc)
ulMapiStatus ULong
MessageType CString(10)
MessageID CString(100)
MailTitle CString(255)
MailSender CString(255)
MailText CString(8192)
MessagePtrPtr ULong
MessagePtr ULong
End
RxMessage Like(MapiMessageType) ! message info
RxSender Like(MapiRecipDescType) ! sender info
RxFile Like(MapiFileDescType) ! attached file info
CODE
If SMAPI:LoggedOn Then
Loc:MessageID = Clip(Prm:MessageID) & Chr(0)
Clear(RxMessage)
Loc:MessagePtrPtr = Address(RxMessage)
Loc:ulMapiStatus = MAPIReadMail( SMAPI:Session,|
0,|
Address(Loc:Me
MAPI_SUPPRESS_
0,|
Loc:MessagePtr
If Loc:ulMapiStatus = SUCCESS_SUCCESS Then
MemCpy( Address(Loc:MessagePtr) , Loc:MessagePtrPtr , 4)
MemCpy( Address(RxMessage) , Loc:MessagePtr , Size(RxMessage) )
If RxMessage:lpszSubject <> 0 Then
MemCpy( Address(Loc:MailTitle) , RxMessage:lpszSubject , 100 )
Else
Loc:MailTitle = 'Untitled'
End
If RxMessage:lpszNoteText <> 0 Then
memcpy( Address(Loc:MailText) , RxMessage:lpszNoteText , 100 )
Else
Clear(Loc:MailText)
End
If RxMessage:lpOriginator <> 0 Then
memcpy( Address(RxSender), RxMessage:lpOriginator, Size(RxSender) )
If RxSender:lpszAddress <> 0 Then
memcpy( Address(Loc:MailSender), RxSender:lpszAddress, 100 )
End
Else
Clear(Loc:MailSender)
End
SMAPI:Rx:MailTitle = Clip(Loc:MailTitle)
SMAPI:Rx:MailText = Clip(Loc:MailText)
SMAPI:Rx:MailSender = Clip(Loc:MailSender)
Loc:ulMapiStatus = MAPIFreeBuffer(Loc:MessagePtr)
If Loc:ulMapiStatus <> SUCCESS_SUCCESS Then
SMAPI:ErrorString = MapiError(Loc:ulMapiStatus)
End
End
End
Return(Loc:ulMapiStatus)
ULONG FAR PASCAL MAPILogoff ( LHANDLE lhSession, ULONG ulUIParam, FLAGS flFlags, ULONG ulReserved )It's Clarion prototype is:
MAPILogoff(ULONG,ULONG,ULONG,ULONG),ULONG,PASCAL,RAW
Compared to everything else to do with MAPI, the code to implement it is very easy.
LogOff PROCEDURE
LocalVars Group,Pre(Loc)
ulMapiStatus ULong
End
CODE
Loc:ulMapiStatus = MAPILogoff( SMAPI:Session,|
0,|
0,|
0)
IF Loc:ulMapiStatus = SUCCESS_SUCCESS then
SMAPI:LoggedOn = False
End
Of course, by it's nature Simple MAPI is limited in features, but at least this gives you some idea of the work involved. If you don't think Simple MAPI fits your situation, try one of the 3rd party templates or VBX/OCX controls - I'm sure they'll have the functionality you're looking for.
Next Time ...
How to locate GPF's in your application.
And Remember ...
The best place for information is MSDN. If you don't subscribe, you can still get to the information via the web at http://msdn.microsoft.com/
|
Posted on Thursday, March 28, 2002 by Carl Barnes You can download the files from: http://www.attryde.com/clarion/col_smapi.htm
|
To add a comment to this article you must log in.
Copyright © 1999-2009 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $169
(includes all back issues since '99)
Renewals from $119
Two years: $269
Renewals from $219