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. 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 not be 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 ();
}