| Tenouk C & C++ | MFC Home | COM Part 4 | Automation 1 | Download | Site Index |


 

 

 

 

 

The Component Object Model - COM part 5

 

 

 

 

 

 

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. MFC COM Client Programs

  2. Containment and Aggregation vs. Inheritance

  3. Note: The IUnknown Interface

 

 

MFC COM Client Programs

 

Basically, writing an MFC COM client program is a no-brainer. You just use AppWizard to generate a normal application. Add the following line in StdAfx.h.

 

#include <afxole.h>

 

 

COM and MFC - C++ code snippet

 

Listing 10.

 

Then add the following line at the beginning of the application class InitInstance() member function.

 

AfxOleInit();

 

COM and MFC - C++ code snippet

Listing 11.

 

You're now ready to add code that calls CoGetClassObject().

 

The MYMFC28C Example: An MFC COM Client

 

The MYMFC28C example is an MFC program that incorporates a true COM version of the client code you saw in MYMFC28A. This is a generic AppWizard MFC Single Document Interface (SDI) EXE program with an added #include statement for the MFC COM headers and a call to AfxOleInit(), which initializes the DLL. A Spaceship option on an added Test menu is mapped to the view class handler function, OnTestSpaceship(). The project also contains a copy of the MYMFC28B component's Interface.h file, shown in Listing 7. You can see an #include statement for this file at the top of mymfc28CView.cpp.

 

The following are MYMFC28C steps.

 

Figure 21: MYMFC28C – New EXE project dialog.

 

Figure 21: MYMFC28C – New EXE project dialog.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 22: MYMFC28C – AppWizard step 1 of 6.

 

Figure 22: MYMFC28C – AppWizard step 1 of 6.

 

Figure 23: MYMFC28C – AppWizard step 2 of 6.

 

Figure 23: MYMFC28C – AppWizard step 2 of 6.

 

Figure 24: MYMFC28C – AppWizard step 3 of 6.

 

Figure 24: MYMFC28C – AppWizard step 3 of 6.

 

Figure 25: MYMFC28C – AppWizard step 4 of 6.

 

Figure 25: MYMFC28C – AppWizard step 4 of 6.

 

Figure 26: MYMFC28C – AppWizard step 5 of 6.

 

Figure 26: MYMFC28C – AppWizard step 5 of 6.

 

Figure 27: MYMFC28C – AppWizard step 1 of 6.

 

Figure 27: MYMFC28C – AppWizard step 1 of 6.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

Figure 28: MYMFC28C – AppWizard project summary.

 

Figure 28: MYMFC28C – AppWizard project summary.

 

Add the following line in StdAfx.h:

 

#include <afxole.h>

 

COM and MFC - C++ code snippet

 

Listing 12.

 

Then add the following line at the beginning of the application class InitInstance() member function:

 

AfxOleInit();

 

COM and MFC - C++ code snippet

 

Listing 13.

 

Adding the Test Spaceship menu item for the testing purpose.

 

Figure 29: Adding Test menu.

 

Figure 29: Adding Test menu.

 

Figure 30: Adding Spaceship menu item.

 

Figure 30: Adding Spaceship menu item.

 

Figure 31: Adding message handler.

 

Figure 31: Adding message handler.

 

Adding the code that calls CoGetClassObject(). Click the Edit Code button and add the following code for CMymfc28CView::OnTestSpaceship().

 

void CMymfc28CView::OnTestSpaceship()

{

    // TODO: Add your command handler code here

    CLSID clsid;

    LPCLASSFACTORY pClf;

    LPUNKNOWN pUnk;

    IMotion* pMot;

    IVisual* pVis;

 

    HRESULT hr;

    if ((hr = ::CLSIDFromProgID(L"Spaceship", &clsid)) != NOERROR)

    {

        TRACE("unable to find Program ID -- error = %x\n", hr);

        return;

    }

    if ((hr = ::CoGetClassObject(clsid, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **) &pClf)) != NOERROR)

    {

        TRACE("unable to find CLSID -- error = %x\n", hr);

        return;

    }

 

    pClf->CreateInstance(NULL, IID_IUnknown, (void**) &pUnk);

    pUnk->QueryInterface(IID_IMotion, (void**) &pMot); // All three

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

                                                       //  should work

    TRACE("main: pUnk = %p, pMot = %p, pDis = %p\n", pUnk, pMot, pVis);

 

    // Test all the interface virtual functions

    pMot->Fly();

    int nPos = pMot->GetPosition();

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

    pVis->Display();

 

    pClf->Release();

    pUnk->Release();

    pMot->Release();

    pVis->Release();

    AfxMessageBox("Test succeeded. See Debug window for output.");

}

 

COM and MFC - C++ code snippet

 

Listing 14.

 

Define the GUIDs for our custom classes and interfaces of the spaceship objects in the CMymfc28CView.cpp at the top of the class implementation.

// {692D03A4-C689-11CE-B337-88EA36DE9E4E}

static const IID IID_IMotion =

{ 0x692d03a4, 0xc689, 0x11ce, { 0xb3, 0x37, 0x88, 0xea, 0x36, 0xde, 0x9e, 0x4e } };

 

