Breaking dependencies with COM-style interfaces

This post is very old. Please bear in mind that information here might be incorrect or obsolete, and links can be broken. If something seems wrong, please feel free to comment or contact me and I'll update the post.

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

Related posts:

  1. C++ background: Static, reinterpret and C-Style casts
  2. Pimpl your C++ code
  3. Inside niven Part. I

This entry was posted in Programming and tagged . Bookmark the permalink.

2 Responses to Breaking dependencies with COM-style interfaces

  1. hehejo says:

    Wozu genau brauche ich nun die Klasse Test?
    Ist die sozusagend die versteckte Implementierung von IExample? Und eine andere Implementierung von IExample könnte den “sayHello”-String auch via ein Webinterface erlangen?

  2. Anteru says:

    Ist ja nur ein Beispiel. Test ist halt eine Klasse die Funktionalität X hat, aber auch das Interface Q unterstützt … die Idee ist dass es a.) noch woanders eine Klasse gibt die Q implementiert, z.B. sei Q sowas wie “ConvertibleToString” und b.) dass Q nen Haufen an Dependencies mit sich zieht (std::string, I/O Streams, etc.) die du nicht immer #includen willst wenn du Test benutzt weil du das “ConvertToString” einmal im Jahr brauchst … und in die Basisklasse IBaseClass willst du es schon gleich zweimal nicht reintun, weil eben nicht alle nach String konvertiert werden können.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>