2

TL;DR

I have a library I want to use in my program which comes in two different versions. Both versions provide the same interface but differ in options used to compile them.

I now want to use a specific version of the library, however, since both versions are suitable for different tasks and the user should define the task to perform I need to decide which library to use at runtime.

I know I could use dlopen and dlsym to load the right library at runtime according to the users choice, however the interface is quite large, and loading everything I need into different function pointers would be quite tedious...

The Problem

I have a library which is shipped in two different versions. Both versions provide the same interface however differ in the task they are suitable for. This is how the file tree looks like:

lib
 \ - lib_task1
      \ - libsharedobj.so
 \ - lib_task2
      \ - libsharedobj.so

I want to provide the possibility for the user to choose which task to perform at runtime. Therefore I need to decide which library to choose at runtime as well. My idea was to write a wrapper which provides the same interface as the library and in which I would dlopen the desired lib and dlsymthe according symbols into function pointers. However, the library interface is quite large and wrapping it as described would be quite tedious, plus its a C interface so it also contains a lot of raw pointers I'd like to not see outside the wrapper.

Here is a small example

// library interface
typedef struct {
  // ...
} a_type;
void do_something(a_type* param);

// wrapper 
class LibWrapper {
private:
    void (*do_something)(a_type*);

    void* lib;
public:
    LibWrapper(const bool task_one) { // specified by the user
        if (task_one) {
            lib = dlopen("/usr/lib/lib_task1/libsharedobj.so", RTLD_NOW);
        } else {
            lib = dlopen("/usr/lib/lib_task2/libsharedobj.so", RTLD_NOW);
        }
        do_something = dlsym(lib, "do_something");
    }

    ~LibWrapper() {
        if (lib) {
            dlclose(lib);
        }
    }

    void do_something(std::unique_ptr<a_type> param) {
        do_something(param.get());
    }
};

The Question

Is there a better way in doing this, or do I really need to load each symbol one by one?

OS: ubuntu 14.04.
Compatibility: C++11

muXXmit2X
  • 2,745
  • 3
  • 17
  • 34
  • Does the main program need to decide more than once, and/or after startup, or does it know during startup which library implementation is desired, and can stick with it until exit? – John Zwinck Nov 27 '17 at 12:49
  • The loading takes place only once per run and the decision can't be changed until the program is started again. – muXXmit2X Nov 27 '17 at 12:51
  • Are you ok with changing library header as suggested by some of the answers? – yugr Nov 29 '17 at 09:59

4 Answers4

0

You can solve this by setting the environment variable LD_LIBRARY_PATH just before starting your program. If necessary, write a launcher program which does just this:

LD_LIBRARY_PATH=lib/lib_task1 ./myprog # or lib_task2

Then ld.so will look for libsharedobj.so in the specified directory first, regardless of which one was linked (i.e. which one ldd myprog shows).

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • Two questions: You say it uses this directory *first*, so it would search in the standard paths afterwards, right? Second: Is there a way to do this without writing an external wrapper (i.e. the starter program), couldn't I also change the variable from inside my program before any library function is called or would that be to late? – muXXmit2X Nov 27 '17 at 13:09
  • A similar trick would be to use LD_PRELOAD to specify the library to load "first" – Gem Taylor Nov 27 '17 at 13:33
  • 1
    @muXXmit2X: Yes, it will still look in standard paths after `LD_LIBRARY_PATH`. No, you cannot set the variable from inside your program--it is too late then. – John Zwinck Nov 28 '17 at 03:01
  • I'm not sure this answers the OPs question - he wants to select library version at runtime, not before program starts. – yugr Nov 28 '17 at 14:11
0

You could look at using objcopy to rename the public symbols in your libraries?

[more detail as requested]

If you rename all the competing functions in both libraries to non-competing namespaces, then you can load them both, and select the symbols you want at runtime.

If you can make the unique names look like real c++ namespaces, then you should be able to reuse the existing header file by #including it inside a namespace definition.

here are some global and namespace mangled names:

             U _Z9MyTestFn1Pv
             U _Z9MyTestFn2Pv
             U _ZN2N19MyTestFn1EPv
             U _ZN2N19MyTestFn2EPv
             U _ZN2N29MyTestFn1EPv
             U _ZN2N29MyTestFn2EPv

You can demangle them here: https://demangler.com/

