14

When loaded a shared library is opened via the function dlopen(), is there a way for it to call functions in main program?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Nikron
  • 823
  • 2
  • 9
  • 13
  • The answers below do a good job of answering the question, but I have to ask--would you be able to explain the broader context of this requirement? When I've found the need to do this, it's either to construct an extensibility/plugin model or because my program isn't very well-factored. – reuben Dec 25 '08 at 06:18
  • it could also be used for inversion control, no? Define the flow of the application in the library while the actual implementation is in the main app – hhafez Dec 25 '08 at 11:19
  • Think of a Perl XS module. It needs to use low-level Perl functions (say newSViv() to create an SV from an integer); it is convenient if the module uses the newSViv() function from Perl, rather than embedding its own copy in the module's shared object. Also, the code needs the standard C library. – Jonathan Leffler Dec 25 '08 at 19:35

3 Answers3

25

Code of dlo.c (the lib):

#include <stdio.h>

// function is defined in main program
void callb(void);

void test(void) {
    printf("here, in lib\n");
    callb();
}

Compile with

gcc -shared -olibdlo.so dlo.c

Here the code of the main program (copied from dlopen manpage, and adjusted):

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

void callb(void) {
    printf("here, i'm back\n");
}

int
main(int argc, char **argv)
{
    void *handle;
    void (*test)(void);
    char *error;

    handle = dlopen("libdlo.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }

    dlerror();    /* Clear any existing error */

    *(void **) (&test) = dlsym(handle, "test");

    if ((error = dlerror()) != NULL)  {
        fprintf(stderr, "%s\n", error);
        exit(EXIT_FAILURE);
    }

    (*test)();
    dlclose(handle);
    exit(EXIT_SUCCESS);
}

Build with

gcc -ldl -rdynamic main.c

Output:

[js@HOST2 dlopen]$ LD_LIBRARY_PATH=. ./a.out
here, in lib
here, i'm back
[js@HOST2 dlopen]$

The -rdynamic option puts all symbols in the dynamic symbol table (which is mapped into memory), not only the names of the used symbols. Read further about it here. Of course you can also provide function pointers (or a struct of function pointers) that define the interface between the library and your main program. It's actually the method what i would choose probably. I heard from other people that it's not so easy to do -rdynamic in windows, and it also would make for a cleaner communication between library and main program (you've got precise control on what can be called and not), but it also requires more house-keeping.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Awesome answer. Somebody has earned 10k rep :-) – Norman Ramsey Dec 25 '08 at 03:35
  • That's an interesting way of doing the casting. Isn't it more normal to convert the return value of dlsym() to the pointer to function, rather than pretend that the pointer you're assigning to is is the same type as the function dlsym() returns? – Jonathan Leffler Dec 25 '08 at 06:14
  • C doesn't say what happens if you cast from a void* to a function pointer. sadly i haven't found the paragraph stating that it's undefined behavior, but the manpage says it states so. – Johannes Schaub - litb Dec 25 '08 at 06:41
  • you find more information on that here: http://www.opengroup.org/onlinepubs/009695399/functions/dlsym.html . Anyway, C++ explicitly forbids casting from void* or object pointers to a function pointer type or back. – Johannes Schaub - litb Dec 25 '08 at 06:42
  • so casting the stuff to void** like i did and then dereferencing is still undefined behavior, but XSI compliant systems will support it, and C++ doesn't explicitly forbid (makes it ill-formed) it (instead, it says it's undefined behavior). – Johannes Schaub - litb Dec 25 '08 at 06:45
  • @litb - I'd like to add a more-than-300-character comment about using union to 'resolve' the problem. May I edit your answer to add that info? – Jonathan Leffler Dec 25 '08 at 19:36
  • Jonathan, well i think you want to use an union, and put the function pointer and void pointer in them. then assign the void pointer and read the function pointer member. it will compile of course, as will the cast to void** , and both are undefined behavior in C. – Johannes Schaub - litb Dec 25 '08 at 21:32
  • @litb: Yes - you got it. The only difference is that the union bypasses warnings that the cast does not. – Jonathan Leffler Dec 26 '08 at 07:35
4

Yes, If you provide your library a pointer to that function, I'm sure the library will be able to run/execute the function in the main program.

Here is an example, haven't compiled it so beware ;)

/* in main app */

/* define your function */

int do_it( char arg1, char arg2);

int do_it( char arg1, char arg2){
  /* do it! */
  return 1;
}

/* some where else in main app (init maybe?) provide the pointer */
 LIB_set_do_it(&do_it);
/** END MAIN CODE ***/

/* in LIBRARY */

int (*LIB_do_it_ptr)(char, char) = NULL;

void LIB_set_do_it( int (*do_it_ptr)(char, char) ){
    LIB_do_it_ptr = do_it_ptr;
}

int LIB_do_it(){
  char arg1, arg2;

  /* do something to the args 
  ...
  ... */

  return LIB_do_it_ptr( arg1, arg2);
}
hhafez
  • 38,949
  • 39
  • 113
  • 143
  • do_it_ptr takes a pointer to a function that expects 3 char arguments; you assign function pointers for functions that just take 2 char arguments. The extern declaration for doit() is hardly needed. The do_it_ptr is not needed; you can just pass do_it by name where you currently pass do_it_ptr. Etc! – Jonathan Leffler Dec 25 '08 at 06:18
  • that's correct :) in fact you could also get rid of the LIB_get_it() and just define a new LIB_do_it( int (*do_it_ptr)(char, char, char) ) { return do_it_ptr( arg1, arg2, arg3 ) } – hhafez Dec 25 '08 at 11:17
1

The dlopen() function, as discussed by @litb, is primarily provided on systems using ELF format object files. It is rather powerful and will let you control whether symbols referenced by the loaded library can be satisfied from the main program, and generally does let them be satisfied. Not all shared library loading systems are as flexible - be aware if it comes to porting your code.

The callback mechanism outlined by @hhafez works now that the kinks in that code are straightened out.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278