Cómo mezclar C++ y C correctamente
ffi (2)
Tengo algunos problemas con esto: necesito escribir un contenedor C para una biblioteca C ++. Digamos que tengo 3 archivos:
-
wrapper.h
typedef struct Foo Foo; Foo* create_foo();
-
wrapper.cpp
extern "C" { #include "wrapper.h" } #include "foo.h" Foo* create_foo() { return new Foo; }
-
foo.h
class Foo { public: Foo(); };
Esto compila bien:
clang++ -std=c++14 wrapper.cpp foo.h wrapper.h -shared -fPIC
clang++ -shared -o libbindings.so a.out
pero al compilar el programa que usa el contenedor C (es un compilador y está vinculado por el lenguaje de programación que usa el contenedor - Crystal), obtengo una referencia indefinida a create_foo () y un error de enlazador
collect2: error: ld returned 1 exit status
.
¿Cómo puedo depurar esto (y qué estoy haciendo mal)?
Aquí hay un ejemplo de trabajo:
wrapper.h ( consciente de C y C ++)
#ifndef WRAPPER_H_
#define WRAPPER_H_
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CPPClass CPPClass;
CPPClass* CPPClass_new();
void CPPClass_do_something(CPPClass* cppclass);
int CPPClass_get_state(CPPClass* cppclass);
void CPPClass_delete(CPPClass* cppclass);
#ifdef __cplusplus
}
#endif
#endif /* WRAPPER_H_ */
wrapper.cpp (solo C ++)
#include "wrapper.h"
class CPPClass
{
int state;
public:
CPPClass(): state(0) {}
void do_something() { ++state; }
int get_state() const { return state; }
};
extern "C" CPPClass* CPPClass_new()
{
return new CPPClass;
}
extern "C" void CPPClass_do_something(CPPClass* cppclass)
{
cppclass->do_something();
}
extern "C" int CPPClass_get_state(CPPClass* cppclass)
{
return cppclass->get_state();
}
extern "C" void CPPClass_delete(CPPClass* cppclass)
{
delete cppclass;
}
use-wrapper.c (solo C)
#include <stdio.h>
#include "wrapper.h"
int main(void)
{
CPPClass* cppclass = CPPClass_new();
if(!cppclass)
{
printf("ERROR: failed to create CPPClass:/n");
return 1;
}
printf("state: %d/n", CPPClass_get_state(cppclass));
CPPClass_do_something(cppclass);
printf("state: %d/n", CPPClass_get_state(cppclass));
CPPClass_delete(cppclass);
}
Compilar CPP
g++ -std=c++11 -shared -fPIC -o libwrapper.so wrapper.cpp
Compilar C
gcc -o use-wrapper use-wrapper.c -L. -lwrapper -lstdc++
Salida:
$ ./use-wrapper
state: 0
state: 1
Espero que ayude.
Está creando un objeto compartido llamado
a.out
, luego otro objeto compartido llamado
libbindings.so
que aparentemente se vincula a
a.out
pero no hace referencia a nada de él.
Ahora, si un conjunto de archivos de entrada no tiene símbolos indefinidos, no se buscan bibliotecas ni se agregan a la salida.
Entonces
libbindings.so
es esencialmente una biblioteca vacía.
Verificar:
% nm a.out | grep create_foo
00000000000006bc T create_foo
% nm libbindings.so | grep create_foo
%
Si tiene varios archivos fuente, debe compilar un archivo objeto a partir de cada fuente (use el indicador de compilación -c) (luego, opcionalmente, combine los objetos en una biblioteca estática --- omita este paso si no va a liberar bibliotecas estáticas) y luego compile un objeto compartido de objetos construidos previamente:
clang++ -c -fPIC foo.cpp
clang++ -c -fPIC bar.cpp
clang++ -shared -o libfoobar.so foo.o bar.o
Si solo tiene una fuente o muy pocos archivos fuente que pueda compilar fácilmente, puede compilar la biblioteca compartida en un solo paso:
clang++ -std=c++14 wrapper.cpp somethingelse.cpp -shared -fPIC -o libbindings.so
Esto no se recomienda para proyectos grandes.