derkarl.org

Wrapping C libraries with Qt/C++

published 2014-01-03 05:31:21 UTC by charles

I’m only going to discuss wrapping “properly done” C libraries!

For this one, I’ll use the fictional “write a file to the network” C library.

The C Library Declaration

struct Writer
{
    char *file;
    void (*allDoneCallback)(void*);
    void *argument; /* this parameter is passed to your callback */
};

/* many C libraries will malloc their struct and return it to you, sensibly so */
void writerCreate(Writer *);

void writerDestroy(Writer *);

void upload(Writer*, char *file);

It’s important to know that any class that doesn’t have a callback that accepts a void* (as opposed to nothing) is fundamentally flawed, and I’m not going to bother demonstrating how to use it :) — a surprising amount of C programmers are incredibly stupid, so many of these exist. It may be possible to make a global list of active C library thingies and check which one got activated. However, if you’re actually using a library that requires this, you get to figure it out for yourself as punishment.

Also, this C library has a simplistic Object-oriented format, that’s important.

Some C libraries want you to call function names that set the callback and its parameter. In this library, the user has to manually set those in the struct, but in some libraries, you may instead call a library function, like maybe:

writerSetCallback(Writer *writer, void (*callbackFunction)(void*), void *parameter);

Our Qt declaration

class QtWriter : public QObject
{
    Q_OBJECT

public:
    QtWriter();
    ~QtWriter();

    void upload(const QString &file);

signals:
    void allDone();

private:
    static allDoneReceiver(void *);
    Writer mWriterImpl;
};

So anyway, it’d be nice to give the “allDone” function as a callback, but you can’t because allDone() has a secret “this” pointer which C functions obviously won’t allow.

The solution is to use a static function (which has no “this” pointer), and then get the “this” pointer out of that void argument that you gave to the C library; doing that is as simple as casting it back to your C++ class name’s pointer from void. In other words, you give the C class a pointer of a type it doesn’t care about, and you then turn it back to the type you know it is, and then use it as the “this” pointer (which is what it is!).

The C++ definition

QtWriter::QtWriter()
{
    ::writerCreate(&mWriterImpl);

    mWriterImpl.allDoneCallback=&QtWriter:allDoneReceiver;
    mWriterImpl.argument=(void*)this;
}

QtWriter::~QtWriter()
{
    ::writerDestroy(&mWriterImpl);
}

void QtWriter::upload(const QString &file)
{
    // actually, you might want to use QFile::encodeName instead of QString::ascii
    ::upload(&mWriterImpl, file.ascii());
}

/*static*/ void QtWriter::allDoneReceiver(void *arg)
{
    // we've set arg to be a pointer to the "this"
    QtWriter *self=(QtWriter*)arg;
    emit self->allDone();
}

And that’s it!

Colophon

I first wrote this page a long time ago, then later redesigned this web site to be, ahem, standards compliant, and took down this page in the process because I considered it “not very helpful”. However, apparently, it did help some people, so I made it a lot more detailed and resurrected it today, on 2006 May 28.

leave comment

Comments

[-] void* 32 months ago

It’s important to know that any class that doesn’t have a callback that accepts a void* (as opposed to nothing) is fundamentally flawed, and I’m not going to bother demonstrating how to use it :) — a surprising amount of C programmers are incredibly stupid, so many of these exist.

Yes, let’s all complain about stupid C programmers that write valid C code that does not follow C++ conventions. Together we can make a difference!

permalink reply
[-] charles 18 months ago

It’s valid C++ code too. It’s still a bad idea in C++ because it requires you to use global variables. I’m pretty sure those are discouraged in C, as well.

permalink reply