Chapter 1. COM+: An Evolution

Innovations in technology do not always take the form of huge leaps and radical paradigm shifts. Often they occur as part of a natural evolution: Two or more related technologies are consolidated into a singular technology that, by virtue of its simplicity, becomes greater than the sum of its parts. So it is with COM+.

COM+ is not so much a new technology as a consolidation of previous Microsoft technologies, mainly transaction processing (MTS) and asynchronous message delivery (MSMQ). These two pre-existing COM-based architectures provide COM+ with a foundation from which many COM+ services originate and others are built. For example, role-based security, object pooling, and synchronization services (all core services of COM+) originated with Microsoft Transaction Server (MTS). Similarly, the architecture of COM+ asynchronous events relies on Microsoft Message Queue (MSMQ).

Although COM+ is built on other technologies, the real benefit of COM+ is that it enables us to focus on features and capabilities as opposed to underlying architecture. And so, we will begin this chapter and our introduction to COM+ by looking at Table 1.1, which shows a list of some of the new features found in COM+.

Table 1.1. New Features in COM+

Feature

Description

Core COM+ Services

Transaction processing

Related objects that manipulate relational databases (and other Resource Managers) should be able to cooperate with one another under the umbrella of a single transaction. Thus, if one object fails in a chain of related database modifications performed by a chain of related objects, all other objects in the chain will have their changes rolled back.

Queuing

Objects should be able to call the methods of other objects asynchronously. This enables the client object to effectively sa message by calling a method on the object without being required to wait for a response. This message is guaranteed to be delivered.

Event notification

Clients can receive events (messages) from objects synchronously or asynchronously. Client applications, such as stock-tickers and Wall Street trading screens, can benefit greatly from this service.

Ancillary COM+ Enhancements

Object pooling

Although an object might be finished with its task and the client who created it might have terminated, COM+ can elect to keep the object alive to service the next request. By using the same object to service the request of different clients, the cost of object creation and initialization can be minimized.

Role-based security

This is a simplified authentication mechanism. Users can be assigned roles, and different roles can have different levels of privilege and access to an object.

Synchronization (Activities)

COM+ components can be configured such that only one logical thread may execute their methods at a time. Although synchronization services have always been an innate part of COM threading, COM+ now externalizes this functionality so that objects may be configured by a developer or system administrator to receive concurrency protection with relatively low performance overhead.

Originally, there were two other core COM+ services that did not make it into the final release of Windows 2000. They were the following:

  • Load balancing. In times of high system stress when many objects are being used by many clients, COM+ automatically transfers the object creation and method fulfillment to another server that has more available resources. This, of course, is entirely transparent to the client. Microsoft chose not to add this feature to Windows 2000 Server, Advanced Server, and Professional, but relegated it to a future release of a product called AppCenter Server (which would later become Application Center 2000). As this book goes to print, Application Center 2000 is in Beta 2 and expected to be released in the fourth quarter of 2000.

  • In-Memory Database (IMDB). IMDB was intended to allow the middle-tier caching of relational database tables. Although it greatly improved data-access times, it was not sufficiently scalable; according to Microsoft, it did not meet the needs of most developers. It was removed from COM+ altogether and is no longer available in Windows 2000.

COM+ and the Declarative Model

New functionality is always welcome, but developers are accustomed to paying a certain price for it. New functionality usually means new APIs to learn and new rules to be aware of. Potentially, it creates unintentional specialists in a development team: "Bob is an expert at working with the security provider APIs"; "Give this project to Anna. She knows how to work with the Distributed Transaction Coordinator"; "Oh, you want to san asynchronous message. Talk to Mikhail, he's worked with MSMQ a lot." Developers are accustomed to paying a price for new functionality because they are primarily in a functional, process-oriented kind of mind-set. Developers expect new functionality to become available to the process through some form of functional API they will have to master. The API functions might exist in either an operating system or third-party DLL; but at the of the day, developers will need to know what new functions to call, in what order, in what DLL, and with what arguments.

COM+ changes this environment a bit, however, and challenges the assumption that more functionality equals more work. If the API approach discussed previously can be thought of as functional, the COM+ paradigm can be termed declarative. The following defines these terms in a bit more detail:

  • Traditional functional. An application's environment is entirely contained in an operating system process (.EXE). This process space has no concept of the developer's programming intent, does not facilitate any form of communication or cooperation between other processes, and does not support or understand objects. Rather, the application is seen as a stream of assembly instructions and is given the absolute minimum of necessary operating system functionality—mainly processor time and an address space (4GB in the case of NT). The application is run entirely in this space, is completely in control of its own lifetime, and calls upon functionality by calling API functions provided by the operating system. Intent is communicated through calling functions. Metaphorically, it is very much like a large, empty, cold-storage warehouse that is entirely the developer's responsibility to fill.

  • COM+ declarative. COM+ runs objects in special surrogate processes, working with them to create, pool, and facilitate objects. Several programs and events are already in place for which an object can sign up. For example, if an object wants to receive method calls asynchronously, it need only sign up for this service or declare its intent, and COM+ provides it. Interestingly, the declaration is not a function call from within the object as you might expect. In fact, absolutely no code in the object itself states its preference to receive method calls asynchronously. Rather, you set this preference by clicking a check box in an administrative program that is native to Windows 2000.

Because a lot of traditional programming is replaced by declaring your intent with a GUI administration tool, you can think of COM+ as programming by wizard to some extent. Learning to use COM+ effectively has more to do with understanding the paradigm of each of the services it provides than mastering some series of functional steps. Then again, this is nothing new. Even hard-core C and C++ developers well versed in the black art of the Windows SDK could not help but be at least mildly impressed with Visual Basic 3.0. Programming VB 3.0 was fast, relatively simple, and a lot of boilerplate code was instantaneously generated by the Visual Basic form designer. On the down side, there was a lot of low-level functionality missing. The same can be said for COM+accepting the simplified abstraction of transactions, queuing, events, and object pooling offered by COM+, you must necessarily relinquish low-level control. However, if you have ever found yourself writing Visual Basic or Java to get something done quickly, this is a sacrifice you have already made and are clearly willing to accept.

The Difference Between COM and COM+

Simply stated, COM+ is an evolved version of COM—not a new technology. COM+ is almost everything that COM is (minus some appendages that are no longer necessary) plus an additional layer of services that ordinary COM objects can declare their intent to use. In short:

  • COM+ = COM – older, now unnecessary appendages + Event Notification + Object Pooling + Transactions + Queuing + Role Based Security

We will cover a few of these appendages in our review of COM in the next section.

In fact, ordinary COM objects can be COM+ objects and participate in at least some COM+ services with no change in code.

To be precise, COM+ is a fusion of traditional COM, MTS, and MSMQ. Just a couple of years ago, it was not uncommon to find a Microsoft-centric development shop working on a large-scale application incorporating the following:

  • COM. Objects written in different languages that can create and call methods on one another over a network.

  • MTS. Enables two or more COM objects to participate in a transaction so that if any object fails to make a change to one or more relational databases (or another type of Resource Manager), all the database work initiated by other objects in the transaction is rolled back.

  • MSMQ. Enables objects to send asynchronous messages that are guaranteed to arrive at the destination. COM is built on Remote Procedure Calls (RPC) and is inherently synchronous. Prior to COM+'s incorporation of MSMQ, it was impossible to call a method on a remote object without waiting for the function to complete and return.

