Nigel Thompson
Microsoft Developer Network Technology Group
March 6, 1995
Click to open or copy the files in the HOUSE1 sample application for this technical article.
Click to open or copy the files in the Animate library.
This technical article is the first in a series of articles that describe creating and using 32-bit Component Object Model (COM) objects with Visual C++™ and the Microsoft® Foundation Class Library (MFC). The sample application that accompanies this article, HOUSE, shows a simple COM object and an application that uses the object. A comparison is drawn between C++ objects and COM objects.
Note To run the HOUSE sample, you must first register the APPLIANCES in the registry. Copy HOUSE.EXE and APPLIANCES.DLL to your local disk. Edit APPLIANCES.REG to show the path to where you installed APPLIANCES.DLL. Type "REGEDIT -s APPLIANCES.REG" in a command window to make the registry entries.
Some time ago (nearly two years) my colleague Herman Rodent wrote a series of technical articles entitled "OLE for Idiots." In the final article, Herman essentially says, "OLE in C is too hard. Wait for MFC to do it for you. Then port your application to C++ and MFC." Well, the Microsoft® Foundation Class Library (MFC) has moved along very nicely since then, and the Windows 2000™ team is busy building the next generation of the Windows NT™ operating system on the Component Object Model (COM), so I thought it was about time to revisit the COM/OLE arena and see how things were going.
With the release of Visual C++™ version 2.1, creating your own COM objects based on the MFC classes has become very easy. In this article, we'll go through creating an application that uses a C++ object to implement a light-bulb object, and then we'll create a COM version of the same thing. In a later article, I'll be showing a bit more on how COM objects can be used, but for now, we'll just concentrate on creating a simple one.
You might well ask, "Why are you doing this? What is the purpose of these COM objects?" And indeed, those are very reasonable questions. I am doing this as an exercise to see exactly how hard it is to create a COM object of my own using the best tools I currently have available. In other words, is it practical to build your own COM objects today? As to what their purpose is, that's a longer answer and really outside the scope of this article, but let me quote my friend and colleague Dale Rogerson: "COM is a sort of object-oriented approach to DLLs. COM is to DLLs what C++ is to C." So if you view COM objects as a better way to reuse your code in a more object-oriented way, you'll have a good handle on why I'm interested in trying to build some COM objects of my own.
I created the HOUSE sample as a single-document-interface (SDI) application using the Microsoft Foundation Class Libraries (MFC) and AppWizard. The application is designed to be a type of house controller. The house has several appliances: TV, radio, and lights. The idea is to design a set of interfaces and objects to implement control of these appliances. For this article, we'll stick to just one appliance: a light bulb.
Step one is to get the framework up and running. The application framework is SDI without any printing, status bar, toolbar, or OLE support. Remember, this isn't an OLE project—it's only about COM objects.
I used AppWizard to create the framework and then removed the document and view classes, leaving just the CMainFrame class and the application class. I tidied up the menus and so on to remove redundant items. The application initialization code was modified to create the main window. The toolbar and status bar were removed, and the main window had code added so that it would size to fit the image. I added a picture of the house to the application's resources and then added code that uses my Animate library to draw the image of the house when a WM_ERASEBKGND message is sent to the main window. You can see the result in Figure 1 (below, just above the heading " 'That's Easy,' Says Sir Robin").
Having managed to get a picture of the house in the application, we now need to add some of the appliances—this is where the fun starts. To begin with, let's keep the objective very simple: Add a single light bulb that we can see.
We can achieve this goal in one of two ways. We could create a C++ class for the object and have it draw itself, or we could create a COM object with a drawing interface. The drawing interface would have a member function that draws the light bulb. Since we know a bit about C++ classes and maybe not so much about COM objects, let's create a C++ version first and then go on to create a COM object that does the same thing later.
Let's create a class called CLightBulb. We need to be able to draw the light bulb in the house somewhere, so it will need some sort of Draw function. Because the bulb is not rectangular, we must draw it with some parts of its image transparent. The CDIB class in the Animate library will draw one device-independent bitmap (DIB) transparently to another DIB surface (by copying nontransparent pixels). So we have all the tools we need to build this simple object.
The CLightBulb class is implemented in the LTBULB.H and LTBULB.CPPfiles. Here's the definition from LTBULB.H:
class CLightBulb : public CObject
{
public:
CLightBulb();
~CLightBulb();
BOOL Create(UINT uiResourceID, CPalette* pPal = NULL);
void Draw(CDC* pDC, int x, int y);
protected:
CDIB m_dibImage;
CPalette* m_pPal;
};
Notice that I have opted for a two-phase form of object creation—a constructor and a Create function. I like to create objects this way because it makes dealing with errors easier. The object has two member variables: a CDIB object to hold the image and a pointer to a CPalette object to keep track of the application's palette.
Let's look now at how the CLightBulb class is implemented in LTBULB.CPP, beginning with the constructor and destructor:
CLightBulb::CLightBulb()
{
m_pPal = NULL;
}
CLightBulb::~CLightBulb()
{
}
Gosh, Nigel, that's really complex. OK, so this is trivial stuff, but trust me—it will get harder. Here's the Create function that actually creates the object:
BOOL CLightBulb::Create(UINT uiResourceID, CPalette* pPal/*= NULL*/)
{
// Try to load the image resource.
if (!m_dibImage.Load(uiResourceID)) return FALSE;
// If a palette was supplied, map the image colors to the palette.
m_pPal = pPal;
if (pPal) {
m_dibImage.MapColorsToPalette(pPal);
}
return TRUE;
}
The image is loaded and mapped to the application's palette, which is no big deal because most of the work is done in the CDIB class. I love code reuse! Finally, let's see the Draw function:
void CLightBulb::Draw(CDC* pDC, int x, int y)
{
// Get a copy of the background the same size as the image.
int w = m_dibImage.GetWidth();
int h = m_dibImage.GetHeight();
CDIBSurface dsBuffer;
dsBuffer.Create(w, h, m_pPal);
CDC* pDCBuf = dsBuffer.GetDC();
CPalette* pOldPal = NULL;
if (m_pPal) {
pDCBuf->SelectPalette(m_pPal, FALSE);
pDCBuf->RealizePalette();
}
pDCBuf->BitBlt(0, 0,
w, h,
pDC,
x, y,
SRCCOPY);
// Flush the GDI queue before we do a direct pixel operation.
::GdiFlush();
// Get the palette index of the top-left pixel, which defines
// the image transparency color.
BYTE* pPixel = (BYTE*) m_dibImage.GetPixelAddress(0, 0);
ASSERT(pPixel);
// Draw the image transparently to the buffer.
m_dibImage.CopyBits(&dsBuffer,
0, 0,
w, h,
0, 0,
PALETTEINDEX(*pPixel));
// Now copy the composite image back to the main DC.
pDC->BitBlt(x, y,
w, h,
pDCBuf,
0, 0,
SRCCOPY);
// Tidy up.
if (pOldPal) pDCBuf->SelectPalette(pOldPal, FALSE);
}
The Draw function can be summarized thus: Copy the background to a buffer; draw the light-bulb shape on top; and finally, copy the composed image back to the screen.
To try out the CLightBulb class, let's put a light bulb in the center of the ceiling of the bottom room (see Figure 1 below). We'll add a CLightBulb object to the main window, and when the main window gets a WM_PAINT message, we'll ask the light-bulb object to draw itself. So in MAINFRM.H, we include a single member variable:
CLightBulb* m_pLightBulb1;
Note that this is a pointer to a CLightBulb object. I could have simply used an instance of the object, but I want to get used to the idea of pointers to objects, so that when we implement them as COM objects, we won't have to change too much.
When the main window is created, a new light bulb object is created:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
[.....]
// Create the main room light-bulb object.
m_pLightBulb1 = new CLightBulb;
ASSERT(m_pLightBulb1);
m_pLightBulb1->Create(IDR_DIB_LIGHT BULB_ON, &m_pal);
return 0;
}
Now all that's left to do is draw the object. The right time to do this is when the main window gets a WM_PAINT message. So we use ClassWizard to add a handler, and then we add a few lines of code:
void CMainFrame::OnPaint()
{
CPaintDC dc(this); // Device context for painting
CPalette* pOldPal = dc.SelectPalette(&m_pal, FALSE);
dc.RealizePalette();
// Ask the main room light bulb to draw itself.
ASSERT(m_pLightBulb1);
m_pLightBulb1->Draw(&dc, 186, 247);
dc.SelectPalette(pOldPal, FALSE);
}
The palette is selected into the device context (DC), and the object is asked to draw itself.
We need to do one more thing—ensure that the object is deleted when the main window goes away:
CMainFrame::~CMainFrame()
{
if (m_pLightBulb1) delete m_pLightBulb1;
}
So now we have a house with a light bulb. Figure 1 shows a screen shot of the story so far.
Figure 1. The house with a single C++ object light bulb
This is one of my favorite lines from my favorite movie. Brave Sir Robin declares crossing the bridge to be easy, right before he gets killed!
So with that thought in mind, let's move right along and create a COM object that will do what the CLightBulb class does, or at least, let's consider what this wonderful new object will look like and how it will behave.
Our light bulb does only one thing so far—draw itself. But an object drawing itself isn't really just an attribute of light bulbs. Many other objects could draw a rendition of themselves, too. So this idea of drawing yourself could be specified as something many different objects might like to support. So drawing yourself might be specified as an interface that an object supports. For example, we might describe the drawing interface like this:
Interface: Drawing
Function: void Draw(CDC* pDC, int x, int y)
So if we knew that an object supported the Drawing interface, we could (somehow or other) call its Draw function to get it to draw itself. I know this is all getting a bit tedious and probably seems rather simplistic, but bear with me.
When we built the CLightBulb class, the header file contained the definition of the class, and given this information, we could call the CLightBulb::Draw function with the correct parameters and so on. But how did we know where the Draw function was located in the C++ object? Well, we didn't. We relied on the C++ compiler to figure that out based on the class definition we supplied in the header file. So in designing an interface, we always need to define how member functions are accessed via that interface. In C++ we do this via the class definition. In implementing an interface for a COM object, we do it in a very similar way. In fact, it's so similar you probably won't see any difference, and the reason for that is that COM interfaces were designed to be trivial to implement in C++. That doesn't mean that you can't use a COM object interface from C. It just means that, if you are using it from C++, it's going to look very much like accessing a C++ class. So we could possibly define our drawing interface like this:
class IDrawing
{
public:
virtual void Draw(CDC* pDC, int x, int y) = 0;
};
This is an abstract base class that has one function defined. You can't create one of these objects because it has a pure virtual function in it ( the = 0 bit). Instead, you must derive your own class from this base class and implement the Draw function to suit your own needs. Someone wishing to call the Draw function in your class only needs to know the definition of IDrawing to understand how to get your object to draw itself.
At this point, the discussion could get very long-winded, and I want to avoid that and cut to the chase. So if you want to know more about COM objects and their interfaces, read Kraig Brockschmidt's book Inside OLE 2 and also Visual C++ 2.0 "Technical Note 38: MFC/OLE IUnknown Implementation," which describes how the IUnknown interface is implemented in the MFC classes. For now, let's just concentrate on implementing our IDrawing interface in C++.
If you search the MSDN Library CD, you'll find a good deal of documentation on remote procedure calls (RPC), Interface Definition Language (IDL), and the Microsoft IDL (MIDL) compiler. All of this stuff is designed to aid you in creating COM objects that work correctly even if they are on remote machines. For our example today, I am going to ignore any issues related to RPC—we can revisit that later.
If you were to look at Kraig's book, you'd see that he implements his interfaces using the macros supplied in the OLE header files. These macros expand to either a C or C++ definition, depending on how they are used. Inasmuch as our focus is on C++ and I don't want anything hidden by a set of macros, I'm going to define our interfaces directly in C++.
If we should decide later that these interfaces might be of use to someone else (possibly a C programmer), we would redefine them using the OLE macros. If we should go so far as to want to use these objects via RPC, we would revisit the definition and translate it into an IDL description and use the MIDL compiler to generate the interface and the marshaling dynamic-link libraries (DLLs) that we would need.
So the final definition of the IDrawing interface for today (from IDRAWING.H) is:
class IDrawing : public IUnknown
{
// Standard IUnknown interface functions
virtual HRESULT STDMETHODCALLTYPE QueryInterface(IID& riid,
LPVOID* ppvObj) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
virtual ULONG STDMETHODCALLTYPE Release(void) = 0;
// This interface
virtual HRESULT STDMETHODCALLTYPE Draw(CDC* pDC,
int x,
int y) = 0;
};
Note the definition of the AddRef, Release, and QueryInterface functions common to all COM object interfaces. I should warn you that although this all looks very hunky-dory, there might just be a Catch-22 here. If you look back at how I implemented CLightBulb::Draw, you'll see that the object's palette is used (via m_pPal). Because the IDrawing interface knows nothing of palettes (yet!), it probably isn't going to do what we need, but let's not worry about that yet. Let's get some sort of object built that uses this interface, and worry about the details of the implementation later.
Because this is a COM interface, it supports AddRef, Release, and of course, QueryInterface. If you look at the QueryInterface parameters, you'll see that the first one is a reference to an IID, which is the unique ID of this interface. So before we can get any further, we need to define an ID. To do this, run the GUIDGEN tool, which comes with Visual C++. (As a convenience, you might want to add this handy tool to your Visual C++ Tools menu). Run the tool, and select the second option (DEFINE_GUID(...)). Copy the result to the Clipboard.
Paste the generated code into the interface header file, and replace the section that says <<name>> with the interface name. Here's the definition of IDrawing's ID from the file IDRAWING.H:
// {15038B10-3D3E-11ce-9EE5-00AA004231BF}
DEFINE_GUID(IID_IDrawing,
0x15038b10, 0x3d3e, 0x11ce, 0x9e, 0xe5, 0x0, 0xaa, 0x0, 0x42, 0x31, 0xbf);
Now we have a unique identifier for this interface and a definition for the functions it supports. This interface file and others that we'll create are kept in a common place so that all components can have access to them. In the HOUSE samples, I used an INCLUDE directory to store this sort of information. Figure 2 shows the directory structure of my project.
Figure 2. The HOUSE project directory structure
Now let's look at how we create a light bulb as a COM object. We are going to create a DLL that will contain the image of the light bulb and all its drawing code. The DLL will export certain functions so that the system can find out which objects are in it and which interfaces they support. In OLE terms, this DLL will be an InProc server—that is, this object will function only as an add-on to an existing application. It can't be used on its own (it's not an executable), and it can't be used remotely from another machine (it has no RPC support), so it's pretty simple, but nonetheless useful.
Fortunately for us, the MFC guys have been burning the midnight oil, and Visual C++ version 2.1 includes much better AppWizard support for what we want. Specifically, the version 2.1 AppWizard allows us to create a DLL based on the MFC classes with those classes statically linked to the DLL. Soooo, I'm going to describe the process using Visual C++ 2.1.
The first step is to construct the framework; this is done using the Visual C++ 2.1 AppWizard. Run Visual C++ and ask to create a new project. In AppWizard, select "MFC AppWizard (DLL)" and check the option to use the static-link version of the MFC libraries (which is not the default); also check the OLE Automation box. Even if you're not going to use any OLE Automation features (and we're not here), you need this option to bring in some startup code. In the HOUSE1 sample, the light-bulb object is a part of the APPLIANCES project subdirectory.
Note It's very important that you use the static-link version of the MFC libraries and not the run-time DLL version because your COM object could be used by a non-MFC application (yes, there are a few of those out there). The MFC classes won't work correctly if you select the DLL option. (The DLL version needs to be working on behalf of an MFC application—they cooperate). For the same reason, any other code you are going to use in your COM object must use the static-link MFC run-time library, too. This was inconvenient for me because I wanted to use my own Animate library, which I had built to use the MFC DLL at run time to save space. So for this project, I had to build a special version that uses the MFC classes statically linked.
So once the framework is created, add your own headers, libraries, and so on to the project as usual. I usually build the framework at this point to make sure I haven't done anything stupid.
All COM (and OLE) IDs are 128-bit values. To save space in the application, these are built into the application one time only rather than, say, using a definition macro to reproduce them every time they are needed. The easiest way I have found to do this is to add all the ID header files to a single .CPP file and to add that .CPP file to the project. Here is GUIDS.CPP from the APPLIANCES project:
// GUIDS.CPP
#include "stdafx.h"
#include <initguid.h>
#include "..\include\appliancesid.h"
#include "..\include\idrawing.h"
GUIDS.CPP includes STDAFX.H, as all CPP files for the project do. It also includes INITGUID.H, which is a standard OLE header file that helps to ensure our ID values get built correctly once and once only. I've skipped ahead just a bit here; we'll get back to creating APPLIANCESID.H shortly.
Now we'll add the code that implements the light-bulb object. Use ClassWizard to create a new class derived from CCmdTarget. (Remember that you need to be running Visual C++ version 2.1.) We are deriving from CCmdTarget because that class has MFC's implementation of the IUnknown interface in it. I called my class CLightBulb. Be sure to check the OLE Automation and OLE Creatable options. If you will expose this object to other applications (such as Visual Basic), you might want to enter an external name (such as Light bulb) for the object. Reminder: Most Basic programmers don't know CThing from a hole in the ground.
When ClassWizard has finished creating the new files, take a look at them. In the .CPP file, you'll see a macro near the bottom that looks like this:
IMPLEMENT_OLECREATE(CLightBulb, "Light bulb", 0x3a015b30, 0x41fc, 0x11ce, 0x9e, 0xe5, 0x0, 0xaa, 0x0, 0x42, 0x31, 0xbf)
This macro implements the class factory support that will allow an external application to create one of your objects. Note that the ClassWizard generated a GUID for the class for you, so you don't need to run GUIDGEN for this object. We'll add the actual code for the implementation of the object in just a minute.
Now that our object has an ID, it's time to add the ID definition file for it that I spoke of earlier. Here's APPLIANCESID.H, which defines the light-bulb object:
// AppliancesID.h
#ifndef _CLSID_Light bulb_
#define _CLSID_Light bulb_
DEFINE_GUID(CLSID_Light bulb,
0x3a015b30, 0x41fc, 0x11ce, 0x9e, 0xe5, 0x0, 0xaa, 0x0, 0x42, 0x31, 0xbf);
#endif // _CLSID_Light bulb_
The ID value in here was simply cut and pasted from the IMPLEMENT_OLECREATE macro shown above.
Our new object needs to be entered in the system registry so that it can be located by applications wishing to use it. Doing this is very easy: Create a .REG file with the appropriate entries, then run REGEDIT to install the new data in the registry. Here's APPLIANCES.REG:
REGEDIT
HKEY_CLASSES_ROOT\CLSID\{3A015B30-41FC-11ce-9EE5-00AA004231BF}
= Light bulb Class
HKEY_CLASSES_ROOT\CLSID\{3A015B30-41FC-11ce-9EE5-0AA004231BF}
\InprocServer32 = c:\apps\ole\appliances\windebug\appliances.dll
The first HKEY. . . line is the human-readable description of the class, and the second HKEY. . . line describes the path to a 32-bit InProc server that implements the class. Note that the lines are split here for readability, but the file itself has only three lines of text in it. Also note that the path to the DLL shown here is the one I was using for development. In practice, your installation utility would need to set this path.
To register the new information, run REGEDIT:
REGEDIT -s APPLIANCES.REG
Now that we have all the framework and support files in place, we can implement the object's code. We'll start by adding the interface definitions and private member variables to the header file. Here's LIGHTBL.H, showing only the bits we need to add to what ClassWizard generated:
class CLightBulb : public CCmdTarget
{
[.....]
// Declare the interface map for this object.
DECLARE_INTERFACE_MAP()
// IDrawing interface
BEGIN_INTERFACE_PART(Drawing, IDrawing)
STDMETHOD(Draw)(CDC* pDC,int x, int y);
STDMETHOD(SetPalette)(CPalette* pPal);
END_INTERFACE_PART(Drawing)
// Member variables
CDIB m_dibImage;
CPalette* m_pPal;
};
As you can see, there are three additional sections. The DECLARE_INTERFACE_MAP macro provides the basic COM interface definition. (More information is in the MFC "Technical Note 38: MFC/OLE IUnknown Implementation" article.) Then we define the IDrawing interface itself using the BEGIN_INTERFACE_PART macro to mark the start of the interface and the END_INTERFACE_PART to mark the end. Each function supported by the interface is defined between these macros. Note the use of the OLE-defined STDMETHOD macro, which ensures that the function has the correct return type and calling convention. If your function returns something other than the standard HRESULT, you will need to use the STDMETHOD_ macro.
OK, so much for using yet more macros, but what do they do? This is covered in MFC Technical Note 38, but to summarize: These macros provide a definition of a nested class. In the case shown here, the nested class will be called XDrawing. The X is used to differentiate it from an interface or regular C++ class. The XDrawing class has AddRef, Release, and QueryInterface members defined automatically, so you don't need to define these yourself. You only need to define your own interface functions.
You might also note that I have rather sneakily included a SetPalette function here. I mentioned earlier (if you remember back that far!) that we might need to deal with palettes. Well, this is how I handled the problem. More about this palette issue later.
Finally, two member variables are declared, one to hold the image of the object and another to point to a palette to use in drawing the object.
Let's look now at how the object is implemented.
The first things to add to the .CPP file that ClassWizard generated are any header files we need. Here's the include set for the light-bulb object (from LIGHTBL.CPP):
#include "stdafx.h"
#include "Appliances.h"
#include "..\include\idrawing.h"
#include "lightbl.h"
#include "resource.h"
IDRAWING.H is the IDrawing interface definition, and RESOURCE.H is required because the light-bulb image is contained in a resource.
We need to add several chunks of code to provide for the basic COM interface support before we get to our own functions. The first thing required is the implementation of the interface map, which is done using another set of MFC macros:
BEGIN_INTERFACE_MAP(CLightBulb, CCmdTarget)
INTERFACE_PART(CLightBulb, IID_IDrawing, Drawing)
END_INTERFACE_MAP()
These macros implement a data table that the CCmdTarget class uses to implement the IUnknown interface. Again, see MFC Technical Note 38 for more details.
The next thing to add is the implementation of IUnknown for our IDrawing interface:
// IUnknown for IDrawing
STDMETHODIMP_(ULONG) CLightBulb::XDrawing::AddRef(void)
{
METHOD_PROLOGUE(CLightBulb, Drawing);
return pThis->ExternalAddRef();
}
STDMETHODIMP_(ULONG) CLightBulb::XDrawing::Release(void)
{
METHOD_PROLOGUE(CLightBulb, Drawing);
return pThis->ExternalRelease();
}
STDMETHODIMP CLightBulb::XDrawing::QueryInterface(REFIID riid, LPVOID* ppVoid)
{
METHOD_PROLOGUE(CLightBulb, Drawing);
TRACE("CLightBulb::XDrawing::QueryInterface\n");
return pThis->ExternalQueryInterface(&riid, ppVoid);
}
These functions are the implementation of the AddRef, Release, and QueryInterface functions that were declared for you in the XDrawing nested class. The METHOD_PROLOGUE macro is required to gain access to the containing class (which is derived from CCmdTarget). In particular, it allows you to use the pThis member variable to access the data and functions in your object.
Because this implementation is trivial, given the name of the interface class (IDrawing), it can be easily implemented as a macro set. See Tom Laird-McConnell's article ("Tom's Handy Dandy MFC/COM Recipe Book") for an example. I didn't use a macro here because I wanted you to see what's going on. Learning first, easy tools later.
Now we can add the code that implements our own interface functions. Here's the code for the Draw function:
STDMETHODIMP CLightBulb::XDrawing::Draw(CDC* pDC, int x, int y)
{
METHOD_PROLOGUE(CLightBulb, Drawing);
TRACE("CLightBulb::XDrawing::Draw");
// Get a copy of the background the same size as the image.
int w = pThis->m_dibImage.GetWidth();
int h = pThis->m_dibImage.GetHeight();
CDIBSurface dsBuffer;
dsBuffer.Create(w, h, pThis->m_pPal);
CDC* pDCBuf = dsBuffer.GetDC();
CPalette* pOldPal = NULL;
if (pThis->m_pPal) {
pDCBuf->SelectPalette(pThis->m_pPal, FALSE);
pDCBuf->RealizePalette();
}
pDCBuf->BitBlt(0, 0,
w, h,
pDC,
x, y,
SRCCOPY);
// Flush the GDI queue before we do a direct pixel operation.
::GdiFlush();
// Get the palette index of the top-left pixel, which defines
// the image transparency color.
BYTE* pPixel = (BYTE*) pThis->m_dibImage.GetPixelAddress(0, 0);
ASSERT(pPixel);
// Draw the image transparently to the buffer.
pThis->m_dibImage.CopyBits(&dsBuffer,
0, 0,
w, h,
0, 0,
PALETTEINDEX(*pPixel));
// Now copy the composite image back to the main DC.
pDC->BitBlt(x, y,
w, h,
pDCBuf,
0, 0,
SRCCOPY);
// Tidy up.
if (pOldPal) pDCBuf->SelectPalette(pOldPal, FALSE);
return NOERROR;
}
This is very similar to the code we used way back in the original CLightBulb C++ class at the beginning of this article—with a couple of additions. First, note the METHOD_PROLOGUE macro at the start, which links this function to its IUnknown implementation and provides the pThis member. Note, too, that this is a standard COM function that returns an HRESULT, and so the return value is of type HRESULT. Apart from these minor additions, the code is the same as we used before.
And finally, we need a way to tell the object which palette the application is using. In the original C++ class, I provided this via the Create member function as an argument. I could have done the same thing here, but I decided to implement it in a more obvious way and provide a SetPalette function, which can be called as many times as you like to map the object's image to the palette the application is currently using. Here's the implementation of SetPalette:
STDMETHODIMP CLightBulb::XDrawing::SetPalette(CPalette* pPal)
{
METHOD_PROLOGUE(CLightBulb, Drawing);
TRACE("CLightBulb::XDrawing::SetPalette");
if (!pPal) return E_INVALIDARG;
pThis->m_pPal = pPal;
// Reload the image and map it to the new palette.
pThis->m_dibImage.Load(IDR_DIB_LIGHT BULB_ON);
pThis->m_dibImage.MapColorsToPalette(pThis->m_pPal);
return NOERROR;
}
So now you have an object you can build.
Let's now turn our attention to how an application uses one of these fine new objects you have created. This is documented well in many places, including Kraig's book, so I will be brief here. In the HOUSE1 project's MAINFRM files, we add a new member variable:
IUnknown* m_pLight bulb2;
This is simply a pointer to an IUnknown interface, so it can point to any COM object. Now let's see how the object is created (from CMainFrame::OnCreate):
// Initialize the OLE support.
BOOL b = AfxOleInit();
ASSERT(b);
// Create a COM light bulb for one of the other rooms.
dprintf2("Creating COM light bulb");
HRESULT hr = ::CoCreateInstance(CLSID_Light bulb,
NULL,
CLSCTX_INPROC_SERVER,
IID_IUnknown,
(LPVOID*)&m_pLight bulb2);
if (FAILED(hr)) {
dprintf1("Failed to create object. SCODE: %8.8lXH (%lu)\n",
GetScode(hr),
GetScode(hr) & 0x0000FFFF);
m_pLight bulb2 = NULL;
}
ASSERT(m_pLight bulb2);
// Get a pointer to the IDrawing interface.
IDrawing* pIDrawing = NULL;
if (m_pLight bulb2->QueryInterface(IID_IDrawing,
(LPVOID*)&pIDrawing) == S_OK) {
// Give the COM object a pointer to our palette.
pIDrawing->SetPalette(&m_pal);
// Free the interface.
pIDrawing->Release();
} else {
dprintf1("Failed to set palette in COM object");
}
A call to CoCreateInstance creates the object. Having got a pointer to its IUnknown interface, we can ask for a pointer to the object's IDrawing interface. If the object supports the IDrawing interface, we use the pointer to call the SetPalette function to let it know which palette the application is using.
Note If you remove the error-handling code, this really becomes four calls: CoCreateInstance, QueryInterface, SetPalette, and Release. Note that the call to Release is important because it indicates that we're finished with the temporary IDrawing interface pointer we obtained.
Drawing the new object is simple:
// Ask the top-left room light bulb to draw itself.
if (m_pLight bulb2) {
// Get a pointer to the IDrawing interface.
IDrawing* pIDrawing = NULL;
if (m_pLight bulb2->QueryInterface(IID_IDrawing,
(LPVOID*)&pIDrawing) == S_OK) {
// Ask it to draw.
ASSERT(pIDrawing);
if (pIDrawing->Draw(&dc, 135, 121) != S_OK) {
dprintf1("IDrawing::Draw failed");
}
// Release the interface.
pIDrawing->Release();
} else {
dprintf1("No IDrawing interface found");
}
}
Note that, instead of keeping the IUnknown pointer to the light-bulb object, I could have simply kept the IDrawing pointer and avoided asking for it each time. I wanted to be obvious here rather than efficient; I'm demonstrating COM object use, not speedy programming techniques.
Visual C++ version 2.1 makes creating COM objects very easy. In the next article, I'll continue developing the house by adding some other appliances and showing how COM object interfaces make controlling a variety of different devices easy.