Breaking dependencies with COM-style interfaces

Ever had the problem that you have a base class, and a few of the derived classes share a certain interface and you didn't want to pull it up to the main class because it drags a whole lot of dependencies with it? Then read on for how to write your own COM-like interface query.<!--more--> The main idea is to have a single function in your base class (in this example, IBaseClass) which allows you to get various interfaces to it. You have to consider how to identify an interface, in this example, I use GUIDs which are reasonably simple to implement yet well suited by being ... um, unique :).

So here we go: We create a base interface class (IInterface, or IUnknown) from which we derive our interfaces. Actually, you could skip that, too, but I think it can be useful for compile-time checks later. Next, we have a function getInterface (id, target) which returns us a pointer to where the interface is. In this example, we won't do reference counting and thread-safe interfaces but a quick hack: The object creates an interface instance on load and always gives away a pointer to it. This works nicely if the interface is not too heavy and is reasonable unless you want to have more than one interface ready - but then, you should rather think about your class hierarchy :).

Here's the example, pretty self-explaining:

#include <iostream>
#include <string>

// Simple POD Guid
struct GUID
{
        bool operator==(const GUID& rhs) const
        {
                return a == rhs.a && b == rhs.b && c == rhs.c && d == rhs.d;
        }

        const unsigned int a, b, c, d;
};

// The base class for all interfaces
struct IInterface
{
        static GUID guid;

        virtual ~IInterface ()
        {
        }
};

// The GUID instance for IInterface
GUID IInterface::guid = {
        // {94BE63E1-3B3A-4553-8591-823FBCD58F8A}
        0x94be63e1, 0x3b3a4553, 0x8591823f, 0xbcd58f8a
};

// An example interface
struct IExample : public IInterface
{
        static GUID guid;

        virtual void sayHello () = 0;
};

// The GUID instance for IExample
GUID IExample::guid = {
        // {C6B4D9CA-7F28-4e7c-870F-DC8F56AD7DC8}
        0xc6b4d9ca, 0x7f284e7c, 0x870fdc8f, 0x56ad7dc8
};

// ---

#include <stdexcept> // for std::invalid_argument

// Our base class
struct IBaseClass
{
        void getInterface (const GUID& iid, void** target)
        {
                if (target == 0)        // can't write to 0 ...
                        throw std::invalid_argument ("target must be not null");
                else                            // call through to the implementation
                        getInterfaceImpl (iid, target);
        }

        virtual ~IBaseClass ()
        {
        }

protected:
        // Each derived class will implement this
        virtual void getInterfaceImpl (const GUID& iid, void** target) = 0;
};

// Non-member call so you don't have to cast to void** on your own.
// Optionally, add a compile-time-check if T is derived from
// IInterface here!
template <typename T>
void getInterface (IBaseClass& object, const GUID& iid, T** target)
{
        object.getInterface (iid, reinterpret_cast<void**>(target));
}

// ---

// Simple test class
class Test : public IBaseClass
{
public:
        Test ()
                : interface_ (this)
        {
        }

        void say (const std::string& s)
        {
                std::cout << s << std::endl;
        }

private:
        // The interface, note that it is private
        class TestInterface : public IExample
        {
        public:
                TestInterface (Test* parent)
                        : parent_ (parent)
                {
                }

                void sayHello ()
                {
                        parent_->say ("Hello");
                }

        private:
                Test* parent_;
        };

        TestInterface interface_;

        // Our own implementation for getInterface
        // Only knows IExample
        void getInterfaceImpl (const GUID& iid, void** target)
        {
                if (iid == IExample::guid)
                        *target = &interface_;
                else
                        throw std::runtime_error ("Unsupported interface");
        }
};

// ---

int main ()
{
        Test t;
        IExample* e = 0;

        getInterface (t, IExample::guid, &e);
        e->sayHello ();
}

Comments

Comments powered by Disqus