A couple of years ago, I worked on a Wall Street trading application that incorporated all of the preceding technologies. Specifically, it was called a foreign exchange trading system( FX system). Basically, foreign exchange traders try to take advantage of any fluctuations in the relative value of different currencies to one another—that is, exchange rates. So, if you were able to buy a British pound (£) for $2 (United States dollars or USD) and the exchange rate changed such that the pound was now worth $2.15 (USD), in converting back you have just made 15 cents! Admittedly, this is not all that compelling an amount at this level; but when you are talking millions of dollars, small percentages add up. At any rate, we used MSMQ to submit trade requests (for example, buy $2,000 (USD) with Yen) and receive events (for example, changes in exchange rates). MTS was used so that different objects involved in changing the database (for example, debiting one account and crediting another) either all succeeded or all failed atomically. COM was used so that our objects could participate in MTS and MSMQ and with one another.

Unfortunately, at the time, MTS, MSMQ, and COM were very separate, distinct entities. They came on different CD-ROMs, had different help files and sample sources, and a different expertise was required to master each technology. Predictably, we had what amounted to an MSMQ team, an MTS team, and a COM team. COM+ unifies these technologies into one cohesive technology. From the developer's perspective, COM+ is basically COM in which the MSMQ and MTS functionality is still provided, but submerged in the operating system.

Fundamental Principles of COM and COM+

To summarize what I've said about COM+ so far, COM+ is COM with new services that are, for the most part, hidden from view and implicitly used by COM objects that declare their intentions to use them. This leaves the traditional COM in the spotlight. Although COM+ does enforce a few conditions and slightly reigns in some freedoms traditionally enjoyed by COM object authors, a good grounding and understanding of COM is the key factor in utilizing COM+ effectively. We will cover features specific to COM+ in depth through most of this book. First, however, we must go over the traditional facets of COM because COM+'s core architecture is, in fact, COM, as it is explained in Chapters 1 through 5. Developers who are already very familiar with COM development can skip ahead to the beginning of Part II, Chapter 4, "Threading and Apartment Models," where we begin our discussion of COM+-specific services.

Of all the courses my company teaches, our COM course is one of the most popular. There is a strange mysticism that surrounds COM and an unwarranted assumption that COM is a complex science, which can only be mastered by a few people. I believe that a host of needlessly complex books and articles exposing and blowing out of proportion very complex but relatively minor facets of COM add to this unfounded fear. Similarly, the evolving nature of COM means that COM carries some of the baggage of its previous incarnation (Object Linking and Embedding, or OLE). This has made it difficult for developers to know which facets in this large-breadth technology are key, which are ancillary, and which are no longer used at all. Learning COM is very much like trying to concentrate on one particular silver-fish in a huge school of them—the fast pitch and sway of the group will consistently confuse your focus. Therefore, let's bring up the whole school, drop it fluttering on the deck, and go through them one by one. Trust me, there really aren't too many fish, and COM isn't too hard to master.

Fundamentally, COM is a distributed object architecture. The basic premise is that you can write objects in any language, and if you (and your language) adhere to a few simple rules, your objects will be able to communicate with client applications that want to use them and with one another across process and network boundaries. All this comes courtesy of COM and Windows NT. Here are the basic rules.

Your object exports its functionality by means of interfaces. Interfaces are groups of logically related functions. Your object might have one or many interfaces, which means that your object has one or many groups of related functions. By way of example, imagine that you want to write a calculation object that can add, subtract, multiply, and divide, but also has some financial functions such as CalculateMortgagePayment, GetPrimeRate, and so on. All of these are essentially mathematical functions, but they actually represent two distinct groups of functions. There are standard mathematical functions (addition, subtraction, and so on), and there are financial functions (CalculateMortgagePayment, GetPrimeRate, and so on). Your object might be well advised to group these different functions into two distinct groups or interfaces: perhaps ICalc and IFinancial (the I stands for interface). In linguistic terms, for C++, you might have what is shown in Listing 1.1:

Changes from COM to COM+

Among other things, COM+ demands all components be in-process (that is, reside in DLLs) and have a type library. It also heavily discourages the use of thesingleton model (multiple clients connected to one object) and discourages the use ofthreads. These and other differences will be discussed later in this section.

Example 1.1.  A Calculation Object Supporting Two Interfaces in C++

class ICalc
{
public:
virtual Add()=0;
virtual Subtract()=0;

};

class IFinancial
{
public:
virtual GetMortgagePayment()=0;
virtual GetPrimeRate()=0;

};
class MyCalcObject: public ICalc, public IFinancial
{
//this class can be cast to either Calc or Financial because it is, in essence
//both classes. The reason will soon be made clear.

Add();
Subtract();
GetMortgagePayment();
GetPrimeRate();

};

C++ pseudo-client

MyCalcObject MyObj;
IFinancial * pFin;
ICalc * pCalc;

pFin=static_cast<IFinancial&gt;(MyObj);
pCalc=static_cast<ICalc&gt;(MyObj);

pCalc-&gt;Add();

If C++ is the implementation language, COM objects tto be multiply inherited from different abstract base classes. Interfaces are represented by abstract base classes in C++, and the actual implementation of the methods of the interfaces is the responsibility of the derived class. In the C++ school of thought, because the derived class is both ICalc and IFinancial, the derived class can therefore be cast to either of its base classes (interfaces) depending on which interface the client wants—ICalc or IFinancial. This is probably confusing, and the reasons for this structure might be entirely unclear at this point. A detailed explanation of how this works is found in Chapter 3, "COM Internals."

In Java, this structure is far more clear (for example, see Listing 1.2).

Example 1.2.  Interfaces as a Native Part of Java

// Interface declerations.
//These are analogous to abstract base classes in C++.

interface ICalc {
public void Add();
public void Subtract();
}

interface IFinancial {
public void GetMortgagePayment();
public void GetPrimeRate();
}

public class MyCalcObject implements ICalc, IFinancial
{
// Class constructor:
public MyCalcObject() { }

// Implementation for ICalc:

public void Add() { }
public void Subtract() { }

// Implmentation for IFinancial:
public void GetMortgagePayment() { }
public void GetPrimeRate() { }
}

Unlike C++, Java language incorporates the notion of an interface. Viewed in this way, it is clear that the object f MyCalcObject is the source of two interfaces: ICalc and IFinancial (see Listings 1.3 and 1.4, and a VB client in 1.5).

Example 1.3.  Java Pseudo-Client

import testcalc.*; // Imports all coclasses from the COM component

MyCalcObject MyObj;
ICalc pCalc;
IFinancial pFin;

MyObject = new MyCalcObject();
pCalc = MyObj;
pFin = MyObj;

Example 1.4.  Visual Basic Pseudo-Server

VB Class "ICalc" 'Classes are denoted by VB's IDE, the syntax used here
{                 'to denote classes is just for clarification purposes.
Function Add()

End Function

Function Subtract()

End Function
}

