0

I'm trying to create a static library to allow me to use a C++ API in my C program. To get started, I'm trying to call an API function called APIName::Api_init(); So far, I have created a "wrapper" library which has the following:

c_api.h:

#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif
EXTERNC int apiname_api_init(char const * user_app_name);
EXTERNC void apiname_api_shutdown();

#undef EXTERNC

c_api.cpp:

#include "c_api.h"
#include "../include/apiname/api.h"   // Link to header files from API provider
int apiname_api_init(char const * user_app_name) {
        return to_return_code(APIName::Api_init(user_app_name));
}

void apiname_api_shutdown() {
        return APIName::api_shutdown();
}

I then compile this into an object file called apiwrapper.o using the following command:

g++  -L../include/libs -lapinameapi  -shared -fPIC -c -D_LINUX_X86_64 -lpthread -lz -lm -lcrypto -lbz2 -I../include -DPROVIDE_LOG_UTILITIES -DTARGET_PLATFORM_LINUX  c_api.cpp -o lapiwrapper.o -lrt

where apinameapi is the shared library from the API provider. This works fine. I then generate the library file using:

ar rc libapiwrapper.a apiwrapper.o 

Again, this is fine.

Now I create my C file (mycode.c), which contains:

#include "mycode.h"
#include <stdio.h>
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <Windows.h>
#else
#include <unistd.h>
#endif
int connectToApi() {

  // Init API
  rc = apiname_api_init("programname");

}

The header file mycode.h contains:

#include <stdbool.h>
#include <stdint.h>
#include "c_api.h"

(obviously there's other stuff as well, but I've left it out for brevity).

So, I compile my main program using:

gcc  -Linclude/libs -lapiwrapper  -shared -DKXVER=3 -fPIC -D_LINUX_X86_64 -lpthread -lz -lm -lcrypto -lbz2 -Iinclude -DPROVIDE_LOG_UTILITIES -DTARGET_PLATFORM_LINUX  src/mycode.c -o lib/mycode.so -lrt -Wno-discarded-qualifiers -Wno-incompatible-pointer-types

which is also fine. Then, I run the program (this particular file gets loaded by another piece of code), and I get

undefined symbol: apiname_api_init

It seems that it's not finding the function in the library file. Where am I going wrong? This is my first time building a library, so it's probably something fairly basic.

EDIT: I think the issue may be coming from the fact that the final compilation (which creates mycode.so) is producing a shared object file, and I need that to run on its own (without needing my generated library file to be present as well). I have to run the final object from another program, so it needs to be standalone. Is there a way I can link it to "bundle" everything together into one final so file?

m0hithreddy
  • 1,752
  • 1
  • 10
  • 17
