PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : extern "C"



SebastianKN
20-06-2007, 09:26
Hallo,

gibt es eine Möglichkeit ein C-Programm mit C++-Code zu erweitern?

Ich habe ein Opensource-Programm, das in C geschrieben ist und möchte in einer einzelnen Datei C++-Code per include einfügen und auch verwenden. Ich bräuchte in dem C-Code also sowas wie ein extern "C++". Das gibt es aber vermutlich nicht... Ich dachte, wenn ich das ganze Programm mit g++ und nicht mit gcc kompiliere, könnte es evtl. klappen. Hab dazu im Makefile gcc durch g++ ersetzt, hat aber leider nix gebracht. Er meckert dann trotzdem an meiner Klasse rum. Muss allerdings sagen, dass das ein, für meine Verhältnisse, sehr großes Makefile ist und ich bin mir nicht ganz sicher ob das ausreichend war was ich im Makefile geändert hab.

Hat jemand sowas schon mal gemacht oder eine Idee? Hab irgendwo gelesen, dass ich da einen Wrapper schreiben muss. Kann mir allerdings nicht vorstellen wie das funktionieren soll. Aber lasse mich gerne eines besseren belehren ;)

Grüße
Sebastian

anda_skoa
20-06-2007, 19:25
Der C Code kann natürlich keine C++ Klassen benutzen, aber du kannst natürlich relativ einfach eine C Wrapper machen, in dem du für jede Methode eine entsprechende C Funktion machst, deren erster Parameter immer die Objektinstanz ist.

Quasi mehr oder weniger so

C++ Header


class Foo
{
public:
Foo(int num) : m_num(i) {}

int num() const { return m_num; }

private:
int m_num;
};


C Header


struct Foo; // Foo muss nicht näher deklariert werden, man benutzt das nur als Pointer

Foo* createFoo(int num);

void destroyFoo(Foo* foo);

int getNumFoo(Foo* foo);


Im Source wäre das dann etwa so



// C++ Header inkludieren

extern "C" {
Foo* createFoo(int num)
{
return new Foo(num);
}

void destroyFoo(Foo* foo)
{
delete foo;
}

int getNumFoo(Foo* foo)
{
return foo->num();
}

}


Ciao,
_

SebastianKN
21-06-2007, 10:21
das sieht doch schon mal interessant aus! Danke

aber irgendwie ist mir das zu umständlich; habs mir etwas einfacher vorgestellt :rolleyes: Vermutlich kann ich dann auch nicht den vollen Sprachumfang von C++ benutzen - ich denke da an so Sachen wie Überladen von Funktionen, Stichwort virtual usw.

Falls sich jemand für das Thema "C und C++ mischen" interessiert, hab ich noch nen interessanten Link gefunden: http://parashift.com/c++-faq-lite/mixing-c-and-cpp.html

Von einem Kollegen hab ich den Tipp bekommen das Ganze über Sockets zu machen. Die Erweiterung für das C-Programm also als eigenständiges C++ Programm schreiben und die Kommunikation über Sockets abwickeln. Ein weiterer Vorteil wäre, dass ich die Erweiterung auch auf einem anderen Rechner laufen lassen könnte. Das ist meiner Meinung nach nicht die schönste Lösung aber auch nicht hässlicher als die Wrapper-Klasse ;)

Nochmals Danke für Deine Hilfe!

Grüße
Sebastian

anda_skoa
21-06-2007, 15:11
Das ist eine ganz andere Kategorie. Out-of-process ist natürlich flexibler und unabhängiger, aber auch schwieriger, weil ja parallel gearbeitet wird, Kommunikation und Sychronisation anfällt, etc.

Eine C API lässt sich quasi automatisationsgestützt generieren, selbst zu C++ das Klassen verwendet (siehe Beispiel).
Das ist dann nicht viel anders, als objektorientierte Programmierung in C.

Ein Beispiel für sowas ist TagLib, eine Bibliothek zur Verarbeitung von Metadaten aus Mediendateien. In C++ geschrieben, aber mit zusätzlicher C API.

Im dem Fall, dass du ein Modell mit kommunizierenden Prozessen vorziehst, rate ich dazu, D-Bus als Kommunikationsmedium zu benutzen.

Ciao,
_

SebastianKN
21-06-2007, 15:50
Das schöne an der Erweiterung ist, dass ich nicht wirklich viel synchronisieren muss. Das C-Programm liefert einfach ein paar Daten an das C++-Programm, das daraus wieder neue Daten berechnet und diese an das C-Programm zurückschickt. Hab mir gedacht ich lasse die Kommunikation in einem eigenen Thread laufen, damit nicht das ganze C-Programm schläft während das C++ Programm rechnet. Für so einen einfachen Datenaustausch sollten normale Sockets reichen. Dann weiss ich was passiert und muss mich auch nicht irgendwo neu einarbeiten ;)

Gruß
Sebastian