VB Class "IFinancial"
{

Public Function GetMortgagePayment()

End Function

Public Function GetPrimeRate()

End Function

}


VB Class "MyCalcObject"
{
implements ICalc
implements IFinancial

Public Function ICalc_add() As Variant
'add implementation
End Function

Public Function ICalc_subtract() As Variant
'add implementation
End Function

Public Function IFinancial_GetMortgagePayment()
'add implementation
End Function

Public Function IFinancial_GetPrimeRate()
'add implementation
End Function


}

Example 1.5. Listing Visual Basic Pseudo-Client

Dim FinInterface as IFinancial
Dim CalcInterface as ICalc
Dim MyObj as new MyCalcObject

'all the following will work
Set FinInterface= MyObj
Set CalcInterface=MyObj
Set FinInterface=CalcInterface
Set CalcInterface=FinInterface

CalcInterface.add 1,2

Like C++ and unlike Java, Visual Basic does not have the native concept of an interface. The net effect is simulated, however, by the keyword implements. When you see this word in Visual Basic, it always means that the class or object in which the implements keyword is found also exposes the object that follows the keyword as an interface. In other words, the object that has the implements keyword in it is claiming: "I am everything that I am; PLUS, I am also that which I claim to implement, and I guarantee that I will support its methods."

The Theoretical Reason for Interfaces

At this point, you might be puzzled as to what benefit interfaces provide. After all, why not just have an object that supports an array of all its different functions and forget about interfaces altogether? The answer is part theoretical and part practical. From a theoretical perspective, the argument for an object supporting one or many interfaces is obvious—a single object that supports a huge list of ungrouped functions is considered to be a blob by many object-oriented circles; it's just not good style. How would you like it if your local supermarket dumped all of the different items it sells in one big pile? (I'm assuming your supermarket doesn't do this, although mine comes dangerously close.) On the other hand, going to a different store for every category of food—the condiment store, the fruit store, the bread store, the meat store, and so on—would not be easy either. So in life, as in objects, an object (supermarket) best serves the customer when it is in a single geographic place yet offers different interfaces (food aisles grouped by category) of some core product (food).

Java goes so far as to incorporate interfaces into its very language. When you declare an interface in Java like the following, you do not provide any implementation for its methods:

interface ICalc {
public void Add();
public void Subtract();
}

Interface Implementation in C++ and VB

In C++, this role is undertaken by the abstract base class; in VB, it is done by using the implements keyword and referencing an interface that exists in a type library and/or another VB ActiveX class in the same project.

The entire purpose of the interface is to act as a kind of contract; when a derived class inherits from the interface, in effect it says, "This interface will be a facet of me. I will honor this contract by fulfilling every method of this interface." Why should a class take on this responsibility? Because if clients and servers agree on what an interface looks like, specifically what functions it has and what arguments those functions take, the client can think solely in terms of functionality of an object with respect to the interfaces it supports. It does not need to be concerned about the actual implementation of the object, its member variables, or any methods it might have that are not part of an interface. The client also doesn't need to worry whether the object's implementation language supports the concepts of objects at all (for example, COBOL or C). Interface-based programming enables you to write entirely generic clients that are only concerned about the functionality of an object and not about its size, member variables, derived class structure, and so on.

In short, clients of objects can think about objects as black boxes that support one or many interfaces. They can look at objects like the one shown in Figure 1.1.

The lollipop-box schematic shown in Figure 1.1 is typical of COM-style schematics. The lollipops represent interfaces, and the box represents the actual COM object whose inner workings are hidden. We have not discussed IUnknown yet, but all COM objects must support the IUnknown interface (a black box with one or many interfaces)—each of which groups one or more related functions.

Theserver object also benefits from using interfaces because it enables the server to implement this functionality any way it chooses. If we are writing our server in C++, we could implement our object as a derived class multiply inherited from abstract base classes. Alternatively, we could have a solitary class that is not derived from anything and instead contains one or many nested classes. In the former case, the class could cast itself to the interface requested by the client; in the latter case, it could simply pass back a pointer to the appropriate nested class. Either way, the client could care less and can be coded strictly in terms of the interface(s) that it knows the server object will ultimately support. The server, on the other hand, is free to implement the object any way it likes—just as long as it supports interfaces that conform to the client's expectations.

A COM object supporting two interfaces—each a grouping of related functions.

Figure 1.1. A COM object supporting two interfaces—each a grouping of related functions.

The Practical Reason for Interfaces

Theory aside, there are a few practical reasons why interfaces make sense in the context of distributed objects. When we talk about distributing an object, what are we really talking about? In COM, and CORBA for that matter, objects do not travel. When a client application on Machine A requests an object on Machine B, the object does not pack its bags, hop a flight over the network, and move onto Machine A. Rather, the object remains on Machine B, and a channel is opened between the client on Machine A and the object on Machine B. Although Machine A might think it has the object, it really has a proxy to the object. Calls made by Machine A on the object are remoted, or forwarded, to the stub of the object on Machine B.

This channel connecting the proxy and stub takes different forms. COM uses Remote Procedure Calls over TCP/UDP.

No matter what protocol or marshaling architecture (standard or custom) you select, the result is the same: Some channel of interprocess/internetwork communication is opened between the client and server object, and this is how they talk. In the case of COM, this channel is based entirely on RPC. If we take a brief look at RPC, it might become clear why interfaces play such an important role in COM.

RPC: The Origin of COM Interfaces?

Let us, for the moment, forget entirely about objects and go back a few years and look at an elegant solution to a classic distributed problem: how to call a method on one machine, but have it transparently execute and return values from another machine elsewhere on the network. The solution is RPC. RPC's premise is very simple. I call a function in my C program in an entirely ordinary way. This function, however, executes on another machine and returns its result to me on my machine. Neat trick, but how is it done?

Proxies and Stubs

A proxy is an entity that stands in for something else and appears to a client to be an actual instance of that something else. Usually, proxies package and then delegate all client requests made to them over the network to the actual entity on the server. On the server side, the proxy's packaged information is unpackaged by an entity referred to as a stub.In the case of RPC, a proxy function appears to the client to be a real, ordinary C function. When called, however, it packages the arguments and sends them to the stub on the server side, which unpacks them and gives them to the actual implementation of the function. Figure 1.2 shows this in greater detail.

RPC and Network Protocols

As mentioned before, COM-based RPC uses TCP/UDP as its base network transport service by default. This can be changed, however; RPC can work on IPX, NetBEUI, TCP/IP, UDP, and a number of others. RPC can also be removed altogether, and developers can define their own protocol using custom marshaling. Custom marshaling is an advanced maneuver and should only be done in rare circumstances. Although it is not discussed in detail in this book, for completeness a source code example of custom marshaling can be found in the book's companion source code.

An RPC call made to the Add() function on a client.

Figure 1.2. An RPC call made to the Add() function on a client.

RPC packages the arguments and sends them across the network to an RPC server. The stub of the RPC server unpacks the arguments and calls the real, server-side implementation of Add(). The first step is to decide what function or functions we want to remote in the first place. Let's use the previous Calculator example and say we want to call the following functions remotely on a server machine separate from the client:

