But First ... a COM Primer

In practice, COM greatly reduces the amount of effort that a developer must make to get a technology working. ADSI uses the COM foundation to provide access to directory data. If you don't understand the fundamentals of COM, ADSI is going to seem unwieldy.

As I mentioned, I've been working with COM for years, and when I started, the only documentation available was two thick programmers' guides that were notorious for containing errors. I thought I understood COM after working with it for a while, but not until I created the IAccessible interface for Microsoft Active Accessibility did I feel I had mastered it.

Therefore, it's worthwhile to spend some time reviewing how COM works, especially with regard to ADSI and Active Directory. Even if you've been working with objects in the past, and even if you know what QueryInterface does, review the next several topics and brush up on COM.

There are several excellent books to help developers understand COM. I recommend Inside COM by Dale Rogerson (Microsoft Press, 1997) and Inside Distributed COM by Guy and Henry Eddon (Microsoft Press, 1998). Another excellent text is Essential COM by Don Box (Addison-Wesley, 1998).

What Is COM?

Simply put, COM is a specification that defines how binary components can communicate. Software applications tend to be monolithic, meaning large and inflexible. COM is designed to allow applications to be broken up into components, each component providing a set of functionality that can be reused. Components expose one or more COM objects, with each object providing some functionality.

An object, according to my dusty old copy of Ian Sommerville's Software Engineering, Third Edition (Addison-Wesley, 1989), is "An entity which has state and a defined set of operations to access and modify that state." This definition of an object holds true for COM's object-oriented model. A COM object encapsulates data and the code needed to modify that data into a single entity. The data is the state of the object. Figure 3-5 shows two COM objects hosted in a component. The component is the vehicle for COM objects and is typically a dynamic-link library (DLL) or an EXE.

Figure 3-5 COM objects hosted in a component.

You're probably thinking, "What's so special about components? Aren't Windows DLLs the same thing as components?" (If you are thinking that, give yourself five points. There may be a test later.) There are many ways to logically separate functionality. Windows DLLs are one such way. What's special about COM is that it also specifies a strict and immutable standard for the way COM objects communicate. With a Windows DLL, you have to know at compile time what functionality is available from the DLL that you want to use for your application. Furthermore, if the DLL should change by adding new functionality, your application might fail to run because the address it used to link to the DLL would probably be different in the new version. COM solves this problem by making a "contract" of sorts between the application and the COM objects. This contract is called an interface.

COM Interfaces

A COM interface is the external means by which applications access the data and functionality of an object. COM objects can have more than one interface, each with a different set of operations. Each interface has a number associated with it called the Interface Identifier (IID). This number is a globally unique identifier (GUID) and is guaranteed to be unique among all computers forever. This 128-bit number is generated by the creator of the COM component and is stored in the computer registry. COM uses GUIDs to avoid naming collisions between two interfaces with the same name. Once defined, an interface cannot be changed. New interfaces can be added, but the original interface remains available. By convention, all COM interface names use mixed case and start with a capital I. As you can see in Figure 3-5, a COM interface is represented in a diagram with a lollipop shape that extends from a rectangular or box-shaped COM object—something Crispin Goswell, a Microsoft developer, calls "boxology."

From a C or C++ perspective, a COM interface is a group of related functions that has a specific memory structure. A COM interface is a pointer to a list of pointers that form the virtual function table or vtable for the object. If you're familiar with C++, you'll find that C++ classes and COM interfaces are very similar but not identical. Figure 3-6 shows a COM interface along with the corresponding memory structure.

Figure 3-6 The pointer and virtual function table that make up a COM interface.

Another key point about COM interfaces is that they can inherit from one another. The derived interface inherits all the function definitions that are part of the original interface. However, COM does not support implementation inheritance and only supports interface inheritance, meaning that COM does not bring along the function code that implements the interface itself.

It is possible to reuse the code available in an existing COM object by having the new interface simply call the methods in the old interface to perform the requested action or by exposing the old interface as one of the interfaces in the new COM object. These techniques are known as containment and aggregation.

As defined by the COM specification, all interfaces for COM objects must inherit from an interface named IUnknown. In diagrams of COM objects, the IUnknown interface typically appears as a lollipop on top of the COM object. (See Figure 3-5.) IUnknown implements only three methods, AddRef, Release, and QueryInterface. The QueryInterface method allows an application to request any other interface that might be available from the object. AddRef and Release are used to determine when the COM object should be deleted from memory by using a reference counting technique.

Methods and Properties

