1
  • Language: C
  • Operating System: Red Hat EL

Starting with a "for instance":

  • Assume I have two libraries: libJUMP.so and libSIT.so.
  • JUMP contains the function jump() and similarly SIT contains the function sit()
  • I have an application that I want to provide to different people; they can either get the jump() feature, the sit() feature, or both. However, I would like to NOT use #ifdef if at all possible.

Header for libJUMP.so:

#ifndef JUMP_H_
#define JUMP_H_

#define JUMP_ENABLED
void jump();

#endif /* JUMP_H_ */

Header for libSIT.so:

#ifndef SIT_H_
#define SIT_H_

#define SIT_ENABLED
void sit();

#endif /* SIT_H_ */

I have an application:

#include "jump.h"
#include "sit.h"

int main()
{
    // #ifdef JUMP_ENABLED
    jump();
    // #endif /* JUMP_ENABLED */

    // #ifdef SIT_ENABLED
    sit();
    // #endif /* SIT_ENABLED */
}

So:

  • Is there a way to do this without using #ifdef? Is there a better way at all?
    • I have heard we could do this by compiling with both SO libraries, and if one is missing when I run the application on the target system, it could just exclude the feature automatically (using some combination of dlopen() and dlsym()?) Any easy examples, if this is indeed correct? An example with my code from above, if possible :D?

If this is a stupid question, or just not possible, please feel free to tell me so. If there is a similar question that this would be considered a duplicate of, let me know and I will delete this post.

