- 浏览: 88893 次
- 性别:
- 来自: 上海
最新评论
-
vimest:
没学会谁要你,真要学还是要自己挣钱,国人用的软件有几个是收费的 ...
IPHONE 新手入门之前 -
21jhf:
只看到你花钱了,没看到你怎么挣钱的,光出不进谁还做Iphone ...
IPHONE 新手入门之前 -
xserver:
<div class="quote_title ...
IPHONE 新手入门之前 -
lich0079:
http://download.imodzone.net/sd ...
IPHONE 新手入门之前 -
JavaFans:
XCode不需要钱,注册了可以在网上下载,跟ios一起的
IPHONE 新手入门之前
http://g8.cx/mapi/
If you want to refer to this content, please link to this page instead of copying it, as I might be adding some more source code examples later.
PLEASE NOTE
The following code samples might be obsolete. They might not work with current version of the mapi33 dll, though they will still work using the out-of-date (but quite well-working and free) version of the mapi33 dll included in the samples zip file.
To get up-to-date information about the mapi33 dll and to buy the most recent version, visit the mapi33 website at
http://www.mapi33.adexsolutions.com/
Extended MAPI with C#
A Basic Introduction to Extended MAPI Using C# and the Mapi33 Library.
Author: Jan Pichler (mail AT jan DOT to)
Last changed: 2004-05-20
Content
1 Abstract and Confession............................................................................................................................ 3
2 MAPI Architecture................................................................................................................................... 4
2.1 MAPI? What is that?........................................................................................................................ 4
2.2 The MAPI Family - CMC, CDO, Simple MAPI and Extended MAPI.................................................. 4
2.2.1 Common Messaging Calls (CMC)................................................................................................. 4
2.2.2 Collaboration Data Objects (CDO)................................................................................................ 4
2.2.3 Simple MAPI............................................................................................................................... 4
2.2.4 Extended MAPI........................................................................................................................... 4
2.2.5 Outlook Redemption..................................................................................................................... 5
2.3 The Concept of Extended MAPI....................................................................................................... 5
2.4 Sources of Documentation................................................................................................................ 5
3 The Mapi33 Library................................................................................................................................... 6
3.1 Introduction...................................................................................................................................... 6
3.2 Code Conversion Examples............................................................................................................... 6
4 C# Functional Code Samples...................................................................................................................... 7
4.1 Starting a MAPI Session................................................................................................................... 7
4.2 Closing a MAPI Session................................................................................................................... 7
4.3 Resolving a Recipient's Name into Name and Email Address.............................................................. 8
4.4 Selecting one or multiple Recipients from the MAPI Address Book..................................................... 9
4.5 Get a String Representation of Recipients Name/Email from Recipient EntryID.................................. 10
4.6 Get Recipient's EntryIDs from Address Book Entries........................................................................ 12
4.7 Tricky: Opening an Outlook PST File and Reading its Contents.......................................................... 12
5 Conclusion.............................................................................................................................................. 17
6 References............................................................................................................................................. 18
1 Abstract and Confession
Many programmers not familiar with MAPI use Simple MAPI or CDO to complete basic email- and contact management tasks. While these 'primitive' tools might carry satisfactory results in certain situations, they have many major disadvantages in comparison to Extended MAPI. While Simple MAPI and CDO are available as ready-to-use COM components, and are easily accessible with nearly every programming language, the Extended MAPI interface uses complex structures and lower-level techniques that imply the use of C-based languages or Delphi. Even in modern and powerful languages like C#, the few available structures make it highly difficult to implement Extended MAPI. The mapi33.dll is an Extended MAPI wrapper entirely written in managed C#. It provides simple access to the Extended MAPI API and enables a MAPI programming comfort similar to appropriate developments in C++. As C/C++ is the most frequently used language for MAPI access, nearly all of the existent documentation is C++ based. Therefore, the knowledge of translating C++ to C# plays an important role in C# based Extended MAPI programming.
This paper will try to give a brief overview of Extended MAPI architecture and explain relevant concepts, while it's main focus lies on illustrating possible implementation approaches with functional code samples.
I am not really familiar with C++. I don't really know a lot about MAPI and the Mapi33 library. I know that some deeply experienced MAPI programmers might roll on the floor when they read a newbie's ideas about how MAPI works. This paper is not intent to give a global, complete overview of the MAPI world, as this is beyond my possibilities. Much more, this paper is supposed to be a superficial introduction for all those unfamiliar with MAPI, a point to start off from and do further research into the desired direction. There might be a little smarter ways to do some things that my sample codes do, even though I think most of the code works very well.
2 MAPI Architecture
2.1 MAPI? What is that?
Microsoft introduced the Messaging Application Programming Interface (MAPI) because they felt "committed to making a reality of this vision of electronic messaging as the "central nervous system" for organizational communication" [1].
MAPI provides a layer functionality between applications and the underlying messaging systems. It allows applications to access different (MAPI enabled) messaging systems without having to support them explicitly. Various applications can interact with each other and various messaging systems - without even having to 'know' the counter part. Practical everyday life samples for this technique are many MS Outlook integrated fax and even SMS applications.
2.2 The MAPI Family - CMC, CDO, Simple MAPI and Extended MAPI
There is a basic difference between Extended MAPI and all other MAPI-derived interfaces: While MAPI 1.0 not only concludes but itself provides the whole range of MAPI functionality, all other MAPI derivates are just wrappers for certain aspects of just this MAPI 1.0 library. Because of that, the direct access of Extended MAPI significantly increases speed while offering the whole range of MAPI functionality, transparency and stability.
2.2.1 Common Messaging Calls (CMC)
The CMC API is an obsolete and therefore deprecated MAPI interface for C/C++ languages. It is built directly on top of the core MAPI subsystem, and limited to the following basic messaging tasks:
* Sending messages
* Retrieving messages
* Looking up addressing information
2.2.2 Collaboration Data Objects (CDO)
The CDO library (aka "OLE Messaging", "Active Messaging") is a COM wrapper for the MAPI library. It is accessible with all development languages that support automation. CDO is primarily designed for client activities. It does not provide the full MAPI functionality - but still more then Simple MAPI does:
* Log onto the messaging system with specific profiles or with anonymous authentication
* Compose messages, address and resolve recipients, send, receive, and read messages, add attachments, automate replies
* Manage calendars, create meetings and appointments
* Manage folders and messages within the information store.
* Manage Addresses, especially within the Personal Address Book (PAB)
2.2.3 Simple MAPI
Simple MAPI is a subset of 12 functions, which enable programmers to add basic messaging support to their applications. Its functionality includes
* Log onto the messaging system
* Compose new messages, add and resolve recipients, send messages
* Retrieve and read messages from the inbox
2.2.4 Extended MAPI
Extended MAPI (or "MAPI 1.0") comprehends the straight-forward access of the whole MAPI library. Originally, Microsoft designed this library for C/C++ implementation only. By and by, all of the required C++ headers were translated to Pascal and used within Delphi. Since the development of the Mapi33 library has reached production quality, C# is also absolutely suited for designing completely MAPI enabled applications.
2.2.5 Outlook Redemption
The Outlook Redemption COM component [7] is no member of the MAPI Family in general. Redemption is - in contrast to all other listed MAPI interfaces - not developed by Microsoft. As the need for an easy-to-use component that handles MAPI issues circumventing today's security warnings arose, Dmitry Streblechenko developed a commercial COM component that provides simplified access to a certain range of Extended MAPI functionality for nearly all development languages, including highest-level languages like Visual Basic Script (VBS) or Visual Basic for Applications (VBA).
Redemption more or less directly offers a small part of the real Extended MAPI objects an structure, but its main focus lies on extending the capabilities of simpler MAPI wrappers like CDO and Simple MAPI (eg. managing profiles, circumventing security warnings when browsing the contact list etc.). Hence, it is not directly comparable to native Extended MAPI.
2.3 The Concept of Extended MAPI
All MAPI interaction is session-based. Before performing any actions in the MAPI context, a session has to be established. At logon time, the system (or the user) has to specify the name of the MAPI profile to work in as well as required access credentials (username and password). Multiple applications are able to - but don't have to - share a single session. After MAPI interaction has finished, a MAPI logoff is performed which closes the active session if it is no longer used.
Most of the MAPI interaction is profile-based. All productive MAPI actions happen in the scope of a initially chosen profile. The only relevant actions that are performed outside a profile are administrative actions regarding the addition, modification and deletion of profiles and corresponding properties.
2.4 Sources of Documentation
There are multiple sources for MAPI documentation, code examples and public discussion of MAPI-related topics. These include
* The good old Google search [3]
* Microsoft's Strategic White Paper for MAPI [1]
* Microsoft's Messaging Application Programming Interface Programmer's Reference [4]
* Numerous MAPI developer websites [5]
* Public forums and discussion boards [6]
3 The Mapi33 Library
3.1 Introduction
The Mapi33 native Extended MAPI wrapper for .NET consists of a single file, MAPI33.dll. It can be used and redistributed freely without any conditions or contributions [2].
Referenced in a project, the library provides the namespace MAPI33 which includes all objects, structures, types, enumerations etc. necessary for accessing Extended MAPI. As the library is entirely written in managed C#, it doesn't require the programmer to use unsafe code blocks or access external APIs or COM components.
Though still under development, the library works very well. There is no adequate documentation available by now, but as most C++ code snippets can be intuitively converted to equivalent Mapi33/C# calls, this doesn't obstruct development at all.
3.2 Code Conversion Examples
The following code sample represents a very easy yet working syntax-only conversion between C++ and Mapi33/C#:
C++ code
4 C# Functional Code Samples
The following examples show some common MAPI tasks.
DOWNLOAD: You may download a fully functional (MS Visual Studio 2003) implementation of all listed sample codes as well as a small test application including the free MAPI33 DLL here.
All methods except the first one presume an established MAPI connection and an active MAPI session (stored in the instance variable session).
4.1 Starting a MAPI Session
The following code starts a MAPI session, which is required for nearly every MAPI operation:
4.3 Resolving a Recipient's Name into Name and Email Address
This method resolves the given part of a recipient's name or email address. The MAPI Library checks the underlying IAddrBook and returns all matches. The method ResolveName() returns Error.Success if there is exactly one match, Error.NotFound if there are no matches and Error.AmbiguousRecip if there are multiple. The parent window's handle is needed in all sample codes for showing dialogs modally.
4.4 Selecting one or multiple Recipients from the MAPI Address Book
This code shows the standard "Select Recipient" dialog and lets the user chose one or multiple recipients.
4.5 Get a String Representation of Recipients Name/Email from Recipient EntryID
This method returns the full name and email string of each passed ENTRYID in a string[2]. It could - for example - return a string[] {"John Doe", "john.doe@microsaft.at"}.
4.6 Get Recipient's EntryIDs from Address Book Entries
The ResolveName() and Address() methods each return a bunch of the recipients properties. This function does nothing but isolate and return the ENTRYID of each given Address Book Entry (equals 1 recipient).
private ENTRYID[] GetEntryIdsFromABEntries(ADRENTRY[] abe)
{
ENTRYID[] entries = new ENTRYID[abe.Length];
// Iterate through Address Book Entries
for (int i=0; i < abe.Length; i++)
{
ADRENTRY aktEntry = abe[i];
byte[] eidBytes = null;
// Iterate through current ADRENTRY's propVals, look for ENTRYID...
for (int j=0; j < aktEntry.PropVals.Length; j++)
{
if (aktEntry.PropVals[j].PropTag == Tags.PR_MEMBER_ENTRYID)
{
// Yes, we got it...
eidBytes = (byte[])((MapiBinary)aktEntry.PropVals[j]).Value;
break;
}
}
// Did we find an ENTRYID?
if (eidBytes != null)
{
// Add it to our array - basically that's it.
entries[i] = new ENTRYID(eidBytes);
}
else
{
entries[i] = null;
}
}
return entries;
}
4.7 Tricky: Opening an Outlook PST File and Reading its Contents
Did you imagine you could do that? Who did not always want to write a little program to access PST files and - for example - extract all email content/attachments/contacts etc. to a physical folder? Now you can.
We use a little trick (in fact, this trick appears to be the only way to do what we want): We create a temporary profile (which is, intrinsically seen, nothing but a normal profile we delete once we are finished), attach our PST file, and access it subsequently as it has become nothing but our profile's new message store. Thanks to the incredible speed of 'native' Extended MAPI, you won't notice any delay caused by our profile operations at all. For demonstration purposes, our method returns just a string containing the subjects and senders of all emails located directly in the inbox folder. You may have to change the inbox folder's name (eg. 'Inbox', 'Posteingang' etc.) for the program to detect it correctly.
My main guidance for this code was the 'MAPI example code for getting folders and messages' by Lucian Wischik [5]. In fact, most of the code is just a C++/C# translation. On his page you can find much more good examples of what MAPI can do.
public string OpenMessageStoreFromFile(IntPtr _Handle, string _Filename)
{
Error hr;
IProfAdmin adm;
string tmpProfileName = "TemporaryProfileToAccessPstFile";
string inboxFolderName = "Posteingang";
// --------------------------------------------------------------------------
// PART ONE: We create a new profile and attach our PST file as message store
// First, we get the profile administration object...
hr = MAPI.AdminProfiles((MAPI.FLAGS) 0, out adm);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// ...and create a new profile
hr = adm.CreateProfile(tmpProfileName, null, _Handle, IProfAdmin.FLAGS.Default);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// Now we can get the administration object for our new profile
IMsgServiceAdmin msa;
hr = adm.AdminServices(tmpProfileName, null, _Handle, IProfAdmin.FLAGS.Default,
out msa);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// We create a new message service of type "MS PST" (a PST file) in this profile
hr = msa.CreateMsgService("MSPST MS", tmpProfileName, _Handle,
IMsgServiceAdmin.FLAGS.ServiceUIAllowed);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// We get the message services table
IMAPITable tblMessageService;
hr = msa.GetMsgServiceTable(IMsgServiceAdmin.FLAGS.Default,
out tblMessageService);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// And the the corresponding UID
Tags[] itags = new Tags[] {Tags.PR_SERVICE_UID, Tags.PR_DISPLAY_NAME};
tblMessageService.SetColumns(itags, IMAPITable.FLAGS.Default);
// In Mapi33, the C++ data type SRowSet has been implemented as rectangular array
MAPI33.MapiTypes.Value[,] rows;
hr = tblMessageService.QueryRows(1, IMAPITable.FLAGS.Default, out rows);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
tblMessageService.Dispose(); // Don't need it any longer
Guid msgSvcGuid = new Guid((byte[])((MapiBinary)rows[0,0]).Value);
// here we configure our message service to use OUR pst file
MAPI33.MapiTypes.Value[] ivals = new MAPI33.MapiTypes.Value[1];
ivals[0] = new MapiString(Tags.PR_PST_PATH, _Filename);
msa.ConfigureMsgService(msgSvcGuid, _Handle,
IMsgServiceAdmin.FLAGS.ServiceUIAllowed, ivals);
// Never forget to dispose all the no longer used MAPI stuff
// NOTE: We won't dispose IProfAdmin for now as we will need it later on to delete
// the temp profile
msa.Dispose(); // Don't need it any longer
// -------------------------------------------------------------------
// PART TWO: Ok. We have attached the PST to our temp profile and got its
// name. Let's see what we can do with it... At first we will look for our
// inbox folder
// So we get a list of all message stores in our profile
IMAPITable tblMsgStores;
hr = session.GetMsgStoresTable(0, out tblMsgStores);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// Define the cols we want to read
itags = new Tags[] {Tags.PR_ENTRYID, Tags.PR_DISPLAY_NAME};
tblMsgStores.SetColumns(itags, IMAPITable.FLAGS.Default);
// Then we simply take the first row (for we have only one message store,
// our PST)...
hr = tblMsgStores.QueryRows(1, 0, out rows);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
tblMsgStores.Dispose(); // Don't need it any longer
// Read its ENTRYID and DISPLAY_NAME
ENTRYID folderEid = null;
string name = "";
if (rows[0,0].GetType() == typeof(MapiBinary))
folderEid = new ENTRYID((byte[])((MapiBinary)rows[0,0]).Value);
if (rows[0,1].GetType() == typeof(MapiString))
name = ((MapiString)rows[0,1]).Value;
if (name.Length == 0 || folderEid == null) throw new Exception("Error PST props");
// Now we can open our message store
IMsgStore store;
hr = session.OpenMsgStore(_Handle, folderEid, Guid.Empty,
IMAPISession.FLAGS.Default, out store);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// We will query the EID of the root folder now
itags = new Tags[] {Tags.PR_IPM_SUBTREE_ENTRYID};
ivals = new MAPI33.MapiTypes.Value[] {new MapiBinary(Tags.PR_IPM_SUBTREE_ENTRYID,
new byte[0])};
hr = store.GetProps(itags, 0, out ivals);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
ENTRYID rootFldEid = new ENTRYID((byte[])((MapiBinary)ivals[0]).Value);
// We can open the root folder now...
MAPI.TYPE type;
MAPI33.IUnknown unkRootFolder = null;
hr = store.OpenEntry(rootFldEid, Guid.Empty, 0, out type, out unkRootFolder);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
if (type != MAPI.TYPE.Folder) throw new Exception("Error: Wrong type");
if (unkRootFolder == null) throw new Exception("Error: Expected folder is NULL");
IMAPIFolder fldRootFolder = (IMAPIFolder)unkRootFolder;
// ... and get a list of all contained objects
MAPI33.IUnknown unkRootFolderObjects = null;
hr = fldRootFolder.OpenProperty(Tags.PR_CONTAINER_HIERARCHY, IMAPITable.IID, 0, 0,
out unkRootFolderObjects);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
if (unkRootFolderObjects == null) throw new Exception("Error: Table is NULL");
IMAPITable tblRootFolderObjects = (IMAPITable)unkRootFolderObjects;
// Again, we define the cols we want to query
itags = new Tags[] {Tags.PR_ENTRYID, Tags.PR_DISPLAY_NAME, Tags.PR_SUBFOLDERS};
hr = tblRootFolderObjects.SetColumns(itags, 0);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// Perform the query
hr = tblRootFolderObjects.QueryRows(50, 0, out rows);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// Not disposing MAPI objects will cause an exception!
unkRootFolder.Dispose();
fldRootFolder.Dispose();
unkRootFolderObjects.Dispose();
tblRootFolderObjects.Dispose();
// Now we iterate through all containes objects to find the inbox folder,
// then we save the names of all contained items into a string and return it.
// There are several other ways to fetch default folders (like inbox, contacts
// etc.), but for this example, identification by name is way sufficient.
ENTRYID fldInboxEid = null;
for (int i=0; i <= rows.GetUpperBound(0); i++)
{
string aktName = ((MapiString)rows[i,1]).Value;
if (aktName.ToLower().Equals(inboxFolderName.ToLower()))
{
// We found our folder
fldInboxEid = new ENTRYID((byte[])((MapiBinary)rows[i,0]).Value);
break;
}
}
if (fldInboxEid == null) throw new Exception("Error: Couldn't find inbox!");
// -------------------------------------------------------------------
// PART THREE: We open our inbox folder and access the contained objects
// Open inbox folder
IUnknown unkInboxFolder;
hr = store.OpenEntry(fldInboxEid, Guid.Empty, 0, out type, out unkInboxFolder);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
if (type != MAPI.TYPE.Folder) throw new Exception("Error: Wrong type");
if (unkInboxFolder == null) throw new Exception("Error: Expected folder is NULL");
IMAPIFolder fldInboxFolder = (IMAPIFolder)unkInboxFolder;
// Get the content table
IMAPITable tblInboxFolderObjects;
hr = fldInboxFolder.GetContentsTable(0, out tblInboxFolderObjects);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// We just want to retrieve the subject and sender of each message
itags = new Tags[] {Tags.PR_SENDER_NAME, Tags.PR_SUBJECT};
hr = tblInboxFolderObjects.SetColumns(itags, 0);
// Perform query
hr = tblInboxFolderObjects.QueryRows(50, 0, out rows); // max. of 50 items
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// Not disposing MAPI objects will cause an exception!
unkInboxFolder.Dispose();
fldInboxFolder.Dispose();
tblInboxFolderObjects.Dispose();
store.Dispose();
// Now we can easily iterate through our results
string result = "";
for (int i=0; i <= rows.GetUpperBound(0); i++)
{
result += string.Format("Msg from \"{0}\": {1}\r\n",
((MapiString)rows[i,0]).Value, ((MapiString)rows[i,1]).Value);
}
// Ok, we have what we wanted - let's remove the temporary profile and finish
adm.DeleteProfile(tmpProfileName, 0);
adm.Dispose(); // Don't need it any longer
return result;
}
5 Conclusion
As you can see, Extended MAPI and C# is no contradiction at all. The advantage of native Extended MAPI in comparison to all other MAPI Interfaces are numerous. With its incredible performance and stability (compared to other interfaces) and with its extensive range of functionality, Extended MAPI in my opinion even compensates the disadvantage of significantly longer and (partly much) more complicated application development. C# implementation is nearly identical to C++, the only difference is the C# programmer's dependence on the Mapi33 library.
Though this library works absolutely flawless, there is a little drawback as - at this time - there is no source code available. Programmers who use Mapi33 will always depend on it, and on the programmer's not changing his mind and Redemption-like charging license fees for future versions of Mapi33.
6 References
[1] Strategic White Paper for MAPI, Microsoft
[2] Mapi33 developer's homepage
http://www.mapi33.freeservers.com/
[3] Google web search
http://www.google.com/
[4] Messaging Application Programming Interface (MAPI) Programmer's Reference, Microsoft
[5] MAPI developer websites:
OutlookCode.com
http://www.outlookcode.com/
Lucian Wischik's brilliant code
http://www.wischik.com/lu/programmer/mapi_utils.html
[6] MAPI forums:
Expert's Exchange
http://www.experts-exchange.com/
WROX Press Programmer's forum
http://p2p.wrox.com/
[7] Outlook Redemption COM component
http://www.dimastr.com/redemption/
Supported by Kreativwarenhandlung Grafik Design
If you want to refer to this content, please link to this page instead of copying it, as I might be adding some more source code examples later.
PLEASE NOTE
The following code samples might be obsolete. They might not work with current version of the mapi33 dll, though they will still work using the out-of-date (but quite well-working and free) version of the mapi33 dll included in the samples zip file.
To get up-to-date information about the mapi33 dll and to buy the most recent version, visit the mapi33 website at
http://www.mapi33.adexsolutions.com/
Extended MAPI with C#
A Basic Introduction to Extended MAPI Using C# and the Mapi33 Library.
Author: Jan Pichler (mail AT jan DOT to)
Last changed: 2004-05-20
Content
1 Abstract and Confession............................................................................................................................ 3
2 MAPI Architecture................................................................................................................................... 4
2.1 MAPI? What is that?........................................................................................................................ 4
2.2 The MAPI Family - CMC, CDO, Simple MAPI and Extended MAPI.................................................. 4
2.2.1 Common Messaging Calls (CMC)................................................................................................. 4
2.2.2 Collaboration Data Objects (CDO)................................................................................................ 4
2.2.3 Simple MAPI............................................................................................................................... 4
2.2.4 Extended MAPI........................................................................................................................... 4
2.2.5 Outlook Redemption..................................................................................................................... 5
2.3 The Concept of Extended MAPI....................................................................................................... 5
2.4 Sources of Documentation................................................................................................................ 5
3 The Mapi33 Library................................................................................................................................... 6
3.1 Introduction...................................................................................................................................... 6
3.2 Code Conversion Examples............................................................................................................... 6
4 C# Functional Code Samples...................................................................................................................... 7
4.1 Starting a MAPI Session................................................................................................................... 7
4.2 Closing a MAPI Session................................................................................................................... 7
4.3 Resolving a Recipient's Name into Name and Email Address.............................................................. 8
4.4 Selecting one or multiple Recipients from the MAPI Address Book..................................................... 9
4.5 Get a String Representation of Recipients Name/Email from Recipient EntryID.................................. 10
4.6 Get Recipient's EntryIDs from Address Book Entries........................................................................ 12
4.7 Tricky: Opening an Outlook PST File and Reading its Contents.......................................................... 12
5 Conclusion.............................................................................................................................................. 17
6 References............................................................................................................................................. 18
1 Abstract and Confession
Many programmers not familiar with MAPI use Simple MAPI or CDO to complete basic email- and contact management tasks. While these 'primitive' tools might carry satisfactory results in certain situations, they have many major disadvantages in comparison to Extended MAPI. While Simple MAPI and CDO are available as ready-to-use COM components, and are easily accessible with nearly every programming language, the Extended MAPI interface uses complex structures and lower-level techniques that imply the use of C-based languages or Delphi. Even in modern and powerful languages like C#, the few available structures make it highly difficult to implement Extended MAPI. The mapi33.dll is an Extended MAPI wrapper entirely written in managed C#. It provides simple access to the Extended MAPI API and enables a MAPI programming comfort similar to appropriate developments in C++. As C/C++ is the most frequently used language for MAPI access, nearly all of the existent documentation is C++ based. Therefore, the knowledge of translating C++ to C# plays an important role in C# based Extended MAPI programming.
This paper will try to give a brief overview of Extended MAPI architecture and explain relevant concepts, while it's main focus lies on illustrating possible implementation approaches with functional code samples.
I am not really familiar with C++. I don't really know a lot about MAPI and the Mapi33 library. I know that some deeply experienced MAPI programmers might roll on the floor when they read a newbie's ideas about how MAPI works. This paper is not intent to give a global, complete overview of the MAPI world, as this is beyond my possibilities. Much more, this paper is supposed to be a superficial introduction for all those unfamiliar with MAPI, a point to start off from and do further research into the desired direction. There might be a little smarter ways to do some things that my sample codes do, even though I think most of the code works very well.
2 MAPI Architecture
2.1 MAPI? What is that?
Microsoft introduced the Messaging Application Programming Interface (MAPI) because they felt "committed to making a reality of this vision of electronic messaging as the "central nervous system" for organizational communication" [1].
MAPI provides a layer functionality between applications and the underlying messaging systems. It allows applications to access different (MAPI enabled) messaging systems without having to support them explicitly. Various applications can interact with each other and various messaging systems - without even having to 'know' the counter part. Practical everyday life samples for this technique are many MS Outlook integrated fax and even SMS applications.
2.2 The MAPI Family - CMC, CDO, Simple MAPI and Extended MAPI
There is a basic difference between Extended MAPI and all other MAPI-derived interfaces: While MAPI 1.0 not only concludes but itself provides the whole range of MAPI functionality, all other MAPI derivates are just wrappers for certain aspects of just this MAPI 1.0 library. Because of that, the direct access of Extended MAPI significantly increases speed while offering the whole range of MAPI functionality, transparency and stability.
2.2.1 Common Messaging Calls (CMC)
The CMC API is an obsolete and therefore deprecated MAPI interface for C/C++ languages. It is built directly on top of the core MAPI subsystem, and limited to the following basic messaging tasks:
* Sending messages
* Retrieving messages
* Looking up addressing information
2.2.2 Collaboration Data Objects (CDO)
The CDO library (aka "OLE Messaging", "Active Messaging") is a COM wrapper for the MAPI library. It is accessible with all development languages that support automation. CDO is primarily designed for client activities. It does not provide the full MAPI functionality - but still more then Simple MAPI does:
* Log onto the messaging system with specific profiles or with anonymous authentication
* Compose messages, address and resolve recipients, send, receive, and read messages, add attachments, automate replies
* Manage calendars, create meetings and appointments
* Manage folders and messages within the information store.
* Manage Addresses, especially within the Personal Address Book (PAB)
2.2.3 Simple MAPI
Simple MAPI is a subset of 12 functions, which enable programmers to add basic messaging support to their applications. Its functionality includes
* Log onto the messaging system
* Compose new messages, add and resolve recipients, send messages
* Retrieve and read messages from the inbox
2.2.4 Extended MAPI
Extended MAPI (or "MAPI 1.0") comprehends the straight-forward access of the whole MAPI library. Originally, Microsoft designed this library for C/C++ implementation only. By and by, all of the required C++ headers were translated to Pascal and used within Delphi. Since the development of the Mapi33 library has reached production quality, C# is also absolutely suited for designing completely MAPI enabled applications.
2.2.5 Outlook Redemption
The Outlook Redemption COM component [7] is no member of the MAPI Family in general. Redemption is - in contrast to all other listed MAPI interfaces - not developed by Microsoft. As the need for an easy-to-use component that handles MAPI issues circumventing today's security warnings arose, Dmitry Streblechenko developed a commercial COM component that provides simplified access to a certain range of Extended MAPI functionality for nearly all development languages, including highest-level languages like Visual Basic Script (VBS) or Visual Basic for Applications (VBA).
Redemption more or less directly offers a small part of the real Extended MAPI objects an structure, but its main focus lies on extending the capabilities of simpler MAPI wrappers like CDO and Simple MAPI (eg. managing profiles, circumventing security warnings when browsing the contact list etc.). Hence, it is not directly comparable to native Extended MAPI.
2.3 The Concept of Extended MAPI
All MAPI interaction is session-based. Before performing any actions in the MAPI context, a session has to be established. At logon time, the system (or the user) has to specify the name of the MAPI profile to work in as well as required access credentials (username and password). Multiple applications are able to - but don't have to - share a single session. After MAPI interaction has finished, a MAPI logoff is performed which closes the active session if it is no longer used.
Most of the MAPI interaction is profile-based. All productive MAPI actions happen in the scope of a initially chosen profile. The only relevant actions that are performed outside a profile are administrative actions regarding the addition, modification and deletion of profiles and corresponding properties.
2.4 Sources of Documentation
There are multiple sources for MAPI documentation, code examples and public discussion of MAPI-related topics. These include
* The good old Google search [3]
* Microsoft's Strategic White Paper for MAPI [1]
* Microsoft's Messaging Application Programming Interface Programmer's Reference [4]
* Numerous MAPI developer websites [5]
* Public forums and discussion boards [6]
3 The Mapi33 Library
3.1 Introduction
The Mapi33 native Extended MAPI wrapper for .NET consists of a single file, MAPI33.dll. It can be used and redistributed freely without any conditions or contributions [2].
Referenced in a project, the library provides the namespace MAPI33 which includes all objects, structures, types, enumerations etc. necessary for accessing Extended MAPI. As the library is entirely written in managed C#, it doesn't require the programmer to use unsafe code blocks or access external APIs or COM components.
Though still under development, the library works very well. There is no adequate documentation available by now, but as most C++ code snippets can be intuitively converted to equivalent Mapi33/C# calls, this doesn't obstruct development at all.
3.2 Code Conversion Examples
The following code sample represents a very easy yet working syntax-only conversion between C++ and Mapi33/C#:
C++ code
IProfAdmin *iprofadmin; hr = pMAPIAdminProfiles(0, &iprofadmin); if (hr != S_OK) return; hr = iprofadmin->CreateProfile("Test", NULL, PtrToUlong(hwnd), 0); IMsgServiceAdmin *imsadmin; hr = iprofadmin->AdminServices("Test", NULL, PtrToUlong(hwnd), 0, &imsadmin); C# code IProfAdmin iprofadmin; hr = MAPI.AdminProfiles((MAPI.FLAGS) 0, out iprofadmin); if (hr != Error.Success) return; hr = iprofadmin.CreateProfile("Test", null, _Handle, 0); IMsgServiceAdmin imsadmin; hr = adm.AdminServices(tmpProfileName, null, _Handle, 0, out imsadmin); This code sample demonstrates a conversion which presumes some basic knowledge about C++ and MAPI data types: C++ code SizedSPropTagArray(2, mscols) = {2, {PR_SERVICE_UID, PR_DISPLAY_NAME}}; mstable->SetColumns((SPropTagArray*)&mscols, 0); SRowSet *msrows; hr = mstable->QueryRows(1, 0, &msrows); mstable->Release(); C# code Tags[] mscols = new Tags[] {Tags.PR_SERVICE_UID, Tags.PR_DISPLAY_NAME}; tblMessageService.SetColumns(mscols, 0); MAPI33.MapiTypes.Value[,] msrows; hr = tblMessageService.QueryRows(1, 0, out msrows); mstable.Dispose();
4 C# Functional Code Samples
The following examples show some common MAPI tasks.
DOWNLOAD: You may download a fully functional (MS Visual Studio 2003) implementation of all listed sample codes as well as a small test application including the free MAPI33 DLL here.
All methods except the first one presume an established MAPI connection and an active MAPI session (stored in the instance variable session).
4.1 Starting a MAPI Session
The following code starts a MAPI session, which is required for nearly every MAPI operation:
Error hr; IMAPISession session; // Initialise MAPI interface driver hr = MAPI.Initialize(null); // Logon to the MAPI session with the following flags: // MAPI.FLAGS.Extended Use Extended MAPI interface // MAPI.FLAGS.LogonUI Show a user dialog if additional credentials required // MAPI.FLAGS.NoMail Do not check for new mail etc. // MAPI.FLAGS.USEDEFAULT Try to use the default profile if possible // There are several other flags, see the MAPI33.MAPI.FLAGS enumeration for details // // This call initialises a MAPI session without logon credentials. As all MAPI // methods, this method does not return the IMAPISession object itself but a Error // value specifying the execution result of the call. The result is either // Error.Success or every other element of the Error enumeration. The object of // the created session is passed as out parameter (out session). hr = MAPI.LogonEx(IntPtr.Zero, null, null, MAPI.FLAGS.Extended | MAPI.FLAGS.LogonUI | MAPI.FLAGS.NoMail | MAPI.FLAGS.USEDEFAULT, out session); // Returns true if successful return (hr == Error.Success); 4.2 Closing a MAPI Session This code closes a MAPI session: session.Logoff(IntPtr.Zero, 0); // You have to dispose every MAPI object explicitly, otherwise an exception // will be thrown at runtime. session.Dispose(); // Finally, we do uninitialise the MAPI driver. MAPI.Uninitialize();
4.3 Resolving a Recipient's Name into Name and Email Address
This method resolves the given part of a recipient's name or email address. The MAPI Library checks the underlying IAddrBook and returns all matches. The method ResolveName() returns Error.Success if there is exactly one match, Error.NotFound if there are no matches and Error.AmbiguousRecip if there are multiple. The parent window's handle is needed in all sample codes for showing dialogs modally.
public string[] ResolveName(string _Name, IntPtr _Handle) { Error hr; IAddrBook ab = null; string[][] txtRecipients = new string[1][]; txtRecipients[0] = new string[0]; try { ADRENTRY[] abe; // We open the standard address book hr = session.OpenAddressBook(_Handle, Guid.Empty, 0, out ab); if (hr != Error.Success) { // Error opening the address book ab.Dispose(); // we MUST dispose stuff! return null; } // We tell the method that we have a fragment of the PR_DISPLAY_NAME // property of a recipient. abe = new ADRENTRY[1]; abe[0] = new ADRENTRY(1); abe[0].PropVals[0] = new MapiString(Tags.PR_DISPLAY_NAME, _Name); // We perform our first call _without_ showing any dialogue, for that // if our lookup fails and we have no matches, there will be shown no // "Recipient unknown" dialog. If there is more than one match, we will // do another resolve-call that will bring up a dialog to select the match. hr = ab.ResolveName(_Handle, 0, null, ref abe); if (hr == Error.AmbiguousRecip) { // We have more than one match - now we will call the method again // and tell it to ask for the user to choose a recipient. abe = new ADRENTRY[1]; abe[0] = new ADRENTRY(1); abe[0].PropVals[0] = new MapiString(Tags.PR_DISPLAY_NAME, _Name); // Next try, this time with dialog hr = ab.ResolveName(_Handle, IAddrBook.FLAGS.Dialog, null, ref abe); } if (hr == Error.Success) { // We have a single match. // Get a suitable string representation of the selected recipient. // Look at the used methods to see details about what happens. txtRecipients = NormalizeAddressEnties(GetEntryIdsFromABEntries(abe)); } ab.Dispose(); // We MUST dispose stuff! // If there are no matches (Error.NoMatch), array is still empty. // Otherwise, we will return the match. return txtRecipients[0]; } catch (Exception e) { // An exception has been thrown - return null ab.Dispose(); // We MUST dispose stuff! return null; } }
4.4 Selecting one or multiple Recipients from the MAPI Address Book
This code shows the standard "Select Recipient" dialog and lets the user chose one or multiple recipients.
public string[][] GetMapiRecipients(IntPtr _Handle) { Error hr; IAddrBook ab = null; string[][] txtRecipients = null; try { // Open address book hr = session.OpenAddressBook(_Handle, Guid.Empty, 0, out ab); ADRENTRY[] abe = new ADRENTRY[0]; ADRPARM abp = new ADRPARM(); abp.Caption = "Choose your recipient(s)..."; // Caption of the dialog abp.Flags = 0x21; abp.DestFields = 1; // How many categories (to/cc/bcc) do we have? abp.DestWellsTitle = "Add following recipients:"; // Some text... abp.DestComps = new ADRPARM.DESTCOMPS[] {ADRPARM.DESTCOMPS.Orig}; abp.DestTitles = new String[]{"TO:"}; // Title for our "TO" category // Show dialog hr = ab.Address(_Handle, ref abp, ref abe); // Get a suitable string representation of the selected recipient. // Look at the used methods to see details about what happens. txtRecipients = NormalizeAddressEnties(GetEntryIdsFromABEntries(abe)); // Important - don't forget! ab.Dispose(); return txtRecipients; } catch (Exception e) { ab.Dispose(); return null; } }
4.5 Get a String Representation of Recipients Name/Email from Recipient EntryID
This method returns the full name and email string of each passed ENTRYID in a string[2]. It could - for example - return a string[] {"John Doe", "john.doe@microsaft.at"}.
private string[][] NormalizeAddressEnties(ENTRYID[] eids) { Error hr; IAddrBook ab = null; IUnknown unknown = null; IMailUser mu = null; ArrayList resolvedRcps = new ArrayList(); try { // We open our standard address book.. hr = session.OpenAddressBook(IntPtr.Zero, Guid.Empty, 0, out ab); if (hr != Error.Success) { ab.Dispose(); return new string[0][]; } for (int i=0; i < eids.Length; i++) { if (eids[i] != null) { ENTRYID ei = eids[i]; string[] entry = new string[2]; MAPI.TYPE retType; // Here we open the recipient object by it's ENTRYID hr = ab.OpenEntry(ei, Guid.Empty,IAddrBook.FLAGS.BestAccess, out retType, out unknown) ; if (hr == Error.Success) { if (unknown != null) { // Is our object really an individual recipient (no group etc.)? if (retType == MAPI.TYPE.IndividualRecipient) { mu = (IMailUser)unknown; // Now we query the following properties: Tags[] itags = new Tags[] {Tags.PR_GIVEN_NAME, Tags.PR_SURNAME, Tags.PR_SMTP_ADDRESS, Tags.PR_EMAIL_ADDRESS}; MAPI33.MapiTypes.Value[] ivals = new MAPI33.MapiTypes.Value[0]; // Now perform query hr = mu.GetProps(itags, IMAPIProp.FLAGS.Default, out ivals) ; // Parse all attributes // NOTE: We check all those PR_SMTP_ADDRESS_A, PR_SMTP_ADDRESS_W // PR_EMAIL_ADDRESS_A, PR_EMAIL_ADDRESS_W etc. tags because we // don't really know what our folder will return. I experienced // different behaviors of Outlook in internet mail mode, Outlook // in workgroup mode, Outlook in Exchange domain etc. ... // I check for a containing "@" because I even got some Exchange // addresses (in /DOM=domain/USER=john.doe -style) returned! // There might be more serious ways to do this, anyway... if (ivals.Length > 0) { for (int k=0; k < ivals.Length; k++) { // HACK: We use ANY tag just to know that it has not been // set yet. PR_DEBUG seems to be a good choice... Tags attrTag = Tags.PR_DEBUG; string attrVal = ""; if (ivals[k] .GetType() == typeof(MapiString)) { attrTag = ((MapiString)ivals[k]).PropTag; attrVal = ((MapiString)ivals[k]).Value; } if (ivals[k].GetType() == typeof(MapiUnicode)) { attrTag = ((MapiUnicode)ivals[k]).PropTag; attrVal = ((MapiUnicode)ivals[k]).Value; } if (attrTag != Tags.PR_DEBUG && attrVal.Length > 0) { switch (attrTag) { case Tags.PR_SURNAME_A: case Tags.PR_SURNAME_W: entry[0] = entry[0] + attrVal; break; case Tags.PR_GIVEN_NAME_A: case Tags.PR_GIVEN_NAME_W: entry[0] = attrVal +" "+ entry[0]; break; case Tags.PR_SMTP_ADDRESS_A: case Tags.PR_SMTP_ADDRESS_W: case Tags.PR_EMAIL_ADDRESS_A: case Tags.PR_EMAIL_ADDRESS_W: if (attrVal.IndexOf("@") != -1 && entry[1] == null) entry[1] = attrVal; break; } } } } mu.Dispose(); mu = null; } } } // Do we have an email address for this recipient? if (entry[1] != null && entry[1].Length > 0) resolvedRcps.Add(entry); unknown.Dispose(); unknown = null; } } } catch { // Well, no success... } // Always dispose... if (ab != null) ab.Dispose(); if (unknown != null) unknown.Dispose(); if (mu != null) mu.Dispose(); return (string[][]) resolvedRcps.ToArray(typeof(string[])); }
4.6 Get Recipient's EntryIDs from Address Book Entries
The ResolveName() and Address() methods each return a bunch of the recipients properties. This function does nothing but isolate and return the ENTRYID of each given Address Book Entry (equals 1 recipient).
private ENTRYID[] GetEntryIdsFromABEntries(ADRENTRY[] abe)
{
ENTRYID[] entries = new ENTRYID[abe.Length];
// Iterate through Address Book Entries
for (int i=0; i < abe.Length; i++)
{
ADRENTRY aktEntry = abe[i];
byte[] eidBytes = null;
// Iterate through current ADRENTRY's propVals, look for ENTRYID...
for (int j=0; j < aktEntry.PropVals.Length; j++)
{
if (aktEntry.PropVals[j].PropTag == Tags.PR_MEMBER_ENTRYID)
{
// Yes, we got it...
eidBytes = (byte[])((MapiBinary)aktEntry.PropVals[j]).Value;
break;
}
}
// Did we find an ENTRYID?
if (eidBytes != null)
{
// Add it to our array - basically that's it.
entries[i] = new ENTRYID(eidBytes);
}
else
{
entries[i] = null;
}
}
return entries;
}
4.7 Tricky: Opening an Outlook PST File and Reading its Contents
Did you imagine you could do that? Who did not always want to write a little program to access PST files and - for example - extract all email content/attachments/contacts etc. to a physical folder? Now you can.
We use a little trick (in fact, this trick appears to be the only way to do what we want): We create a temporary profile (which is, intrinsically seen, nothing but a normal profile we delete once we are finished), attach our PST file, and access it subsequently as it has become nothing but our profile's new message store. Thanks to the incredible speed of 'native' Extended MAPI, you won't notice any delay caused by our profile operations at all. For demonstration purposes, our method returns just a string containing the subjects and senders of all emails located directly in the inbox folder. You may have to change the inbox folder's name (eg. 'Inbox', 'Posteingang' etc.) for the program to detect it correctly.
My main guidance for this code was the 'MAPI example code for getting folders and messages' by Lucian Wischik [5]. In fact, most of the code is just a C++/C# translation. On his page you can find much more good examples of what MAPI can do.
public string OpenMessageStoreFromFile(IntPtr _Handle, string _Filename)
{
Error hr;
IProfAdmin adm;
string tmpProfileName = "TemporaryProfileToAccessPstFile";
string inboxFolderName = "Posteingang";
// --------------------------------------------------------------------------
// PART ONE: We create a new profile and attach our PST file as message store
// First, we get the profile administration object...
hr = MAPI.AdminProfiles((MAPI.FLAGS) 0, out adm);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// ...and create a new profile
hr = adm.CreateProfile(tmpProfileName, null, _Handle, IProfAdmin.FLAGS.Default);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// Now we can get the administration object for our new profile
IMsgServiceAdmin msa;
hr = adm.AdminServices(tmpProfileName, null, _Handle, IProfAdmin.FLAGS.Default,
out msa);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// We create a new message service of type "MS PST" (a PST file) in this profile
hr = msa.CreateMsgService("MSPST MS", tmpProfileName, _Handle,
IMsgServiceAdmin.FLAGS.ServiceUIAllowed);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// We get the message services table
IMAPITable tblMessageService;
hr = msa.GetMsgServiceTable(IMsgServiceAdmin.FLAGS.Default,
out tblMessageService);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// And the the corresponding UID
Tags[] itags = new Tags[] {Tags.PR_SERVICE_UID, Tags.PR_DISPLAY_NAME};
tblMessageService.SetColumns(itags, IMAPITable.FLAGS.Default);
// In Mapi33, the C++ data type SRowSet has been implemented as rectangular array
MAPI33.MapiTypes.Value[,] rows;
hr = tblMessageService.QueryRows(1, IMAPITable.FLAGS.Default, out rows);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
tblMessageService.Dispose(); // Don't need it any longer
Guid msgSvcGuid = new Guid((byte[])((MapiBinary)rows[0,0]).Value);
// here we configure our message service to use OUR pst file
MAPI33.MapiTypes.Value[] ivals = new MAPI33.MapiTypes.Value[1];
ivals[0] = new MapiString(Tags.PR_PST_PATH, _Filename);
msa.ConfigureMsgService(msgSvcGuid, _Handle,
IMsgServiceAdmin.FLAGS.ServiceUIAllowed, ivals);
// Never forget to dispose all the no longer used MAPI stuff
// NOTE: We won't dispose IProfAdmin for now as we will need it later on to delete
// the temp profile
msa.Dispose(); // Don't need it any longer
// -------------------------------------------------------------------
// PART TWO: Ok. We have attached the PST to our temp profile and got its
// name. Let's see what we can do with it... At first we will look for our
// inbox folder
// So we get a list of all message stores in our profile
IMAPITable tblMsgStores;
hr = session.GetMsgStoresTable(0, out tblMsgStores);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// Define the cols we want to read
itags = new Tags[] {Tags.PR_ENTRYID, Tags.PR_DISPLAY_NAME};
tblMsgStores.SetColumns(itags, IMAPITable.FLAGS.Default);
// Then we simply take the first row (for we have only one message store,
// our PST)...
hr = tblMsgStores.QueryRows(1, 0, out rows);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
tblMsgStores.Dispose(); // Don't need it any longer
// Read its ENTRYID and DISPLAY_NAME
ENTRYID folderEid = null;
string name = "";
if (rows[0,0].GetType() == typeof(MapiBinary))
folderEid = new ENTRYID((byte[])((MapiBinary)rows[0,0]).Value);
if (rows[0,1].GetType() == typeof(MapiString))
name = ((MapiString)rows[0,1]).Value;
if (name.Length == 0 || folderEid == null) throw new Exception("Error PST props");
// Now we can open our message store
IMsgStore store;
hr = session.OpenMsgStore(_Handle, folderEid, Guid.Empty,
IMAPISession.FLAGS.Default, out store);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// We will query the EID of the root folder now
itags = new Tags[] {Tags.PR_IPM_SUBTREE_ENTRYID};
ivals = new MAPI33.MapiTypes.Value[] {new MapiBinary(Tags.PR_IPM_SUBTREE_ENTRYID,
new byte[0])};
hr = store.GetProps(itags, 0, out ivals);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
ENTRYID rootFldEid = new ENTRYID((byte[])((MapiBinary)ivals[0]).Value);
// We can open the root folder now...
MAPI.TYPE type;
MAPI33.IUnknown unkRootFolder = null;
hr = store.OpenEntry(rootFldEid, Guid.Empty, 0, out type, out unkRootFolder);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
if (type != MAPI.TYPE.Folder) throw new Exception("Error: Wrong type");
if (unkRootFolder == null) throw new Exception("Error: Expected folder is NULL");
IMAPIFolder fldRootFolder = (IMAPIFolder)unkRootFolder;
// ... and get a list of all contained objects
MAPI33.IUnknown unkRootFolderObjects = null;
hr = fldRootFolder.OpenProperty(Tags.PR_CONTAINER_HIERARCHY, IMAPITable.IID, 0, 0,
out unkRootFolderObjects);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
if (unkRootFolderObjects == null) throw new Exception("Error: Table is NULL");
IMAPITable tblRootFolderObjects = (IMAPITable)unkRootFolderObjects;
// Again, we define the cols we want to query
itags = new Tags[] {Tags.PR_ENTRYID, Tags.PR_DISPLAY_NAME, Tags.PR_SUBFOLDERS};
hr = tblRootFolderObjects.SetColumns(itags, 0);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// Perform the query
hr = tblRootFolderObjects.QueryRows(50, 0, out rows);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// Not disposing MAPI objects will cause an exception!
unkRootFolder.Dispose();
fldRootFolder.Dispose();
unkRootFolderObjects.Dispose();
tblRootFolderObjects.Dispose();
// Now we iterate through all containes objects to find the inbox folder,
// then we save the names of all contained items into a string and return it.
// There are several other ways to fetch default folders (like inbox, contacts
// etc.), but for this example, identification by name is way sufficient.
ENTRYID fldInboxEid = null;
for (int i=0; i <= rows.GetUpperBound(0); i++)
{
string aktName = ((MapiString)rows[i,1]).Value;
if (aktName.ToLower().Equals(inboxFolderName.ToLower()))
{
// We found our folder
fldInboxEid = new ENTRYID((byte[])((MapiBinary)rows[i,0]).Value);
break;
}
}
if (fldInboxEid == null) throw new Exception("Error: Couldn't find inbox!");
// -------------------------------------------------------------------
// PART THREE: We open our inbox folder and access the contained objects
// Open inbox folder
IUnknown unkInboxFolder;
hr = store.OpenEntry(fldInboxEid, Guid.Empty, 0, out type, out unkInboxFolder);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
if (type != MAPI.TYPE.Folder) throw new Exception("Error: Wrong type");
if (unkInboxFolder == null) throw new Exception("Error: Expected folder is NULL");
IMAPIFolder fldInboxFolder = (IMAPIFolder)unkInboxFolder;
// Get the content table
IMAPITable tblInboxFolderObjects;
hr = fldInboxFolder.GetContentsTable(0, out tblInboxFolderObjects);
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// We just want to retrieve the subject and sender of each message
itags = new Tags[] {Tags.PR_SENDER_NAME, Tags.PR_SUBJECT};
hr = tblInboxFolderObjects.SetColumns(itags, 0);
// Perform query
hr = tblInboxFolderObjects.QueryRows(50, 0, out rows); // max. of 50 items
if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());
// Not disposing MAPI objects will cause an exception!
unkInboxFolder.Dispose();
fldInboxFolder.Dispose();
tblInboxFolderObjects.Dispose();
store.Dispose();
// Now we can easily iterate through our results
string result = "";
for (int i=0; i <= rows.GetUpperBound(0); i++)
{
result += string.Format("Msg from \"{0}\": {1}\r\n",
((MapiString)rows[i,0]).Value, ((MapiString)rows[i,1]).Value);
}
// Ok, we have what we wanted - let's remove the temporary profile and finish
adm.DeleteProfile(tmpProfileName, 0);
adm.Dispose(); // Don't need it any longer
return result;
}
5 Conclusion
As you can see, Extended MAPI and C# is no contradiction at all. The advantage of native Extended MAPI in comparison to all other MAPI Interfaces are numerous. With its incredible performance and stability (compared to other interfaces) and with its extensive range of functionality, Extended MAPI in my opinion even compensates the disadvantage of significantly longer and (partly much) more complicated application development. C# implementation is nearly identical to C++, the only difference is the C# programmer's dependence on the Mapi33 library.
Though this library works absolutely flawless, there is a little drawback as - at this time - there is no source code available. Programmers who use Mapi33 will always depend on it, and on the programmer's not changing his mind and Redemption-like charging license fees for future versions of Mapi33.
6 References
[1] Strategic White Paper for MAPI, Microsoft
[2] Mapi33 developer's homepage
http://www.mapi33.freeservers.com/
[3] Google web search
http://www.google.com/
[4] Messaging Application Programming Interface (MAPI) Programmer's Reference, Microsoft
[5] MAPI developer websites:
OutlookCode.com
http://www.outlookcode.com/
Lucian Wischik's brilliant code
http://www.wischik.com/lu/programmer/mapi_utils.html
[6] MAPI forums:
Expert's Exchange
http://www.experts-exchange.com/
WROX Press Programmer's forum
http://p2p.wrox.com/
[7] Outlook Redemption COM component
http://www.dimastr.com/redemption/
Supported by Kreativwarenhandlung Grafik Design
相关推荐
"Demo - Extended MAPI in Delphi 2010" 这个标题和描述指向了一个特定的示例项目,它展示了如何在Delphi 2010编程环境中使用Extended MAPI(扩展的MAPI)技术。Extended MAPI是Microsoft的一项技术,允许应用程序...
在.NET Compact Framework环境下,开发针对Windows Mobile等移动设备的软件时,也需要处理邮件功能,这就需要用到MAPI的C#封装。 本文将详细介绍如何在.NET Compact Framework中使用MAPI .NET CF,以及提供的文件...
Message send by mapi with ADO
The Extended MAPI is one of the first COM technologies provided by Microsoft. Extended MAPI is core API for Microsoft Exchange Server, Microsoft Outlook, CDO, messaging, etc.
本文将深入探讨如何在C#环境中使用`IMAPI`和`MAPI`来发送带有附件的电子邮件。 首先,让我们了解`IMAPI`和`MAPI`。`MAPI`是一种标准的API,允许开发者创建能够与各种邮件系统交互的应用程序。它提供了丰富的功能,...
With Easy MAPI you can use the Extended MAPI, Simple MAPI and WAB interfaces in your Delphi application(s) to get access to this data. Since the Easy MAPI components also implement the Extended MAPI...
HRESULT hr = MAPILogonEx(NULL, NULL, NULL, MAPI_EXTENDED | MAPI_NEW_SESSION, &session); if (SUCCEEDED(hr)) { // 设置邮件信息 LPMESSAGE msg = NULL; hr = session->ComposeMessage(NULL, NULL, 0, ...
根据提供的标题、描述和部分内容,本文将重点解析与MAPI(Messaging Application Programming Interface)相关的知识点。MAPI是一种由Microsoft开发的API集,主要用于与电子邮件客户端和服务器进行交互,特别是...
"使用 MAPI 实现邮件发送" 一、简述 使用 MAPI(Messaging Application Programming Interface)可以实现邮件发送功能。在本文中,我们将使用 Simple MAPI,这是一个子集,提供了一组易于使用的函数和相关数据结构...
MAPI接口编程技术 MAPI(Messaging Application Program Interface)是Microsoft提供的一种电子邮件程序接口,允许开发者使用Outlook Express系统进行电子邮件的发送和接收。该接口提供了一些基本的电子邮件处理...
Use this free .NET component to access low-level features provided by MAPI in Windows Forms applications as well as in .NET-based Outlook COM add-ins. The component is a mixture of managed and ...
**MAPI(Messaging Application Programming Interface)**是一种在Windows操作系统中广泛使用的邮件和消息传递应用程序接口。它允许开发者创建能够与各种邮件系统交互的应用程序,包括发送、接收、存储和管理邮件。...
### 使用MAPI控件实现邮件发送的技术要点 在IT领域,特别是软件开发中,通过编程技术来实现自动化邮件发送是一项非常实用且常见的功能。本文将详细介绍如何利用Microsoft Application Programming Interface (MAPI)...
**MAPI(Messaging Application Programming Interface)**是一种在Windows操作系统中用于构建邮件应用程序的接口,它允许程序员通过系统级服务直接与邮件服务器通信。MAPI提供了一套标准的API,使得开发者能够实现...
标题 "mapi.zip_mapi" 暗示了这个压缩包与MAPI(Messaging Application Programming Interface)有关,它是一个在Microsoft Windows操作系统上广泛使用的API,用于处理电子邮件和消息系统。MAPI允许应用程序发送和...
标题中的“Internet与WEB服务源代码_simple_mapi_demo”指的是一个关于互联网技术和Web服务的源码示例,其中“simple_mapi_demo”可能是这个示例的特定部分,它可能涉及了简单邮件传输协议(MAPIMail)的实现。...
2. **设置MAPI控件属性**:在设计视图中,对添加的MAPI控件设置属性,如`MAPI.Logon`用于登录邮件系统,`MAPI.Session`获取邮件会话,`MAPI.Recipients`设置收件人,`MAPI.Subject`设置邮件主题,`MAPI.Body`设置...
logonSuccess = MAPILogonEx(0, "", "", MAPI_LOGON_UI Or MAPI_EXTENDED, session, 0&) ``` 4. 创建邮件:使用`MAPIMessagesOpen`函数创建一个新的邮件对象。这将允许我们设置邮件的主题、收件人、抄送人和正文。 ...
**MAPI(Messaging Application Programming Interface)是微软提供的一种标准接口,用于在应用程序中实现电子邮件的发送和接收。这种接口允许开发者直接与邮件服务器通信,从而创建具有邮件功能的应用程序。** 在...
MFCMAPI是一款基于Microsoft Foundation Class (MFC) 库实现的MAPI(Messaging Application Programming Interface)参考应用程序。它主要用于连接到Exchange服务器,通过MAPI接口进行邮件、日历、联系人等信息的...