// {692D03A5-C689-11CE-B337-88EA36DE9E4E}

static const IID IID_IVisual =

{ 0x692d03a5, 0xc689, 0x11ce, { 0xb3, 0x37, 0x88, 0xea, 0x36, 0xde, 0x9e, 0x4e } };

 

COM and MFC - C++ code snippet

 

Listing 15.

 

Copy (not add to the project) the Interface.h file from MYMFC28B project into the MYMFC28C project directory. Put the #include statement in the CMymfc28CView.cpp.

 

COM and MFC - C++ code snippet

 

Listing 16.

 

Register the previous mymfc28B.dll using the regsvr32 (regsvr for older Windows OS) utility at command prompt.

 

Figure 32: Registering DLL using regsvr32.

 

Figure 32: Registering DLL using regsvr32.

 

Build and run MYMFC28C program through the debugger. Select the Test Spaceship menu and see the TRACE in the Debug Window.

 

 

Figure 33: MYMFC28C output.

 

Figure 33: MYMFC28C output.

 

Figure 34: MYMFC28C in action.

 

Figure 34: MYMFC28C in action.

 

The following messages seen through the Debug Window.

...

...

Loaded symbols for 'F:\mfcproject\mymfc28B\Debug\mymfc28B.dll'

CSpaceship ctor

{692D03A4-C689-11CE-B337-88EA36DE9E4E} - CSpaceship::XMotion::QueryInterface

{692D03A5-C689-11CE-B337-88EA36DE9E4E} - CSpaceship::XMotion::QueryInterface

main: pUnk = 00423FEC, pMot = 00423FEC, pDis = 00423FF0

CSpaceship::XMotion::Fly

m_nPosition = 100

m_nAcceleration = 101

CSpaceship::XMotion::GetPosition

m_nPosition = 100

m_nAcceleration = 101

nPos = 100

CSpaceship::XVisual::Display

m_nPosition = 100

m_nColor = 102

CSpaceship::XMotion::Release

CSpaceship::XMotion::Release

CSpaceship::XVisual::Release

CSpaceship dtor

Info: AfxDllCanUnloadNow returning S_OK

The thread 0x9DC has exited with code 0 (0x0).

The program 'F:\mfcproject\mymfc28C\Debug\mymfc28C.exe' has exited with code 0 (0x0).

 

To test the client and the component, you must first run the component to update the Registry. Several utilities such as regsvr32 can be used to do this. Both client and component show their progress through TRACE calls, so you need the debugger. You can run either the client or the component from the debugger. If you try to run the component, you'll be prompted for the client pathname as shown below.

 

Figure 35: Client program is required when running the component standalone.

 

Figure 35: Client program is required when running the component standalone.

 

Figure 36: Browsing and selecting the client program.

 

Figure 36: Browsing and selecting the client program.

 

Figure 37: MYMFC28C in action.

 

Figure 37: MYMFC28C in action.

 

In either case, you don't have to copy the DLL because Windows finds it through the Registry.

 

Containment and Aggregation vs Inheritance

 

In normal C++ programming, you frequently use inheritance to factor out common behavior into a reusable base class. The CPersistentFrame class (discussed in Module 9) is an example of reusability through inheritance. COM uses containment and aggregation instead of inheritance. Let's start with containment. Suppose you extended the spaceship simulation to include planets in addition to spaceships. Using C++ by itself, you would probably write a COrbiter base class that encapsulated the laws of planetary motion. With COM, you would have "outer" CSpaceship and CPlanet classes plus an "inner" COrbiter class. The outer classes would implement the IVisual interface directly, but those outer classes would delegate their IMotion interfaces to the inner class. The result would look something like this.

 

Figure 38: COM Containment.

 

Figure 38: COM Containment.

 

Note that the COrbiter object doesn't know that it's inside a CSpaceship or CPlanet object, but the outer object certainly knows that it has a COrbiter object embedded inside. The outer class needs to implement all its interface functions, but the IMotion functions, including QueryInterface(), simply call the same IMotion functions of the inner class. A more complex alternative to containment is aggregation. With aggregation, the client can have direct access to the inner object's interfaces. Shown here is the aggregation version of the space simulation.

 

Figure 39: COM aggregation.

 

Figure 39: COM aggregation.

 

The orbiter is embedded in the spaceship and planet, just as it was in the containment case. Suppose the client obtains an IVisual pointer for a spaceship and then calls QueryInterface() for an IMotion pointer. Using the outer IUnknown pointer will draw a blank because the CSpaceship class doesn't support IMotion. The CSpaceship class keeps track of the inner IUnknown pointer (of its embedded COrbiter object), so the class uses that pointer to obtain the IMotion pointer for the COrbiter object.

