Search code examples
c++externlinkagedlsym

Conflicting C linkage declaration of two functions in different C++ namespaces


In C++ code, I would like to be able to include two headers for two different versions of a shared C library that I load at runtime (with dlopen/dlsym on linux, GetProcAddress on windows).

For one execution, I load only one shared library (.so on linux, .dll on windows), the chosen version is determined by a parameter given to my program on the command line.

For each version of the C library, I hesitate between including one header for function declarations or the other one for function pointers types declaration (or both).

Headers for functions declarations are of this form :

#ifdef __cplusplus
extern "C" 
{
#endif

extern int func(int argInt);

#ifdef __cplusplus
}
#endif

Let's call the 2 versions of it my_header_old.h and my_header_new.h.

Headers for functions pointers types declarations are of this form :

typedef int (*func)(int argInt)

Let's call the 2 versions of it my_header_ptr_types_old.h and my_header_ptr_types_new.h.

This second form seems mandatory since I need to cast the result of dlsym/GetProcAddress which is of type void* to functions pointers types.

My first question is :

Is it mandatory to include the header for functions declarations in my case or can I use only the header for functions pointers types declarations ?

Since declarations in headers are very similars, I try to avoid conflicts with namespace :

namespace lib_old
{
#include "my_header_ptr_old.h"
}

namespace lib_new
{
#include "my_header_ptr_new.h"
}

My second question is :

Is it correct to declare functions pointers types this way in this case ?

I can do the same for the 1st form of headers but I'm not sure it's usefull, according to the first question above. Nevertheless, If I try it on windows, it compiles fine without warnings. Unfortunately, on linux I get:

my_header_new.h: warning: conflicting C language linkage declaration 'int lib_new::func(int)'

my_header_old.h: note: previous declaration 'int lib_old::func(int)'

The warning seems important according to the answers of this question. Furthemore, none of the answers purpose a workaround.

Since I didn't found any way to solve the conflict-problem without modifying the prototype of the functions in the headers my_header_new.h and my_header_old.h, I think a better way is to resolve the problem by using only the second form (my_header_ptr_old.h and my_header_ptr_new.h).

Eventually, I saw in comments that "C linkage moots the namespacing" and some "ABI conflicts" could happen "when you use both versions in the same translation unit" and I'm interested in sources on this subject.


Solution

  • I would consider the following approach with shared objects whose version might change and also ABI might change:

    foo version 1 (foo.1.cpp -> foo.1.so):

    #include "foo.h"
    
    #include <iostream>
    
    #define VERSION 1
    
    namespace foo::v1 {
        void bar() {
            std::cout << "version: " << VERSION << std::endl;
        }
    }
    
    namespace {
        call_table_v1 const ct = {
            .bar = foo::v1::bar,
        };
    
        call_table_description const ctd = {
            .version = VERSION,
            .call_table = &ct,
        };
    }
    
    call_table_description get_call_table_description(void)
    {
        return ctd;
    }
    

    foo version 2 (foo.2.cpp -> foo.2.so):

    #include "foo.h"
    
    #include <iostream>
    
    #define VERSION 2
    
    namespace foo::v2 {
        void bar(int param) {
            std::cout << "version: " << VERSION << ", param: " << param << std::endl;
        }
    }
    
    namespace {
        call_table_v2 const ct = {
            .bar = foo::v2::bar,
        };
    
        call_table_description const ctd = {
            .version = VERSION,
            .call_table = &ct,
        };
    }
    
    call_table_description get_call_table_description(void)
    {
        return ctd;
    }
    

    foo generic header to access different versions:

    #ifndef FOO_H_
    #define FOO_H_
    
    #ifdef __cplusplus
    extern "C" {
    #endif /* __cplusplus */
    
    struct call_table_v1 {
      void (*bar)(void);
    };
    
    struct call_table_v2 {
      void (*bar)(int);
    };
    
    struct call_table_description {
      int version;
      void const *call_table;
    };
    
    struct call_table_description get_call_table_description(void);
    
    #ifdef __cplusplus
    }
    #endif /* __cplusplus */
    
    #endif /* FOO_H_ */
    

    Main program to access libraries:

    #include <assert.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    #include <dlfcn.h>
    
    #include "foo.h"
    
    int main(int argc, char **argv) {
    
      int const version = atoi(argv[1]);
    
      void *lib;
      switch (version) {
      case 1:
        lib = dlopen("./foo.1.so", RTLD_NOW);
        break;
      case 2:
        lib = dlopen("./foo.2.so", RTLD_NOW);
        break;
      default:
        fprintf(stderr, "unsupported version %i of library\n", version);
        return EXIT_FAILURE;
      }
    
      if (!lib) {
        perror("could not open library");
        return EXIT_FAILURE;
      }
    
      typeof(get_call_table_description) *call_table_description_getter_f =
          dlsym(lib, "get_call_table_description");
    
      struct call_table_description const ctd = call_table_description_getter_f();
      assert(ctd.version == version);
    
      switch (ctd.version) {
      case 1: {
        struct call_table_v1 const *pct_v1 = ctd.call_table;
        pct_v1->bar();
      } break;
      case 2: {
        struct call_table_v2 const *pct_v2 = ctd.call_table;
        pct_v2->bar(42);
      } break;
      default:
        assert(0);
      }
    
      dlclose(lib);
    
      return 0;
    }
    

    Build and run to check it out:

    dpronin-gentoo➜  dlopen  ᐅ  g++ -shared -fPIC foo.1.cpp -ofoo.1.so 
    dpronin-gentoo➜  dlopen  ᐅ  g++ -shared -fPIC foo.2.cpp -ofoo.2.so
    dpronin-gentoo➜  dlopen  ᐅ  gcc main.c -g -omain                  
    dpronin-gentoo➜  dlopen  ᐅ  ./main 1
    version: 1
    dpronin-gentoo➜  dlopen  ᐅ  ./main 2
    version: 2, param: 42