int Add(int x, int y)
int Subtract(int x, int y)

Let's say that we have a client application (written in C) on Machine A that calls the Add() function. The Add() function actually executes on Machine B, but Machine A does not know that. As far as the programmer developing the client application is concerned, Add() is an ordinary C function just like any other, and the programmer of the client application does not code a call to this function any differently. So what do we need? Consider the following:

  • Client side. We need a C function Add() for the client that takes two integers and returns one integer. However, the body of this function should not actually perform the arithmetic. Rather, the body of this function should package up the arguments X and Y and somehow sthem over the network to the server running on Machine B where the actual work of the function (addition) will occur. Then, Add() should get the result from the server and make it available to the client application, just as if the addition had been done right there on the client. The fact that Add() looks to the client like the real function but is really just delegating the call over a network makes Add() a proxy function.

  • Server side. We need an application to listen to the network for requests from clients who will call their proxy Add() function. This server should provide the actual implementation for Add() and return the result back across the network to the waiting client.

Interface Definition Language

Theoretically, if RPC knows exactly what my function signatures look like (the number and byte size of the arguments) and whether they are passed by value or reference, RPC can conceivably create a proxy and stub function in C (or any language) for the client and server.

RPC (like COM) has a way of doing this; it is called Interface Definition Language(IDL). In the same way a C compiler generates assembly/machine code from a .C source file, the IDL compiler (MIDL.EXE) generates C code from an .IDL file. This generated code defines a proxy and stub .C file that is compiled with the client and server, respectively. Ignoring the generated files for the moment, let's take a look inside the IDL file.

If we define our functions in RPC IDL, we will have something similar to what is shown in Listing 1.6.

Example 1.6.  A Simple RPC IDL File

[ uuid (C2557720-CA46-1067-B31C-00DD010662DA),
]
interface Calc
{

int Add([in] int x,[in] int y);
int Subtract([in] int x, [in] int y);

}

For the moment, let's concentrate solely on the functions. They look like straight C prototypes except for the [in] tags. These tags simply say that the following argument is to be passed by value, so no value is expected to come back and change what the variable holds. RPC needs to know this because it ultimately generates argument-packing and unpacking code based on the argument types and their tags. Therefore, if the server seeks to change the value sent in by the client (indicated in IDL by the tag [in,out] and a variable is a pointer preceded by a *), it needs to generate additional code.

IDL looks a lot like ordinary C with the addition of tags that can remove ambiguities inherent in the C language. There are, as you can imagine, all manner of tags and special handling of strings, arrays, and so on. See the book's source code (http://www.newriders.com/complus) for some complicated examples of IDL. For now, let's widen our scope a bit and look at the other main part of the IDL (see Listing 1.7).

Example 1.7.  In Interfaces in Traditional RPC IDL

[ uuid (C2557720-CA46-1067-B31C-00DD010662DA),
]
interface Calc
{

This is interesting. IDL's requirement that we group our functions in an interface and give that interface a numeric identifier (this is called the interface's IID, which is a form of something called a Globally Unique Identifier [GUID]) seems to imply that the client needs to specify which specific interface it wants. RPC clients can be presented with many different interfaces from the server; each interface grouping logically related functions. The IDL for a complete Calculator object looks like the code shown in Listing 1.8.

Example 1.8. Complete IDL Listing with GUIDS.

[ uuid (C2557720-CA46-1067-B31C-00DD010662DA),
]
interface Calc
{

int Add([in] int x, [in] int y);
int Subtract([in] int x, [in] int y);

}
[ uuid (C2557721-CA46-1067-B31C-00DD010662DA),
]
interface Financial
{
double GetMortgagePayment([in] int period, [in] double face_amount, [in] double interest_rate );

double GetPrimeRate();


}

The client can look at the server made from this IDL as an object that supports two interfaces, Calc and Financial. The client can choose which one it wants at any given time or can use both at the same time. Conceptually, a server EXE that exposes one or more interfaces is very much like a classic black box object—it has one or more logical groupings of related functions (interfaces).

Compiling the IDL File