Now suppose the client obtains an IMotion pointer and then calls QueryInterface() for IVisual. The inner object must be able to navigate to the outer object, but how? Take a close look at the CreateInstance() call back in Figure 24-10. The first parameter is set to NULL in that case. If you are creating an aggregated (inner) object, you use that parameter to pass an IUnknown pointer for the outer object that you have already created. This pointer is called the controlling unknown. The COrbiter class saves this pointer in a data member and then uses it to call QueryInterface() for interfaces that the class itself doesn't support. The MFC library supports aggregation. The CCmdTarget class has a public data member m_pOuterUnknown that holds the outer object's IUnknown pointer (if the object is aggregated). The CCmdTarget member functions ExternalQueryInterface(), ExternalAddRef(), and ExternalRelease() delegate to the outer IUnknown if it exists. Member functions InternalQueryInterface(), InternalAddRef(), and InternalRelease() do not delegate.

 

The IUnknown Interface info

 

As discussed previously, the IUnknown interface defines three member functions that must be implemented for each object that is exposed. The prototypes for these functions reside in the header file, Ole2.h.

These functions provide the fundamental interface through which OLE can access objects. The following Tables are the information summary for those functions.

 

Item

Description

Function

IUnknown::QueryInterface

Use

Returns a pointer to a specified interface on an object to which a client currently holds an interface pointer. This function must call IUnknown::AddRef on the pointer it returns.

Prototype

HRESULT QueryInterface(REFIID iid, void ** ppvObject);

Parameters

iid - [in] Identifier of the interface being requested.

ppvObject - [out] Address of pointer variable that receives the interface pointer requested in riid. Upon successful return, *ppvObject contains the requested interface pointer to the object. If the object does not support the interface specified in iid, *ppvObject is set to NULL.

Return value

S_OK if the interface is supported, E_NOINTERFACE if not.

Include file

MFC.

Remark

For any one object, a specific query for the IUnknown interface on any of the object's interfaces must always return the same pointer value. This allows a client to determine whether two pointers point to the same component by calling QueryInterface on both and comparing the results. It is specifically not the case that queries for interfaces (even the same interface through the same pointer) must return the same pointer value. There are four requirements for implementations of QueryInterface() (In these cases, "must succeed" means "must succeed barring catastrophic failure."):

  • The set of interfaces accessible on an object through IUnknown::QueryInterface must be static, not dynamic. This means that if a call to QueryInterface() for a pointer to a specified interface succeeds the first time, it must succeed again, and if it fails the first time, it must fail on all subsequent queries.

  • It must be reflexive - if a client holds a pointer to an interface on an object, and queries for that interface, the call must succeed.

  • It must be symmetric - if a client holding a pointer to one interface queries successfully for another, a query through the obtained pointer for the first interface must succeed.

  • It must be transitive - if a client holding a pointer to one interface queries successfully for a second, and through that pointer queries successfully for a third interface, a query for the first interface through the pointer for the third interface must succeed.

Implementations of QueryInterface() must never check ACLs (Access Control List). The main reason for this rule is because COM requires that an object supporting a particular interface always return success when queried for that interface. Another reason is that checking ACLs on QueryInterface() does not provide any real security because any client who has access to a particular interface can hand it directly to another client without any calls back to the server. Also, because COM caches interface pointers, it does not call QueryInterface() on the server every time a client does a query.

 

Table 1.

 

 

Item

Description

Function

IUnknown::AddRef

Use

The IUnknown::AddRef method increments the reference count for an interface on an object. It should be called for every new copy of a pointer to an interface on a given object.

Prototype

ULONG AddRef(void);

Parameters

None.

Return value

Returns an integer from 1 to n, the value of the new reference count. This information is meant to be used for diagnostic/testing purposes only, because, in certain situations, the value may be unstable.

Include file

MFC.

Remark

Objects use a reference counting mechanism to ensure that the lifetime of the object includes the lifetime of references to it. You use IUnknown::AddRef to stabilize a copy of an interface pointer. It can also be called when the life of a cloned pointer must extend beyond the lifetime of the original pointer. The cloned pointer must be released by calling IUnknown::Release.

Objects must be able to maintain (231)-1 outstanding pointer references. Therefore, the internal reference counter that IUnknown::AddRef maintains must be a 32-bit unsigned integer. Call this function for every new copy of an interface pointer that you make. For example, if you are passing a copy of a pointer back from a function, you must call IUnknown::AddRef on that pointer. You must also call IUnknown::AddRef on a pointer before passing it as an in-out parameter to a function; the function will call IUnknown::Release before copying the out-value on top of it.

 

Table 2.

 

Item

Description

Function

IUnknown::Release

Use

Decrements the reference count for the calling interface on a object. If the reference count on the object falls to 0, the object is freed from memory.

Prototype

ULONG Release(void);

Parameters

None.

Return value

Returns the resulting value of the reference count, which is used for diagnostic/testing purposes only.

Include file

MFC

Remark

If IUnknown::AddRef has been called on this object's interface n times and this is the n+1th call to IUnknown::Release, the implementation of IUnknown::AddRef must cause the interface pointer to free itself. When the released pointer is the only existing reference to an object (whether the object supports single or multiple interfaces), the implementation must free the object. Aggregation of objects restricts the ability to recover interface pointers. Call this function when you no longer need to use an interface pointer. If you are writing a function that takes an in-out parameter, call IUnknown::Release on the pointer you are passing in before copying the out-value on top of it.

 

Table 3.

 

 

------------End-------------

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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 | COM Part 4 | Automation 1 | Download | Site Index |