As in an object-oriented model, COM objects support the concept of methods and properties. A method represents some action that an object can perform, whereas a property represents a unit of state data for an object. Applied to COM, the interface functions that perform some operation are known as methods and interface functions that read or modify state data are known as properties. Each property typically has two functions, one for reading the data (get_propertyname) and one for writing or modifying the data (put_propertyname).

Automation

Programs using Visual Basic, VBScript, and JScript can still access the functionality available in COM objects, but they do not have to concern themselves with the various COM interfaces. The mechanism that interpreted scripting languages such as VBScript and JScript use to access COM objects is named Automation (originally known as OLE Automation). Automation allows applications to call interface methods at run time without having to know the details of the object before hand. This feature is known as late binding, and while very flexible and lightweight, it does impose a performance penalty.

Automation is built on the COM foundation and specifies that a COM object support the IDispatch interface. Although a complete description of IDispatch is beyond the scope of this book and is a topic in itself, it can be summarized as follows: The IDispatch interface, composed of four methods, is constructed in such a way that a client can use this single interface and its supporting technologies to access any Automation compatible method implemented by a COM object. The four methods in IDispatch are GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, and Invoke. These methods are used by the application hosting the Automation object to discover and use the functionality provided by the object.

Here's an example of Automation. When a VBScript program requests a property of an object, it uses a statement similar to this:

 strName = myObj.Name 

In this example, the program is requesting the value of the Name property of the object referenced by myObj. When this line is executed, the scripting engine calls the GetIDsOfNames method of the object's IDispatch interface, passing the string "Name" as a parameter. The object returns a number that is associated with the Name property. This number is known as the dispatch identifier (DISPID). VBScript then passes the number to the Invoke method of IDispatch and requests the property value. The Invoke method uses the DISPID to figure out which method to call. Internally, the correct function, get_Name, is called to retrieve the information. VBScript will then assign the value retrieved to the variable named strName. If the VBScript statement was written myObj.Name = "Carl", VBScript would call Invoke requesting the property be updated. Internally, this call maps to put_Name with the string "Carl" as a parameter.

C and C++ applications can use the IDispatch methods as well, but there is little reason to. With compiled languages, the exact property or method being requested is known at compile time. Instead of looking up the DISPID and then using Invoke, the compiler simply writes the exact address of the vtable into the program code. This mechanism is known as early binding and imposes very little overhead on performance. Since objects are generally loaded in the same address space as the application, calling the interface method through the vtable is like calling any other function. Even the latest versions of Visual Basic (5.0 and later) can perform early binding by referencing a file, called a type library, with the object and interface definitions.

COM Example

A good example of a COM component is a spelling checker. There was a time when both e-mail programs and word processing programs came with separate spelling checkers, each with its own, and usually different, dictionary. By placing the functionality of a spelling checker in a component, you can ensure that the code that verifies spelling and utilizes the dictionary can be reused by any application.

In our spell-checking component, only one object is exposed. It represents the spell-checking session and contains state data such as the current word being checked and information about the dictionary. Our simple spell-checking component could expose an interface named ISpellCheck, with methods named CheckSpelling and AddWord. A single read-only property, WordsAdded, would be defined for this interface, indicating the number of words added to the dictionary.

After developing and releasing the spell-checking component, we might want to update the ISpellCheck::CheckSpelling method by adding a parameter that specifies the language to be used. Since ISpellCheck cannot be modified after it's been published, the developer of the spell-checking component could create a new interface, named ISpellCheck2, that defines a CheckSpelling method that accepts the language parameter. Older applications would continue to work fine with the original interface, and new applications could take advantage of the new functionality and use ISpellCheck2 instead. Figure 3-7 shows a representation of our spell-checking object.

Interface methods in documentation typically use C++ syntax, for example ISpellCheck::CheckSpelling (parametertype name)

Figure 3-7 A spell-checking COM object, which has the original ISpellCheck interface and the new ISpellCheck2 interface.

Accessing Objects

The final item I want to cover in this overview of COM components is how to actually get a reference to an object and access its interfaces. There are dozens of ways to create objects or references to an existing object. Describing each method is beyond the scope of this brief introduction, but I'll cover the most basic cases.

C and C++

Normally, C and C++ programs use the COM function CoGetObject to get a reference to an existing object. By providing a class identifier (CLSID), COM looks up the object class in the registry, loads the proper file, and activates the object. A CLSID is a GUID that uniquely identifies an object. If the calling program has requested an interface other than IUnknown, COM calls QueryInterface on IUnknown with the IID and returns a pointer to that interface to the caller.

The process for creating a new object is somewhat easier: an application uses the CoCreateInstance function supplied by COM. Unlike binding to an existing object, this creates a new object based on the object class requested. Once you have a pointer to an interface of an object, you can access all the exposed functionality of that object.

