MFC/COM Objects 7: Creating and Using COM Objects with OLE Automation Interfaces

Nigel Thompson
Microsoft Developer Network Technology Group

April 24, 1995

Click to open or copy the files in the OLEAUTO1 sample application for this technical article.

Click to open or copy the files in the Animate library.

Abstract

This article is seventh and last in a series that looks at creating and using COM objects with Visual C++™ and the Microsoft® Foundation Class Library (MFC). In this article we'll look at creating and using Component Object Model (COM) objects with OLE Automation interfaces. We will create some simple objects that will enable us to perform simple sprite animation from an application written in either Visual C++ or Visual Basic®.

Note: I did the Visual Basic evaluation using an internal version of Visual Basic 4.0 (32-bit), which is not yet available. To run these samples, you need the correct registry entries for the COM objects. The simplest way to install the applications, dynamic-link libraries (DLLs), and registry entries is to run the simple setup program provided. Please also note that the drawing library was built using classes from my Animate library.

Introduction

In the previous article ("MFC/COM Objects 6: Using COM Objects from Visual Basic"), we took a look at using Component Object Model (COM) objects from Visual Basic® and found that, although it can work, it requires some C language glue code to make it feasible. Having to write support code in C isn't my idea of writing a Visual Basic application. In this article, we're going to expand on the basic COM object a bit by adding support for OLE Automation. Having added this support, we'll see that these new COM objects can be used directly from both Visual Basic applications and C or C++ applications as well. In fact, it turns out that using these objects from Visual Basic is wonderfully simple.

I thought that, inasmuch as I was going to be writing a fair bit of code, I would try to create something useful along the way. One of the things that's difficult to do well from Visual Basic is any form of animation, because Visual Basic has no direct palette support and no concept of irregular-shaped objects. So I thought it would be a nice idea to create some OLE Automation objects to solve these two problems: palette management and irregularly shaped sprites.

Because this article is about creating COM objects and not really about animation, I won't be going into any details about why we do things the way we do in order to animate a scene. There are plenty of other places you can read about that! (You might take a look at my book, Animation Techniques in Win32.)

So let's begin by describing the two objects we will create and then look at exactly how they are built and how they are used from a Visual C++™ application and a Visual Basic application.

The Palette Object

In order to get the best possible performance from a Microsoft® Windows® graphics application running on a palettized display device (256 colors), we need to create and use an identity palette. Generally I like to create the palette for an animation from the background image used for the scene. I then make sure that all the sprites in that scene use the same palette. So we need to be able to do the following things with a palette:

  1. Create an identity palette from a given bitmap.

  2. Make the palette the currently active palette in the system.

There are a few other things we need to do, but let's stick to the obvious stuff for the moment.

The Drawing Object

We're going to be using a lot of bitmaps in any animation: one for the background of the scene, one for each sprite in the scene, and one that we will use to compose changes to the scene before making those changes visible to the user. We also need to be able to take any given bitmap and map all its colors to a specific palette. So the drawing object must be able to do these things:

  1. Load an existing image from a bitmap file.

  2. Create a new image of a given size.

  3. Map colors to a given palette.

  4. Draw itself to the screen.

  5. Draw itself to another bitmap, using transparency to support irregularly shaped objects.

OLE Automation Interfaces

We don't have space here to go into a full-blown description of what OLE Automation is all about, so you'll have to make do with a summary <grin>. OLE Automation provides a way to define an interface to a COM object, an interface that can be called by any software that understands the interface description. This interface description is not simply a C header file, but a detailed description of the names of methods and properties an object supports, together with a description of each argument a method takes. From this description, one can write some C code to call the member functions and manipulate the properties of an object.

As an example, consider our drawing object. One of its properties will be a handle to a device context that can be used for drawing onto it. We'll call this property hDC. The drawing object will also have a member function that asks the object to draw itself to some point in a destination device context (DC). We will call this member Draw and its C definition will look like this:

void Draw(long hDC, long x, long y);

Fortunately, we aren't going to need to generate this interface description ourselves, because once more the gallant lads of the Visual C++ group have implemented a wizard that does all the work. In fact, creating OLE Automation objects turns out to be very easy indeed.

Creating COM Objects with OLE Automation Interfaces

Because a lot of what we need to do to create a COM object was covered in the earlier articles in this series, I will focus on the bits related to adding the OLE Automation support rather than describing the entire process in detail. We will be building a dynamic-link library (DLL) called DRAWING.DLL that contains two COM objects. The COM objects will be based on some C++ classes that I wrote for my Animate library. I've created two dummy C++ classes, CColorPalette and CImage, from which the COM objects will be created. I actually implemented CColorPalette and CImage using my Animate class library. In other words, I'm assuming that you already have an animation engine of some sort that can serve as the basis for your COM object.

Let's begin with building the basic DLL framework. Run AppWizard and create a new "MFC AppWizard (DLL)". I called my project Drawing, and it creates DRAWING.DLL. Make sure you check the Static-link MFC Libs option; it is not the default. Check the OLE Automation option, too. Once the framework has been created, you can add any headers, libraries, and so on of your own that you will need. Remember, these need to be statically linked if they use Microsoft Foundation Class (MFC) objects. In my case, I linked my Animate library, which has a special static build option.

In the Include directory, I created an empty file called DRAWINGID.H. This will contain the globally unique IDs (GUIDs) of our drawing objects. In the project, I added a file called GUIDS.CPP, which is used to create instances of all the GUID values we need:

// guids.cpp
#include "stdafx.h"
#include <initguid.h>

#include "..\include\DrawingID.h"

As you can see, this isn't exactly complex!

Now we use ClassWizard to create a new class that will provide the OLE Automation support for our palette object. Using ClassWizard, I created a new class called CColorPalObj in the PALOBJ.H and PALOBJ.CPP files. CColorPalObj is derived from CCmdTarget. You must check the OLE Automation and OLE Creatable options for this new class. When ClassWizard creates PALOBJ.CPP, it includes a GUID definition for our new object; we must copy this definition into DRAWINGID.H:

// DrawingID.h

#ifndef _CLSID_DRAWING_
#define _CLSID_DRAWING_

DEFINE_GUID(CLSID_ColorPalette,
0x068fca73, 0x6dff, 0x11ce, 0x9e, 0xfe, 0x0, 0xaa, 0x0, 0x42, 0x31, 0xbf);

DEFINE_GUID(CLSID_DrawingSurface,
0x068fca75, 0x6dff, 0x11ce, 0x9e, 0xfe, 0x0, 0xaa, 0x0, 0x42, 0x31, 0xbf);

#endif // _CLSID_DRAWING_

The version shown above also includes the GUID definition for the drawing object we'll add a little later. You need to do a little editing here to extract the GUID from the source code, which looks like this in PALOBJ.CPP:

IMPLEMENT_OLECREATE(CColorPalObj, "ColorPalette", 0x68fca73, 0x6dff, 0x11ce, 0x9e, 0xfe, 0x0, 0xaa, 0x0, 0x42, 0x31, 0xbf)

We also need to create a .REG file to be used with REGEDIT.EXE to register the location of our object at run time. I created DRAWING.REG like this:

REGEDIT
HKEY_CLASSES_ROOT\CLSID\{068fca73-6dff-11ce-9Efe-00AA004231BF} = ColorPalette Class
HKEY_CLASSES_ROOT\CLSID\{068fca73-6dff-11ce-9Efe-00AA004231BF}\InprocServer32 = c:\apps\ole\drawing\x86-d\drawing.dll
HKEY_CLASSES_ROOT\Drawing.ColorPalette = Drawing ColorPalette
HKEY_CLASSES_ROOT\Drawing.ColorPalette\CLSID = {068fca73-6dff-11ce-9Efe-00AA004231BF}
HKEY_CLASSES_ROOT\CLSID\{068fca75-6dff-11ce-9efe-00aa004231bf} = DrawingSurface Class
HKEY_CLASSES_ROOT\CLSID\{068fca75-6dff-11ce-9efe-00aa004231bf}\InprocServer32 = c:\apps\ole\drawing\x86-d\drawing.dll
HKEY_CLASSES_ROOT\Drawing.DrawingSurface = Drawing DrawingSurface
HKEY_CLASSES_ROOT\Drawing.DrawingSurface\CLSID = {068fca75-6dff-11ce-9efe-00aa004231bf}

Note   Some of the text is wrapped in the code above; it should not be wrapped in the file you create. Please take a look at DRAWING.REG to see the exact form. Please also note that it is very important to include leading zeroes in the GUID numbers. So, for example, when ClassWizard creates a GUID that begins with 0x68fca73..., you need to pad it out to 068fca73.... I know this sounds elementary, but failing to do this means that the registry will not find the entry correctly, and you will waste hours trying to figure out why your objects are not available.

Having built the registry file, run REGEDIT:

REGEDIT -s drawing.reg

This will make the appropriate entries in the registry. You can run REGEDT32.EXE to look at them and confirm you got what you wanted.

Creating the OLE Automation Interfaces

Because we will only use these objects through their OLE Automation interfaces, we don't need to create any COM interface definitions for them. This turns out to be a big win because ClassWizard has great support for adding OLE Automation interfaces and no support for adding COM interfaces.

Once you've added one OLE Automation property or method, adding the others is trivial, so we'll just look at how to add one. I wanted to be able to build a default palette for testing, so I created a method for the color palette object called CreateColorCube. To do this, I ran ClassWizard and selected the CColorPalObj class. Click the OLE Automation tab in the dialog box, and then click the Add method... button. Set the external name to CreateColorCube, the return type to BOOL, and select No parameters. That's all there is to it. Click the OK button and compile.

Now you have a COM object with an OLE Automation method called CreateColorCube. Of course, it doesn't do anything yet. We need to add the implementation code next.

ClassWizard created a stub: CColorPalObj::CreateColorCube. This is how I implemented the function:

BOOL CColorPalObj::CreateColorCube() 
{
   // Create a color cube palette.
    if (!m_Pal.CreateWash()) {
        TRACE("Failed to create color cube\n");
        return FALSE;
    }

    // Make it an identity palette.
    if (!m_Pal.SetSysPalColors()) {
        TRACE("Failed to make identity palette\n");
        return FALSE;
    }

   return TRUE;
}

The object has a CColorPalette object as its m_Pal member, and it just so happens <grin> that the CColorPalette class has all the functionality we need to implement the method. OK, so this is cheating, but it shows where your code needs to go.

Adding other methods is very similar. Properties are similar, too. Use ClassWizard again, but this time click Add Property. Type in the name of the property and click the Get/Set Methods option. If you don't want one of these (if your property is read-only, for example), simply delete the name of the method you don't need. Here's the implementation of the hPal property:

long CColorPalObj::GetHPal() 
{
   // Return the GDI palette object handle.
   return (long)(m_Pal.m_hObject);
}

Note that in the wonderful world of 32-bit programming, we can cast a lot of things to be long and pass them happily to Visual Basic programs. Obviously, we need to be very careful when handling these long values in our code, inasmuch as we have no type-checking being done by the compiler. So if you cast the long to be a handle to an aardvark, the compiler will let you do aardvark things to the handle in your code. This doesn't mean your code will work—it'll just compile! So be sure you know what data types are really being used behind the long parameter that is passed to you.

The Tricky Parts

If you've followed the story so far, you're probably thinking that all of this is way too easy. Well, that's what I thought, too, until I came to the part where one of my objects (a palette) needed to talk to another object (a drawing surface). I was implementing the function in the palette that allows it to be created from the color table in the drawing surface. It seemed simple: The Visual Basic application would simply pass the handle to the drawing surface object as an argument in a palette object method. Unfortunately, this so-called handle is a pointer to an IDispatch interface, which has very little connection to the C++ object I needed to access, so in the true tradition of the programmer at large, I hacked up a solution. My solution was to add a method to the drawing surface object that returns a pointer to its color table. This is extremely nasty and only works at all because the drawing surface object and the palette object are in the same address space (that is, the same process). Unfortunately, time has prevented me from developing a more elegant solution. Here's my implementation:

