Platform SDK: Exchange 2000 Server |
[This is preliminary documentation and subject to change.]
This topic covers the most frequently asked questions about programming with Microsoft® Exchange 2000 Server.
Why can't I write text to the Stream object of a newly added BodyPart object?
How do I set message headers on Message or BodyPart objects when there aren't properties for them?
How can CDO objects expose multiple dual interfaces?
How do I get all properties for all items using SELECT *?
What type of URL should I use: file or http?
What's changed in the security model for the Web Store?
Although both the IBodyPart.BodyParts and IMessage.Attachments properties return IBodyParts interfaces on collection objects, the objects contained within each collection normally differ. For MIME encoded messages, a good rule of thumb is that all body parts with content-disposition set to "attachment" are returned in the IMessage.Attachments collection. Depending upon the complexity of the MIME structure, these body parts can exist deep in the body part hierarchy. The IBodyPart.BodyParts collection, on the other hand, contains only that object's immediate child BodyPart objects.
For example, a common MIME structure is expressed in the diagram below as a BodyPart hierarchy below the Message object, labeled A. Each object is designated with a letter and appropriate mail header fields are listed in each box representing an object:
A <--- content-type="multipart/mixed" B <--- content-type="multipart/related" C <--- content-type="text/plain" D <--- content-type="text/html" E <--- content-disposition="attachment" F <--- content-disposition="attachment"
Object A is the Message object, the root of the hierarchy. All others are BodyPart objects. The IMessage.Attachments collection for the Message object would contain BodyPart objects E and F. For each object, its IBodyPart.BodyParts collections would contain only the direct descendants of the object in the hierarchy (remember that instances of the Message COM class expose the IBodyPart interface as well as instances of the BodyPart COM class). For object A (the Message object), this would include objects B, E and F. For object B (a BodyPart object), the collection would contain objects C and D. Objects E and F have empty BodyParts collections as they do not have descendants.
For UUENCODE formatted messages, the hierarchy is only one level deep, as all body parts are necessarily deemed attachments. In this case, only the Message object has descendents, and the IMessage.Attachments and IBodyPart.BodyParts collections are the same. For example,
A B <-- UUENCODE attachment C <-- ditto D <-- ditto
ADO Stream objects are always one of two types: text or binary. When you first add a BodyPart object to a Message object's body part hierarchy, the content media type defaults to application/octet-stream, rendering a binary decoded content stream (this only applies to the decoded content stream, as the encoded content stream is by definition text). The _Stream.WriteText method fails because this method is only defined to function on streams that are of the text type.
To use the WriteText method, you have to set stream type to text. You can do this in one of two of the following ways:
' EXAMPLE for Step 1 ' Assume that we have a BodyPart object reference in the variable Bp1, ' and that we wish to add a body part that will hold a text-based ' content stream. Dim Bp2 as IBodyPart Dim Strm as ADODB.Stream Set Bp2 = Bp1.AddBodyPart Bp2.ContentMediaType = "text/plain" Set Strm = Bp2.GetDecodedContentStream Strm.WriteText "this is the text content here" ' Don't forget to flush after you've made your deposit Strm.Flush ' EXAMPLE for 2 Dim Bp3 as IBodyPart Set Bp3 = Bp1.AddBodyPart Set Strm = Bp3.GetDecodedContentStream Strm.Type = adTypeText Strm.WriteText "<P>this is some <I>more</I> content</P>" Strm.Flush Bp3.ContentMediaType = "text/html" ' ...
All message and body part headers can be accessed through an appropriate Fields collection, for example, IMessage.Fields, or IBodyPart.Fields. Each mail header resides in the urn:schemas:mailheader namespace. For example, the Message-ID mail header is identified with the full name urn:schemas:mailheader:message-id.
The provided fields within the urn:schemas:mailheader namespace are the most common headers defined by the internet community. However, you can add other headers not a part of this default set by adding it to the Fields collection within the urn:schemas:mailheader namespace. For example, 'urn:schemas:mailheader:Some-Header.'
Dim Msg as New CDO.Message Dim Flds as ADODB.Fields Set Flds = Msg.Fields With Flds .Append("urn:schemas:mailheader:message-id") = id .Append("urn:schemas:mailheader:custom") = "some value" .Update End With
The short answer is: they can't. By definition, a dual interface is one that both extends the IDispatch interface, and provides access to defined methods both through the vtble and through the seven methods defined by the IDispatch interface (hence the name dual). Because COM uses proxy/stubs to marshal arguments between separate apartments, objects can only really expose one IDispatch interface and you can't trick COM proxy/stubs by returning different addresses depending upon which interface you request IDispatch from (readers interested in knowing the details of this rule are directed to the section marked Advanced below). Therefore, there's one and only one IDispatch interface per object.
This restriction is no problem for languages such as Microsoft® Visual Basic® and Microsoft® Visual J++® that support direct binding to OLE Automation compatible interfaces. All CDO interfaces use these types, so you can navigate to and use each in the standard way for that language. For example, the CDO Message object exposes the IMessage, IDataSource, and IBodyPart dual interfaces. In Visual Basic, you first add references to the ADODB and CDO type libraries, then type each interface variable using the types in these type libraries, and finally use the Set keyword to navigate to each interface. With Microsoft® Visual J++®, use the jactivex tool to create necessary Microsoft virtual machine-compatible Java packages, and then import these packages into their .java source file(s). Examples of each procedure are given in the following examples:
Dim iMsg as CDO.Message Dim iDsrc as CDO.IDataSource Dim iBp as CDO.IBodyPart Dim iDisp as Object Set iBp = New CDO.Message ' Have IBodyPart Set iDsrc = iBp ' Have IDataSource Set iMsg = iBp ' Have IMessage Set iDisp = iBp ' Have IDispatch
import cdo.*; class Main { public static void main( String args[] ) { IMessage iMsg = null; IDataSource iDsrc = null; IBodyPart iBp = null; Object iDisp = null; iBp = (IBodyPart) new Message(); // IBodyPart iDsrc = (IDataSource) iBp; // IDataSource iMsg = (IMessage) iBp; // IMessage iDisp = (Object) iBp; // IDispatch // ... } }
In both cases, we finish with references to all three CDO interfaces and a reference to the IDispatch interface each on the same object.
The situation is different for scripting languages such as Microsoft® Visual Basic® Scripting Edition (VBScript), and Microsoft® JScript™. With these languages, all interactions with COM objects occur through the object's IDispatch interface. Since each object can only have one IDispatch interface, we need separate objects with different implementations of IDispatch, not separate interfaces. Furthermore, we need a way for these languages to access these other objects. To facilitate this, many CDO interfaces define properties and the GetInterface method that return other objects (rather than interfaces) exposing the appropriate IDispatch implementation. Yet the illusion of "interface navigation" is produced because essentially, it appears to the scriptwriter that the interface itself is being returned. The following code demonstrates this illusion using the CDO Message object:
Option Explicit Dim iMsg Dim iDsrc Dim iBp Set iMsg = CreateObject("CDO.Message") ' IMessage (IDispatch) Set iDsrc = iMsg.DataSource ' IDataSource (IDispatch) Set iDsrc = iMsg.GetInterface("IDataSource") ' Again IDataSource (IDispatch) Set iBp = iMsg.BodyPart ' IBodyPart (IDispatch) Set iBp = iMsg.GetInterface("IBodyPart") ' Again IBodyPart (IDispatch)
var iMsg = null; var iBp = null; var iDsrc = null; iMsg = New ActiveXObject("CDO.Message"); // IMessage (IDispatch) iBp = iMsg.BodyPart; // IBodyPart (IDispatch) iBp = iMsg.GetInterface("IBodyPart"); // Again IBodyPart (IDispatch) iDsrc = iMsg.DataSource; // IDataSource (IDispatch) iDsrc = iMsg.GetInterface("IDataSource"); // Again IDataSource (IDispatch)
In each case, the script actually interacts with separate objects (object identities really), each exposing an appropriate implementation of IDispatch; no interface navigation per se is occurring. Instead, object navigation is occurring. The scriptwriter need not be concerned with such details however; the CDO objects handle all of the details internally. Most importantly, the CDO object behaves the same irrespective of which object is used.
If you write code that depends upon object identity (the physical address of the IUnknown interface), you should be aware of the behavior outlined above. If you use the interface properties such as IMessage::get_DataSource or IMessage::get_BodyPart, or you use the GetInterface method to retrieve interfaces, you're actually getting a different object identity. Consider the following code:
#include <iostream.h> #include "cdoex.h" #include "cdoex_i.c" void main() { CoInitialize(NULL); IMessage* pMsg = NULL; IDataSource* pDsrc = NULL; IUnknown* pUnk = NULL; IUnknown* pUnk2 = NULL; /* ** Create an instance of the Message CoClass */ CoCreateInstance(CLSID_Message, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pUnk); /* ** have IUnknown (controlling) for Message object ** Get the IMessage interface on the object */ pUnk->QueryInterface(IID_IMessage,(void**)&pMsg); /* ** Get IDataSource interface using property. */ pMsg->get_DataSource(&pDsrc); /* ** Navigate back to IUnknown interface using new IDataSource prop. */ pDsrc->QueryInterface(IID_IUnknown,(void**)&pUnk2); /* ** Check if these are the same address ** We know the answer...they're different, but let's ** be scientific about it... */ if(pUnk == pUnk2) cout << "pUnk == pUnk2" << endl; else cout << "pUnk != pUnk2!" << endl; pUnk->Release(); pUnk2->Release(); pDsrc->Release(); pMsg->Release(); CoUninitialize(); }
If you compiled and executed this code, you would find that the program would emit
pUnk != pUnk2
In fact, this is the correct behavior: a separate object identity (and hence a different IUnknown address) is returned when an interface is retrieved through a property on the interface or with GetInterface. This time, the IDispatch interface covers the implementation of the requested interface (IDataSource), essentially "shifting" from the previous.
You can think of it this way: C++, Microsoft® Visual Basic®, and Visual J++ can use multiple oleautomation interfaces on the same object. Scripting languages, however, require an object model with a one-to-one correspondence between object and IDispatch interface. The answer is to provide both: CDO objects conform to both models, exposing multiple oleautomation compatible interfaces on the object for use by C++, Microsoft® Visual Basic®, and Visual J++, and spawning separate objects (one for each type of interface) to accommodate scripting languages. For each spawned object, the IDispatch implementation covers the functionality of the target dual interface in "round-robin" fashion.
You may be wondering why all of this is needed. It may appear at first glance that with a clever enough implementation of the IUnknown methods for each interface, you could trick the client into using separate IDispatch implementations by returning the appropriate interface address through an interface property or GetInterface. However, this only works when the CDO object resides in the same apartment as the caller. If the client calls from a separate COM apartment (or process, or even machine process), COM silently provides a proxy and stub manager to remote the calls. The client is then invoking methods through interfaces aggregated on the proxy object, not directly on the object itself. The proxy rigorously enforces the COM object identity laws and additionally assumes that there is only one address for each interface exposed by the object. Being ignorant of our devious attempt to return "appropriate" IDispatch physical addresses in round robin fashion to the client, it instead preempts us, always returning the first IDispatch address retrieved by the client, and irrespective of how this interface is subsequently requested. To circumvent this behavior, we are forced to return interfaces exposed on different object identities (and therefore though different proxy objects) to get the new IDispatch address (through the new proxy object) to the client in the other apartment. The price we pay for this approach is one interface (vtbl) duplicate for each exposed interface per IDispatch "shift."
This behavior is essential to understand if you intend to aggregate CDO interfaces on your objects.
Use the file scheme. For example:
file://./backofficestorage/domain/store/folder/path/to/item
When using the file: OLE DB URL namespace, you do not need to explicitly specify the provider binder used to bind the item. The Web Store OLE DB provider is registered for this namespace. For example:
Dim Rec as New Record Rec.Open "file://./backofficestorage/domain/store2/folder1/item3.doc", , adModeReadWrite ' The connection is implicitly created by the Open command. Dim Conn as Connection Set Conn = Rec.ActiveConnection ....
HTTP URLs are supported. However, the OLE DB root binder will use the Provider binder for the Internet Publishing (MSDAIPP) provider if you do not explicitly specify the Web Store OLE DB Provider (ExOledb.datasource) binder. The MSDAIPP OLE DB provider is currently not supported for use with the Web Store and Microsoft® Exchange 2000 Server.
To explicitly specify the Web Store OLE DB Provider binder, set the Datasource property on the ADO Connection object used to bind to the item:
Dim Conn as New ADODB.Connection Conn.Provider="exoledb.datasource" Conn.Open "http://servername/public" Dim Rec as New Record Rec.Open "http://servername/public/folder1/item.eml", Conn, adModeReadWrite ...
Note that you must specify at least the path to the root folder of the information store from which you plan to open items in the call to Conn.Open. You do not need to specify the full path to each item to open the connection to the store. To access items in another store, you need a separate Connection object. For example:
Dim Conn1 as New ADODB.Connection Dim Conn2 as New ADODB.Connection Conn1.Provider="exoledb.datasource" Conn1.Open "http://servername/public_store_1" Conn2.Provider="exoledb.datasource" Conn2.Open "http://servername/public_store_2" Dim Rec as New Record Rec.Open "http://servername/public_store_1/folder1/item.eml", Conn1, adModeReadWrite ... Rec.Open "http://servername/public_store_2/folder2/item2.eml", Conn2, adModeReadWrite
Yes. Exchange 2000 Server information stores still support the Messaging API (MAPI) and other technologies based upon MAPI such as CDO 1.2.1.
Yes. All forms written using C++ and MAPI will continue to function as before.
The Microsoft Web Store supports folder, item, and property level access control and stores this information using the standard Microsoft® Windows NT®, Microsoft® Windows® 2000 security descriptor format. This means that applications can secure any item in the Web Store and properties of these items using both grant and deny access control entries. For backward compatibility, the per-folder MAPI PR_ACL_TABLE format is still supported. For more information, see.
The Web Store also supports an XML format for security descriptors. See . This format enables application developers that must run code on hosts that do not have access to the stock Win32 security APIs to read and write Web Store item descriptors. Such hosts include Microsoft® Windows® 95, and Microsoft® Windows® 98. Note that this format can only be used to manage descriptors for items in the Web Store.
The Web Store defines a special security principle (trustee) called a Role. Roles are defined using item and folder properties and contain a list of other Web Store roles and Microsoft® Windows® 2000 trustees. A role is only valid within descriptors for Web Store items (folders, etc.) and is meaningless for other objects, such as Active Directory and file system objects. These roles are not the same as "roles" in Microsoft® Outlook®, or roles in COM+. For more information, see .
You cannot get all properties for all items since each item in the folder could conceivably have different properties defined for each. When running SELECT * commands on a folder, you receive only the list of properties that are defined by the schema for the folder. To get all the properties for a particular item, bind to it directly using ADO, CDO, OLE DB, or HTTP/WebDAV. See the Web Store Schema section in the Concepts and Architecture chapter of this documentation.