Sharon
  • 3,471
  • 13
  • 60
  • 93
  • 1
    Does this answer your question? [Why does the order in which libraries are linked sometimes cause errors in GCC?](https://stackoverflow.com/questions/45135/why-does-the-order-in-which-libraries-are-linked-sometimes-cause-errors-in-gcc) – the busybee May 29 '20 at 11:00
  • I tried changing the order, but it didn't seem to make any difference. Thanks anyway. – Sharon May 29 '20 at 11:13
  • 1
    @Sharon It is an interesting question to tinker on. But can you give clarity on this particular line "Then, I run the program". There is no main() function in any code you posted. The final output is also ``.so`` file, where are you using it ? – m0hithreddy May 29 '20 at 11:28
  • @MohithReddy it is called from another file in another language. So, I want to generate mycode.so as a shared object, and then from the other code (in a different, specialist language) can import the C object and run it. Does that make sense? So it will call something like mycode.connectToApi(). – Sharon May 29 '20 at 11:33

3 Answers3

1

The command ar rc libapiwrapper.a apiwrapper.o creates a static library. If you link with that you will also need to link in its dependancies. What you need is to create a shared library (.so file). You'll probably need someting like this (I have split the compile and link steps in idividual commands):

g++ -lapinameapi -c -D_LINUX_X86_64 -I../include -DPROVIDE_LOG_UTILITIES -DTARGET_PLATFORM_LINUX c_api.cpp -o lapiwrapper.o
g++ -shared -fPIC -o libapiwrapper.so lapiwrapper.o -L../include/libs -lpthread -lz -lm -lcrypto -lbz2 -lrt

Finally it's just a matter of linking the executable with this shared library instead of the static library.

Brecht Sanders
  • 6,215
  • 1
  • 16
  • 40
0

I tried to reproduce the problem using libssl and libcrypto. So in my case, I need to produce an api_wrapper which uses the functions of libssl and libcrypto and it should be standalone. But unfortunately, as you were asking in the EDIT, two or more shared libraries cannot be bundled together. Please refer to this link Merge multiple .so shared libraries. If your so-called different, specialist language is able to load a static library as well, then you can proceed as follows.

My api_wrappers

apiconnect.c

#include "apiconnect.h"
#include <openssl/ssl.h>

int api_connect()
{
        SSL_library_init();
        OpenSSL_add_all_algorithms();
        SSL_load_error_strings();

        return 0;
}

apiconnect.h

int api_connect();
  • How I produced libapi.a

As my code is dependent on libssl and libcrypto, I started collecting their static versions from /usr/lib/x86_64-linux-gnu/. Then I extracted the contents of libssl.a and libcrypto.a using ar x library. I got around 700 object files. apiconnect.c is compiled to produce apiconnect.o and added to extracted object files. Then I packed all of them together to produce libapi.a. Below shows my terminal session:

root@kali:/tmp/apiconnect# ls 
apiconnect.c  apiconnect.h  libapi
root@kali:/tmp/apiconnect# cd libapi/
root@kali:/tmp/apiconnect/libapi# cp /usr/lib/x86_64-linux-gnu/libssl.a /usr/lib/x86_64-linux-gnu/libcrypto.a .
root@kali:/tmp/apiconnect/libapi# ar x libssl.a 
root@kali:/tmp/apiconnect/libapi# ar x libcrypto.a 
root@kali:/tmp/apiconnect/libapi# ls | wc -l 
702
root@kali:/tmp/apiconnect/libapi# cd ..
root@kali:/tmp/apiconnect# gcc -c apiconnect.c 
root@kali:/tmp/apiconnect# ls 
apiconnect.c  apiconnect.h  apiconnect.o  libapi
root@kali:/tmp/apiconnect# cp apiconnect.o libapi/
root@kali:/tmp/apiconnect# cd libapi/
root@kali:/tmp/apiconnect/libapi# ls *.a
libcrypto.a  libssl.a
root@kali:/tmp/apiconnect/libapi# rm *.a
root@kali:/tmp/apiconnect/libapi# ar rc libapi.a *
root@kali:/tmp/apiconnect/libapi# ls libapi.a
libapi.a
  • Testing

Since I don't know different, specialist language and I don't have it, for testing purposes I moved libapi.a and apiconnect.h to another system which does not have libssl or libcrypto installed. I tried to compile a test program which calls api_connect().

Test Program:

#include "apiconnect.h"

int main()
{
   api_connect();
   return 0;
}

Compilation:

jhansi@poseidon:/tmp/apitesting$ ls 
apiconnect.h  libapi.a  test.c
jhansi@poseidon:/tmp/apitesting$ cat test.c 
#include "apiconnect.h"

int main()
{
api_connect();
return 0;
}
jhansi@poseidon:/tmp/apitesting$ gcc -L./ test.c -lapi
.//libapi.a(threads_pthread.o): In function `CRYPTO_THREAD_lock_new':
(.text+0x46): undefined reference to `pthread_rwlock_init'
.//libapi.a(threads_pthread.o): In function `CRYPTO_THREAD_read_lock':
(.text+0x85): undefined reference to `pthread_rwlock_rdlock'
.//libapi.a(threads_pthread.o): In function `CRYPTO_THREAD_write_lock':
(.text+0xa5): undefined reference to `pthread_rwlock_wrlock'
.//libapi.a(threads_pthread.o): In function `CRYPTO_THREAD_unlock':
(.text+0xc5): undefined reference to `pthread_rwlock_unlock'
.//libapi.a(threads_pthread.o): In function `CRYPTO_THREAD_lock_free':
(.text+0xea): undefined reference to `pthread_rwlock_destroy'
.//libapi.a(threads_pthread.o): In function `CRYPTO_THREAD_run_once':
(.text+0x115): undefined reference to `pthread_once'
.//libapi.a(threads_pthread.o): In function `CRYPTO_THREAD_init_local':
(.text+0x135): undefined reference to `pthread_key_create'
.//libapi.a(threads_pthread.o): In function `CRYPTO_THREAD_set_local':
(.text+0x167): undefined reference to `pthread_setspecific'
.//libapi.a(threads_pthread.o): In function `CRYPTO_THREAD_cleanup_local':
(.text+0x187): undefined reference to `pthread_key_delete'
.//libapi.a(threads_pthread.o): In function `openssl_init_fork_handlers':
(.text+0x1e3): undefined reference to `pthread_once'
.//libapi.a(threads_pthread.o): In function `fork_once_func':
(.text+0x16): undefined reference to `pthread_atfork'
.//libapi.a(threads_pthread.o): In function `CRYPTO_THREAD_get_local':
(.text+0x153): undefined reference to `pthread_getspecific'
.//libapi.a(dso_dlfcn.o): In function `dlfcn_globallookup':
(.text+0x13): undefined reference to `dlopen'
(.text+0x26): undefined reference to `dlsym'
(.text+0x31): undefined reference to `dlclose'
.//libapi.a(dso_dlfcn.o): In function `dlfcn_bind_func':
(.text+0x1a7): undefined reference to `dlsym'
(.text+0x272): undefined reference to `dlerror'
.//libapi.a(dso_dlfcn.o): In function `dlfcn_load':
(.text+0x2e1): undefined reference to `dlopen'
(.text+0x359): undefined reference to `dlclose'
(.text+0x395): undefined reference to `dlerror'
.//libapi.a(dso_dlfcn.o): In function `dlfcn_pathbyaddr':
(.text+0x452): undefined reference to `dladdr'
(.text+0x4c7): undefined reference to `dlerror'
.//libapi.a(dso_dlfcn.o): In function `dlfcn_unload':
(.text+0x6a4): undefined reference to `dlclose'
collect2: error: ld returned 1 exit status

But unfortunately, it turns out to be that the object files we included are dependent on libpthread and libdl, and after including them in the compilation, I was successful to compile and run the program without errors.

jhansi@poseidon:/tmp/apitesting$ ls 
apiconnect.h  libapi.a  test.c
jhansi@poseidon:/tmp/apitesting$ gcc -L./ test.c  -lapi -lpthread -ldl
jhansi@poseidon:/tmp/apitesting$ ls 
a.out  apiconnect.h  libapi.a  test.c
jhansi@poseidon:/tmp/apitesting$ ldd a.out 
        linux-vdso.so.1 (0x00007ffedd2d7000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd3d0f30000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fd3d0d2c000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd3d093b000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fd3d1633000)
jhansi@poseidon:/tmp/apitesting$ ./a.out 
jhansi@poseidon:/tmp/apitesting$ 

From the output of ldd a.out, it is evident that our executable is not dependent on either of libssl and libcrypto, and of course it is not dependent on libapi.a since it is statically linked. But it is dependent libpthread.so and libdl.so, So if I were you, I would be loading libpthread.so and libdl.so along with libapi.a

I know, what is wrote is specific to libssl and libcrypto, but I hope you will find some luck with the libraries you are trying. Cheers :)

m0hithreddy
  • 1,752
  • 1
  • 10
  • 17
0

You need to extern "C" your CPP file the same way you did your header; otherwise the method names will be mangled in the object.

#ifdef __cplusplus
extern "C" {
#endif

int apiname_api_init(char const * user_app_name) {
        return to_return_code(APIName::Api_init(user_app_name));
}

void apiname_api_shutdown() {
        return APIName::api_shutdown();
}

#ifdef __cplusplus
}
#endif
HerrJoebob
  • 2,264
  • 16
  • 25