Read it Now!
Reprint Licensing

CDO & MAPI Programming with Visual Basic: Developing Mail and Messaging Applications

By Dave Grundgeiger

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Introduction
Messaging is the transmission of information using electronic means. The information transmitted can be anything: email, documents, images, meeting requests, music, purchase orders, faxes, etc., etc., etc. Anything that can be represented in digital form can be the content of a message. Usually, messaging uses store-and-forward technology. That means that the sender and receiver of a message don't need to be in direct contact with each other. The sender first gives the message to a messaging server. The server stores the message until it is able to forward it either to the intended recipient or to another server that will forward it to the recipient. That's the kind of messaging described in this book.
Messaging technology is used for more than just passing messages from one user to another asynchronously. There are natural extensions to this technology that support new ways of communicating and sharing information. For example, you're familiar with the concept of the discussion list—a use of messaging that enables groups of users to discuss topics of interest in public forums. In addition, message stores can be thought of as databases, providing a central place to store data that is not forwarded but waits for users to come view it. This is the concept behind Microsoft Exchange Server's public folders. Messaging technology is being used in new and unique ways to manage personal information of all kinds, including schedules, tasks, and contacts. By the time you reach the end of this book, you'll have learned how to tap into all of this and more in your own programs.
Messaging technology makes people more productive and efficient. Because of the asynchronous nature of email, communicators do not have to coordinate a time when they can both (or all) communicate at the same time. As mentioned, discussion lists are a great example of this. Rather than coordinating a huge meeting where everyone can contribute their opinions, discussion list members can contribute to the list and read others' communications when and where it's convenient. Messaging technology makes new things possible.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: MAPI
The Messaging Application Programming Interface (MAPI) specification was written in collaboration with more than 100 software vendors, and therefore (theoretically) it represents an industry-wide consensus on the features that should be supported by messaging platforms. Of the messaging choices available on the Microsoft Windows platforms, MAPI has the richest feature set and is the most flexible.
Although "API" is part of MAPI's name, it's not an API, or Application Programming Interface, in the way that Windows programmers usually think of that term. While many other APIs expose their capabilities through function libraries, MAPI is object-oriented. MAPI uses Microsoft's Component Object Model (COM) to provide a way to instantiate messaging objects, which in turn expose methods that can be called to manipulate those objects. I'll explain COM later in the book, when we need it to understand the structure of Collaboration Data Objects (CDO). For now, it's enough to note that MAPI uses COM features that aren't supported by Visual Basic, so it's not possible to program MAPI directly from Visual Basic. So why have a chapter on MAPI if it can't be used directly from Visual Basic? The reason is that MAPI is the foundation for many messaging technologies that are accessible to Visual Basic, including CDO. To fully understand CDO and other MAPI-based technologies, it is necessary to understand MAPI.
In addition to a survey of MAPI architecture, this chapter provides information about other technology choices available to Visual Basic developers.
Figure 2-1 shows the major components in the MAPI architecture. The purpose and use of each component are described in this and following sections.
Figure 2-1: The MAPI architecture
The designers of MAPI recognized that messaging applications have certain features in common, and that these features can be factored out of the applications into separate components. These common features are:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
MAPI Architecture
Figure 2-1 shows the major components in the MAPI architecture. The purpose and use of each component are described in this and following sections.
Figure 2-1: The MAPI architecture
The designers of MAPI recognized that messaging applications have certain features in common, and that these features can be factored out of the applications into separate components. These common features are:
  • A front end, or client, to manipulate messaging objects (or present them to the user)
  • A message store, to store messaging objects (typically on disk)
  • A transport provider, to move messaging objects from one location to another
  • An address book provider, to allow storing, retrieving, and looking up user addresses in a directory
