| Tenouk C & C++ | MFC Home | Windows Message Processing & Multithreaded Programming 2 | COM Part 2 | Download | Site Index |


 

 

 

 

 

The Component Object Model - COM Part 1

 

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this tutorial are listed below. Don’t forget to read Tenouk’s small disclaimer. The supplementary notes for this tutorial are OLE, mfc, COleObjectFactory class and .Net.

  1. The Component Object Model - COM

  2. ActiveX Technology Background

  3. The Component Object Model

  4. The Problem That COM Solves

  5. The Essence of COM

  6. What Is a COM Interface?

  7. The IUnknown Interface and the QueryInterface() Member Function

  8. Reference Counting: The AddRef() and Release() Functions

  9. Class Factories

  10. The CCmdTarget Class

 

 

The Component Object Model - COM

 

The Component Object Model (COM) is the foundation of much of the new Microsoft ActiveX technology, and after five years it's become an integral part of Microsoft Windows. Now we already have COM ++ and most Windows programming will involve COM, so you'd better start learning it now. But where do you begin? You could start with the Microsoft Foundation Class classes for ActiveX Controls, Automation, and OLE, but as useful as those classes are, they obscure the real COM architecture. You've got to start with fundamental theory, and that includes COM and something called an interface.

Here you'll get the theory you need for the next six modules. You'll learn about interfaces and how the MFC library implements interfaces through its macros and interface maps.

 

An ActiveX Technology Background

 

