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

Aqsis on Summer of Code

Aqsis has been accepted into Google's Summer of Code program. If you are a student, and interested in 3D, I can just recommend to check this out. The Aqsis Team is a friendly, highly-talented group of people, and if you have some spare time I bet you'll enjoy working with them. Moreover, you can earn some money :)

Web development

When I created this category, I thought I'd be doing more web stuff, but somehow I got side tracked into other projects. Well, until now, I'm playing with F13R2 at the moment, and preparing a new page (check the status at http://beta.shelter13.net). Read on for a short history of my web adventures.

Past

I've started with a small web portal back in the days when my domain was "Darkside Conflict" and PHP4 was just introduced recently. The first bigger project was a portal software in the spirit of PHPNuke - you know, this kind of does-it-all with this nifty little boxes everywhere. Well, this boxes are back today, in form of those "gadgets", but that's a different story. Anyway, that portal stuff was the first big web application I did, and it used to be real fun because back in those days, you had to write practically everything yourself, no template engine, no database abstraction, PEAR was just beginning, so it was interesting and rewarding. Moreover, the engine turned out to be quite scalable ... Not long after that, I started to revamp the (in those days rather simple) Aqsis homepage. I took the best parts of the portal and slapped a page together that survived even a Slashdotting :). The system was running for approximately one year (with over one million visitors during that time) until I had not enough time to maintain it; eventually it was replaced with XOOPS. The Aqsis page was rather interesting, as it did not have one big framework behind it but rather small code blocks with little dependencies, and this turned out to be really good. Moreover, it was really safe, there was no successful hack attempt during that year. With this in mind, I continued work on F13, leading to a new version of it that was powering (and still is, but not for too long from now on) the Shelter13.net page. F13 is the result of a few years of web development practice. It uses powerful libraries where possible, adheres to many standards (uses only standard-conformant SQL, creates XHTML 1.1 strict pages, etc. ) and turned out to be easily modifyable (it's also powering another page without much fuss).

Future

Although F13 in its current revision is really nice and an excellent starting point for developing web pages, it's a bit too flexible in some regards. For example, the styling is based on Smarty and works basically by having a wrapper that calls into the API modules to retrieve news, then polishes them up and passes them on into the presentation layer. The problem is that the presentation has to know what news are, and the polish code is quite specific. This is something I want to fix in the next revision, F13 R2, which will use XML/XSLT for presentation only. The example will look like this, the API returns a chunk of XML which is transformed by some XSLT into a form that can be used by the presentation XSL. This does work already, but it needs a bit of polish before I can go online with it. You can take a look at a pure-HTML mockup at http://beta.shelter13.net, which is supposed to replace the current front page eventually. It's nothing ground-breaking new, and to be honest, I'm not that happy about the JavaScript tabs, but without those, it looks really boring. The shadows are hacked together at the moment, as soon as I switch over to XSL presentation I'll have proper shadows by inserting additional divs during the transform. The JScript needs a bit touchup, too, and maybe I can make something more interesting with it, depending on time. Anyway, that page is going to use the next-gen F13 R2 framework, so I hope that the projects pages will be slightly more up-to-date and that I'll be able to fix presentation problems more easily. I'm looking forward to your comments about that page, so don't hesitate to comment ;) especially as I don't have to unlock you first any more. There is a know issue with IE7 and possibly earlier versions, too, which don't display the shadows properly, so try with Firefox if you can.