Now that we have an acceptable IDL file that describes the functions we want to remote, what's next? Here are the basic steps for compiling the IDL to generate proxy and stub files. By compiling these C files into the client and server applications, RPC becomes possible:

  1. Run MIDL.EXE with this IDL file as a command line argument. Assuming the IDL file was named CALC.IDL, MIDL generates the following files: CALC_C.C, CALC_S.C, and CALC.H.

  2. Include CALC_C.C in your client project (that's what the _C stands for, client). This file has the proxy functions for Add, Subtract, GetMortgagePayment, and GetPrimeRate. The client programmer calls these functions in the normal way and is unaware that he is actually just forwarding the call to the server.

  3. Include CALC_S.C in your server project. This contains the stubs for all the functions defined in your IDL file. Basically, stubs are a jumping off point; they simply serve to receive the marshaled arguments from the client, unpackage them, and immediately want to hand off the unpacked information to your implementation.

  4. Write implementations for Add, Subtract, GetMortgagePayment and GetPrimeRate. Sooner or later, someone actually has to write the real code body for these functions, right? So, you do this in the server. If you look at the generated _s.c file, you might notice that the stubs there reference the real versions of the functions that are definitely not in the generated files. It is your responsibility to write them. RPC is just the delivery guy; you have to make the pie.

  5. Run the server.

  6. Run the client.

What Does COM Have to Do with RPC?

COM has everything to do with RPC. Modern-day COM is built on RPC, and much of its architecture is tied heavily to it. The same compiler (MIDL.EXE) is used to generate traditional C RPC proxy and stub files, or COM C++ proxy files and TLB files (see the next section "From Traditional RPC to COM and COM+"). If you take the concepts discussed so far (an RPC server supports one or many interfaces, each one of which groups one or more logically related functions) and imagine substituting EXE for object, you will begin to understand COM.

RPC Details Omitted

I am leaving a few things out that are not pertinent right now. For example, the client needs to specify the protocol, IP address, and port, and then create a binding to the server. The server, on the other hand, needs to create a pool of threads, register its interface handle(s), and explicitly begin to listen for client connections. Take a look at the example in the book's companion source code (http://www.newriders.com/complus). You will find a complete RPC client and server that you can examine to get a sense of the initialization requirements for each.

There are many additions to traditional RPC where COM is involved, and we will soon cover most of them; but the basic idea behind each addition is the same. I have a client on Machine A that wants to instantiate and talk to an object on Machine B. In the same way that RPC solved this problem for C developers in the 1980s, Microsoft RPC has been updated to provide COM support for the object authors of today. In the simplest possible terms, you can think of the traditional multi-interfaced RPC server EXE as having just become an object supporting one or more interfaces. Although the client application has only a few more steps to go through (and more features to take advantage of); an RPC client binding to an RPC server is not all that different from a COM client creating and binding to a COM object.

From Traditional RPC to COM and COM+

RPC was good, but not good enough. It did a great job of interprocess/internetwork communications but was missing several important features. Following are the three features we deal with first:

  • Support for languages other than C

  • Support for objects, not just flat, ANSI-C style functions

  • Capability to automatically find and run server when client requests it

Let's deal with the first item: support for languages other than C. This is a tall order, especially given that MIDL.EXE generates proxy and stub files in C whose purpose in life is to be compiled into the client and server applications. To provide support for other languages, MIDL could, conceivably, generate files for other languages—but for which ones? Also, what about languages, such as Visual Basic, where a significant amount of logic and functionality is built into the development environment itself? What exactly would MIDL generate in this case?

Microsoft devised an answer that turned the problem around. Instead of producing files that supported different languages, other languages would have to learn to support the files MIDL generated. Don't laugh; this is a viable option when you're Microsoft. Thus, the concept of the type library was born. A type library (or TLB file) can be thought of as a universal, binary header file. A type library is really a compiled, tokenized form of an IDL file that serves the following two purposes:

  • A TLB contains all the function prototypes, interface definitions, andcoclasses and can be easily parsed and read by any application or development environment via Win32 functions. Acoclass is a collection of interfaces—a COM object, basically. We will covercoclasses in detail throughout the book. Languages such as Visual Basic can read type libraries and determine what methods and properties a COM object supports. This is what fuels IntelliSense in Visual Basic, Visual J++, and Visual C++ (see Figure 1.3).IntelliSense is a productivity enhancer that saves typing and reduces syntactical errors by providing pop-up lists of global functions, objects, constants, and member methods of classes. When you press the period after typing the name of class, a list of that class's methods will automatically appear, allowing you to choose a method. IntelliSense obtains this information by reading type libraries files.

    IntelliSense is a productivity enhancer that reads the type library and provides context-sensitive lists.

    Figure 1.3. IntelliSense is a productivity enhancer that reads the type library and provides context-sensitive lists.

  • A TLB is used formarshaling—that is, remoting the function calls. In short, the TLB is used to construct the proxy and stubs necessary for internetwork communications between client applications and objects. Unlike the traditional client/server C files of RPC, however, the TLB does not need to be compiled into the client and server. Rather, the COM subsystem can read the type library at runtime (when the client requests an object defined in the type library) and can set up all the necessary RPC structures at that time. In this way, the networking mechanics do not need to be compiled into the executables; thus, COM does not need to cater to the peculiarities of each particular language. Furthermore, by taking the remoting outside of the executables, you have effectively created a binary standard for remoteability. This is a central concept for COM and will come up again when we discuss OLE automation marshaling in Chapter 5, "Method Invocation and Marshaling."

Therefore, the type library both describes the different interfaces defined in an IDL file and provides the necessary networking support to enable the methods to be invoked over process and network boundaries. Can we assume that all COM objects will have a type library? Can we further assume that all COM development first begins with the creation of an IDL file? To answer the first question, if we suppress a few exceptions for the time being, the answer is yes. To function in COM+, COM objects always need type libraries, and every object is described in a type library somewhere. To answer the second question, do we always start with an IDL file? Well, kind of.

Rare Type Library Exceptions

From a purely technical perspective, a COM object can get away without a type library. In in-process (DLL based) objects accessed from C++ clients, interfaces can be called directly through what are calledvtables (discussed in Chapter 3) Also, a standard interface called IDispatch, which we will cover in the "Inheriting from IDispatch " section, can also allow objects to be called without a type library. Practically speaking, however, a type library has become essential.

IDL: the Beginning or the End?

If you understand the true purpose of IDL, you will rightfully assume that writing your IDL is the first step in authoring a COM object. After all, MIDL.EXE, which reads your IDL, looks at the interfaces and their methods and then generates the appropriate proxy/stub code you need to write your client and server. The IDL file is where you universally declare your interfaces and make your contract with the world about what interfaces your object will support. It then follows that writing the IDL file must come first. This is true when you are developing COM components in C++; but when developing in many other languages, such as Visual Basic, Visual J++, Delphi, PowerBuilder, JBuilder, and so on, you might never actually see an IDL file. The IDL file is generated behind the scenes by the development environments.

COM Objects in Visual C++

To illustrate how to implement COM and COM+ objects, let's take a look at how we would begin to write our Calculator object in Visual C++ and then in Visual Basic. Note that this object can operate outside of COM+ as a stand-alone COM object, but it will just as easily operate within COM+ as a COM+ object and be able to participate in COM+ services.

In Visual C++, you have a few options when authoring COM objects. The first option is to write them using the standard Win32 SDK with no help from wizards or class libraries. This is the purest way to do it, but it is also the slowest and most error-prone. Today, most developers useATL (Active Template Library), which is a template-based class library that exists to speed the development of COM objects. If you use ATL, the Visual C++ Integrated Development Environment (IDE) can aid you in your development. It provides a number of code generation wizards, classes, and so on that can encapsulate and abstract common tasks and leave you little else to do but write functionality. We are going to use ATL for some of our C++ demonstrations; but for now we are going to look at a C++ COM object written using the Win32 SDK.

The first step is to create a Win32 DLL project. COM+ objects always reside in DLLs. Although ordinary COM has a good deal of support for out-of-process or EXE servers, COM+ demands that your object lives in in-process (DLL) COM components. The reasons for this become apparent shortly; for now, take my word on this, and let's move to the next step.

There is a lot of boilerplate functionality that every COM component needs: four functions the DLL must implement and export, class factories (which are used to actually create COM objects), and additional boilerplate functionality. It is too early yet to delve into all of the details, so I am going to skip a lot for now. Let's assume generic boilerplate code has already been cut and pasted from the last successful COM DLL project and is present.

Include a blank IDL file into the project. In this IDL file, define your interfaces. For our Calculator object, we will write something such as that shown in Listing 1.9.

Example 1.9.  COM IDL Describing ICalc, IFinancial, and the CalcSDK Object

// comcalc.idl : IDL source for comcalc.dll
//
// This file will be processed by the MIDL tool to
// produce the type library (comcalc.tlb) and marshaling code.

import "oaidl.idl";
import "ocidl.idl";

[
object,
uuid(638094E5-758F-11d1-8366-0000E83B6EF3),
dual,
oleautomation,
helpstring("ICalc Interface"),
pointer_default(unique)
]
interface ICalc : IUnknown
{
[id(1), helpstring("method Add")] HRESULT Add([in] int x, [in] int y, [out,retval] int * r );
[id(2), helpstring("method Divide")] HRESULT Divide([in] int x, [in] int y, [out,retval] int * r);
};

[
uuid(638094E1-758F-11d1-8366-0000E83B6EF3),
version(1.0),
helpstring("calcsdk 1.0 Type Library")
]
library COMCALCLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(638094E0-758F-11d1-8366-0000E83B6EF3),
helpstring("Calc Class")
]
coclass CalcSDK
{
[default] interface ICalc; 
};
};

There is a lot of detail here, but we're going to focus on just one or two things for now. First, let's take a look at the interface declaration for ICalc (see Listing 1.10).

Example 1.10.  Every Interface Begins with an Attribute Group

[
object,
uuid(638094E5-758F-11d1-8366-0000E83B6EF3),
oleautomation,
helpstring("ICalc Interface"),
pointer_default(unique)
]
interface ICalc : IUnknown
{
[id(1), helpstring("method Add")]
HRESULT Add([in] int x, [in] int y, [out,retval] int * r );
[id(2), helpstring("method Subtract")]
HRESULT Subtract([in] int x, [in] int y,
[out,retval] int * r);
};

