Usando bibliotecas C++ em código C

O BRL-CAD é escrito majoritariamente em C, enquanto o OSL é escrito em C++. Para que o código C possa usar código C++ é preciso usar uns truques. No meu caso, eu queria disponibilizar uma classe C++ para ser acessada pelo C.

Para indicar que um código C irá usar uma função sua, o código C++ deve usar a diretiva extern "C". Por exemplo, se temos a função void foo(int x), ela ficará assim:

extern "C" void foo(int x);

ou alternativamente

extern "C" {
    void foo(int x);
}

Entretanto, o código C não entende essa diretiva. Por isso, o cabeçalho que ele deverá ver é

void foo(int x);

Um truque para o código C e C++ compartilharem o mesmo header, é usar a macro __cpluscplus que só é setada se o código for C++. Podemos usar o seguinte cabeçalho para ambos os códigos:

#ifdef __cplusplus
extern "C" {
#endif
    void foo(int x);
#ifdef __cplusplus
}
#endif

Exportando uma classe

Agora suponha que queremos exportar uma classe, como por exemplo a classe A, definida como:

class A {
    int x;
 public:
    A(int _x) : x(_x){};
    int getx() { return x; };
};

Precisamos definir funções estilo C para encapsular os métodos da class A, inclusive o construtor e o destrutor.

/* wrapper para construtor */
A* init(int x){
    A* objA = new A(x);
    return objA;
}
/* wrapper destrutor */
void destroy(A** objA){
    delete *objA;
    *objA = NULL;
}
/* wrapper para A::getx() */
int getx(A* objA){
    return objA->getx();
}

Para que o código C mantenha uma referência para o objeto da classe A, podemos usar um ponteiro opaco, já que a estrutura da classe A é desconhecida para o código C. Além disso, para poder compartilhar as funções acima com o código C++, devemos criar um apelido para nosso ponteiro opaco, de A.

typedef void A;

Agora A* representa um ponteiro opaco que pode apontar para um objeto da classe A, independente de sua estrutura. Aliás, ao invés de usarmos void, recomenda-se usar struct A para que haja uma diferenciação dos tipos dos pointeiros opacos [2]. Isso é interessante no caso em que há duas classes A e B, e erroneamente fazemos um ponteiro da classe B apontar para um objeto da classe A, como no código abaixo:

typedef void A;
typedef void B;
A* objA = init(10);
B* objB = objA; 

Se ao invés de void usarmos structs,

typedef struct A A;
typedef struct B B;
A* objA = init(10);
B* objB = objA; /* Gera um warning */

Temos uma garantia adicional contra erros não-intencionais.

Exemplo completo

lib.h:

 
#include <stdio.h>

#ifdef __cplusplus
class A {
    int x;
 public:
    A(int _x);
    int getx();
};

#else
typedef struct A A;
#endif

#ifdef __cplusplus
extern "C" {
#endif

    A* init(int x);
    void destroy(A** objA);
    int getx(A* objA);

#ifdef __cplusplus
}
#endif

lib.cpp:

#include "lib.h"
A::A(int _x) : x(_x) {}
int A::getx(){ return x; }

A* init(int x){
    A* objA = new A(x);
    return objA;
}
void destroy(A** objA){
    delete *objA;
    *objA = NULL;
}
int getx(A* objA){
    return objA->getx();
}

main.c:

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

int main (){

    A *objA = init(3); 
    printf("%d\n", getx(objA));
    destroy(&objA);
    return 0;
}

Compilando

Podemos usar o cmake para simplificar o processo de compilação:

add_library(lib lib.cpp)
add_executable(main main.c)
target_link_libraries(main lib)

Referências

[1] http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html
[2] Stack overflow: A question about exporting a C++ class to C

Os comentários estão fechados.

%d bloggers like this: