Anyone good with dlopen() and friends?

Not at all Arduino-related, hence the Bar Sport, just wondering if one of the resident C guys can fill in some blanks for me on a Linux app I'm working on.

The app in question uses a plugin framework, where there will be a directory of .so files that can be enumerated and loaded at runtime. I've gotten the dlopen() and dlsym() calls to work, no problem there. However, I'm a little unsure how the dynamically linked code fits in. I assume, unlike a shared library that is linked at compile time, there could be differences in namespace. For example:

  • If your main executable dynamically loads pluginA.so and pluginB.so, is it reasonable for the entry point (init function) name to be identical across plugins? E.g....

main executable:

void *loadPlugin (char *fname) {
    void *lib = dlopen(fname, RTLD_LAZY);
    if (lib == NULL) return NULL;
    
    void *sym = dlsym(lib, "plugin_init");
    if (sym == NULL) {
        dlclose(lib);
        return NULL;
    }
    
    typedef void (*init_t)(void);
    init_t  init = (init_t)sym;
    
    (*init)();
    
    return lib;
}

Both pluginA.c and pluginB.c would have this:

void plugin_init() { ... }

Would those conflict, or since they are not compiled together, do their human-readable names mean nothing? (Assuming if that's the case, dlsym() can still resolve the different names since it has the handle to the shared object in question, and doesn't have to resolve the name to an address through the main executable's symbol table?)

Whew, OK... and question two:

  • Can pluginA and pluginB call functions in the main application? Obviously, they would have to include a header file with the functions declared, but the actual definition would be in a module they aren't linked to. Does dynamic loading resolve this, or would I need to deliver exported functions (exported from main to plugin) through function pointers?

In other words...

mainapp.c

#include "mainapp.h"

void do_something() { ... }

mainapp.h

void do_something();

plugin

#include "mainapp.h";

void plugin_init() {
    ...
    do_something();
}

I'm fighting with a segfault at the moment (unrelated -- a bad pointer I suspect), but it seems that one of my earlier tests failed because plugin.so had a fwd declaration for do_something(), but had no link to its function body. Could've been the result of other problems, just wondering if that's actually the case, and if so, what's the typical way of handling that sort of thing?

How do you feel about C++ (specifically, classes)?

Unprepared. :) It's on the to-do list.

I can use classes, and I "get" them generally, from having coded them in perl and PHP. But I couldn't write a competent C++ library from scratch.

Bummer. They make plugins significantly less work.

It's on the to-do list.

Feel like moving it higher on the list?

If the ultimate goal were to have a finished product, then I might consider switching languages (especially since it IS my goal to learn C++ as well), but I feel like that’s running away from the challenge. The whole project is reinventing the wheel, but it gives me a practical use for a lot of things I want to understand how to do.

It’s not really relevant, but a little background might put things into context… The app is a media player. I have code modules that parse .wav and .mp* files (have mpg, mp2, mp3, and working on mp4/mov right now), various tag formats, and output systems (well, currently system… ALSA). These were and are being written by me to learn the ins and outs of the file formats, and to give me something to work towards while I learned C. (It was my first real C project to parse, and then play, a wave file.) The world doesn’t need another audio file parser, I can use any number of existing libraries to do this, but it’s fun and I have a few long-term projects that need audio support, so all the effort serves a purpose.

I currently have standalone binaries that will play .wav files and .mp3 files, and because I can, I use them a lot while at the computer to play music. Next step is to have a single binary that loads a collection of file parsers and then selects the appropriate container parser and codec decoder to render a file. Again, I’m basically describing vlc here… or gstreamer or Amarok or … But those are all black boxes to me, as are all the media files that pass through them.

I don’t need to dynamically load these plugins – I own the source of the plugins and the main app, and could compile them together or as shared libs and be done with it. But this gives me an excuse to figure out dynamic loading, which is an interesting challenge that I almost have figured out (I think), I just don’t understand much beyond the simple working demo that I have, and I’m sure what I want to know is documented somewhere, I just haven’t found it yet.

So as a very long answer to a simple question – do I want to convert this to C++ to avoid the hassle? Not really. In this case, it’s a little like avoiding the gym because the weights are heavy. heheh Hope that makes sense.

An update after more testing:

It seems having the same entry point function name isn't a problem. (Two plugins loaded, each with their own init() symbol, no obvious conflicts.) Could still be a subtle trap there I suppose, but for the time being, I'll assume it's OK to do.

Loaded plugins were indeed unable to resolve symbols back into the main code. Not surprising, just not really sure yet what the proper solution is. Is it considered poor practice to reach back, plugin to main code? Am I supposed to export function pointers as needed? Or is there some mechanism I'm not aware of yet?

From some study into kernel hacking, it seems loadable modules don't behave the same way -- they tend to have unique function names (providing structs of function pointers to implement generic system function calls when necessary), and also call kernel functions at-will. At this point, I think the difference is that kernel modules are linked in at runtime as if they were compiled in, not merely accessible by address the way dynamic libraries are. Just a guess right now.

SirNickity: So as a very long answer to a simple question -- do I want to convert this to C++ to avoid the hassle? Not really. In this case, it's a little like avoiding the gym because the weights are heavy. heheh Hope that makes sense.

It does. But you are giving up a set of weights perfectly matched to you and your needs for old, damaged, wrongly sized weights.

SirNickity: It seems having the same entry point function name isn't a problem. (Two plugins loaded, each with their own init() symbol, no obvious conflicts.) Could still be a subtle trap there I suppose, but for the time being, I'll assume it's OK to do.

No traps.

Loaded plugins were indeed unable to resolve symbols back into the main code.

There are three ways to solve the problem... 1. A library shared by the add-in modules and the executable. 2. Passing pointers to functions from the executable to the add-in modules (e.g. via an initialization function). 3. Using dlopen / dlsym from the add-in module to dynamically bind to the executable.

Is it considered poor practice to reach back, plugin to main code?

Of course not. There are a set of problems that can only be solved by doing that sort of thing.

(providing structs of function pointers to implement generic system function calls when necessary)

Which is essentially a class filled with pure virtual functions.

OK, pretty much what I figured .. although I hadn't yet thought of a dynamic link back to the main executable. Sneaky.

Thanks for your help. I do see how classes could make a few things easier. There's a good chance I'll come back and rewrite this in C++ when I feel like I've got a handle on it. For that matter, it'll probably be the project that teaches me C++. But not quite yet. ;)

Really need to get that AAC support finished... I'm using KDE's Amarok to play some right now. Those KDE guys ... It took several releases and a few bug reports (that I've found! there were probably more) to decide it might be a good idea to hide the EQ pre-amp slider when the backend audio engine doesn't support it. Commit comment says "it can be confusing". You think? My approach might have been to implement it.. but eh.