Depending on the needs of the application, there may be more than one occurrence of any of these components. For example, a user may want to send a message to a list of other users, some via the Internet and some via fax. That user needs two transport providers: one that knows how to send messages through the Internet, and one that knows how to send faxes. The frontend need not have any knowledge of how to do either.
In "the old days," messaging applications were written from scratch to include all of this functionality in a single application. The designers of such an application would decide how messages were to be stored (perhaps in a proprietary database format), and how they were to be transmitted (perhaps via a proprietary protocol over a LAN, over a WAN, or simply between files on a mainframe computer). A shortcoming of this approach is that all users who wanted to exchange messages would have to use the same messaging application, because the storage and transport mechanisms were specific to that application.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Other Messaging Technologies
MAPI is a powerful and flexible method for gaining access to messaging capability. Unfortunately, MAPI is not directly accessible to Visual Basic applications. Several messaging technologies are accessible to Visual Basic, some of them providing an interface to MAPI and others ignoring MAPI altogether. The following sections briefly describe these technologies and the circumstances under which they should (or shouldn't) be used.
Common Messaging Calls (CMC) is a platform-independent API for sending and receiving email. CMC was developed by a group that included the X.400 API Association, the organization that sets standards for programming interfaces to X.400. X.400 is a standard for connecting email networks, and for connecting users to email networks. Applications that are written to the CMC standard are easier to port to other platforms that support CMC than are applications that are written to the MAPI specification because CMC is not a Windows-specific API. However, CMC does not provide the rich interfaces that MAPI and CDO do. For example, CMC doesn't support the concept of folders, so client applications only have access to the user's Inbox and Outbox. On Windows platforms, CMC is built on top of MAPI, so it benefits from MAPI's transport and message store transparency. CMC is not discussed further in this book.
Like CMC, Simple MAPI is a set of functions for accessing mail system functionality. It predates the specification that is now called "MAPI," and at the time it was released it was itself simply called "MAPI." When the specification that is now called "MAPI" was released, the old MAPI was renamed "Simple MAPI," and the new MAPI was called "Extended MAPI," or just "MAPI." Got it?
The Simple MAPI API is callable from Visual Basic and is explained in its entirety in Chapter 3. However, Microsoft recommends that Simple MAPI be used only to maintain code that is already written in Simple MAPI. New code should use CMC, MAPI, the MAPI ActiveX Controls, CDO, CDO for Windows 2000, or CDO for Exchange 2000.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Obtaining MAPI
MAPI may or may not already be on your target users' systems. It is typically installed during the installation of a MAPI-compatible messaging application such as Microsoft Outlook. If your application's users don't have such an application, you may want to distribute MAPI with your application. Microsoft permits this, with certain restrictions. Further information and MAPI itself are available at http://www.microsoft.com/exchange/downloads/intro.htm.
The download available at this URL gives you the MAPI Subsystem and some standard service providers, such as the Personal Folders message store provider and the Personal Address Book address book provider. Note well that if your application requires additional service providers, you'll have to investigate the licensing requirements of those components before distributing them with your application.
To learn how to obtain CDO, see Chapter 5.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Programmatically Discovering Whether MAPI Is Present
Application programs can examine the Windows registry to determine whether messaging components have been installed on the computer. The registry key used for this purpose is:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Messaging Subsystem
Table 2-4 shows the entry names to look for. A value of "1" for any entry means that the corresponding component is present on the computer.
Table 2-4: Registry Entries for Determining Whether Messaging Components are Installed
Messaging Component
Registry Entry Name
MAPI
"MAPIX"
Simple MAPI
"MAPI"
CMC
"CMC"
CDO
"OLEMessaging"
Your Visual Basic application has merely to check for these registry entries to determine whether specific messaging components have been installed. Unfortunately, the Visual Basic functions for registry access—SaveSetting, GetSetting, GetAllSettings, and DeleteSetting—are not flexible enough to retrieve data from arbitrary registry keys. Because checking for messaging components is potentially a useful feature, I'll explain how to access arbitrary registry keys from Visual Basic. I'll also give you a function that nicely wraps up the cumbersome stuff.
To access arbitrary registry entries, it is necessary to use the Win32 registry API functions RegOpenKeyEx, RegQueryValueEx, and RegCloseKey. Win32 API functions are made available to Visual Basic through Visual Basic's
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Programmatically Discovering Profile Names and the Default Profile
Application programs can examine the Windows registry to determine the names of the profiles set up for a user, as well as which profile is the user's default profile. The registry key used for this purpose on a Windows 9x system is:
HKEY_CURRENT_USER\Software\Microsoft\Windows Messaging Subsystem\Profiles
On Windows NT, the key is:
HKEY_CURRENT_USER\Software\Microsoft\WINDOWS NT\CURRENTVERSION\Windows Messaging 
Subsystem\Profiles
Regardless of operating system, the key has a string value called DefaultProfile that contains the name of the user's default MAPI profile (if the user has a default profile). In addition, the key has a subkey for each profile that has been set up for the user. The name of each subkey corresponds to the name of the associated profile. Example 2-3 shows two functions for accessing this information on a Windows 9x machine. GetDefaultProfileName returns the user's default profile name, and GetProfileNames returns a string array containing the names of the user's profiles. As in Example 2-2, several registry-related constants and API functions must be declared.
Example 2-3. Obtaining Profile Names from the Windows 9x Registry
' Place these declarations at the module level.
Public Const HKEY_LOCAL_MACHINE = &H80000002
Public Const HKEY_CURRENT_USER = &H80000001
Public Const KEY_QUERY_VALUE = &H1
Public Const ERROR_SUCCESS = 0&
Public Const ERROR_FILE_NOT_FOUND = 2&

Public Type FILETIME
        dwLowDateTime As Long
        dwHighDateTime As Long
End Type

Public Declare Function RegOpenKeyEx Lib "advapi32.dll" _
   Alias "RegOpenKeyExA" ( _
   ByVal hKey As Long, _
   ByVal lpSubKey As String, _
   ByVal ulOptions As Long, _
   ByVal samDesired As Long, _
   phkResult As Long _
) As Long

Public Declare Function RegQueryValueEx Lib "advapi32.dll" _
   Alias "RegQueryValueExA" ( _
   ByVal hKey As Long, _
   ByVal lpValueName As String, _
   ByVal lpReserved As Long, _
   lpType As Long, _
   lpData As Any, _
   lpcbData As Long _
) As Long

Public Declare Function RegCloseKey Lib "advapi32.dll" ( _
   ByVal hKey As Long) As Long

Public Declare Function RegQueryInfoKey Lib "advapi32.dll" _
   Alias "RegQueryInfoKeyA" ( _
   ByVal hKey As Long, _
   ByVal lpClass As String, _
   lpcbClass As Long, _
   ByVal lpReserved As Long, _
   lpcSubKeys As Long, _
   lpcbMaxSubKeyLen As Long, _
   lpcbMaxClassLen As Long, _
   lpcValues As Long, _
   lpcbMaxValueNameLen As Long, _
   lpcbMaxValueLen As Long, _
   lpcbSecurityDescriptor As Long, _
   lpftLastWriteTime As FILETIME _
) As Long

Public Declare Function RegEnumKeyEx Lib "advapi32.dll" _
   Alias "RegEnumKeyExA" ( _
   ByVal hKey As Long, _
   ByVal dwIndex As Long, _
   ByVal lpName As String, _
   lpcbName As Long, _
   ByVal lpReserved As Long, _
   ByVal lpClass As String, _
   lpcbClass As Long, _
   lpftLastWriteTime As FILETIME _
) As Long

Public Function GetDefaultProfileName( ) As String

   ' Returns the name of the user's default profile. If none, an empty
   ' string is returned.
   
   Dim hKey As Long           ' handle to registry key
   Dim nResult As Long        ' results from API function calls
   Dim nType As Long          ' type of data read from registry
   Dim strData As String      ' data read from registry
   Dim nBufferSize As Long    ' size of data buffer
   
   ' Initialize data buffer.
   strData = String(256, 0)
   nBufferSize = Len(strData)
   
   ' Open the registry key where the entry resides.
   nResult = RegOpenKeyEx(HKEY_CURRENT_USER, _
      "SOFTWARE\Microsoft\Windows Messaging Subsystem\Profiles", _
      0, KEY_QUERY_VALUE, hKey)
      
   If nResult <> ERROR_SUCCESS Then
      Err.Raise 335, App.Title, "Could not access system registry"
   End If
   
   ' Read the desired entry.
   nResult = RegQueryValueEx(hKey, "DefaultProfile", 0, nType, _
      ByVal strData, nBufferSize)
      
   ' Done with the registry.
   RegCloseKey hKey
   
   ' Did we find the desired registry entry?
   If nResult = ERROR_SUCCESS Then
      ' Truncate the returned data at the terminating null.
      strData = Left(strData, InStr(strData, Chr(0)) - 1)
      ' Return result.
      GetDefaultProfileName = strData
   ' else was the registry entry not found?
   ElseIf nResult = ERROR_FILE_NOT_FOUND Then
      GetDefaultProfileName = ""
   Else ' else there was some error
      Err.Raise 335, App.Title, "Could not access system registry"
   End If

End Function ' GetDefaultProfileName

Public Sub GetProfileNames(ByRef astr( ) As String)

   ' Loads astr with the names of the profiles that have been
   ' set up for the user.
   
   Dim hKey As Long           ' handle to registry key
   Dim nResult As Long        ' results from API function calls
   Dim strData As String      ' data read from registry
   Dim nSubkeys As Long       ' number of subkeys
   Dim nMaxBufferSize As Long ' length of longest subkey name + 1
   Dim nBufferSize As Long    ' size of data buffer
   Dim nIndex As Long         ' for enumerating the subkeys
   Dim astrRetVal( ) As String ' for gathering results
   Dim ft As FILETIME         ' dummy required for registry api calls
   
   ' Open the registry key where the subkeys reside.
   nResult = RegOpenKeyEx(HKEY_CURRENT_USER, _
      "SOFTWARE\Microsoft\Windows Messaging Subsystem\Profiles", _
      0, KEY_QUERY_VALUE, hKey)
      
   If nResult <> ERROR_SUCCESS Then
      Err.Raise 335, App.Title, "Could not access system registry"
   End If
   
   ' Determine the number of keys to enumerate, and the size of the buffer
   ' needed for the longest subkey name.
   nResult = RegQueryInfoKey(hKey, 0, 0, 0, nSubkeys, _
      nMaxBufferSize, 0, 0, 0, 0, 0, ft)
      
   ' Allocate results array.
   ReDim astrRetVal(1 To nSubkeys) As String
   
   ' Retrieve subkeys.
   For nIndex = 1 To nSubkeys
   
      ' Initialize data buffer.
      strData = String(nMaxBufferSize, 0)
      nBufferSize = Len(strData)
      
      ' Get the subkey name.
      nResult = RegEnumKeyEx(hKey, nIndex - 1, strData, nBufferSize, _
         0, 0, 0, ft)
   
      ' Everything OK?
      If nResult = ERROR_SUCCESS Then
         ' Truncate the returned data at the terminating null.
         strData = Left(strData, InStr(strData, Chr(0)) - 1)
         ' Return result.
         astrRetVal(nIndex) = strData
      Else ' else there was some error
         Err.Raise 335, App.Title, "Could not access system registry"
      End If
      
   Next nIndex
      
   ' Done with the registry.
   RegCloseKey hKey
   
   ' Return the array of names.
   astr = astrRetVal

End Sub ' GetProfileNames
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Summary
In this chapter you learned about the MAPI architecture. Although MAPI isn't directly callable from Visual Basic, the MAPI architecture strongly influences the structure of the technologies that are callable from Visual Basic, most notably CDO. You learned about the handful of messaging technologies that Visual Basic developers can use. Some of these are based on MAPI, and some on Internet mail protocols. Finally, you learned how to get MAPI from Microsoft, and how to test a system programmatically for the presence of messaging components.
Now it's time to write some software. Turn the page, and I'll show you how to send and receive email using the Simple MAPI API.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Simple MAPI
Simple MAPI is a set of 12 functions that were created to allow developers to access Microsoft Mail post offices programmatically. At the time the API was created, it was known simply as MAPI. This API was later superseded by the completely different and much richer Extended MAPI. At that time, the old API was renamed Simple MAPI, and today the name MAPI by itself refers to Extended MAPI. Incidentally, Simple MAPI has been rewritten to use MAPI (i.e., Extended MAPI) internally.
There is no reason to use Simple MAPI in new development. The MAPI ActiveX controls, explained in the next chapter, are a Simple MAPI wrapper that is far easier to use than the API itself. However, Simple MAPI remains available and documented in order to support existing applications that were written to use it. This chapter explains how Simple MAPI is used from Visual Basic. If you're not maintaining Simple MAPI code, it's perfectly safe to skip this chapter.
API stands for Application Programming Interface. In general, an API is any documented methodology for a software application to make use of functionality provided by another software application. On the Windows platforms, this exposure of functionality historically has been provided as function libraries implemented in dll files (although this is changing). You may be familiar with the Windows API—a set of operating system services exposed as functions implemented in Windows operating system dll s. Similarly, Simple MAPI is a set of functions implemented in a dll, namely, mapi32.dll.
If you've ever developed an ActiveX component using Visual Basic or another language, don't confuse the methods exposed by such a component with the functions that are exported by a dll. The former uses Microsoft's Component Object Model (COM) technology, which is not involved in Simple MAPI. The latter uses a mechanism that existed before COM and is still in widespread use (although as I said, this is changing; COM is now the preferred way to expose functionality to other programs).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Accessing APIs from Visual Basic
API stands for Application Programming Interface. In general, an API is any documented methodology for a software application to make use of functionality provided by another software application. On the Windows platforms, this exposure of functionality historically has been provided as function libraries implemented in dll files (although this is changing). You may be familiar with the Windows API—a set of operating system services exposed as functions implemented in Windows operating system dll s. Similarly, Simple MAPI is a set of functions implemented in a dll, namely, mapi32.dll.
If you've ever developed an ActiveX component using Visual Basic or another language, don't confuse the methods exposed by such a component with the functions that are exported by a dll. The former uses Microsoft's Component Object Model (COM) technology, which is not involved in Simple MAPI. The latter uses a mechanism that existed before COM and is still in widespread use (although as I said, this is changing; COM is now the preferred way to expose functionality to other programs).
Visual Basic gives us a way to call functions exported by dll s. It requires two steps:
  1. Use the Declare statement to make the Visual Basic compiler aware of the function that is to be called.
  2. Call the function.
The Declare statement has two forms, depending on whether the statement declares a subroutine or a function. The form for a subroutine is:
[Public | Private] Declare Sub
name Lib "libname" [Alias "aliasname"] 
[([arglist])]
and the form for a function is:
[Public | Private] Declare Function name Lib "libname" [Alias "aliasname"] 
[([arglist])] [As type]
Regardless of which form is used,
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Establishing a Session
All technologies based on MAPI, as Simple MAPI is, require that a MAPI session be established prior to sending and receiving messages. MAPI sessions were explained in Chapter 2. To establish a MAPI session using Simple MAPI, call the MAPILogon function, as shown here:
Dim nMAPISession As Long

' Initiate a MAPI session.
nRetVal = MAPILogon(0, "MyProfile", "", MAPI_NEW_SESSION, _
   0, nMAPISession)
The MAPILogon function has several parameters. Its Declare statement looks like this:
Public Declare Function MAPILogon Lib "MAPI32.DLL" ( _
   ByVal UIParam As Long, _
   ByVal User As String, _
   ByVal Password As String, _
   ByVal Flags As Long, _
   ByVal Reserved As Long, _
   Session As Long _
) As Long
The parameters to the MAPILogon function are:
UIParam
The handle of the window that is to be considered the parent of any dialog boxes displayed by MAPI during logon.
Depending on the values passed in the User and Flags parameters, MAPI may need to display a logon dialog box. Pass the hWnd property of a form to make that form the parent window. Pass a to indicate that any dialog boxes should be application-modal. If no dialog box is displayed during logon, UIParam is ignored.
User
This is the name of the profile that is to be used for the session. (MAPI profiles were explained in Chapter 2.) To cause MAPI to prompt the user for a profile to use, pass an empty string in the User parameter and specify the MAPI_LOGON_UI flag in the Flags parameter.
Password
Always pass an empty string in this parameter. If passwords are necessary, they can be specified in the profile. The Password parameter is a holdover from a time prior to Extended MAPI and is now obsolete. To avoid confusion, I'll say this again: leave the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Sending Mail
After logging on to MAPI with the MAPILogon function, messages can be created and sent. The process is as follows:
  1. Declare a variable of type MAPIMessage and set the values of its members.
  2. Declare an array of MAPIRecip and call MAPIResolveName on each element.
  3. If attachments are desired, declare an array of MapiFile and set each element appropriately. Sending attachments is covered later in this chapter.
  4. Call MAPISendMail to send the message.
Example 3-1 shows how to send a message, assuming the MAPILogon function has already been called successfully.
Example 3-1. Sending a Message
Dim nRetVal As Long
Dim MyMessage As MAPIMessage
Dim MyRecips( ) As MapiRecip
Dim MyFiles( ) As MapiFile
   
' Set the subject and body text.
MyMessage.Subject = "Test message from Simple MAPI."
MyMessage.NoteText = "This is the body text of the message."

' Add a recipient.
MyMessage.RecipCount = 1
ReDim MyRecips(1 To MyMessage.RecipCount) As MapiRecip
nRetVal = MAPIResolveName(nMAPISession, 0, "Annemarie", 0, 0, MyRecips(1))

' Send the message.
nRetVal = MAPISendMail(nMAPISession, 0, MyMessage, MyRecips, MyFiles, 0, 0)
The MAPIMessage datatype is defined in mapi32.txt like this:
Type MAPIMessage
    Reserved As Long
    Subject As String
    NoteText As String
    MessageType As String
    DateReceived As String
    ConversationID As String
    Flags As Long
    RecipCount As Long
    FileCount As Long
End Type
The member elements of this datatype are:
Reserved
This member is not used and must be 0.
Subject
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Sending File Attachments
To add attachments to an outgoing message:
  1. Prepare a variable of type MAPIMessage to represent the message (as already described in this chapter).
  2. Set the FileCount member of the MAPIMessage variable equal to the desired number of attachments.
  3. Dimension an array of MapiFile elements with as many elements as there are files to attach.
  4. Set the member values of each array element.
  5. Pass the array to the MAPISendMail function.
Example 3-3 shows a code fragment that sends a single file attachment to a single recipient. This code assumes that a session has already been established via a call to the MAPILogon function, and that the session handle is held in a variable called nMAPISession. The message thus sent, when viewed in Microsoft Outlook 98, is shown in Figure 3-3.
Example 3-3. Sending a File Attachment
Dim nRetVal As Long
Dim MyMessage As MAPIMessage
Dim MyRecips( ) As MapiRecip
Dim MyFiles( ) As MapiFile
   
' Set the subject and body text.
MyMessage.Subject = "Test message from Simple MAPI, with attachment."
MyMessage.NoteText = "This is the body text of the message."

' Add a recipient.
MyMessage.RecipCount = 1
ReDim MyRecips(1 To MyMessage.RecipCount) As MapiRecip
nRetVal = MAPIResolveName(nMAPISession, 0, "Annemarie", 0, 0, MyRecips(1))

' Add an attachment.
MyMessage.FileCount = 1
ReDim MyFiles(1 To MyMessage.FileCount) As MapiFile
MyFiles(1).PathName = "c:\autoexec.bat"

' Send the message.
nRetVal = MAPISendMail(nMAPISession, 0, MyMessage, MyRecips, MyFiles, 0, 0)
Figure 3-3: Message received from code in Example 3-3, as displayed by Microsoft Outlook 98
Note the technique used when declaring the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Reading Mail
Three API functions are related to reading messages. Each one handles a different aspect of the process, and all three must be used to retrieve incoming messages successfully. These functions are:
MAPIFindNext
Iterates through the messages in the user's Inbox, returning a message ID for each message.
BMAPIReadMail
Fetches a message to the local machine, given the message ID returned from MAPIFindNext.
BMAPIGetReadMail
Reads a fetched message.
The code in Example 3-4 loops through the messages in the user's Inbox and prints the subject text of each message to the Visual Basic Immediate window. This code assumes that a session has already been established via a call to the MAPILogon function, and that the session handle is held in a variable called nMAPISession.
Example 3-4. Reading Messages
Dim nRetVal As Long
Dim nRetValFindNext As Long
Dim strSeedMessageID As String
Dim strMessageID As String
Dim nMsg As Long
Dim MyMessage As MAPIMessage
ReDim MyRecips(0 To 0) As MapiRecip
ReDim MyFiles(0 To 0) As MapiFile
Dim recipOriginator As MapiRecip
Dim nRecipients As Long
Dim nFiles As Long

' This tells MAPIFindNext to start with the first message.
strSeedMessageID = ""

Do ' for each message in the user's Inbox

   ' Get the next message ID.
   strMessageID = String(512, 0)
   nRetValFindNext = MAPIFindNext(nMAPISession, 0, "", _
      strSeedMessageID, 0, 0, strMessageID)

   ' if there was another message
   If nRetValFindNext = SUCCESS_SUCCESS Then
   
      ' Fetch message associated with the message ID.
      nRetVal = BMAPIReadMail(nMsg, nRecipients, nFiles, nMAPISession, 0, _
         strMessageID, MAPI_ENVELOPE_ONLY Or MAPI_PEEK, 0)
      Debug.Assert nRetVal = SUCCESS_SUCCESS
      
      ' Prepare the MyRecips and MyFiles arrays to receive
      ' the recipient and attachment information.
      If nRecipients > 0 Then
         ReDim MyRecips(0 To nRecipients - 1) As MapiRecip
      End If
      If nFiles > 0 Then
         ReDim MyFiles(0 To nFiles - 1) As MapiFile
      End If
      
      ' Read the fetched message.
      nRetVal = BMAPIGetReadMail(nMsg, MyMessage, MyRecips, MyFiles, _
         recipOriginator)
      Debug.Assert nRetVal = SUCCESS_SUCCESS
      
      ' Write the subject line to the immediate window.
      Debug.Print MyMessage.Subject
      
      ' The message ID becomes the seed for the next call to MAPIFindNext.
      strSeedMessageID = strMessageID
   End If
Loop While nRetValFindNext = SUCCESS_SUCCESS
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Reading File Attachments
Attachments are retrieved from the message store and copied to temporary local files automatically when messages are read. After a call to BMAPIGetReadMail, attachment information appears in the array passed to that function's File parameter. Have another look at the BMAPIGetReadMail call from Example 3-4:
' Read the fetched message.
nRetVal = BMAPIGetReadMail(nMsg, MyMessage, MyRecips, MyFiles, _
   recipOriginator)
Just prior to this call in Example 3-4, the MyFiles array was dimensioned to have as many elements as there are attachments to the email. The BMAPIGetReadMail call then fills each element with information about the corresponding attachment. Each element holds a MapiFile user-defined type, shown earlier in this chapter and repeated here:
Type MapiFile
    Reserved As Long
    Flags As Long
    Position As Long
    PathName As String
    FileName As String
    FileType As String
End Type
The usage of each member is the same as that described in Section 3.4 earlier in this chapter.
What you do with this information depends on your application's requirements. One option would be to allow the user to copy an attachment from an email to a disk file. Because email attachments are stored as temporary files when the email is fetched, this feature can be implemented simply by calling Visual Basic's FileCopy statement to copy the file from its temporary location to a destination specified by the user. Assuming that MyFiles is defined as in Example 3-4, that BMAPIGetReadMail has been called as already described, that nIndex identifies the attachment to be copied, and that strCopyTo holds the destination path and filename, the following statement does the job:
FileCopy MyFiles(nIndex).PathName, strCopyTo
Note that the PathName member of the MapiFile type contains the full path of the file attachment, including the filename.
Another nice feature is to allow the user to open an attached document directly from an email. This is a little trickier to implement because Visual Basic doesn't have a statement for opening documents. To implement this feature, it is necessary to use the Windows API call,
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Showing the Address Book
If you're writing an application that allows the user to enter recipients for an outgoing message, it's convenient for the user to have a way to display the address book and to select recipients directly from it. In Simple MAPI, this is done using a combination of two functions: BMAPIAddress and BMAPIGetAddress. BMAPIAddress displays the address book and records the user's selections (internally), returning a handle to the selections through an out parameter. BMAPIGetAddress accepts the handle as input and returns (again through an out parameter) an array of MapiRecip records representing the address entries selected from the address book. This array can then be passed to the MAPISendMail function. Example 3-5 demonstrates this process. Figure 3-4 shows the address book dialog box displayed by the MAPI system when BMAPIAddress is called.
Example 3-5. Showing the Address Book
' Assume that a MAPI session has already been established, and that
' nMAPISession holds the session handle.
   
Dim nRetVal As Long
Dim MyMessage As MAPIMessage
Dim MyRecips( ) As MapiRecip
Dim MyFiles( ) As MapiFile
Dim nInfo As Long
Dim nRecipients As Long
   
' Set the subject and body text of the message.
MyMessage.Subject = "Test message from Simple MAPI."
MyMessage.NoteText = "This is the body text of the message."

' Show the address book to allow the user to select recipients.
nRetVal = BMAPIAddress(nInfo, nMAPISession, 0, "Select Names", 4, "", _
   nRecipients, MyRecips, 0, 0)
   
If nRetVal = SUCCESS_SUCCESS Then
   ' Load the recipients into MyRecip( ).
   ReDim MyRecips(0 To nRecipients - 1)
   nRetVal = BMAPIGetAddress(nInfo, nRecipients, MyRecips)
   
   If nRetVal = SUCCESS_SUCCESS Then
      ' Send the message.
      MyMessage.RecipCount = nRecipients
      nRetVal = MAPISendMail(nMAPISession, 0, MyMessage, MyRecips, _
         MyFiles, 0, 0)
   End If
End If
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Showing Recipient Properties
In Microsoft Outlook, the user can right-click a message recipient to view that recipient's properties. Simple MAPI provides this feature through the MAPIDetails function. MAPIDetails causes MAPI to invoke the underlying address book provider's address entry dialog box. This allows the user to display and modify the address entry properties associated with a specified recipient. Here's a sample call to this function:
nRetVal = MAPIDetails(nMAPISession, 0, MyRecip, 0, 0)
The MapiRecip variable that is passed to MAPIDetails could be from one of the elements in the recipients array of an incoming message. A sample address entry properties dialog box is shown in Figure 3-8.
Figure 3-8: An address entry properties dialog box
MAPIDetails is declared as follows:
Public Declare Function MAPIDetails Lib "MAPI32.DLL" Alias "BMAPIDetails" ( _
   ByVal Session As Long, _
   ByVal UIParam As Long, _
   Recipient As MapiRecip, _
   ByVal Flags As Long, _
   ByVal Reserved As Long _
) As Long
Its parameters are:
Session
The session handle that was obtained in a previous call to MAPILogon. Alternatively, pass to cause MAPI to log on the user and create a session that exists only for the duration of the call. If necessary, a dialog box is displayed to request further logon information from the user.
UIParam
The handle of the window that is to be considered the parent of any dialog boxes displayed by MAPI during the call. The window handle of a Visual Basic form is found in its hWnd property. Pass to indicate that any dialog boxes should be application-modal.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Microsoft's Helper Functions
Some of the Simple MAPI functions are a little cumbersome to call, to say the least. In particular, it seems a bit excessive to have to call both BMAPIReadMail and BMAPIGetReadMail simply to retrieve a list of messages. Similarly, why should we have to call both BMAPIAddress and BMAPIGetAddress to display the address book and retrieve the list of selected address entries? To help make things a little easier, Microsoft has provided a couple of helper functions in Knowledge Base article Q163216, Updated Mapivb32.bas for Simple MAPI on 32-Bit Platforms. (Search for this article on the Microsoft Developer Network [MSDN] web site at http://msdn.microsoft.com.) The article provides MAPIReadMail, which wraps BMAPIReadMail and BMAPIGetReadMail, and MAPIAddress, which wraps BMAPIAddress and BMAPIGetAddress.
Here is the syntax for the MAPIReadMail function:
Function MAPIReadMail(Session As Long, UIParam AsLong, MessageID As String, Flags 
As Long, Reserved As Long, Message As MAPIMessage, Orig As MapiRecip,RecipsOut( ) 
As MapiRecip, FilesOut( ) As MapiFile) As Long
The parameters are:
Session
The session handle that was obtained in a previous call to MAPILogon. Alternatively, pass to cause MAPI to log on the user and create a session that exists only for the duration of the call. If necessary, a dialog box is displayed to request further logon information from the user.
UIParam
The handle of the window that is to be considered the parent of any dialog boxes displayed by MAPI during the call. The window handle of a Visual Basic form is found in its hWnd property. Pass to indicate that any dialog boxes should be application-modal.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Summary
In this chapter, you learned about Simple MAPI, the precursor to MAPI. You learned how to use the Declare statement to gain access to this API (and others) and how to use the API to read and send messages, with or without attachments. You also learned a couple of nice extras, such as showing the address book.
In the next chapter, you'll see a technology that is built on Simple MAPI—the MAPI ActiveX controls. If you're looking for a quick start in messaging from Visual Basic, read on.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 4: The MAPI ActiveX Controls
ActiveX controls are a great way for Visual Basic programmers to reuse functionality written by other programmers. Using the Messaging Application Programming Interface (MAPI) ActiveX controls, a Visual Basic programmer can create mail-enabled code in less than five minutes. The controls encapsulate the know-how of dealing with the underlying mail system, leaving the programmer free to focus on business requirements.
The downside is that the MAPI controls don't expose MAPI's full functionality. For example, MAPI has rich features for creating and accessing multiple folders, but the MAPI controls are able to access only the Inbox folder. Similarly, MAPI makes it easy to define custom fields on message items, but the MAPI controls are unable to access custom fields. This lack of depth comes from the fact that the MAPI controls are built on Simple MAPI. Simple MAPI was introduced in Chapter 1.
If you need a rich interface to MAPI, skip ahead to Chapter 5. However, if you need to work with legacy code that uses the MAPI controls, or if you just need a quick way to send emails programmatically, read on.
The remainder of this chapter assumes that you're familiar with ActiveX controls in general—that is, you know how to add a control to a form, how to set and read properties in code and at design time, and how to call methods.
The MAPI controls are included in the Professional and Enterprise editions of Visual Basic. The Learning Edition doesn't include them. Microsoft doesn't sell the controls individually, and it's not legal to get them from a friend, so Learning Edition owners must upgrade if they want to use these controls.
Of course, before the MAPI controls can be used, your computer must have MAPI installed, it must have message store, address book, and transport providers installed, and a MAPI profile must be set up. Chapter 2, discussed these requirements at length. The best test that your computer is set up correctly for messaging application development is to ensure that you can send and receive emails using an existing commercial MAPI-compliant email application, such as Microsoft Outlook.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Getting Started
The remainder of this chapter assumes that you're familiar with ActiveX controls in general—that is, you know how to add a control to a form, how to set and read properties in code and at design time, and how to call methods.
The MAPI controls are included in the Professional and Enterprise editions of Visual Basic. The Learning Edition doesn't include them. Microsoft doesn't sell the controls individually, and it's not legal to get them from a friend, so Learning Edition owners must upgrade if they want to use these controls.
Of course, before the MAPI controls can be used, your computer must have MAPI installed, it must have message store, address book, and transport providers installed, and a MAPI profile must be set up. Chapter 2, discussed these requirements at length. The best test that your computer is set up correctly for messaging application development is to ensure that you can send and receive emails using an existing commercial MAPI-compliant email application, such as Microsoft Outlook.
By default, the MAPI controls do not appear in your Toolbox. To add them to the Toolbox for your project, choose Components from the Project menu. The Components dialog box appears, as shown in Figure 4-1.
Figure 4-1: The Components dialog box
Scroll down the list of components until you find Microsoft MAPI Controls 6.0. Select the checkbox if it's not selected already, then click OK. After doing so, you'll see the two MAPI controls in your Toolbox. (If you don't see the Toolbox at all, choose View Toolbox from the Visual Basic menu.) The tool icons are shown in Figure 4-2.
Figure 4-2: A Toolbox with MAPI controls
Once the Toolbox has the MAPI controls, the controls can be added to a form. Typically, one of each is added to one of the forms in a project. The MAPI controls are invisible at runtime, so it doesn't matter where they're placed. Once they've been added to a form, they can be referred to in code to manipulate messages.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The MAPISession Control
The MAPISession control is used for signing onto and off of the MAPI Subsystem. Your program must sign onto the MAPI Subsystem before it can work with messages. Signing on establishes a MAPI session; signing off releases the session.
During sign on, your program tells MAPI which profile to use during the session. MAPI accesses the profile to discover which mail services (message store, address book, and transport providers) to load. Again, refer to Chapter 2 for more information on profiles and service providers. Once signed on, you can use the MAPIMessages control to work with messages in the MAPI Inbox.
To sign on, set the MAPISession control's UserName property to the name of the profile you want to use, then call the SignOn method, as shown here:
With MAPISession1
   .UserName = "MyProfile"
   .SignOn
End With
I've left the Password property blank because the profile itself specifies any username and password needed to authenticate to the underlying service providers. Older mail systems, such as Microsoft Mail, didn't use profiles—they required the username and password to be set directly in the MAPISession control.
Given the MAPISession control's default values, the previous code is equivalent to:
With MAPISession1
   .DownLoadMail = True
   .LogonUI = True
   .NewSession = False
   .Password = ""
   .UserName = "MyProfile"
   .SignOn
End With
It's likely that you'll want to adjust these property settings to achieve precisely the effects you're looking for, so I'll address each in turn.
The DownLoadMail property tells the MAPI subsystem to retrieve mail from the mail server during the sign-on process. This forces your modem to dial out if you're using dialup networking. You may wish to set this property to False if you want to allow the user to manipulate messages in the Inbox while offline. After working offline, you can force a dial out to send messages from the Outbox and to receive new messages. This is done by logging out of the MAPI Subsystem (calling the SignOff method), then setting the DownLoadMail property to
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The MAPIMessages Control
The MAPIMessages control is used to manipulate messages. After signing on to MAPI using the MAPISession control, you must set the SessionID property of the MAPIMessages control equal to the SessionID property of the MAPISession control, like this:
MAPIMessages1.SessionID = MAPISession1.SessionID
After doing so, you can compose and manipulate messages until you call the SignOff method of the MAPISession control.
The MAPI controls access only a user's Inbox. No other folders are available, not even other message folders such as Sent Items or Deleted Items, nor any nested folders that have been created inside or nested within the Inbox.
The MAPIMessages control has properties related directly to messages (e.g., MsgSubject, MsgNoteText), properties related to attachments (e.g., AttachmentName, AttachmentType), and properties related to recipients (e.g., RecipDisplayName, RecipType). These three categories are important because each property in each group actually references an array of values. The mechanism for accessing these values is similar in all three cases.
Consider the message-related properties. It's clear that an Inbox may have more than one message. When reading the MsgNoteText property, for example, there must be a way to tell the control which message to access. The MsgIndex property has been provided for this purpose. All of the properties that reference messages retrieve their values from the message that is indicated by the current value of MsgIndex. This is referred to as the currently indexed message. The total number of messages is given by the MsgCount property. MsgIndex is zero-based, so its value can range from through (MsgCount - 1). It is an error to set the MsgIndex property to a value higher than MsgCount - 1. To give an example, the following code loops through all messages, adding each message's subject line to a list box named
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Sending Mail
Sending a text message programmatically is simple. Assuming that you've already signed on with the MAPISession control and that you've set the MAPIMessages control's SessionID property equal to the MAPISession control's SessionID property, the following code does the job:
With MAPIMessages1
   .Compose
   .MsgSubject = "This is the subject."
   .MsgNoteText = "This is the message body."
   .RecipIndex = 0
   .RecipDisplayName = "Dave"
   .Send
End With
Calling the MAPIMessages control's Compose method tells the control that you are about to set some properties for a new outgoing message. Unlike incoming messages, there can never be more than one outgoing message at a time. The value of the MsgIndex property for the outgoing message is -1.
The MsgSubject and MsgNoteText properties are self-explanatory, being the subject and body portions of the message, respectively.
Unlike composing a message, there is no explicit method to call for adding a recipient, and the RecipIndex property is never -1. The number of recipients is controlled by how you set the RecipIndex property. In the code shown previously, the act of setting RecipIndex to automatically causes RecipCount to become 1. RecipCount is always automatically one greater than the highest value to which you have set RecipIndex. You never set RecipCount directly. The previously shown code can be modified to send to two recipients as follows:
With MAPIMessages1
   .SessionID = MAPISession1.SessionID
   .Compose
   .MsgSubject = "This is the subject."
   .MsgNoteText = "This is the message body."
   .RecipIndex = 0
   .RecipDisplayName = "Dave"
   .RecipIndex = 1
   .RecipDisplayName = "Annemarie"
   .Send
End With
The second recipient was added by incrementing RecipIndex and setting RecipDisplayName to a new name. Note that you can't assign a variant string array to RecipDisplayName to reduce coding; the control's type library identifies this property as a String, so if you try to assign anything else, Visual Basic generates a Type Mismatch error.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Sending File Attachments
Sending a file as an attachment is easy. To add attachments to an outgoing message:
  1. Set the AttachmentIndex property of the MAPIMessages control appropriately (0 for the first attachment, 1 for the second attachment, etc.).
  2. Set the AttachmentPathName property to the fully qualified path of the file you would like to send. This is the path of the file as it exists on the sending system. This path is not communicated to the receiving mail client.
  3. Set the AttachmentName property to a short name for the file. This is typically the filename and extension of the file, but it can be any string. The receiving mail client uses this string to name the file on the receiving system, so setting it to a reasonable filename and extension, such as "MyResume.doc", is helpful.
  4. Set the AttachmentType property to mapData, which indicates that the attached file is a data file (as opposed to an embedded OLE object, to be discussed shortly).
  5. Set the AttachmentPosition property to indicate at which character position the attachment should appear. Some mail clients display a representation of the file within the body of the message at the character position indicated by AttachmentPosition. (However, some ignore this value and display the attachments separately.)
    Note the following in regard to the AttachmentPosition property:
    • This property is zero-based, meaning that the first character in the message is at position 0, and the last character in the message is at Len(MAPIMessages1.MsgNoteText) - 1.
    • It is not permissible for AttachmentPosition to be outside of the above range.
    • It is not permissible to have two attachments at the same position.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Sending Embedded OLE Objects
It's technically possible, but difficult, to send an embedded OLE attachment using the MAPI controls. (OLE is a mechanism that allows users to create and edit documents containing items or "objects" created by multiple applications. In the context of sending email, an embedded OLE attachment is an OLE object—for example, a range of cells from a spreadsheet—that is transmitted along with the message. A general discussion of OLE is not within the scope of this book; consult Microsoft documentation for further information about this technology.)
If you need to send embedded OLE attachments, you're better off using Collaboration Data Objects (CDO). However, I'll explain briefly how it could be done with the MAPI controls.
As explained in the previous section, sending a file attachment requires setting the AttachmentType property of the MAPIMessages control to mapData. There are two other legal values for this property: mapEOLE and mapSOLE. Either value can be used to send an OLE object. The difference is that mapEOLE is for an "editable" OLE object, and mapSOLE is for a "static" (i.e., non-editable) OLE object.
On the receiving end, the mail client displays the OLE object within the body of the message. If the AttachmentType property was set to mapEOLE, the user can double-click the object to begin editing it. For this to work, the user must have software on his or her system that knows how to edit the kind of object that was sent. For example, if you send an OLE object that contains a range of cells from a Microsoft Excel spreadsheet, the receiving user must have Excel in order to edit the object. However, it is not necessary to have Excel simply to view the object. (OLE objects carry inside them a static visual representation of their data.)
To attach the OLE object to an outgoing message, you must serialize the object into a file, and set the AttachmentPathName property of the MAPIMessages control to the fully qualified path of the file. Serialization is the process of copying an object's state to some kind of sequential storage device, such as a file. Copying to a non-volatile storage device, such as a file, is also known as
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Reading Mail