Sdílet prostřednictvím


Messaging Frequently Asked Questions

Topic Last Modified: 2006-12-06

This topic covers the most frequently asked questions about programming with Microsoft® Exchange Server 2007 .

Why do I get an 0x80040220 error when using cdoSendUsingPickup?

When using Exchange 2007, you may encounter the following error when using cdoSendUsingPickup:

CDO.Message.1 (0x80040220)
The "SendUsing" configuration value is invalid.

In Exchange 2007, default read access to the Microsoft Internet Information Services (IIS) metabase is restricted. To avoid getting this error, you need to either specify the pickup directory in your code, or run your application under an account that has read access to the metabase. For more information, see Specifying the Pickup Directory.

What's the difference between the IBodyPart.BodyParts collection and the IMessage.Attachments collection on a Message object?

Although both the IBodyPart.BodyParts and IMessage.Attachments properties return IBodyParts interfaces on collection objects, the objects contained within each collection normally differ. For messages that are encoded as Multipurpose Internet Mail Extensions (MIME), a good rule of thumb is that all body parts with content-disposition set to "attachment" are returned in the IMessage.Attachments collection. Depending on 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; 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, which is the root of the hierarchy. All other objects 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 because they do not have descendants.

For Unix-to-Unix encode (UUENCODE) formatted messages, the hierarchy is only one level deep, because all body parts are necessarily deemed attachments. In this case, only the Message object has descendants, and the IMessage.Attachments and IBodyPart.BodyParts collections are the same. For example,

A
  B <-- UUENCODE attachment
  C <-- ditto
  D <-- ditto

Why can't I write text to the Stream object of a newly added BodyPart object using _Stream::WriteText?

Microsoft ActiveX® Data Objects (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 defined to function only 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 ways:

  • Before retrieving the decoded content stream, set the BodyPart object's content media type to a text-based type, such as "text/plain." You can use either the IBodyPart.ContentMediaType property or the urn:schemas:mailheader:content-type field in the IBodyPart.Fields collection. Once set, the returned content stream will be of the text type.
  • Manually change the stream type using the _Stream.Type property on the Stream object. Note that in most cases, you will also need to set the content media type after setting and flushing the stream, as shown in the following examples:

Example

Visual Basic

' EXAMPLE for Step 1
'   Assume that you have a BodyPart object reference in the variable Bp1,
'   and that you 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"

 'Commit the changes back
 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 more content</P>"
 Strm.Flush
 Bp3.ContentMediaType = "text/html"
 ' ...

How do I set message headers on Message or BodyPart objects when there aren't properties for them?

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 fields provided in the urn:schemas:mailheader: namespace are the most common headers defined by the Internet community. However, you can add other headers that are not a part of this default set by adding it to the Fields collection in 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

How can CDO objects expose multiple dual interfaces?

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 through the v-table and through the seven methods defined by the IDispatch interface (hence the name dual). Because Component Object Model (COM) uses proxy/stubs to marshal arguments between separate apartments, objects can expose only one IDispatch interface. In addition, you can't trick COM proxy/stubs by returning different addresses depending upon the interface from which you request IDispatch (for more details on this rule, see Advanced ). 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 Collaboration Data Objects (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 ActiveX Data Objects database (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 Visual J++, use the jactivex tool to create necessary Microsoft virtual machine compatible Java packages, and then import these packages into their .java source files. The following examples illustrate these procedures:

Visual Basic

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

Visual J++

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, you 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 Visual Basic Scripting Edition (VBScript), and Microsoft JScript®. With these languages, all interactions with COM objects occur through the object's IDispatch interface. Because each object can have only one IDispatch interface, you need separate objects with different implementations of IDispatch, not separate interfaces. Furthermore, you 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. However, this produces the illusion of " interface navigation" because it appears to the scriptwriter that the interface itself is being returned. The following code demonstrates this illusion using the CDO Message object:

VBScript

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)

Visual JScript

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), each of which exposes 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 the details internally. Most importantly, the CDO object behaves in the same manner, regardless of which object is used.

Note to C/C++ Programmers (Advanced)

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 interface properties such as IMessage::get_DataSource or IMessage::get_BodyPart, or if you use the GetInterface method to retrieve interfaces, you're actually getting a different object identity. Consider the following code:

C++, IDL

#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
** You 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, 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: Microsoft Visual C++®, Visual Basic, and Visual J++ can use multiple Automation 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 Automation-compatible interfaces on the object for use by Visual C++, 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 wonder 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 unaware of the 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 regardless of how this interface is subsequently requested. To circumvent this behavior, we are forced to return interfaces exposed on different object identities (and, therefore, through different proxy objects) to get the new IDispatch address (through the new proxy object) to the client in the other apartment. The price paid for this approach is one interface (v-table) duplicate for each exposed interface per IDispatch "shift."

It is essential that you understand this behavior if you intend to aggregate CDO interfaces on your objects.