Use objcopy --redefine-sym old=new or objcopy --redefine-syms=filename to do the renaming. The renaming can be generated using nm and sed.

With some additional clever macro work, you could even have it write a c-style function table prototype. You would still need to populate that table, though, but by having the real prototypes it is less likely that you will suffer the fat finger errors that are too easy with dlsym.

With your header reading something like the following to allow pointer declarations:

int (PTR_MAYBE FirstExternalFn) ( int firstArg,  ...  ) ;
std::stringint (PTR_MAYBE SecondExternalFn) ( bool firstArg,  ...  ) ;

or this may be too much, but allows the jump table to be auto-populated as well:

RETURNS(int) (PTR_MAYBE FirstExternalFn) ARGUMENTS ( int firstArg,  ...  ) ENDLINE
RETURNS(std::stringint) (PTR_MAYBE SecondExternalFn) ARGUMENTS ( bool firstArg,  ...  ) ENDLINE

A third way, intended to help populate a shim virtual c++ class, requires that you decorate every argument to the form OneWordType (name) which is relatively easy with templates i.e. const char* name becomes DecorateArg<char>::const_ptr (name). This then allows you to call the declaration! Of course the cost with using a virtual class is that the code is carrying round a this pointer that you will never use, and you are making an extra shim call.

note this file is included multiple times and there is no include guard, but it is not really intended for direct use any more. You could #error if a parent include is not defined.

You could use it in a number of ways:

//Declare the external methods
#define PTR_MAYBE 
#define ARGUMENTS 
#define ENDLINE ;
namespace FirstLib {
    #include Header.hpp
}
// And the second library
namespace SecondLib {
    #include Header.hpp
}

//define the indirection table declaration
#define PTR_MAYBE *
struct Indirection
{
    #include Header.hpp
    void* dummy;
};
namespace FirstLib 
{
    extern Indirection indirection;
}
namespace SecondLib 
{
    extern Indirection indirection;
}

// populate the table
#define ARGUMENTS(...) 
#define ENDLINE ,
namespace FirstLib 
{
    Indirection indirection =
    {  
        #include Header.hpp
        nullptr
    };
}
/* looks like:
         FirstExternalFn,
         SecondExternalFn,
         nullptr
*/
namespace SecondLib 
{
    Indirection indirection =
    {  
        #include Header.hpp
        nullptr
    };
}

That is all a bit nasty macro hackery, but if you are willing to build the table yourself, you don't need ARGUMENTS or ENDLINE, which keeps it more sane.

We now have access to 2 indirection tables, FirstLib::indirection, SecondLib::indirection, where you can call the library methods more or less directly from your main code, by assigning a pointer to your favourite:

std::cout << currentIndirection->FirstExternalFn(nullptr);
Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
  • But then you need runtime dispatching of every symbol. – John Zwinck Nov 28 '17 at 03:02
  • Well, yes, you still need to do some kind of runtime redirection, but it is less awful than dlsym(). If you are sneaky you could make your renamed symbols look like they are in a namespace, so then you can #include the existing headers into the same namespace. – Gem Taylor Nov 28 '17 at 10:14
  • Could you clarify how this allows OP to select function implementations at _runtime_? – yugr Nov 28 '17 at 14:28
0

This is very similar in spirit to Is there an elegant way to avoid dlsym when using dlopen in C?. Basically you are looking for an equivalent of Windows import libraries which would provide stub symbols to satisfy static linker and then dlopen dynamic library with real implementations at runtime.

Import libraries are not supported on Linux out of the box so people usually implement stubs by hand or via scripts tailored for particular project (e.g. GLEW). I've recently developed Implib.so to generate POSIX-compatible import libraries automatically. Modulo bugs, you should be able to use it as

$ implib-gen.py --dlopen-callback=mycallback

(mycallback selects appropriate version of library and dlopens it).

yugr
  • 19,769
  • 3
  • 51
  • 96
0

One way to obfuscate the setting of LD_PRELOAD might be that when your program starts, if LD_PRELOAD is not set suitably, determine which setting you want, add it, and immediately execv with your own arguments to restart! I think LD_PRELOAD makes the better sense here, if it does what you need, as it is easier to detect if it is set the way you need.

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
  • This is a popular solution but note that by the time you can restart application it might have already done some work or called problematic functions from other constructors (it's impossible to ensure that your constructor runs first). Also `LD_PRELOAD` is ignored for setuids. – yugr Dec 01 '17 at 10:00