C Bindings für C++ Klassen programmieren

Der folgende Text ist ein Auszug aus einer Mail, die ich am 30. März 2004 auf der Mailingliste der Essener Linux User Group geschrieben habe.

ich habe mich neulich gefragt, wie es die QT, bzw. auch der KDE schaffen C Bindings zu einer C++ Lib zu erzeugen?!

Diese Frage ist schon ein paar Tage alt, aber noch hat niemand sie beantwortet. Vielleicht ist das folgende Beispiel für den einen oder anderen nützlich.

Schreibt man dann in C++ “Funktionen”, die sich um Methoden legen und linkt diese Funktionen?

Ja, genau.

Ist das Resultat denn dann überhaupt mit einem reinen C-Compiler zu benutzen?

Ja. Der Trick besteht darin, zu einer gebenen Klass myclass.h und ihrer Implementierung myclass.cpp ein Binding-Modul binding.cpp mit dazugehöriger Headerdatei binding.h zu schreiben, welches die Methoden aus myclass.cpp in C zur Verfügung stellt. binding.h ist ein reiner C-Header und kann in beliebige C-Programme eingebunden werden.

Beispiel:

// ----- myclass.h -----

class myclass {

 private:

  int m;

 public:

  myclass();
  int get();
  void set(int i);
};

// ----- myclass.cpp -----

#include "myclass.h"

myclass::myclass()
{
  m = 42;
}

int myclass::get()
{
  return m;
}

void myclass::set(int i)
{
  m = i;
}

myclass ist eine einfache Klasse mit einer Membervariablen, einem Konstruktor und zwei Methoden zum Auslesen und Setzen der Membervariablen.

Zugriffe aus C-Programmen bekommen folgende Schnittstelle:

// ----- binding.h -----

typedef void *myclass_t;

myclass_t myclass_new();
int myclass_get(myclass_t obj);
void myclass_set(myclass_t obj, int i);
void myclass_delete(myclass_t obj);

(Zeiger auf) Objekte vom Typ myclass bekommen in C den Typ myclass_t. Für alle Methoden werden C-Funktionen bereit gestellt, als zusätzlichen ersten Parameter das Objekt übergeben bekommen. Zusätzlich muss es eine Funktion geben, die die erzeugten Objekte wieder zerstört. Das gilt selbst dann, wenn die eigentliche Klasse keinen explizten Destruktor hat.

Das Binding ist wie folgt implementiert:

// ----- binding.cpp -----

#include "myclass.h"

extern "C" {
#include "binding.h"
}

myclass_t myclass_new()
{
  return (new myclass);
}

int myclass_get(myclass_t obj) 
{
  return ((class myclass*)obj)->get();
}

void myclass_set(myclass_t obj, int i)
{
  ((class myclass*)obj)->set(i);
}

void myclass_delete(myclass_t obj)
{
  delete (class myclass*) obj;
}

Wichtig ist, die Datei binding.h mit extern “C” einzubinden, damit die Funktionsnamen in Form von C-kompatiblen Linkersymbolen bereit gestellt werden, obwohl binding.cpp ein C++ Programm ist.

Schließlich noch ein C-Programm, welches die Klasse benutzt:

// ----- main.c -----

#include 
#include "binding.h"

int main()
{
  myclass_t o = myclass_new();

  printf("o = %d\n", myclass_get(o));
  myclass_set(o, 4711);
  printf("o = %d\n", myclass_get(o));

  myclass_delete(o);
  return 0;
}

Das C-Programm braucht (und kann) den Header myclass.h nicht einbinden, sondern “sieht” lediglich die Funktionen aus binding.h

Die Klasse und das Binding werden mit einem C++ Compiler, das Hauptprogramm mit einem C-Compiler übersetzt.

g++ -c -o myclass.o myclass.cpp
g++ -c -o binding.o binding.cpp
gcc -c -o main.o main.c

Schließlich wird alles zusammen gelinkt:

gcc -o main -lstdc++ main.o binding.o myclass.o

Da in binding.cpp die C++ Funktion new() verwendet wird, ist es nötig, die C++ Standardbibliothek dazu zu linken. Ein passendes Makefile für GNU Make könnte so aussehen:

# Makefile

CC        = gcc
CXX       = g++
CFLAGS    = -O2 -Wall
CXXFLAGS  = -O2 -Wall
LDFLAGS   = -s
LOADLIBES = -lstdc++

all:      main

main:     main.o binding.o myclass.o

clean:
          rm -f main *.o

In real relevanten Fällen ist das alles noch ein bisschen komplizierter, weil die Klassen Operatoren überladen, Streams benutzen oder (bei QT) Signals and Slots verwenden. Das alles lässt sich aber mit mehr oder weniger großen Verrenkungen (sprich Pointern, Callback-Funktionen etc.) in C abbilden.

Außerdem geht in C das Exception Handling verloren und muss, sofern relevant, durch die C-übliche Fehlerbehandlung mit Return Codes ersetzt werden.

Richtig komplex wird es bei Templates. Hier bietet es sich an, Template-freie Klassen für die wichtigsten Basistypen zu erzeugen und für diese dann getrennte Bindings bereit zu stellen.

Ich hoffe, Euch nicht völlig verwirrt zu haben. ;-)