Introduction to Markup Services

Markup Services is a set of interfaces and objects that allow you to manipulate the contents of an HTML document. This article introduces these interfaces and objects.

  • Tags Vs. Elements
    • Valid and Invalid Documents
    • IMarkupServices
    • MarkupContainer
    • MarkupPointer
  • Positioning Markup Pointers
    • Comparing_Pointer_Positions
    • Navigating the Pointer
  • Pointer Gravity
  • Pointer Cling
  • Creating a New Element
  • Inserting an Element
  • Removing an Element
  • Inserting Text
  • Removing Content
  • Replacing Content
  • Moving Content
  • Copying Content
  • References
  • Related topics

Tags Vs. Elements

A few concepts help to make Markup Services easier to understand. One such concept is the idea of an html tag versus its representation inside the browser, which is known as an element.

It is important to distinguish between tags and elements in viewing HTML content. HTML content includes tags, such as <B> and <B>, that specify the representation of text in a document. When a page is accessed by the browser, the HTML parser reads the contents of the file and creates elements from the tags. It is the elements, as objects, that you can program. Similarly, it is the elements that Markup Services can manipulate.

For example, an HTML file might contain the following text:


However, when the browser's parser reads this text, the internal configuration of elements making up the state of the document looks like the following:


In other words, the parser turns the contents of the HTML file into elements—in this case, a few more than existed in the original file. Note that the html, head, TITLE, and body elements were automatically constructed by the parser for completeness. Furthermore, the parser ended the first paragraph element when the second was introduced. Although your HTML file might not include end tags, the Windows Internet Explorer representation of your file automatically includes end tags for each element. In addition, any required elements not included in your file, such as <HTML> and <BODY>, will be automatically inserted into Internet Explorer's representation of the file; both begin tags and end tags will be included.

The second concept worth noting is the idea of a tree versus a stream. Consider the following document:

My <B>dog</B> has fleas.

This document consists of the text "My dog has fleas" and a single b (bold) element. The bold element begins just before the letter d in "dog" and ends right after the letter g.

This example could be modeled as a tree, with text as the leaves and elements as interior nodes.

          |     |      |
         "My"   B  "has fleas."

By modeling a document as a tree, all that would be needed to arbitrarily manipulate it would be tree-like operations, such as adding/removing children. An application programming interface (API) for such a model most likely would be called Tree Services.

However, the type of content that Microsoft Internet Explorer 4.0 and later is capable of modeling is more powerful than simple trees. Consider the following example:

Where do <B>you <I>want to</B> go</I> today?

This document has b and i (italic) elements. Everything between the <B> and </B> tags will display as bold, and everything between the </I> and </I> tags will display as italic. However, it is impossible to model this document as a tree, because the i element is not properly nested under the b element. The b element ends before the i element ends, and it begins before the i element begins. This is an example of partially overlapping elements, which are typical of some HTML content.

Because of this, Markup Services does not provide tree-like manipulation operations. Instead it exposes a stream-based model for content manipulation. Thus, instead of using Tree Services to refer to this kind of data structure, Markup Services is used to avoid confusion between the two models.

In a tree-based model of content manipulation, the content is expressed as nodes in a tree, and each element or chunk of text is a node. The nodes are manipulated with tree-like operations, usually the insertion and removal of child nodes from parent nodes.

In a stream-based model for content manipulation, such as that expressed by Markup Services, the content of the document is navigated by the use of iterator-like objects, such as markup pointers, and the content is manipulated by range-like operations. This was demonstrated by the preceding "Where do you want to go today?" example, which included partially overlapping elements and the use of two markup pointers, each specifying where the begin and end tags are to be located. This stream-based model is a super-set of the tree-based model.

Valid and Invalid Documents

Another concept that makes Markup Services easier to understand is that of creating and manipulating invalid documents.

Notice that the previous "My dog has fleas" example might not be considered a valid HTML document by all browsers. If that example were to be copied to an HTML file (a text file ending with .html, for example) and loaded into an HTML browser, the browser's parser would produce a significantly different document. For example, the Internet Explorer parser would read this document and then represent it internally as follows:

<BODY>My <B>dog</B> has fleas.</BODY></HTML>

The parser reads a given input and tries to produce a valid HTML document. At a minimum, a valid HTML document must have html, head, TITLE, and body elements. The parser creates these elements for you, and puts them in their proper locations.

