4

I am having some problems with this: I need to write a C wrapper for a C++ library. Say I have 3 files:

  • 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();
    };
    

This compiles fine:

clang++ -std=c++14 wrapper.cpp foo.h wrapper.h -shared -fPIC

clang++ -shared -o libbindings.so a.out

but when compiling the program that uses the C wrapper (it is compiler and linked by the programming language that uses the wrapper - Crystal), I get an undefined reference to create_foo() and a linker error collect2: error: ld returned 1 exit status. How can I debug this (and what am I doing wrong)?

andrewdotnich
  • 16,195
  • 7
  • 38
  • 57
Zihemu
  • 121
  • 2
  • 9

2 Answers2

2

Here is a working example:

wrapper.h (C & C++ aware)

#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 (C++ only)

#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 (C only)

#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);
}

Compile CPP

g++ -std=c++11 -shared -fPIC -o libwrapper.so wrapper.cpp

Compile C

gcc -o use-wrapper use-wrapper.c -L. -lwrapper -lstdc++

Output:

$ ./use-wrapper 
state: 0
state: 1

Hope that helps.

Galik
  • 47,303
  • 4
  • 80
  • 117
1

You are creating a shared object named a.out, then another shared object named libbindings.so that ostensibly links to a.out but references nothing from it. Now if a set of input files doesn't have any undefined symbols, no libraries are searched or added to the output. So libbindings.so is essentially an empty library. Verify:

 % nm a.out | grep create_foo
 00000000000006bc T create_foo
 % nm libbindings.so | grep create_foo
 %

If you have several source files, you should build an object file from each source (use -c compilation flag), (then optionally combine the objects into a static library --- skip this step if you are not releasing static libraries) then build a shared object from previously built objects:

  clang++ -c -fPIC foo.cpp
  clang++ -c -fPIC bar.cpp
  clang++ -shared -o libfoobar.so foo.o bar.o

If you only have one source, or very few source files you can easily compile together, you can build the shared library in one step:

  clang++ -std=c++14 wrapper.cpp somethingelse.cpp -shared -fPIC -o libbindings.so

This is not recommended for large projects.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Thanks! But the other answer was more complete and might help more people, even though I understand that the whole problem was in how I built the library. Thanks, again :) – Zihemu Aug 09 '15 at 11:54