Reliable Software Logo

Rationalizing OLE


Building smart OLE on top of, you know, the other OLE.

First of all, you have to tell the world that you're going to use OLE. Here's a little class that will do it for you. Just embed an object of this class in some high-level object that's constructed before you do anything with OLE and destroyed after you're done with OLE. In a Windows program, the Controller is a perfect host for UseOle.

class UseOle
{
public:
    UseOle () { OleInitialize (0); }
    ~UseOle () { OleUninitialize (); }
};

class Controller
{
public:
    Controller(HWND hwnd, CREATESTRUCT * pCreate);
    ~Controller() { PostQuitMessage(0); }
    void Paint (HWND hwnd);
private:
    UseOle useOle;
};

Next, you need to create an OLE object that is the provider of interfaces. The abstract base class CoObject declares the method AcquireInterface. The actual class SObject (smart object) provides one particular implementation of CoObject that uses, internally, OLE's infamous IUnknown.

class CoObject
{
public:
    virtual void * AcquireInterface (IID const & iid) = 0;
	virtual ~CoObject () {}
};

class SObject: public CoObject
{
public:
    SObject (CLSID const & classId, bool running = false);
    ~SObject ()
    {
        if (_iUnk)
            _iUnk->Release ();
    }
    void * AcquireInterface (IID const & iid);

private:
    IUnknown * _iUnk;
};

Let's have a look at the implementation of the constructor and the AcquireInterface method.

The constructor gets hold of the object by calling the API GetActiveObject and/or CoCreateInstance. The difference between the two is that GetActiveObject will try to get hold of an already running object, whereas CoCreateInstance will try the same, but if it fails, it'll start whatever exe is necessary for this object to run. Some objects actually specify a preference: they want a new server to be started every time CoCreateInstance is called. GetActiveObject lets you bypass that.

Notice that this is just one example how you get hold of an OLE object. You may want to play with some of the parameters--for instance, I am passing CLSCTX_SERVER as an argument to CoCreateInstance. That will make sure that the object will live in a separate process from the client. In many cases you'd rather have the object as an inproc server--a DLL that is loaded into the client's address space. For more details, look up CoCreateInstance in your friendly help.

SObject::SObject (CLSID const & classId, bool running)
    :_iUnk (0)
{
    HRESULT hr = S_OK;
    if (running)
    {
        ::GetActiveObject (classId, 0, & _iUnk);
    }
    if (_iUnk == 0)
    {
        hr = ::CoCreateInstance (
                   classId,
                   0,
                   CLSCTX_SERVER,
                   IID_IUnknown,
                   (void**)& _iUnk);
    }
    if (FAILED (hr))
        throw HEx (hr, "Couldn't create instance");
}

I will explain the strange exception type, HEx, in a moment.

Here's our implementation of AcquireInterface that simply calls the QueryInterface method of IUnknown (or, should I say, the unfortunate QueryInterface of the unfortunate IUnknown).


void * SObject::AcquireInterface (IID const & iid)
{
    void * p = 0;
    HRESULT hr = _iUnk->QueryInterface (iid, & p);
    if (FAILED (hr))
    {
        if (hr == E_NOINTERFACE)
            throw "No such interface";
        else
            throw HEx (hr, "Couldn't acquire interface");
    }
    return p;
}

The method AcquireInterface is one of these exceptional Acquire methods of Resource Management that release raw resources. We won't call it other than inside of the constructor of a smart interface pointer. (By the way, the template parameter is an IID pointer because the compiler won't accept references as template arguments. I'm not sure why.)

So here it is, the template for a smart interface pointer.


template<class I>class SFace
{
public:
    ~SFace ()
    {
        if (_i)
            _i->Release ();
    }
    I * operator-> () { return _i; }
protected:
    SFace () : _i (0) {}
    SFace (void * i)
    {
        _i = static_cast<I*> (i);
    }
protected:
    I * _i;
};

As you can see, this particular template can't be instantiated. That's because all its constructors are protected. But don't worry, we'll create other classes that will provide their own specialized constructors.

Here's one that uses our CoObject (or any other object derived from it) as an interface source.


template<class I, IID const * iid>class SObjFace: public SFace<I>
{
public:
    SObjFace (CoObject & obj)
        : SFace<I> (obj.AcquireInterface (*iid))
    {}
};

Finally, let me introduce the HEx class (HRESULT Exception). It is an exception class that is capable of displaying meaningful error messages. For my limited purposes, I chose to simply display the messages directly on the canvas of the main screen. Feel free to implement your own Display method to pop up a message box or something.

class HEx
{
public:
    HEx (HRESULT hr, char const * str = 0)
        : _hr (hr), _str (str)
    {}
    void Display (int x, int y, Canvas & canvas)
    {
        if (_str != 0)
        {
            canvas.Text (x, y, _str);
            y += 20;
        }
        if (FAILED (_hr))
        {
            char * msg;
            ::FormatMessage (
                FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                0,
                _hr,
                MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
                reinterpret_cast<char *> (& msg),
                0,
                0);
            canvas.Text (x, y, msg);
            ::LocalFree (msg);
        }
    }
private:
    HRESULT _hr;
    char const * _str;
};

There will be sample code for downloading. I just need to explain what Automation is.