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.
|
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>
Listing 10.
Then add the following line at the beginning of the application class InitInstance() member function.
AfxOleInit();
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 22: MYMFC28C – AppWizard step 1 of 6.
Figure 23: MYMFC28C – AppWizard step 2 of 6.
Figure 24: MYMFC28C – AppWizard step 3 of 6.
Figure 25: MYMFC28C – AppWizard step 4 of 6.
Figure 26: MYMFC28C – AppWizard step 5 of 6.
Figure 27: MYMFC28C – AppWizard step 1 of 6.
-----------------------------------------------------------------------------------
Figure 28: MYMFC28C – AppWizard project summary.
Add the following line in StdAfx.h:
#include <afxole.h>
Listing 12.
Then add the following line at the beginning of the application class InitInstance() member function:
AfxOleInit();
Listing 13.
Adding the Test Spaceship menu item for the testing purpose.
Figure 29: Adding Test menu.
Figure 30: Adding Spaceship menu item.
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.");
}
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 } };
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.
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.
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 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 36: Browsing and selecting the client program.
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.
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.
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.
QueryInterface() - Identifies which OLE interfaces the object supports.
AddRef() - Increments a member variable that tracks the number of references to the object.
Release() - Decrements the member variable that tracks the instances of the object. If an object has zero references, Release frees the object.
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."):
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:
MSDN What's New (MFC Feature Pack) - feature pack.
DCOM at MSDN.
COM+ at MSDN.
COM at MSDN.
Unicode and Multi-byte character set: Story and program examples.