Sagar
  • 9,456
  • 6
  • 54
  • 96
  • I would create a new #define called for example `JUMP_OR_SIT` which is either `jump` or `sit`. – wimh Mar 03 '15 at 16:56
  • Does [this](http://stackoverflow.com/a/5592898/1971013) help? – meaning-matters Mar 03 '15 at 17:05
  • First of all, The example you've given is far too vague to recommend a solid solution. Your answer is probably autoconf, if compile-time checking is OK. It can check existing libraries and put appropriate `define`s in a config header file. Linux Kernel Development book suggests avoiding `ifdef` inside functions as much as possible. In your case, the book would recommend defining an empty sit function if `SIT_ENABLED` is not defined, and you'll still be able to call sit anywhere. – holgac Mar 03 '15 at 17:08
  • @meaning-matters sadly not. It talks of `darwin`, which is fine because I think there would be some similarity between apple gcc and standard gcc. However, all he talks about is stripping symbols, not enabling or disabling certain functions at runtime. – Sagar Mar 03 '15 at 17:09
  • @holgac that's my aim - to avoid `#ifdef` within functions. I shall look into `autoconf`. I was trying to keep the example short - sorry if it's vague. I can add any more details required. My final aim is to have certain functions available (or not) at runtime, if that makes more sense? – Sagar Mar 03 '15 at 17:14
  • Well, the answer depends on whether you need to check the existence of a library at runtime. For example, you might say "sit if you can, else jump" in some function and "sit if you can, else do nothing" in another, which would further complicate the task. Or at some point, you might be interested in whether `sit` succeeded. In those cases, you might want to surround the sit-calling functions with ifdefs instead. Things *will* get hairy if at some point you'd actually depend on `sit` regardless of its availability. But for the basic case, empty functions are fine. – holgac Mar 03 '15 at 17:22
  • Why did you tag this as C++ when you explicitly state in your question you are using the C language? – Thomas Matthews Mar 03 '15 at 17:44
  • @ThomasMatthews that was an out of habit C/C++ typo - have to use it when searching something on Google otherwise I get the letter C. I've removed it. – Sagar Mar 03 '15 at 19:17

1 Answers1

2

Consider these three files. First, jump.c:

#include <stdio.h>

int jump(const double height)
{
    fflush(stdout);
    fprintf(stderr, "Jumping %.3g meters.\n", height);
    fflush(stderr);
    return 0;
}

Second, sit.c:

#include <stdio.h>

int sit(void)
{
    fflush(stdout);
    fprintf(stderr, "Sitting down.\n");
    fflush(stderr);
    return 0;
}

Third, example.c to use one or both of the above, depending on whether they (as libjump.so or libsit.so, respectively) exist in the current working directory:

#include <stdio.h>
#include <dlfcn.h>

static const char *jump_lib_path = "./libjump.so";
static int (*jump)(const double) = NULL;

static const char *sit_lib_path = "./libsit.so";
static int (*sit)(void) = NULL;

static void load_dynamic_libraries(void)
{
    void *handle;

    handle = dlopen(jump_lib_path, RTLD_NOW | RTLD_LOCAL);
    if (handle) {
        jump = dlsym(handle, "jump");
        /* If no jump symbol, we don't need the library at all. */
        if (!jump)
            dlclose(handle);
    }

    handle = dlopen(sit_lib_path, RTLD_NOW | RTLD_LOCAL);
    if (handle) {
        sit = dlsym(handle, "sit");
        /* If no sit symbol, the library is useless. */
        if (!sit)
            dlclose(handle);
    }
}

int main(void)
{
    int retval;

    load_dynamic_libraries();

    if (jump) {
        printf("Calling 'jump(2.0)':\n");
        retval = jump(2.0);
        printf("Returned %d.\n\n", retval);
    } else
        printf("'jump()' is not available.\n\n");

    if (sit) {
        printf("Calling 'sit()':\n");
        retval = sit();
        printf("Returned %d.\n\n", retval);
    } else
        printf("'sit()' is not available.\n\n");

    return 0;
}

Let's first compile and run the example program:

gcc -Wall -O2 example.c -ldl -o example
./example

The program outputs that neither jump() or sit() are available. Let's compile jump.c into a dynamic library, libjump.so, and then run the example again:

gcc -Wall -O2 -fPIC -shared jump.c -Wl,-soname,libjump.so -o libjump.so
./example

Now, the jump() function works. Let's compile sit.c, too, and run the example a final time:

gcc -Wall -O2 -fPIC -shared jump.c -Wl,-soname,libsit.so -o libsit.so
./example

Here, both functions get called, and everything just works.


In example.c, jump and sit are function pointers. We initialize them to NULL, so that we can use if (jump) to check if jump points to a valid function.

The load_dynamic_libraries() function uses dlopen() and dlsym() to obtain the function pointers. Note that if the dynamic library is opened successfully, and the necessary symbol is found, we do not dlclose() it because we want to keep the dynamic library in memory. (We only dlclose() it if it looks like it is not the kind of library we want.)

If you want to avoid the if (jump) and if (sit) clauses, you can use stubs like

int unsupported_jump(const double height)
{
    return ENOTSUP;
}

int unsupported_sit(void)
{
    return ENOTSUP;
}

and at the end of load_dynamic_libraries(), divert the functions to the stubs instead of NULL pointers, i.e.

if (!jump)
    jump = unsupported_jump;
if (!sit)
    sit = unsupported_sit;

Note that function-like interfaces are easiest to use, because the function pointer acts as the effective prototype. If you need objects, I recommend using getter functions. Objects do work just fine, as long as you remember that dlsym() returns a pointer to the object; using a getter function, that is explicit in the getter function pointer type.

Plug-in interfaces commonly have a single function (say, int properties(struct plugin *const props, const int version)), which is used to populate a structure of function and object pointers. The application supplies the version of the structure it uses, and the plug-in function returns either success or failure, depending on whether it can populate the structure to accommodate that version.

As plug-ins are typically stored in a single directory (/usr/lib/yourapp/plugins/ is very common), you can trivially load all plugins by using opendir() and readdir() to scan the file names in the plug-in directory one by one, dlopen()ing each one, obtaining the properties() function pointer, and calling it to see what kinds of services the plugin provides; typically creating an array or a linked list of the plugin structures.

All of this is very, very simple and straightforward in Linux, as you can see. If you want a specific plug-in functionality example, I recommend you pose that as a separate question, with more details on what kind of functionality the interface should expose -- the exact data structures and function prototypes do depend very much on what kind of application we have at hand.

Questions? Comments?

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • Thank you so much! This is by far the easiest explanation I have seen, and is exactly what I was looking for. If I could, I'd buy you a beer, but since I can't - another thank you is in order! Thank you! – Sagar Mar 04 '15 at 14:13