You can use Markup Services to remove or rearrange the content of the document in an arbitrary fashion, once the parser has completed parsing the document or even before the document is completely parsed. For example, you can remove the html and/or body elements entirely. You could place the head element inside the body element. Such documents are termed invalid documents.

This illustrates a fundamental Markup Services feature that is capable of creating and manipulating invalid documents. This feature provides maximum flexibility to a programmer, and allows a document to be manipulated without the arcane and complex rules required by an HTML parser. Thus, you can temporarily alter a document through invalid states on the way to creating a valid document.


Having understood the fundamental concepts of Markup Services, you now are ready to take a closer look at Markup Services interfaces. The best place to start is the IMarkupServices interface. This interface is the starting point for all the Markup Services, such as IMarkupContainer and IMarkupPointer. The IMarkupServices interface also contains all the methods for performing actual manipulations of elements of the document.

You can perform a QueryInterface operation on a document object for IID_IMarkupServices to obtain IMarkupServices.


While elements can be created without the context of a IMarkupContainer, in order to relate elements and text to each other, a IMarkupContainer must be used.

The following sample code demonstrates how to create a IMarkupContainer from the IMarkupServices interface using the IMarkupServices::CreateMarkupContainer method.

HRESULT CreateMarkupContainer(
    IMarkupContainer **ppContainer

Initially, a newly created IMarkupContainer contains no markup. Specifically, there are no html, head, or body elements. The initial state of a IMarkupContainer is not what the parser produces when an empty file is parsed in. Rather, the parser automatically inserts these elements as a courtesy.

Normally, IMarkupContainers are created to contain markup that is being staged for insertion into the primary IMarkupContainer. The primary IMarkupContainer is the one, for example, that the browser creates to hold the HTML being parsed in. You can perform a QueryInterface operation on an HTML document for IID_IMarkupContainer to obtain the primary IMarkupContainer. For example, you could query the IHTMLDocument2 interface for IID_IMarkupContainer.


A IMarkupPointer is not part of the content of a IMarkupContainer (also known as a document). The primary purpose of the IMarkupPointer is to specify a position in a document. Consider the following example:

My <B>d[p1]og</B> has fleas.

The position of the IMarkupPointer is represented by the [p1] pointer. Although [p1] is positioned between the letters d and o in dog, this does not mean that there are any additional characters in the document, or that the content of our example has been altered. There can be any number of pointers in a document without altering the document.

Markup pointers are positioned between pieces of document content. These pieces can be one of three things: the start of the influence of an element, the end of the influence of an element, or text. Thus, markup pointers are like editor carets. Because markup pointers themselves are not content, their positions are mutually indistinguishable when located at the same point in the HTML content. That is, if two markup pointers are next to each other, it is not possible to tell which one is to the left or right of the other. All that can be said about them is that they are at the same location in the content.

You can create a markup pointer by calling the IMarkupServices::CreateMarkupPointer method on the IMarkupServices interface.

HRESULT CreateMarkupPointer(
    IMarkupPointer **ppPointer

Positioning Markup Pointers

When a markup pointer initially is created, it is in a special state called unpositioned, meaning it is not yet between any content in particular. The following are three primary ways you can position a markup pointer into a markup (all are available on the IMarkupPointer interface):

The IMarkupPointer::MoveAdjacentToElement method takes two parameters: an IHTMLElement and an enumeration literal that indicates the relative position to that element to place the pointer. This enumeration has the following four possible values:

HRESULT MoveAdjacentToElement(
    IHTMLElement *elementTarget,


Thus, positioning [p1] before the end (ELEMENT_ADJ_BeforeEnd) of the b element in the preceding example would result in the following:

My <B>dog[p1]</B> has fleas.

Now consider the following example:


The [p1] could be considered to be positioned after the begin tag of the b element, or before the begin tag of the i element. This illustrates that certain positions in a document can be redundantly specified.

Another way to position a markup pointer is to use the IMarkupPointer::MoveToContainer method on the IMarkupPointer interface. This method takes an IMarkupContainer interface and a Boolean that indicates whether to position the pointer at the beginning or end of the IMarkupContainer.

HRESULT MoveToContainer(
    IMarkupContainer *containerTarget,
    BOOL fAtStart

Thus, you can get a pointer positioned at the extreme edge of a document, as is demonstrated in the following example:


Here, [p1] is at the far left of the document and [p2] is at the far right.

The third way to position a pointer is to use IMarkupPointer::MoveToPointer to move the pointer to a position in the document already indicated by another IMarkupPointer.

HRESULT MoveToPointer(
    IMarkupPointer *pointerTarget

Often, the IMarkupPointer::MoveToPointer method is used to record a position in a document while another pointer is used to inspect the surroundings.


The relative position between two markup pointers can be established with a number of comparison methods of the IMarkupPointer interface, as the following five examples demonstrate.

    IMarkupPointer *compareTo,
    BOOL *fResult

    IMarkupPointer *compareTo,
    BOOL *fResult

HRESULT IsLeftOfOrEqualTo(
    IMarkupPointer *compareTo,
    BOOL *fResult

    IMarkupPointer *compareTo,
    BOOL *fResult

HRESULT IsRightOfOrEqualTo(
    IMarkupPointer *compareTo,
    BOOL *fResult

Thus, if you want to know whether [p1] is left of and not equal to [p2], you can use the following example:

BOOL fResult;
IMarkupPointer * pointer 1, * pointer 2;


[p1]->IsLeftOf( pointer2, & fResult );

if (fResult)
    // [p1] is to the left of pointer2

Once a IMarkupPointer is placed in a IMarkupContainer, you can use it to inspect the surrounding content and/or move it around that content. Two methods, IMarkupPointer::Left and IMarkupPointer::Right, on the IMarkupPointer interface are the sole mechanisms for doing this.

    BOOL fMove,
    IHTMLElement **ppElement,
    long *plCch,
    OLE_CHAR *pch

    BOOL fMove,
    IHTMLElement **ppElement,
    long *plCch,
    OLE_CHAR *pch

All but the first argument are optional. The fMove parameter controls whether the pointer is moved past the surrounding content. If FALSE, the pointer does not move at all; rather, it describes the surrounding content. If TRUE, in addition to describing the surrounding content, the pointer is moved across that surrounding content.

To find out what is to the left of a pointer, you can call the IMarkupPointer::Left method. To find out what is to the right of a pointer, you can call the IMarkupPointer::Right method. The pContextType parameter of IMarkupPointer::Left and IMarkupPointer::Right returns whatever is next to the pointer (the context).

The following are some possible types of context.

CONTEXT_TYPE_None Nothing is to the left or right of the pointer. This occurs only when the pointer is positioned at the extreme left or right of the IMarkupContainer.
CONTEXT_TYPE_Text In the given direction, there is text.
CONTEXT_TYPE_EnterScope In the given direction, an element is coming into scope. Thus, if looking left, an end tag is present; if looking right, a begin tag is present.
CONTEXT_TYPE_ExitScope In the given direction, an element is going out of scope. Thus, if looking left, a begin tag is present; if looking right, an end tag is present.
CONTEXT_TYPE_NoScope In the given direction, a no-scope element exists. These are elements into which you cannot get a IMarkupPointer positioned (br, for example).


If the ppElement parameter is non-NULL, and the context type is one of EnterScope, ExitScope, or NoScope, the ppElement parameter will return the element that is coming into scope, exiting scope, or is no-scope, respectively.

If the context is Text, the pCch and pch parameters are relevant. The pCch parameter serves three purposes:

  • It limits the number of characters that IMarkupPointer::Left or IMarkupPointer::Right will look for.
  • It limits how much text actually exists in the given direction.
  • It describes how large a buffer the pch parameter points to (if it is non-NULL).

The pCch parameter can be NULL or -1 upon entry, indicating that IMarkupPointer::Left or IMarkupPointer::Right should look across an arbitrary amount of text, up to the next no-scope element or element scope transition.

The table following the next example describes what IMarkupPointer::Left or IMarkupPointer::Right will return in a variety of situations.

[p1]Where [p2]<I>do </I>[p3]<B>you <BR>[p4]want</B> to go today[p5]?
Ptr Direction Type Element Cch In Cch Out Text Notes
[p1] IMarkupPointer::Left None - - - - Moves to the left edge of the container.
[p1] IMarkupPointer::Right Text - 2 2 Wh Returns a maximum of two characters.
[p1] IMarkupPointer::Right Text - -1 6 - Returns the total number of available characters to the right, up to the next markup.
[p1] IMarkupPointer::Right Text - 345 6 Where Returns the number of characters available, up to the maximum specified. In this case, 6 of 345 characters are returned.
[p2] IMarkupPointer::Left Text - NULL - - Asks which character is to the left, not how many characters.
[p2] IMarkupPointer::Right EnterScope I - - - Moves begin tags to the right.
[p3] IMarkupPointer::Left ExitScope I - - - Moves end tags to the right.
[p4] IMarkupPointer::Left NoScope BR - - - Does not usually require end tags.
[p5] IMarkupPointer::Left Text - 100 12 NULL Retrieves only the number of characters moved, not the characters themselves.


Note   The IMarkupPointer::Left and IMarkupPointer::Right methods provide the mechanism for walking the document.


To determine where the immediate element of an IMarkupPointer interface is currently positioned, use the following IMarkupPointer::CurrentScope method:

HRESULT CurrentScope(
    IHTMLElement **ppElementCurrent

Using the previous "Where do you want to go today?" example, the IMarkupPointer::CurrentScope for [p1] is NULL, because there is no unended begin tag to its left. The IMarkupPointer::CurrentScope of [p4] is the<B> tag. Note that the br is a NoScope type tag.

Pointer Gravity

Normally, when a document is modified, pointers in that document pretty much stay where they were before the operation occurred. For example, consider the following document with two pointers in it.


If this document were to be modified with the insertion of the text XYZ between the letters e and f, the document would look like the following example:


Note that [p1] and [p2] are still located between the same pieces of text that they were before the operation. Consider the following:


Now consider what would happen if the letter Z were to be inserted between the letters x and y. Remember that the pointer does not constitute content, and that the letters x and y are next to each other. There would be the following two possibilities as to where the pointer could be positioned after the insert.

x[p1]Zy or xZ[p1]y

This is when gravity comes into play. For example, usually when content is inserted exactly where the pointer is, it is ambiguous as to where that pointer should end up. Gravity eliminates that ambiguity. Left gravity causes the pointer to be positioned to the left of the newly inserted content, and right gravity causes the pointer to be positioned to the right of the newly inserted content.

This applies not only to the insertion of text, but also to the insertion of an element. Consider the following:


Here, [p1] has right gravity and [p2] has left gravity. If a b element were to be inserted around the letter b, the following would result:


Notice how the pointers now are inverted with respect to their original relative positions. Both were positioned such that it was ambiguous as to where they should go, given this insertion of the b element.

The default gravity on a pointer is left gravity. You can retrieve and set the gravity of a IMarkupPointer with the following methods on the IMarkupPointer interface.


HRESULT Gravity(
    POINTER_GRAVITY *pGravityOut

HRESULT SetGravity(
    POINTER_GRAVITY newGravity

Pointer Cling

Consider the following markup:


Now consider what would happen to [p1] if the preceding example were to be modified such that the text bc were to be moved between the letters x and y. Again, there are two alternatives.

[p2]a[p1]dxbcy or [p2]adxb[p1]cy

In both examples, [p2] is unaffected, because it is not located near where the primitive operation is being performed. However, notice that in the example on the left [p1] does not have IMarkupPointer::Cling, whereas the example on the right does. Effectively, IMarkupPointer::Cling causes a pointer to be considered part of the content with respect to the movement of that content. Where that content goes, so do pointers with IMarkupPointer::Cling that are within that content.

Again, there can be ambiguities. Consider the following where [p1] has IMarkupPointer::Cling:


If the letter b were to be moved between the letters x and y, consider whether the [p1] would be associated with the letter b. Again, gravity is used to disambiguate this. If [p1] has right gravity, it follows the letter b. If it has left gravity, it is associated with the content to its left (which, in this case, is the letter a) and will not follow the letter b.

If content is deleted instead of being moved, IMarkupPointer::Cling also controls the destiny of pointers. Consider the following example:


If the letters b and c are deleted and [p1] does not have IMarkupPointer::Cling, [p1] will remain positioned in the document between the remaining content that surrounds it.


However, if [p1] has IMarkupPointer::Cling, [p1] will become unpositioned just like the deleted content ([p1] is removed from the document, but not destroyed in that it can be reinserted again and used).


IMarkupPointer::Cling can be queried and set with the methods IMarkupPointer::Cling and IMarkupPointer::SetCling of the IMarkupPointer interface.

    BOOL *pClingOut

    BOOL NewCling

Creating a New Element

A new element can be created using the IMarkupServices::CreateElement method of the IMarkupServices interface.


HRESULT CreateElement(
    TAG_ID tagID,
    OLECHAR *pchAttrs,
    IHTMLElement **ppNewElement

For example, IMarkupServices::CreateElement ( TAGID_B, "id=anID", & pElement ) will create a new b element with the attribute IHTMLElement::id set to "anID". The attributes parameter is optional. Attributes can be set on the element after its creation, but you might get better performance if you specify the attributes at the point the element is created. There also might be some attributes that can be specified only at element creation.

You can create a new element by cloning an existing element. To do this, use the IMarkupServices::CloneElement method of the IMarkupServices interface.

HRESULT CloneElement(
    IHTMLElement *pElementCloneElementMe,
         IHTMLElement **ppNewElement

Inserting an Element

An element can be inserted into a document by calling the IMarkupServices::InsertElement method of the IMarkupServices interface.

HRESULT InsertElement(
    IHTMLElement *pElementInsertThis,
    IMarkupPointer *pPointerStart,
    IMarkupPointer *pPointerFinish

The start pointer describes where the element is to come into scope, and the finish pointer specifies where it goes out of scope. The element to be inserted currently must not be in a document, and both pointers must be positioned in the same IMarkupContainer. For example, consider calling IMarkupServices::InsertElement on a b element with the following pointers:

My [pstart]dog[pend] has fleas.

They would produce a document with the following content:

My [pstart]<B>dog[pend]</B> has fleas.

Effectively, the start pointer is where the begin tag is placed, and the finish pointer is where the end tag is placed. Note that the pointers have left gravity, and position themselves to the left of the newly inserted content. If [pstart] were to have right gravity, the following would have resulted instead:

My <B>[pstart]dog[pend]</B> has fleas.

There is no restriction as to where a new element can be inserted. Thus, you can insert as many body elements as you want, or you can insert B elements into the head of the document. However, if the document were to be displayed or interacted with in such a state, the result would be undefined and is subject to change in Markup Services.

Removing an Element

Removing an element does not require markup pointers. You simply call the IMarkupServices::RemoveElement method of the IMarkupServices interface, passing the element to be removed.

HRESULT RemoveElement(
    IHTMLElement *pElementRemoveThis

The element must be in a document at the time of the operation. After the operation, the element no longer is in any document. Thus, it is available for insertion.

Note  To remove an element and reinsert it in the exact same location, you must, before removing the element, insert markup pointers next to the start and end of the element. This way, the markup pointers record the range the element influenced when it was in the markup. The markup pointers can then be used to reinsert the element. Of course, make sure the pointers do not have IMarkupPointer::Cling, because they might be unpositioned when the element is removed.


Inserting Text

To insert text into markup, use the IMarkupServices::InsertText method.

HRESULT InsertText(
    OLECHAR *pch,
    long cch,
    IMarkupPointer *pPointerTarget

This method takes a single IMarkupPointer and inserts the text at that point in the markup. The position of the markup pointer after the insertion (either before of after the newly inserted text) depends on the gravity property of the IMarkupPointer. The cch parameter can be set to -1 to indicate that IMarkupServices::InsertText should assume that the text to insert is NULL terminated.

Removing Content

You can remove a contiguous section of content in a IMarkupContainer with the IMarkupServices::Remove method of the IMarkupServices interface.

    IMarkupPointer *pPointerSourceStart,
    IMarkupPointer *pPointerSourceFinish

Here two markup pointers are supplied: one denoting where to start the removal, and another indicating the end. All textual content between the pointers is removed. Also, any markup that falls completely between the pointers is removed. Any markup that begins before the start pointers or ends after the end pointer is not removed. Consider the following example:

         <------------------- b ------------------->
     <--------- i -----------> <---------- u ----------->
                      <----- s ------->                                                    

When the IMarkupServices::Remove method is called on these pointers, the following results:

         <------------- b ------------->
     <------- i --------><------- u -------->

Notice that the s element is completely gone. Also notice that the i and u elements are still there, even though one of their tags was in the middle of the removal range. There also still is content between the pointers. This content can be only tags that were the start or end of elements that were partially selected before the removal operation. Note that the b element is unaffected, because it is entirely surrounded by the range removed.

Replacing Content

The two previous examples can be used to remove and insert content. Together these operations are similar to a replacing content operation, such as the one in the following example:

int MarkupSvc::RemoveNReplace(
    MSHTML::IHTMLDocument2Ptr pDoc2,
    _bstr_t bstrinputfrom, _bstr_t bstrinputto)
    HRESULT              hr = S_OK;
    //IHTMLDocument2 *   pDoc2;
    IMarkupServices  *   pMS;
    IMarkupContainer *   pMarkup;
    IMarkupPointer   *   pPtr1, * pPtr2;
    TCHAR            *   pstrFrom = _T( bstrinputfrom );
    TCHAR            *   pstrTo = _T( bstrinputto );
    pDoc2->QueryInterface( IID_IMarkupContainer, (void **) & pMarkup );
    pDoc2->QueryInterface( IID_IMarkupServices, (void **) & pMS );

    // need two pointers for marking
    pMS->CreateMarkupPointer( & pPtr1 );
    // beginning and ending position of text.
    pMS->CreateMarkupPointer( & pPtr2 ); 

    // Set gravity of this pointer so that when the replacement text
    // is inserted it will float to be after it.
    pPtr1->SetGravity( POINTER_GRAVITY_Right ); // Right gravity set

    // Start the search at the beginning of the primary container

    pPtr1->MoveToContainer( pMarkup, TRUE );

    for ( ; ; )
        hr = pPtr1->FindText( (unsigned short *) pstrFrom, 0, pPtr2, NULL );

        if (hr == S_FALSE) // did not find the text

        // found it, removing..
        pMS->Remove( pPtr1, pPtr2 );
        //inserting new text
        pMS->InsertText( (unsigned short *) pstrTo, -1, pPtr1 );
    if (hr == S_FALSE) return FALSE;
    else return(TRUE);

Moving Content

You can move a range of content from one place to another with the IMarkupServices::Move method of the IMarkupServices interface.

    IMarkupPointer *pPointerSourceStart,
    IMarkupPointer *pPointerSourceFinish,
    IMarkupPointer *pPointerTarget

The IMarkupServices::Move operation takes three markup pointers: two for the source range to move, and a third for the destination. The effect on the content in the source markup is identical to the IMarkupServices::Remove operation. The content that was in the source will be moved into the markup specified by the target pointer.

All elements completely encompassed by the source range are moved intact to the target. That is, the identities of these elements are preserved. Elements that are completely outside the range of the source are unaffected, and are not transferred to the destination. However, elements that partially overlap the source range are cloned, and their IMarkupServices::CloneElements are moved to the target. Thus, given the preceding example in the IMarkupServices::Move operation, if that range were to be moved to the position shown in the following example:


It would produce the following:


Notice that [pdest] is to the left of the newly inserted moved content. This is because it had left gravity. Notice that there are I' and U' elements. These are clones of the original i and u elements that were left back at the source. Elements can live only in one markup, and must influence one contiguous range in that markup. Note that the s element was not influenced by IMarkupServices::CloneElement. This is because the s element was entirely surrounded by the start and end pointers in the source before the move.


Quite often, after a move (or a copy for that matter), you will want to have two pointers to the left and right, respectively, of the newly inserted content. To do this, create two markup pointers before the move: one with left gravity and another with right gravity. Place these two pointers at the destination pointer, then perform the move. After the move, the pointer with left gravity will be located to the left, and the pointer with right gravity will be located to the right of the newly moved/copied content.

The destination of the move can be between the source start and the source end of the range to move.


Copying Content

You can duplicate a range of content with the IMarkupServices::Copy method of the IMarkupServices interface.

    IMarkupPointer *SourceStart,
    IMarkupPointer *SourceEnd,
    IMarkupPointer *Target

Copying has the same effect on the destination markup that the IMarkupServices::Move method does, without disturbing the source.


The following articles and books provide more information about the Component Object Model (COM).

  • The Component Object Model
  • COM Technical Overview
  • Inside OLE, 2nd Edition, by Kraig Brockschmidt (Microsoft Press)
  • Understanding ActiveX and OLE, by David Chappell (Microsoft Press)
  • Inside COM, by Dale Rogerson (Microsoft Press)
  • Inside Visual C++, by David Kruglinski (Microsoft Press)
  • Professional DCOM Programming, by Dr. Richard Grimes (Wrox Press)





Other Resources

Visual Studio Developer Center