anda_skoa
21-06-2007, 15:53
Dann könnstest du einfacher sogar eine Pipe zur Kommunikation verwenden, bzw. das C++ als Kind ausführen und über stdin/stdout kommunizieren.

Ciao,
_

SebastianKN
21-06-2007, 16:04
hab leider noch nie Pipes zur Kommunikation verwendet (ausser in der Kommandozeile ;) ). Werds mir aber mal anschauen.

Das schöne an den Sockets ist, dass ich das C++ Programm evtl. auch auf einem anderen Rechner laufen lassen kann. Da es sich dabei um einen genetischen Algorithmus handelt mit Laufzeiten im Sekunden- bis Minutenbereich, wäre das evtl. sogar von Vorteil, da man den Algorithmus dann auf einem schnelleren Rechner laufen lassen könnte.

Danke für Deine Unterstützung! Kommt meiner Dipl-Arbeit bestimmt zu Gute :)

anda_skoa
23-06-2007, 15:41
Sockets, besonders Netzwerksockets, haben das Problem, dass praktisch jeder verbinden kann.

Ist natürlich bei einer Diplomarbeit nicht so wichtig, in einer realen Applikation sollte man das aber berücksichtigen.

Außerdem müssen die Verbindungsparameter irgendwie bekannt gegeben/konfiguriert werden müssen, etc.

Ciao,
_

panzi
24-06-2007, 17:20
Wenn es eh so eine einfache Kommunikation ist (also A fragt B und B Antwortet) dann brauchst eh nicht mehr als 3 Methoden deiner Klasse zu wrappen. Einfach so wie anda_skoa esgezeigt hat: Eine C-Funktion um ein Objekt anzulegen, eine Funktion um es zu löschen und eine die als Parameter die "Frage" bekommt und die "Antwort" als Returnwert. Die C++ Klasse kann dann intern soviele andere Klassen/Methoden/was auch immer verwenden wie sie will. Gewrapped müssen nur die 3 werden.

panzi
24-06-2007, 17:22
Zusätzlich kann bei Sockets praktisch zu jedem Zeitpunkt die Verbindung reißen. Das muss behandelt werden damit das Programm mit so einer Situation umgehn kann. Das erfordert aufwendigen Fehlerbehandlungscode, da es kein C++ Sockets mit Exceptions gibt. Man muss 100.000 Returnwerte abfragen.

SebastianKN
02-07-2007, 15:23
hab das mit der Wrapper-Geschichte mal ausprobiert und es funktioniert bei mir leider nicht.

Hab folgendes gemacht:

- eine einfache C++ Klasse erstellt (also cplusplus.h und cplusplus.cpp ähnlich wie im Beispielcode s.o.)
- Wrapper erstellt (.h und .cpp)

wrapper.h

#ifndef _WRAPPER_H_
#define _WRAPPER_H_

struct Cpp;

#ifdef __cplusplus
extern "C"
{
#endif /* ifdef __cplusplus */

12: Cpp* createCPP(int);
13: int getNum(Cpp*);
14: void deleteCpp(Cpp*);

#ifdef __cplusplus
}
#endif /* ifdef __cplusplus */


#endif

wrapper.cpp

#include "wrapper.h"
#include "cplusplus.h"

#ifdef __cplusplus
extern "C"
{
#endif /* ifdef __cplusplus */

Cpp* createCPP(int n) {
return new Cpp(n);
}

int getNum(Cpp* temp) {
return temp->num();
}

void deleteCpp(Cpp* temp) {
delete temp;
}

#ifdef __cplusplus
}
#endif /* ifdef __cplusplus */

- test.c mit main()

test.c

#include <stdio.h>
#include "wrapper.h"


int main(int argc, char** argv) {
Cpp* test;

test = createCPP(5);
printf("Test: %d\n", getNum(test));
deleteCpp(test);

return 0;
}

- compiliert

c++ Klasse mit "g++ -c cplusplus.cpp"
wrapper mit "g++ -c wrapper.cpp"
test.c mit "gcc -c test.c"

cplusplus.cpp und wrapper.cpp werden ohne Fehler compiliert. Bei der test.c gibt es Fehler:


seven@seven:~/dipl/tmp> gcc -Wall -c test.c
In file included from test.c:3:
wrapper.h:12: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘*’ token
wrapper.h:13: error: expected ‘)’ before ‘*’ token
wrapper.h:14: error: expected ‘)’ before ‘*’ token
test.c: In function ‘main’:
test.c:7: error: ‘Cpp’ undeclared (first use in this function)
test.c:7: error: (Each undeclared identifier is reported only once
test.c:7: error: for each function it appears in.)
test.c:7: error: ‘test’ undeclared (first use in this function)
test.c:9: warning: implicit declaration of function ‘createCPP’
test.c:10: warning: implicit declaration of function ‘getNum’
test.c:11: warning: implicit declaration of function ‘deleteCpp’


Es scheint als ob er das Cpp* test; nicht mag. In der wrapper.h ist ein struct Cpp; definiert.
Der Zeiger test (aus test.c) müsste ja eigentlich auf eine struct Cpp zeigen. Die Funktion createCpp() (aus wrapper.cpp) liefert mir aber doch einen Zeiger auf eine Instanz der Klasse Cpp zurück. Kann das überhaupt funktionieren oder hab ich irgendwas falsch verstanden?

locus vivendi
02-07-2007, 16:05
Es scheint als ob er das Cpp* test; nicht mag. In der wrapper.h ist ein struct Cpp; definiert.
Wenn du in C ein struct so definierst,
"struct X { };", dann musst du es mit dem Keyword struct verwenden, also z.B.
"struct X x1;" um eine Variable zu definieren. In C++ reicht ja bekanntlich "X x1;" aus.
Du kannst aber in C auch so etwas hier "typedef struct X { } X;" definieren. Dann kannst du auch in C "X x1;" sagen.

Hier übrigens der Beitrag aus der C++ Faq zum Thema Mixen von C und C++:
http://parashift.com/c++-faq-lite/mixing-c-and-cpp.html

SebastianKN
02-07-2007, 18:51
stimmt, C unterscheidet sich da wohl etwas.
Hab in die test.c ein "struct" vor das "Cpp* test" gesetzt, leider ohne Erfolg.

Fehlermeldung:



seven@seven:~/dipl/tmp> make clean && make
rm -f *.o test
gcc -c test.c
In file included from test.c:3:
wrapper.h:12: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘*’ token
wrapper.h:13: error: expected ‘)’ before ‘*’ token
wrapper.h:14: error: expected ‘)’ before ‘*’ token
test.c: In function ‘main’:
test.c:9: error: ‘Cpp’ undeclared (first use in this function)
test.c:9: error: (Each undeclared identifier is reported only once
test.c:9: error: for each function it appears in.)
test.c:9: error: expected expression before ‘)’ token
make: *** [test.o] Fehler 1


Ich werd das wohl etwas anders machen müssen. Brauch im C-Code auch nicht unbedingt nen Pointer auf die Instanz.
Aber falls jemand noch ne Idee hat - immer her damit ;-)

jeebee
02-07-2007, 18:53
du musst nur noch das struct auch in wrapper.h:12 vor Cpp* setzen (wohl auch in wrapper.cpp -- damit die Signaturen übereinstimmen.) Schlussendlich ist das ja auch Code den der C-Compiler verstehen muss.

MfG jeebee

SebastianKN
02-07-2007, 19:31
danke Dir (und allen anderen, die geholfen haben)! Damit gehts. Leider kann ich das Ganze nur mit dem g++ linken und nicht mit dem gcc. Muss ich mich wohl doch noch in autoconf/automake einarbeiten :rolleyes:

[Für Alle, die keinen Pointer wollen, sondern ein handle auf die Instanz, werden hier fündig: http://www.heise.de/foren/go.shtml?read=1&msg_id=10535126&forum_id=44546]


Das Ganze vielleicht nochmal im Überblick:

Makefile

CC = gcc
CPP = g++
OBJ= test.o wrapper.o cplusplus.o
BIN= test

$(BIN):$(OBJ)
$(CPP) -o $(BIN) $(OBJ)

test.o: test.c wrapper.h cplusplus.h
$(CC) -c test.c

wrapper.o: wrapper.cpp wrapper.h
$(CPP) -c wrapper.cpp

cplusplus.o: cplusplus.cpp cplusplus.h
$(CPP) -c cplusplus.cpp

clean:
rm -f *.o $(BIN)


cplusplus.h


#ifndef _cplusplus_h
#define _cplusplus_h

class Cpp
{
public:
Cpp(int i) : m_num(i) {}

int num();

private:
int m_num;
};

#endif



cplusplus.cpp


#include "cplusplus.h"
#include <iostream>
using namespace std;

int Cpp::num() {
return m_num;
}



wrapper.h


#ifndef _WRAPPER_H_
#define _WRAPPER_H_

struct Cpp;

#ifdef __cplusplus
extern "C"
{
#endif /* ifdef __cplusplus */

struct Cpp* createCpp(int);
int getNum(struct Cpp*);
void deleteCpp(struct Cpp*);

#ifdef __cplusplus
}
#endif /* ifdef __cplusplus */


#endif



wrapper.cpp


#include "wrapper.h"
#include "cplusplus.h"

#ifdef __cplusplus
extern "C"
{
#endif /* ifdef __cplusplus */

struct Cpp* createCpp(int n) {
return new Cpp(n);
}

int getNum(struct Cpp* temp) {
return temp->num();
}

void deleteCpp(struct Cpp* temp) {
delete temp;
}

#ifdef __cplusplus
}
#endif /* ifdef __cplusplus */



test.c


#include <stdio.h>
#include "wrapper.h"


int main(int argc, char** argv) {
struct Cpp *test;

test = createCpp(5);
printf("Test: %d\n", getNum(test));
deleteCpp(test);

return 0;
}