This is certainly far more complex than our RPC definition, which looked like that shown in Listing 1.11.

Example 1.11.  Standard Non-COM RPC Interface Declaration

 
[ uuid (C2557720-CA46-1067-B31C-00DD010662DA)
]
interface Calc
{

int Add([in] int x, [in] int y);
int Subtract([in] int x, [in] int y);

}

However, it doesn't seem that much more complex. The tags you see on top between the []s are often the same or very similar for every interface, and their meaning is not hard to fathom. The following gives a quick run-down of some of the more common tags, including some COM+-specific tags:

  • object. Denotes that this interface belongs to a COM object and is not an ordinary RPC interface. This changes the nature of the code generated by MIDL.

  • uuid. A 128-bit statistically unique number that uniquely identifies the following interface. Every interface has its own uuid. Technically, this is the interface's "identifier" or IID. Because IIDs are just GUIDs, you sometimes hear people refer to an interface's identifier as its GUID.

  • oleautomation. The methods of this interface use COM's built-in mechanism for packing and unpacking arguments over the network and remoting function calls.

  • helpstring. Appears in the user-friendly object browsers provided by many development environments.

  • pointer_default. Tells RPC how to chase down C-style pointers and arrays (if used).

The following COM+ Specific tags act as configuration hints for COM+. They are not strictly necessary, but they provide information that allows COM+ to automatically configure the components when they are first installed in COM+.

  • QUEUEABLE: Indicates that methods of the interface can be called asynchronously via queued components. Queued components are the subject of Chapter 10, "Queued Components." An interface marked with this tag will automatically be configured as "queued" when registered in COM+.

  • TRANSACTION_REQUIRED: Indicates that this component requires a transaction and forces this configuration on the component when installed in COM+.

  • TRANSACTION_SUPPORTED: Same as above, except transactions are not required but supported.

  • TRANSACTION_NOT_SUPPORTED: Same as above, however, transactions are not supported.

  • TRANSACTION_REQUIRES_NEW: Same as above, however, a new transaction must be created for such a component when instantiated.

These tags define the interface that follows so that MIDL (and COM+) know exactly how to deal with this particular interface. Let's take a look at the actual declaration of this interface:

interface ICalc : IUnknown

Notice that this seems to be using a C++/Java syntax for inheritance. ICalc seems to be derived from some interface called IUnknown. The simple truth is every interface must inherit from IUnknown. There are absolutely no exceptions to this rule. We will get into the anatomy of IUnknown in Chapter 3, but for now, look at IUnknown as a small interface (it only contains three functions) from which every other interface in all of "COM-dom" must inherit. This rule ensures that all interfaces will support the three IUnknown functions, and because all interfaces are implemented as C++ abstract base classes, it ensures that the object that derives from them will ultimately be responsible for providing an implementation for these functions.

The coclass

The next interesting section of the COM IDL file contains something we have never seen in our ordinary RPC IDL file—the coclass (see Listing 1.12).

Example 1.12.  IDL with coclass

[
uuid(638094E1-758F-11d1-8366-0000E83B6EF3),
version(1.0),
helpstring("calcsdk 1.0 Type Library")
]
library COMCALCLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(638094E0-758F-11d1-8366-0000E83B6EF3),
helpstring("Calc Class")
]
coclass CalcSDK
{
[default] interface ICalc;
};
};

So far, we have spent most our time talking about interfaces, but we haven't spent much time talking about the objects that will expose and implement these interfaces. This is exactly what a coclass represents—the object itself. In COM, a client first asks for an instance of a particular object and then asks for the interface that contains the functionality it wants. Therefore, this IDL says, "I have an object called CalcSDK, and that object will support the ICalc interface." If there were other interfaces declared in this IDL that made sense for CalcSDK to support, they would simply be listed in the coclass (see Listing 1.13).

Example 1.13. The CalcSDK coclass Supporting Interfaces


coclass CalcSDK
{
[default] interface ICalc;
interface IFinancial;
..other interfaces would be listed here
};

The last unexplained aspect of the IDL file is the library block:

Example 1.14. Library Block in the IDL File


[
uuid(638094E1-758F-11d1-8366-0000E83B6EF3),
version(1.0),
helpstring("calcsdk 1.0 Type Library")
]
library COMCALCLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(638094E0-758F-11d1-8366-0000E83B6EF3),
helpstring("Calc Class")
]

coclass CalcSDK
…

This is what is called thetype library, or library block. All coclasses must be defined inside this block. Interfaces, on the other hand, can be declared inside or outside of this block (for the ramifications of each, see the following sidebar "Declaring Interfaces Inside and Outside the Library Block").

You might notice that the type library has auuid—or as it is more commonly called, aGUID—as does the coclass within the block. This is because COM always refers to entities by their GUIDs (not by string names). The atomic, addressable elements of COM are type libraries, coclasses, and interfaces. coclass and interface GUIDs are necessary for clients to unambiguously reference the object and interface they want. Type library GUIDs are also important because a client application might want to load, browse, or otherwise manipulate an object's type library.

Implementing the coclass in in C++

Now that our IDL file is complete, we need to write an actual implementation of the coclass CalcSDK. This implementation must actually implement the methods of the interfaces it supports. This raises the following questions:

Declaring Interfaces Inside and Outside the Library Block

Interestingly, if interfaces are declared inside the type library block of an IDL file, MIDL does not generateC and H files when run on this IDL file. If, on the other hand, the interfaces are declared outside of the library block, MIDL does generate C and H files.There is no documentation on this convention, but this is probably because VB-generated type libraries always have their interface declarations inside the library block, whereas C++ COM components tend to declare their interfaces outside of and usually before the type library block. MIDL is, perhaps, assuming that you do not want anything other than a type library when a VB-style IDL file is in use.

  • What is the relation or binding between this IDL file and our C++ object?

  • How will COM link our C++ class implementation with the type library that will be generated from this IDL file?

In traditional RPC, MIDL generates proxy/stub files that are compiled into the client and server application; the same is true for COM based RPC. If we run MIDL.EXE on the COMCALC.IDL file, MIDL generates (among other things) a file called COMCALC.H. This is all we need to implement our coclass. In C++, our coclass implementation has the form shown in Listing 1.15.

Example 1.15. C++ implementation of a CalscSDK coclass

//calcsdkimpl.cpp, the C++ implementation of a COM object

#include "comcalc.h" //has the necessary C++ abstract base classes
//for all interfaces declared in the IDL file.
//This is a MIDL generated file.
class CalcSDK: public ICalc //the ICalc abstract base class was
//defined in the MIDL generated file
//"comalc.h"

{

public:
// IUnknown methods: ignore for now
//NOTE:STDMETHOD is a macro that, among a couple other things, expands to the
// keyword virtual. Below are the methods of IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);

//
STDMETHOD(Add)(int x, int y, int*r)
{
*r=x+y;
return S_OK;
}

STDMETHOD(Subtract)(int x, int y, int*r)
{
*r=x-y;
return S_OK;

}
COMCalc() { };

};