The terminology is changing as fast as the technology, and not all groups within Microsoft can agree on how to use the terms ActiveX and OLE. Think of ActiveX as something that was created when the "old" OLE collided with the Internet. ActiveX includes not only those Windows features built on COM (which you'll study in this part of the book) but also the Microsoft Internet Information Server (Internet Information Services is a service used in Windows also having acronym IIS)) family and the WinInet programming interface.

Yes, OLE is still here, but once again it stands for Object Linking and Embedding, just as it did in the days of OLE 1. It's just another subset of ActiveX technology that includes odds and ends such as drag and drop. Unfortunately (or fortunately, if you have existing code), the MFC source code and the Windows API have not kept current with the naming conventions. As a result, you'll see lots of occurrences of "OLE" and "Ole" in class names and in function names, even though some of those classes and functions go beyond linking and embedding. In this part of the book, you might notice references to the "server" in the code generated by AppWizard. Microsoft has now reserved this term for database servers and Internet servers; "component" is the new term for OLE servers.

Bookstore computer sections are now full of books on OLE, COM, and ActiveX. We don't claim to offer that level of detail here, but you should come away with a pretty good understanding of COM theory. We've included a closer connection to the MFC library classes than you might see in other books, with the exception of MFC Internals (Addison-Wesley, 1996) by George Shepherd and Scot Wingo. The net result should be good preparation for the really heavy-duty ActiveX/COM books, including Kraig Brockschmidt's Inside OLE, 2nd ed. (Microsoft Press, 1995) and Don Box's Essential COM (Addison-Wesley, 1998). A good mid-level book is Dale Rogerson's Inside COM (Microsoft Press, 1997).

One more thing: don't expect this stuff to be easy. Kraig Brockschmidt reported "six months of mental fog" before he started understanding these concepts. A thorough knowledge of the C++ language is the minimum prerequisite. Don't be afraid to dig in and write code. Make sure you can do the easy things before getting into advanced areas like multithreaded COM, custom marshaling, and distributed COM (DCOM). As said by Microsoft, COM and .NET are complementary development technologies. See also what Wiki says about COM.

 

 

The Component Object Model

 

COM is an "industry-standard" software architecture supported by Microsoft, Digital Equipment Corporation, and many other companies. It's by no means the only standard. Indeed, it competes directly against other standards, such as The Common Object Request Broker Architecture, CORBA from the Open Software Foundation (OSF), maintained by Object Management Group (OMG). Some people are working to establish interoperability between COM and other architectures, but my guess is that COM will become the leading standard.

 

The Problem That COM Solves

 

The "problem" is that there's no standard way for Windows program modules to communicate with one another. "But," you say "what about the DLL with its exported functions, Dynamic Data Exchange (DDE), the Windows Clipboard, and the Windows API itself, not to mention legacy standards such as VBX and OLE 1? Aren't they good enough?" Well, no. You can't build an object-oriented operating system for the future out of these ad hoc, unrelated standards. With the Component Object Model, however, you can, and that's precisely what Microsoft is doing.

 

The Essence of COM

 

What's wrong with the old standards? A lot. The Windows API has too large a programming "surface area": more than 350 separate functions. VBXs don't work in the 32-bit world. DDE comes with a complicated system of applications, topics, and items. How you call a DLL is totally application-specific. COM provides a unified, expandable, object-oriented communications protocol for Windows that already supports the following features:

So what is COM? That's an easier question to ask than to answer. At DevelopMentor (a training facility for software developers), the party line is that "COM is love." That is, COM is a powerful integrating technology that allows you to mix all sorts of disparate software parts together at runtime. COM allows developers to write software that runs together regardless of issues such as thread-awareness and language choice.

COM is a protocol that connects one software module with another and then drops out of the picture. After the connection is made, the two modules can communicate through a mechanism called an interface. Interfaces require no statically or dynamically linked entry points or hard-coded addresses other than the few general-purpose COM functions that start the communication process. An interface (more precisely, a COM interface) is a term that you'll be seeing a lot of.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

What Is a COM Interface?

 

Before digging into the topic of interfaces, let's re-examine the nature of inheritance and polymorphism in normal C++. We'll use a planetary-motion simulation (suitable for NASA or Nintendo) to illustrate C++ inheritance and polymorphism. Imagine a spaceship that travels through our solar system under the influence of the sun's gravity. In ordinary C++, you could declare a CSpaceship class and write a constructor that sets the spaceship's initial position and acceleration. Then you could write a non-virtual member function named Fly() that implemented Kepler's laws to model the movement of the spaceship from one position to the next, let say, over a period of 0.1 second. You could also write a Display() function that painted an image of the spaceship in a window. The most interesting feature of the CSpaceship class is that the interface of the C++ class (the way the client talks to the class) and the implementation are tightly bound. One of the main goals of COM is to separate a class's interface from its implementation.

If we think of this example within the context of COM, the spaceship code could exist as a separate EXE or DLL (the component), which is a COM module. In COM the simulation manager (the client program) can't call Fly() or any CSpaceship constructor directly: COM provides only a standard global function to gain access to the spaceship object, and then the client and the object use interfaces to talk to one another. Before we tackle real COM, let's build a COM simulation in which both the component and the client code are statically linked in the same EXE file. For our standard global function, we'll invent a function named GetClassObject().

If you want to map this process back to the way MFC works, you can look at CRuntimeClass, which serves as a class object for CObject-based classes. A class object is a meta-class (either in concept or in form). In this COM simulation, clients will use this global single abstract function (GetClassObject()) for objects of a particular class. In real COM, clients would get a class object first and then ask the class object to manufacture the real object in much the same way MFC does dynamic creation. GetClassObject() has the following three parameters:

 

BOOL GetClassObject(int nClsid, int nIid, void** ppvObj);

 

The first GetClassObject() parameter, nClsid, is a 32-bit integer that uniquely identifies the CSpaceship class. The second parameter, nIid, is the unique identifier of the interface that we want. The third parameter is a pointer to an interface to the object. Remember that we're going to be dealing with interfaces now, (which are different from classes). As it turns out, a class can have several interfaces, so the last two parameters exist to manage interface selection. The function returns TRUE if the call is successful.

Now let's go back to the design of CSpaceship. We haven't really explained spaceship interfaces yet. A COM interface is a C++ base class (actually, a C++ struct) that declares a group of pure virtual functions. These functions completely control some aspect of derived class behavior. For CSpaceship, let's write an interface named IMotion, which controls the spaceship object's position. For simplicity's sake, we'll declare just two functions, Fly() and GetPosition(), and we'll keep things uncomplicated by making the position value an integer. The Fly() function calculates the position of the spaceship, and the GetPosition() function returns a reference to the current position. Here are the declarations:

 

struct IMotion

{

    virtual void Fly() = 0;

    virtual int& GetPosition() = 0;

};

 

class CSpaceship : public IMotion

{

protected:

    int m_nPosition;

public:

    CSpaceship() { m_nPosition = 0; }

    void Fly();

    int& GetPosition() { return m_nPosition; }

};

 

The actual code for the spaceship-related functions, including GetClassObject() - is located in the component part of the program. The client part calls the GetClassObject() function to construct the spaceship and to obtain an IMotion pointer. Both parts have access to the IMotion declaration at compile time. Here's how the client calls GetClassObject():

 

IMotion* pMot;

GetClassObject(CLSID_CSpaceship, IID_IMotion, (void**) &pMot);

 

Assume for the moment that COM can use the unique integer identifiers CLSID_CSpaceship and IID_IMotion to construct a spaceship object instead of some other kind of object. If the call is successful, pMot points to a CSpaceship object that GetClassObject() somehow constructs. As you can see, the CSpaceship class implements the Fly() and GetPosition() functions and our main program can call them for the one particular spaceship object, as shown here:

 

int nPos = 50;

pMot->GetPosition() = nPos;

pMot->Fly();

nPos = pMot->GetPosition();

TRACE("new position = %d\n", nPos);

 

Now the spaceship is off and flying. We're controlling it entirely through the pMot pointer. Notice that pMot is technically not a pointer to a CSpaceship object. However, in this case, a CSpaceship pointer and an IMotion pointer are the same because CSpaceship is derived from IMotion. You can see how the virtual functions work here: it's classic C++ polymorphism.

Let's make things a little more complex by adding a second interface, IVisual, which handles the spaceship's visual representation. One function is enough - Display(). Here's the whole base class:

 

struct IVisual

{

    virtual void Display() = 0;

};

 

Are you getting the idea that COM wants you to associate functions in groups? You're not imagining it. But why? Well, in your space simulation, you probably want to include other kinds of objects in addition to spaceships. Imagine that the IMotion and IVisual interfaces are being used for other classes. Perhaps a CSun class has an implementation of IVisual but does not have an implementation of IMotion, and perhaps a CSpaceStation class has other interfaces as well. If you "published" your IMotion and IVisual interfaces, perhaps other space simulation software companies would adopt them.

Think of an interface as a contract between two software modules. The idea is that interface declarations never change. If you want to upgrade your spaceship code, you don't change the IMotion or the IVisual interface; rather, you add a new interface, such as ICrew. The existing spaceship clients can continue to run with the old interfaces, and new client programs can use the new ICrew interface as well. These client programs can find out at runtime which interfaces a particular spaceship software version supports.

Consider the GetClassObject() function as a more powerful alternative to the C++ constructor. With the ordinary constructor, you obtain one object with one batch of member functions. With the GetClassObject() function, you obtain the object plus your choice of interfaces. As you'll see later, you start with one interface and then use that interface to get other interfaces to the same object.

So how do you program two interfaces for CSpaceship? You could use C++ multiple inheritance, but that wouldn't work if two interfaces had the same member function name. The MFC library uses nested classes instead, so that's what we'll use to illustrate multiple interfaces on the CSpaceship class. Not all C++ programmers are familiar with nested classes, so I'll offer a little help. Here's a first cut at nesting interfaces within the CSpaceship class:

 

class CSpaceship

{

protected:

    int m_nPosition;

    int m_nAcceleration;

    int m_nColor;

public:

    CSpaceship()

        { m_nPosition = m_nAcceleration = m_nColor = 0; }

    class XMotion : public IMotion

    {

    public:

        XMotion() { }

        virtual void Fly();

        virtual int& GetPosition();

    } m_xMotion;

 

    class XVisual : public IVisual

    {

    public:

        XVisual() { }

        virtual void Display();

    } m_xVisual;

 

    friend class XVisual;

    friend class XMotion;

};

 

It might make sense to make m_nAcceleration a data member of XMotion and m_nColor a data member of XVisual. We'll make them data members of CSpaceship because that strategy is more compatible with the MFC macros, as you'll see later. Notice that the implementations of IMotion and IVisual are contained within the "parent" CSpaceship class. In COM, this parent class is known as the class with object identity. Be aware that m_xMotion and m_xVisual are actually embedded data members of CSpaceship. Indeed, you could have implemented CSpaceship strictly with embedding. Nesting, however, brings to the party two advantages:

 

  1. Nested class member functions can access parent class data members without the need for CSpaceship pointer data members, and

  2. The nested classes are neatly packaged along with the parent while remaining invisible outside the parent.

 

Look at the code below for the GetPosition() member function.

 

int& CSpaceship::XMotion::GetPosition()

{

    METHOD_PROLOGUE(CSpaceship, Motion) // makes pThis

    return pThis->m_nPosition;

}

 

Notice also the double scope resolution operators, which are necessary for nested class member functions. METHOD_PROLOGUE is a one-line MFC macro that uses the C offset of operator to retrieve the offset used in generating a this pointer to the parent class, pThis. The compiler always knows the offset from the beginning of parent class data to the beginning of nested class data. GetPosition() can thus access the CSpaceship data member m_nPosition.

Now suppose you have two interface pointers, pMot and pVis, for a particular CSpaceship object. Don't worry yet about how you got these pointers. You can call interface member functions in the following manner:

 

pMot->Fly();

pVis->Display();

 

What's happening under the hood? In C++, each class (at least, each class that has virtual functions and is not an abstract base class) has a virtual function table, which is otherwise known as a vtable. In this example, that means there are vtables for CSpaceship::XMotion and CSpaceship::XVisual. For each object, there's a pointer to the object's data, the first element of which is a pointer to the class's vtable. The pointer relationships are shown here.

 

A COM objects pointer relationship

 

Figure 1: A COM objects pointer relationship.

 

 

Theoretically, it's possible to program COM in C. If you look at the Windows header files, you'll see code such as this:

 

#ifdef __cplusplus

        // C++-specific headers

#else

        /* C-specific headers */

#endif

 

In C++, interfaces are declared as C++ structs, often with inheritance; in C, they're declared as C typedef structs with no inheritance. In C++, the compiler generates vtables for your derived classes; in C, you must "roll your own" vtables, and that gets tedious. It's important to realize, however, that in neither language do the interface declarations have data members, constructors, or destructors. Therefore, you can't rely on the interface having a virtual destructor, but that's not a problem because you never invoke a destructor for an interface.

 

The IUnknown Interface and the QueryInterface() Member Function

 

Let's get back to the problem of how to obtain your interface pointers in the first place. COM declares a special interface named IUnknown for this purpose. As a matter of fact, all interfaces are derived from IUnknown, which has a pure virtual member function, QueryInterface(), which returns an interface pointer based on the interface ID you feed it. Once the interface mechanisms are hooked up, the client needs to get an IUnknown interface pointer (at the very least) or a pointer to one of the derived interfaces. Here is the new interface hierarchy, with IUnknown at the top:

 

struct IUnknown

{

    virtual BOOL QueryInterface(int nIid, void** ppvObj) = 0;

};

 

struct IMotion : public IUnknown

{

    virtual void Fly() = 0;

    virtual int& GetPosition() = 0;

};

 

struct IVisual : public IUnknown

{

    virtual void Display() = 0;

};

 

To satisfy the compiler, we must now add QueryInterface implementations in both CSpaceship::XMotion and CSpaceship::XVisual. What do the vtables look like after this is done? For each derived class, the compiler builds a vtable with the base class function pointers on top, as shown here.

 

Classes’ virtual table (vtable)

 

Figure 2: Classes’ virtual table (vtable).

 

GetClassObject() can get the interface pointer for a given CSpaceship object by getting the address of the corresponding embedded object. Here's the code for the QueryInterface() function in XMotion:

 

BOOL CSpaceship::XMotion::QueryInterface(int nIid, void** ppvObj)

{

    METHOD_PROLOGUE(CSpaceship, Motion)

    switch (nIid) {

    case IID_IUnknown:

    case IID_IMotion:

        *ppvObj = &pThis->m_xMotion;

        break;

    case IID_IVisual:

        *ppvObj = &pThis->m_xVisual;

        break;

    default:

        *ppvObj = NULL;

        return FALSE;

    }

    return TRUE;

}

 

Because IMotion is derived from IUnknown, an IMotion pointer is a valid pointer if the caller asks for an IUnknown pointer. The COM standard demands that QueryInterface() return exactly the same IUnknown pointer value for IID_IUnknown, no matter which interface pointer you start with. Thus, if two IUnknown pointers match, you can assume that they refer to the same object. IUnknown is sometimes known as the "void*" of COM because it represents the object's identity.

The following code snippet is a GetClassObject() function that uses the address of m_xMotion to obtain the first interface pointer for the newly constructed CSpaceship object:

 

BOOL GetClassObject(int& nClsid, int& nIid, void** ppvObj)

{

    ASSERT(nClsid == CLSID_CSpaceship);

    CSpaceship* pObj = new CSpaceship();

    IUnknown* pUnk = &pObj->m_xMotion;

    return pUnk->QueryInterface(nIid, ppvObj);

}

 

Now your client program can call QueryInterface() to obtain an IVisual pointer, as shown here:

 

IMotion* pMot;

IVisual* pVis;

GetClassObject(CLSID_CSpaceship, IID_IMotion, (void**) &pMot);

pMot->Fly();

pMot->QueryInterface(IID_IVisual, (void**) &pVis);

pVis->Display();

 

Notice that the client uses a CSpaceship object, but it never has an actual CSpaceship pointer. Thus, the client cannot directly access CSpaceship data members even if they're public. Notice also that we haven't tried to delete the spaceship object yet, that will come shortly.

There's a special graphical representation for interfaces and COM classes. Interfaces are shown as small circles (or jacks) with lines attached to their class. The IUnknown interface, which every COM class supports, is at the top, and the others are on the left. The CSpaceship class can be represented something like this.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

------------------------------------------------------------------

Class and its interfaces.

 

Figure 3: Class and its interfaces.

 

Reference Counting: The AddRef() and Release() Functions

 

COM interfaces don't have virtual destructors, so it isn't cool to write code like the following:

 

delete pMot;  // pMot is an IMotion pointer; don't do this

 

COM has a strict protocol for deleting objects; the two other IUnknown virtual functions, AddRef() and Release(), are the key. Each COM class has a data member, m_dwRef, in the MFC library, which keeps track of how many "users" an object has. Each time the component program returns a new interface pointer (as in QueryInterface()), the program calls AddRef(), which increments m_dwRef. When the client program is finished with the pointer, it calls Release(). When m_dwRef goes to 0, the object destroys itself. Here's an example of a Release() function for the CSpaceship::XMotion class:

 

DWORD CSpaceship::XMotion::Release()

{

    METHOD_PROLOGUE(CSpaceship, Motion) // makes pThis

    if (pThis->m_dwRef == 0)

        return 0;

    if (--pThis->m_dwRef == 0)

    {

        delete pThis; // the spaceship object

        return 0;

    }

    return pThis->m_dwRef;

}

 

In MFC COM-based programs, the object's constructor sets m_dwRef to 1. This means that it isn't necessary to call AddRef() after the object is first constructed. A client program should call AddRef(), however, if it makes a copy of an interface pointer.

 

Class Factories

 

Object-oriented terminology can get a little fuzzy sometimes. Smalltalk programmers, for example, talk about "objects" the way C++ programmers talk about "classes." The COM literature often uses the term "component object" to refer to the object plus the code associated with it. COM carries with it the notion of a "class object," which is sometimes referred to as a "class factory". To be more accurate, it should probably be called an "object factory". A COM class object represents the global static area of a specific COM class. Its analogy in MFC is the CRuntimeClass. A class object is sometimes called a class factory because it often implements a special COM interface named IClassFactory. This interface, like all interfaces, is derived from IUnknown. IClassFactory's principal member function is CreateInstance(), which in our COM simulation is declared like this:

 

virtual BOOL CreateInstance(int& nIid, void** ppvObj) = 0;

 

Why use a class factory? We've already seen that we can't call the target class constructor directly; we have to let the component module decide how to construct objects. The component provides the class factory for this purpose and thus encapsulates the creation step, as it should. Locating and launching component modules and thus establishing the class factory, is expensive, but constructing objects with CreateInstance() is cheap. We can therefore allow a single class factory to create multiple objects.

What does all this mean? It means that we screwed up when we let GetClassObject() construct the CSpaceship object directly. We were supposed to construct a class factory object first and then call CreateInstance() to cause the class factory (object factory) to construct the actual spaceship object.

Let's properly construct the spaceship simulation. First we declare a new class, CSpaceshipFactory. To avoid complication, we'll derive the class from IClassFactory so that we don't have to deal with nested classes. In addition, we'll add the code that tracks references:

 

struct IClassFactory : public IUnknown

{

    virtual BOOL CreateInstance(int& nIid, void** ppvObj) = 0;

};

 

class CSpaceshipFactory : public IClassFactory

{

private:

    DWORD m_dwRef;

public:

    CSpaceshipFactory() { m_dwRef = 1; }

    // IUnknown functions

    virtual BOOL QueryInterface(int& nIid, void** ppvObj);

    virtual DWORD AddRef();

    virtual DWORD Release();

    // IClassFactory function

    virtual BOOL CreateInstance(int& nIid, void** ppvObj);

};

 

Next we'll write the CreateInstance() member function:

 

BOOL CSpaceshipFactory::CreateInstance(int& nIid, void** ppvObj)

{

    CSpaceship* pObj = new CSpaceship();

    IUnknown* pUnk = &pObj->m_xMotion;

    return pUnk->QueryInterface(nIid, ppvObj);

}

 

Finally, here is the new GetClassObject() function, which constructs a class factory object and returns an IClassFactory interface pointer.

 

BOOL GetClassObject(int& nClsid, int& nIid, void** ppvObj)

{

    ASSERT(nClsid == CLSID_CSpaceship);

    ASSERT((nIid == IID_IUnknown) || (nIid == IID_IClassFactory));

    CSpaceshipFactory* pObj = new CSpaceshipFactory();

    *ppObj = pObj; // IUnknown* = IClassFactory* = CSpaceship*

}

 

The CSpaceship and CSpaceshipFactory classes work together and share the same class ID. Now the client code looks like this (without error-checking logic):

 

IMotion* pMot;

IVisual* pVis;

IClassFactory* pFac;

GetClassObject(CLSID_CSpaceship, IID_IClassFactory, (void**) &pFac);

pFac->CreateInstance(IID_IMotion, &pMot);

pMot->QueryInterface(IID_IVisual, (void**) &pVis);

pMot->Fly();

pVis->Display();

 

Notice that the CSpaceshipFactory class implements the AddRef() and Release() functions. It must do this because AddRef() and Release() are pure virtual functions in the IUnknown base class. We'll start using these functions in the next iteration of the program.

 

The CCmdTarget Class

 

We're still a long way from real MFC COM-based code, but we can take one more step in the COM simulation before we switch to the real thing. As you might guess, some code and data can be "factored out" of our spaceship COM classes into a base class. That's exactly what the MFC library does. The base class is CCmdTarget, the standard base class for document and window classes. CCmdTarget, in turn, is derived from CObject. We'll use CSimulatedCmdTarget instead, and we won't put too much in it, only the reference-counting logic and the m_dwRef data member. The CSimulatedCmdTarget functions ExternalAddRef() and ExternalRelease() can be called in derived COM classes. Because we're using CSimulatedCmdTarget, we'll bring CSpaceshipFactory in line with CSpaceship, and we'll use a nested class for the IClassFactory interface.

We can also do some factoring out inside our CSpaceship class. The QueryInterface() function can be "delegated" from the nested classes to the outer class helper function ExternalQueryInterface(), which calls ExternalAddRef(). Thus, each QueryInterface() function increments the reference count, but CreateInstance() calls ExternalQueryInterface(), followed by a call to ExternalRelease(). When the first interface pointer is returned by CreateInstance(), the spaceship object has a reference count of 1. A subsequent QueryInterface() call increments the count to 2, and in this case, the client must call Release() twice to destroy the spaceship object.

One last thing, we'll make the class factory object a global object. That way we won't have to call its constructor. When the client calls Release(), there isn't a problem because the class factory's reference count is 2 by the time the client receives it. The CSpaceshipFactory constructor sets the reference count to 1, and ExternalQueryInterface(), called by GetClassObject() sets the count to 2.

 

 

Continue on next module.....

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

  1. DCOM at MSDN.

  2. COM+ at MSDN.

  3. COM at MSDN.

  4. Win32 process, thread and synchronization story can be found starting from Module R.

  5. MSDN MFC 7.0 class library online documentation.

  6. MSDN MFC 9.0 class library online documentation - latest version.

  7. MSDN Library

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | Windows Message Processing & Multithreaded Programming 2 | COM Part 2 | Download | Site Index |