Regardless of how an interface to an object is obtained, the calling program is expected to call the interface's Release function to allow the object to clean up after itself.

Listing 3-3 shows a C++ sample that creates and uses the Microsoft Agent COM object.

 #include <comdef.h>  // C++ compiler COM support
#include "AgtSvr.h"  // Microsoft Agent support
#include "AgtSvr_i.c"  // Microsoft Agent class and interface IDs
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    HRESULT hResult;
    _variant_t varPath;
    _bstr_t bstrSpeak;
    _bstr_t bstrPlay;
    long lCharID;
    long lRequestID;
    IAgentEx *pAgentEx;
    IAgentCharacterEx *pCharacterEx = NULL;
    // Initalize COM
    CoInitialize(NULL);
    // Create an instance of the Microsoft Agent
    hResult = CoCreateInstance( CLSID_AgentServer,
                                NULL,
                                CLSCTX_SERVER,
                                IID_IAgentEx,
                                (LPVOID *)&pAgentEx);
    // Was object created?
    if ( SUCCEEDED( hResult ) )
        {
        varPath = "merlin.acs";
        // Load the merlin character
        hResult = pAgentEx->Load( varPath, &lCharID, &lRequestID );
        if ( SUCCEEDED( hResult ) )
            {
            // Get the IAgentCharacterEx interface
            pAgentEx->GetCharacterEx( lCharID, &pCharacterEx );             // Display the character
            pCharacterEx->Show( FALSE, &lRequestID );
            // Instruct the character to talk
            bstrSpeak = "COM just looks like magic.";
            pCharacterEx->Speak( bstrSpeak, NULL, &lRequestID );
            // Instruct the character to perform an action
            bstrPlay = "DoMagic2";
            pCharacterEx->Play( bstrPlay, &lRequestID );
            // Repeat, with a different phrase and action
            bstrSpeak = "Now let's learn about Active Directory!";
            pCharacterEx->Speak( bstrSpeak, NULL, &lRequestID );
            bstrPlay = "Reading";
            hResult = pCharacterEx->Play( bstrPlay, &lRequestID );
            // Wait 17 seconds to allow the Agent to finish
            Sleep(17000);
            // Stop any motion and hide the character
            hResult = pCharacterEx->Stop( lRequestID );
            hResult = pCharacterEx->Hide( FALSE, &lRequestID );
            Sleep( 5000 );
            }
        }
    // Clean up
    if ( pCharacterEx )
        {
        // Release the character interface
        pCharacterEx->Release();
        // Unload the character
        pAgentEx->Unload( lCharID );
        }
    // Release the Agent
    if ( pAgentEx )
        pAgentEx->Release();
    // Unload COM
    CoUninitialize();
    return 0;
}

Listing 3-3 The COMAgent.cpp sample demonstrates the capabilities of COM.

Visual Basic and Scripting Languages

Getting and creating objects from within Visual Basic and scripting languages is much easier than in C and C++. C and C++ developers must worry about interface pointers and releasing those interfaces; developers using Visual Basic and scripting languages are relieved of this responsibility.

To get a reference to an existing object in Visual Basic and the scripting languages you can use the GetObject function. To instantiate a COM object that does not already exist, you must first create the object. Visual Basic and VBScript provide the CreateObject function to instantiate new objects. Visual Basic developers can also create instances of objects using the As New option of the Dim statement. Developers working with JScript can create new COM objects using the new operator coupled with the supplied ActiveXObject object. Here are some examples:

 Visual Basic/VBScript:
    Set objAgent = CreateObject("Agent.Control.2")
Visual Basic:
    Dim objAgent As New Agent.Control.2
JScript:
    objAgent = new ActiveXObject ("Agent.Control.2") 

In all these examples, the result is a new object of the class Agent.Control.2 and a reference to it stored in the variable objAgent.

Listing 3-4 shows the same Agent sample discussed in the previous section, but written in VBScript. What does it do? Well, run the COMAgent.vbs sample on the companion CD on a machine running Windows 2000 and find out!

 Set objAgent = CreateObject("Agent.Control.2")
objAgent.Connected = True
objAgent.Characters.Load "Merlin"
With objAgent.Characters("Merlin")
.Show
.Speak "COM just looks like magic."
.Play "DoMagic2"
.Speak "Now let's learn about Active Directory!"
.Play "Reading"
WScript.Sleep 17000
.Stop
.Hide
WScript.Sleep 5000
End With

Listing 3-4 The COMAgent.vbs sample demonstrates the capabilities of COM.



MicrosoftR WindowsR 2000 Active DirectoryT Programming
MicrosoftR WindowsR 2000 Active DirectoryT Programming
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 108

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net