That's all there is to it—more or less. All of the scary, complex network goo is done by MIDL. You can imagine that the link between our C++ class, and the type library occurs in the MIDL-generated header file comcalc.h. This file is where MIDL creates an abstract base class for ICalc from which we are inheriting the lines shown in Listing 1.16.

Example 1.16.  Including comcalc

#include "comcalc.h" //has the necessary C++ abstract base classes
//for all interfaces
//declared in the IDL file.

class CalcSDK: public ICalc //the ICalc abstract
// base class was defined in the
// MIDL generated file
//after we compiled the IDL file.
{

After we implement all the methods of the interface in our implementation class, CalcSDK becomes a bona fide COM object. The TLB file is created at the same time comcalc.h is, and it will be used by COM to pack and unpack (marshal) arguments and remote method calls. The C++ implementation object, MIDL-generated header file, and TLB file combine to create a fully-functional COM object in a DLL.

Visual Basic

Developing a COM component in Visual Basic is far more straightforward. The IDL/Type Library layer is still present, but as we'll see, it is entirely hidden from view and created behind the scenes by the development environment. Let's begin by creating a new ActiveX DLL project as shown in Figure 1.4.

Next, we create a new class module and call it Calc (see Figure 1.5).

Project creation dialogue in Visual Basic.

Figure 1.4. Project creation dialogue in Visual Basic.

New class creation menu for Visual Basic.

Figure 1.5. New class creation menu for Visual Basic.

Now, we add the Add and Subtract functions to the class and provide an implementation (see Figure 1.6).

The last step is to compile our application. VB registers our component during the compilation process. There is now a new COM object (Calc) with one interface (also called Calc, or _Calc to be exact) available and ready to use on this machine. We can prove this by creating a new Visual Basic project (a standard EXE this time) and writing a client application for the new object.

After creating a new standard EXE Visual Basic project, select Project, References from the Visual Basic menu. Thislists all the type libraries of all COM components currently registered with the system. You will notice that VBCalc is now in this list as shown in Figure 1.7.

Class methods for Calc.

Figure 1.6. Class methods for Calc.

Visual Basic Registration

COM components must be registered before they can be used. This means that certain registry entries need to be made so that COM understands how to interact with the object. We will cover registration in Chapter 2, "COM Fundamentals."

The Project References dialogue box: a listing of registered type libraries.

Figure 1.7. The Project References dialogue box: a listing of registered type libraries.

By selecting VBCalc, you have brought this type library into the current project and extended the development environment to include the Calc object as if it were a native VB object. This is really no different than including a header file containing a class prototype in C++ or importing a package in Java—compilers need information about an object before they can create and use it. By selecting the VBCalc type library, Visual Basic can learn all it needs to know about Calc by simply reading the type library. We can now create a Calc object simply by saying this:

Dim MyCalc as New VBCalc.Calc

Microsoft introduced a new programmer's aide called IntelliSense with Visual Basic 5. IntelliSense can read Calc's (or any object's) type library and offer context-sensitive lists of methods or properties for an object as shown in Figure 1.8.

IntelliSense reads Calc's type library and offers some method suggestions.

Figure 1.8. IntelliSense reads Calc's type library and offers some method suggestions.

If this program is completed and run, an instance of the Calc object is created, used, and released when the client application exits.

It is important to realize that although we did not interact with an IDL file directly or run MIDL ourselves, a type library was created. The VB IDE created a type library behind the scenes when we compiled the component, and it continues to update this type library as we make changes and additions to the component. So, where is this type library? It is stuffed into the resources of the DLL.

Finding Registered Type Libraries

Every major development environment that advertises the capability to create COM components can probably create type libraries. Some development environments do so explicitly (Visual C++) in such a way that you can interact with the IDL. Others create type libraries implicitly (Visual Basic, Visual J++, Delphi, PowerBuilder, and so on). Whatever your development environment does, however, there will come a time when you need to wean yourself from the abstractions of your IDE and view the type library directly. Visual Basic, for example, encounters some positively ghastly problems and hiccups when developing COM components, particularly when incrementally adding/modifying interfaces and recompiling over time. At some point, you will be dead in your tracks and unable to determine a problem without bypassing VB and looking directly at the type library. How do you do this? The answer can be found in a free program calledOLEView.EXE.

OLEView.EXE ships and installs with Visual C++, but it is freely available on the Microsoft Web site at http://msdn.microsoft.com/downloads/sdks/platform/platform.asp. (You cannot download the utility by itself. It must be downloaded as part of the PlatformSDK.) Among many other things, this program enables you to browse the type libraries of all components currently registered on your system. There is no substitute for this. When a client application tells you that no such interface exists but you're sure it does, you need to look at the type library to confirm or deny your assumptions. This is easy to do with OLEView.EXE. Just run the program and put yourself in expert mode, as shown in Figure 1.9.

The Type Libraries section of OLEView.EXE.

Figure 1.9. The Type Libraries section of OLEView.EXE.

Next, click the Type Libraries portion of the tree and look for the type library you are interested in (See Figure 1.10). Right-click on it.

Now double-click on VBCalc and take a look at the resulting IDL shown in Figure 1.11.

Summary information for the VBCalc type library.

Figure 1.10. Summary information for the VBCalc type library.

Inspecting VBCalc's type library using OLEView.

Figure 1.11. Inspecting VBCalc's type library using OLEView.

This is the type library that our VB project generated, which is shown in Listing 1.17.

Example 1.17.  Reverse-Compiled IDL for VBCalc

// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: VBCalc.dll

[
uuid(10513762-A601-11D3-8090-00E0811008ED),
version(2.0)
]
library VBCalc
{
importlib("STDOLE2.TLB");
// Forward declare all types defined in this typelib
interface _Calc;

[
odl,
uuid(1051376B-A601-11D3-8090-00E0811008ED),
version(1.0),
hidden,
dual,
nonextensible,
oleautomation
]
interface _Calc : IDispatch {
[id(0x60030000)]
HRESULT Add(
[in] short x,
[in] short y,
[out,retval] short* );
[id(0x60030001)]
HRESULT Subtract(
[in] short x,
[in] short y,
[out, retval] short* );
};

[
uuid(10513764-A601-11D3-8090-00E0811008ED),
version(1.0)
]
coclass Calc {
[default] interface _Calc;
};
};

The The Library Block

Let's break this out by sections. The IDL file begins by declaring a type library block. All interfaces and coclasses can be defined in this block, so you can think of the type library block as containing all the elements that will be in the type library. Library blocks are one of the components of a type library that can be individually referenced, which is why they have a GUID (see Listing 1.18).

Example 1.18.  IDL Declaration Tags for a Type Library Block

// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: VBCalc.dll

[
uuid(10513762-A601-11D3-8090-00E0811008ED),
version(2.0)
]
library VBCalc
{

COM uses GUIDs behind the scenes to refer to type libraries—not string names. In VB, for example, when you browse the type libraries on your machine using Project, References, you see the names of the type libraries as shown in Figure 1.12. However, in reality, type libraries are listed in the registry as shown in Figure 1.13.

Therefore, you can see why type libraries are really referenced by GUID and not by name. In fact, it is possible to have two type libraries with identical names but different GUIDs.

Type libraries are listed in VB.

Figure 1.12. Type libraries are listed in VB.

Type libraries are listed in the registry by GUID.

Figure 1.13. Type libraries are listed in the registry by GUID.

Interface Block, Revisited

Moving past the type library definition, we can see our interface definition shown in Listing 1.19.

Example 1.19.  The VB-generated Calc Interface

 
[
odl,
uuid(1051376B-A601-11D3-8090-00E0811008ED),
version(1.0),
hidden,
dual,
nonextensible,
oleautomation
]
interface _Calc : IDispatch {

We have seen many of these tags before, but there are a few new ones: odl, hidden, dual, nonextensible, and oleautomation. Many of these tags are informational and not strictly necessary (hidden, dual, oleautomation), and one is a bit of a throwback to earlier days (odl). Let's go through them:

  • odl. This stands for Object Definition Language. There was a time early in COM's history when things called ODL (not IDL) files roamed the earth. ODL was the predecessor to IDL and was used before COM and RPC were fully fused to create what Microsoft trumpeted as DCOM, or Distributed COM. ODL files are not really used anymore except in Microsoft Foundation Class applications, which still coddle the older form of COM. At any rate, the odl tag should really be replaced by the object tag—the result would be the same.

  • hidden. This tag indicates that VB should not display the interface in its object browser or when employing IntelliSense. Note that a hidden interface can still be used, and variables of that type can be declared; it just won't be seen in browsers. In Visual Basic, the same effect can be achieved by preceding an interface name with the _ character. Visual Basic treats these interfaces as hidden.

  • nonextensible. This tag means the interface is not allowed to support new methods (or stop supporting old ones) at runtime. This might seem strange, but it is possible through something called late binding for objects to support new methods on-the-fly. This tag assures the client that this will not happen.

  • oleautomation. Simply put, this indicates that all of the arguments of all the methods use Visual Basic data types. COM knows exactly how to pack and unpack these data types to facilitate a remote method call. COM has what it calls an OLE automation marshaler that performs this task. This marshaler exists in a system DLL called oleaut32.dll. Therefore, this tag basically indicates that COM will use oleaut32.dll to marshal data over the network.

  • dual. This tag indicates that this interface supports early or late binding. We will be discussing this in Chapter 5.

Inheriting from IDispatch

The final puzzler of this section is the following line:

interface _Calc : IDispatch {

In the earlier section "Implementing COM Objects in Visual C++," I said all interfaces must inherit from IUnknown, and there are absolutely no exceptions. If this is true, why does Calc inherit from something called IDispatch? IDispatch inherits from IUnknown, so Calc is sure to inherit those three special methods that I have not yet explained (all in good time, I promise). IDispatch is an interface used to support what is called late binding, which we will talk about in Chapter 5. For now, just note that by inheriting from IDispatch, we are forcing the coclass that supports the interface Calc to also support the IDispatch interface. It will soon become clear why this is useful.

There is some significance to the underscore of the name Calc. This is a VBism—Visual Basic treats all interfaces that begin with a _'as hidden and does not display them in its object browser or IntelliSense. It is clearly VB's intent that the client not see or know about the interface Calc, which might seem odd given that COM's whole point is to think of objects solely in terms of the interfaces they support. Unfortunately, this won't be the last time Visual Basic will violate or complicate the COM paradigm: VB sends a lot of mixed messages when it comes to COM. Take a look at the coclass declaration shown in Listing 1.20.

Example 1.20.  The Calc coclass

[
uuid(10513764-A601-11D3-8090-00E0811008ED),
version(1.0)
]
coclass Calc {
[default] interface _Calc;
} ;

You might notice that there is an object called Calc, and it supports an interface called _Calc, which is hidden. Why is this? Because ultimately the VB client does not see the Calc object as a black box that supports an interface; instead it lumps the interface and the object together. The VB client looks like Listing 1.21.

Example 1.21.  Creating the Calc Object in VB

Dim MyCalc as new VBCalc.Calc

Msgbox myCalc.add(1,2)

If VB were true to COM, however, the code would be (but unfortunately isn't) like that shown in Listing 1.22.

Example 1.22. The way VB should do things but doesn't

Dim MyCalc as new VBCalc.Calc
Dim ICalc as _Calc

Set ICalc = Calc     'get the _Calc interface from the Calc object
Msgbox ICalc.add(1,2)

To summarize, VB lumps the default interface (this is what the [default] tag means) and the coclass together so that to the client it appears that the Calc object is a simple class that has functions. This isn't COM reality, but it is time you know: VB sometimes lies.

Method Declarations

Finally, let's look at _Calc's method declarations shown in Listing 1.23.

Example 1.23.  IDL for Add and Subtract declarations

[id(0x60030000)]
HRESULT Add(
[in] short x,
[in] short y,
[out, retval] short* );
[id(0x60030001)]
HRESULT Subtract(
[in] short x,
[in] short y,
[out, retval] short* );

Theid tag in front of each method is provided so the methods can be called by number, not just name. This is important for late binding, which is covered in Chapter 5.

Another interesting aspect of these prototypes is that they all return something called an HRESULT. An HRESULT is a four-byte integer used to indicate the success or failure of the function call. Interface methods cannot return data values as return values—only error codes in the form of HRESULT s. This certainly puts a damper on the party and seems to imply that the only way to return values from a method call is by passing variables by reference.

Fortunately, there is the[out,retval] tag. This tag indicates that the argument is really the return value of the function. When a development environment, such as VB, sees this tag in the type library, it can generate client-side code that performs some clever rearranging so that the following code is possible:

result=Calc.Add(1,2)

Technically, Add() returns an error code, not a data result. What we are seeing is an illusion created by the client-side development environment. So how can we determine if an error occurred? Exceptions. Exceptions are implemented differently in different languages, but they are basically a mechanism that allows errors to be thrown to some form of catcher/handler. In VB, exceptions take the form shown in Listing 1.24.

Example 1.24.  Exception Handling in VB

on error goto eh

result=Calc.Add(1,2)

exit sub
eh:
msgbox "An Error Occurred!"

In C++

try
{
ICalc-&gt;Add(1,2);

}
catch(_com_error ce)
{

MessageBox( ce.Description );
}

Raw Functions

In Visual C++, you can use exceptions or you can use what are called Raw functions, where the true nature of the method is preserved and can be called in the manner shown in Listing 1.25.

Example 1.25.  Using Raw Functions in C++

int result=0;
HRESULT hr=ICalc-&gt;raw_Add(1,2,&result);

Summary

COM+ is the next evolutionary step from COM: It extends the basic architecture of traditional COM by incorporating additional enterprise-scale services that COM+ objects can take advantage of. However, the more things change, the more things stay the same. COM+ inherits much of the traditional COM architecture including the following:

  • The use of interfaces to logically group related methods

  • The use of type libraries to describe objects, interfaces, and their methods

  • Dependence on RPC for remote method calls from client-to-server object

  • A separation of interface and implementation

In the next chapter, we'll go one level deeper and discuss how COM/COM+ objects are actually implemented.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.94.156