Programming with Microsoft Visual C++.NET 6ed [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming with Microsoft Visual C++.NET 6ed [Electronic resources] - نسخه متنی

George Shepherd, David Kruglinski

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید








Revisiting COM


The most important concept to understand about COM programming is that it is interface-based. As you saw in Chapter 22, you don't need real COM or even Microsoft runtime support to use interface-based programming. All you need is some discipline.

Think back to the spaceship example in Chapter 22. We started out with a single class named CSpaceship that implemented several functions. Seasoned C++ developers usually sit down at the computer and start typing a class like this:

class CSpaceship {
void Fly();
int& GetPosition();
};

However, the procedure is a little different with interface-based development. Instead of writing the class directly, with interface-based programming you spell out an interface before implementing it. In Chapter 22, the Fly and GetPosition functions were moved into an abstract base class named IMotion:

struct IMotion {
virtual void Fly() = 0;
virtual int& GetPosition() = 0;
};

We then inherited the CSpaceship class from the IMotion interface, like this:

class CSpaceship : IMotion {
void Fly();
int& GetPosition();
};

Notice that at this point the motion interface has been separated from its implementation. When you practice interface development, the interface comes first. You can work on the interface as you develop it, making sure it's complete but not bloated. But once the interface has been published (that is, once a lot of other developers have started coding to it), it is frozen and can never change.

This subtle distinction between class-based programming and interface-based programming seems to introduce some programming overhead. But it turns out to be one of the key points for understanding COM. By collecting the Fly and the GetPosition functions in an interface, you develop a binary signature. That is, by defining the interface ahead of time and talking to the class through the interface, you give the client code a potentially language-neutral way of talking to the class.

Gathering functions together into interfaces is itself quite powerful. Say you want to describe something other than a spaceship—an airplane, for example. It's certainly conceivable that an airplane would also have Fly and GetPosition functions. Interface programming provides a more advanced form of polymorphism—polymorphism at the interface level, not only at the single-function level.

Separating interface from implementation is the basis of interface-based development. COM is centered on interface programming. It enforces the distinction between interface and implementation. In COM, the only way client code can talk to an object is through an interface. However, gathering functions together into interfaces isn't quite enough. One more ingredient is needed—a mechanism for discovering functionality at run time.


The Core Interface: IUnknown


The key element that makes COM different from ordinary interface programming is this rule: The first three functions of every COM interface are the same. The core interface in COM, IUnknown, looks like this:

struct IUnknown {
virtual HRESULT QueryInterface(REFIID riid, void** ppv) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
};

Every COM interface derives from this interface (which means that the first three functions of every COM interface you'll ever see will be QueryInterface, AddRef, and Release). To turn IMotion into a COM interface, you derive it from IUnknown, like this:

struct IMotion : IUnknown {
void Fly();
int& GetPosition();
};





Note

If you want these interfaces to work out-of-process, you have to make each function return an HRESULT. You'll see this when we cover attributed ATL later in the chapter.


AddRef and Release deserve some mention because they're part of IUnknown. AddRef and Release allow an object to control its own lifetime if it chooses to. As a rule, clients are supposed to treat interface pointers like resources: Clients acquire interfaces, use them, and then release them when they're done using them. Objects learn about new references to themselves via AddRef. Objects learn that they have been unreferenced through the Release function. Objects often use this information to control their lifetimes. For example, many objects self-destruct when their reference count reaches zero.

Here's how some client code might use the spaceship:

void UseSpaceship() {
IMotion* pMotion = NULL;
pMotion = GetASpaceship(); // This is a member of the
// hypothetical Spaceship
// API. It's presumably an
// entry point into some DLL.
// Returns an IMotion* and
// causes an implicit AddRef.
If(pMotion) {
pMotion->Fly();
int i = pMotion->GetPosition();
pMotion->Release(); // done with this instance of CSpaceship
}
}

The other (and more important) function within IUnknown is the first one: QueryInterface. QueryInterface is the COM mechanism for discovering functionality at run time. If someone gives you a COM interface pointer to an object and you don't want to use that pointer, you can use the pointer to ask the object for a different interface to the same object. This mechanism and the fact that interfaces remain constant once published are the key ingredients that allow COM-based software to evolve safely over time. The result is that you can add functionality to your COM software without breaking older versions of the clients running that software. In addition, clients will have a widely recognized means of acquiring that new functionality once they know about it.

For example, you add functionality to the implementation of CSpaceship by adding a new interface named IVisual. Adding this interface makes sense because you can have objects in three-dimensional space that move in and out of view. You might also have an invisible object in three-dimensional space (a black hole, for example). Here's the IVisual interface:

struct IVisual : IUnknown {
virtual void Display() = 0;
};

A client might use the IVisual interface like this:

void UseSpaceship() {
IMotion* pMotion = NULL;
pMotion = GetASpaceship(); // Implicit AddRef
if(pMotion) {
pMotion->Fly();
int i = pMotion->GetPosition();
IVisual* pVisual = NULL;
PMotion->QueryInterface(IID_IVisual, (void**) &pVisual);
// Implicit AddRef within QueryInterface
if(pVisible) {
pVisual->Display(); // uncloaking now
pVisual->Release(); // done with this interface
}
}
pMotion->Release(); // done with this instance of IMotion
}

Notice that the preceding code uses interface pointers very carefully: It uses them only if the interface was acquired properly, and then it releases the interface pointers when it is done using them. This is raw COM programming at the lowest level—you acquire an interface pointer, you use the interface pointer, and you release it when you're done with it.



/ 319