BOOL CColorPalObj::CreateFromImage(LPDISPATCH pDispatch) 
{
    ASSERT(pDispatch);
    IDrawSurfObj dso;
    dso.AttachDispatch(pDispatch, FALSE);
    BITMAPINFO* pBMI = (BITMAPINFO*) (dso.GetBitmapInfo());
    ASSERT(pBMI);
   return m_Pal.Create(pBMI);
}

I was forced to do a similar thing when I implemented the method in the drawing surface object, which maps it to the set of colors in a palette. If you have a better solution, e-mail it to me: nigelt@microsoft.com. I'm sure I can magic up a prize for you.

As a side note, I had to use ClassWizard to create an OLE Automation interface for these objects, so I could call them at all. See the next section for how that's done.

Using OLE Automation Objects from Visual C++

This is my idea of a daft (that's English for silly) project. You take an application written in a nice low-level language like C and some nice low-level functions (palette management), and then use some complex high-level glue to make them talk to each other. Of course, some daft ideas occasionally produce useful results, and in any case, I couldn't get my first Visual Basic test application to work, so I had to do it this way! Visual Basic is nice if it all works, but it's hell to debug, so I found that creating a Visual C++ test application for my OLE Automation objects was actually very helpful.

Once again it's wizard time. ClassWizard has an option to import an object description file and create a simple C++ interface class for that object automatically. So to be able to talk to my drawing surface and palette objects, I used ClassWizard to create an interface class for each of them. Once you've done that, the object's OLE Automation methods effectively become methods in the interface class, which you can call directly from your code. That makes calling object methods easy and only leaves one problem to solve—how to create the objects in the first place.

There are two ways to create an object: You can call COleDispatchDriver::CreateDispatch and pass the GUID of the object, or you can call COleDispatchDriver::CreateDispatch and pass the text name of the object that it put in the registry. Because the latter method is what we'll be doing from Visual Basic, I chose to use that in my C++ test application as well.

To show you just how easy these objects are to use, here's the entire setup code for the animation that VCDRAW.EXE runs:

void CVCDrawView::OnInitialUpdate() 
{
   CView::OnInitialUpdate();

    COleException oe;
    int w, h;

    // Create a drawing surface object for the background.
    BOOL b = m_iBkgnd.CreateDispatch("Drawing.DrawingSurface", &oe);
    if (!b) goto _exit;

    // Load the image.
    b = m_iBkgnd.LoadFile("dogbk1.bmp");
    if (!b) {
        AfxMessageBox("Failed to load dogbk1.bmp");
        goto _exit;
    }

    // Create a palette from the background image.
    b = m_iPalette.CreateDispatch("Drawing.ColorPalette", &oe);
    if (!b) goto _exit;

    b = m_iPalette.CreateFromImage(m_iBkgnd.m_lpDispatch);
    if (b) {
        m_iPalette.MakeIdentity();
    } else {
        goto _exit;
    }

    // Map the image colors to the new palette.
    b = m_iBkgnd.MapToPalette(m_iPalette.GetHPal());
    if (!b) goto _exit;

    // Create a drawing surface object for the red ball.
    b = m_iRedBall.CreateDispatch("Drawing.DrawingSurface", &oe);
    if (!b) goto _exit;
    b = m_iRedBall.LoadFile("redball.bmp");
    if (!b) {
        AfxMessageBox("Failed to load redball.bmp");
        goto _exit;
    }

    // Map it to the palette.
    m_iRedBall.MapToPalette(m_iPalette.GetHPal());

    // Set its initial position.
    m_iRedBallX = 0;
    m_iRedBallY = 50;
    m_vy = 0;

    // Create the off-screen buffer.
    b = m_iBuffer.CreateDispatch("Drawing.DrawingSurface", &oe);
    if (!b) goto _exit;
    w = m_iBkgnd.GetWidth();
    h = m_iBkgnd.GetHeight();
    b = m_iBuffer.CreateNew(w, h, m_iPalette.GetHPal());
    if (!b) goto _exit;

    // Start the update timer.
    m_uiTimer = SetTimer(1, 50, NULL);

   // Prevent stupid save? messages.
_exit:
   CVCDrawDoc* pDoc = GetDocument();
    pDoc->SetModifiedFlag(FALSE);
}

The timer event causes the ball position to be updated, and then a new frame is created by calling Render and then Draw:

// Compose a new image in the off-screen buffer.
void CVCDrawView::Render()
{
    // Copy the background image to the off-screen buffer.
    m_iBkgnd.Draw(m_iBuffer.GetHDC(), 0, 0);

    // Draw the sprites in position.
    m_iRedBall.CopyWithTransparency(m_iBuffer.m_lpDispatch,
                                    m_iRedBallX, m_iRedBallY);

}

// Copy the off-screen buffer to the screen.
void CVCDrawView::Draw()
{
    CDC* pDC = GetDC();
    
    // Apply the palette.
    m_iPalette.Apply((long)pDC->GetSafeHdc());

    // Draw the buffer.
    m_iBuffer.Draw((long)(pDC->GetSafeHdc()),
                          2, 250);

    // Done with palette
    m_iPalette.Remove((long)pDC->GetSafeHdc());
}

And apart from a few twiddly bits, that's the guts of the entire application. So perhaps calling OLE Automation objects from Visual C++ isn't so silly after all.

Using OLE Automation Objects from Visual Basic

I implemented the same bouncing ball demonstration in Visual Basic that I did in Visual C++ in the VCDraw sample. I used our internal build of Visual Basic 4.0 (32-bit) for this and was very pleased with the results on my Windows NT™ 3.51 (beta) machine. A test run under Windows 95 wasn't so compelling. The Visual Basic application runs, but not as fast as it did on my Windows NT machine. Try the two applications for yourself, and see what happens.

I don't want to present all the Visual Basic code in the article, but here are the bits that are equivalent to those of the C++ application above:

Private Sub Form_Load()
    ' Create the bkgnd drawing surface.
    Set Bkgnd = CreateObject("Drawing.DrawingSurface")
    
    ' Load the bkgnd image.
    b% = Bkgnd.LoadFile("dogbk1.bmp")
    
    ' Create a palette object.
    Set Palette = CreateObject("Drawing.ColorPalette")
    
    ' Get the palette from the background image.
    b% = Palette.CreateFromImage(Bkgnd)
    
    ' Make it an identity palette.
    Palette.MakeIdentity
        
    ' Map the bkgnd image to our palette.
    b% = Bkgnd.MapToPalette(Palette.hPal)
    
    ' Load the red ball and map it.
    Set RedBall = CreateObject("Drawing.DrawingSurface")
    RedBall.LoadFile ("redball.bmp")
    RedBall.MapToPalette (Palette.hPal)
    iRedBallX = 0
    iRedBallY = 50
    vy = 0
    
    ' Create the off-screen buffer.
    Set Buffer = CreateObject("Drawing.DrawingSurface")
    Buffer.CreateNew Bkgnd.Width, Bkgnd.Height, Palette.hPal
    
End Sub

Compare this with the start-up code for the Visual C++ example. The Visual Basic code is very similar. Here are the rendering and drawing routines:

Sub Render()
    ' Copy the background image to the off-screen buffer.
    Bkgnd.Draw Buffer.hDC, 0, 0
    
    ' Draw the sprites in place.
    RedBall.CopyWithTransparency Buffer, iRedBallX, iRedBallY
    
End Sub
Sub Draw()
    ' Copy the off-screen buffer to the screen.
    ' Make the palette active.
    Palette.Apply (BufferPic.hDC)
    
    ' Show the picture.
    Buffer.Draw BufferPic.hDC, 0, 0
    
    ' Done with palette
    Palette.Remove (BufferPic.hDC)
End Sub

If you don't think this is trivial to use, try doing the same animation without these objects! The palette management alone could be your life's work.

So What's Missing?

Neither the Visual Basic application nor the Visual C++ application handles palette messages from the system. This is just laziness on my part with regard to the Visual C++ application, but in the case of the Visual Basic sample, I didn't want to go to the effort of adding even more methods to the palette object (or maybe creating even more objects), inasmuch as solving this problem has nothing to do with OLE Automation objects. Plenty of other articles discuss palette message handling, so if you want to fix the problem, the information you need is just a search away. (A query in the MSDN Library for animation palette message should provide you with the information you need.)