7

I figure out I can have the implementation of parts of a class in a shared lib, as far as the symbols are loaded when used.

myclass.h
---

class C {
void method();
}

main.cpp
---

#include "myclass.h"
int main() {
    //dynamically load mylib.so using dlopen/dlsym/dlclose
        ...
    C *c = new C();
    c->method();
    delete c;
}

mylib.so compiled separately:
====
mylib.cpp
---

#include "mylib.h"
void C::method() {
...
}

This works fine.

However once I finished using C::method(), I would like to unload it, so I can change, recompile and reload it without having to restart the main program

int main() {
    //dynamically load mylib.so using dlopen/dlsym/dlclose
        ...
    C *c = new C();
    c->method();
    delete c;
    // close the lib, remove the handle...
         ....
    char pause;
    cin << pause; // before carrying on change the C::method, recompile it

    //dynamically load *Again* mylib.so using dlopen/dlsym/dlclose
        ...
    C *c = new C();
    c->method();
    delete c; 
}

The problem is that it does not unload the library when doing the first dlclose, probably because an instance of my class C exists. Any way to force this?

(using g++ 4.2.3, on Linux Ubuntu 10.04)

Ben
  • 165
  • 2
  • 5
  • I don't think it is tracked that you have an instance of a class (especially by pointer). How do you know, library is not unloaded? – Krizz Jan 10 '12 at 16:27
  • Can you call `dlerror` after `dlclose` to see why the library wasn't unloaded ? – parapura rajkumar Jan 10 '12 at 16:34
  • @Krizz: the behavior remains the same despites the changes I do. E.g. cout <<"changes made" << endl won't be displayed. Also strace points out the library being opened only once. 27732 open("[...]ReloadablePart.so", O_RDONLY) = 4 ... 27732 close(4) = 0 27732 write(1, "Calling reloadedPart...\n", 24) = 24 Btw, I do not really understand the "close(...) =0" information ; if the lib is unloaded, why can I call the symbols of it after? – Ben Jan 10 '12 at 17:03
  • @parapura rajkumar: no error displayed after "cout << dlerror() << endl", however doing so will prevent my application from displaying any output – Ben Jan 10 '12 at 17:41
  • Are you opening the library with `RTLD_GLOBAL`? That might cause other libraries to use symbols within it, preventing it from unloading when closed. If you just want to find symbols with `dlsym` then you don't need that flag. – Mike Seymour Jan 10 '12 at 17:53

1 Answers1

11

Short Answer

It won't work the way you are doing it. There might also be other problems with your approach that haven't bitten you yet.

Why it doesn't work

Undefined symbols in your program/library are resolved at different times. On most systems, data references (global variables, class vtables, etc.) are always resolved when your program/library is loaded. Code references are resolved when they are first used on some systems ("lazy lookup"; it happens on Linux and Mac OS X, at least), unless some special options are set (RTLD_NOW parameter for dlopen or LD_BIND_NOW environment variable). Once these are resolved, no new lookup will take place.

If you dlopen your library with the RTLD_GLOBAL flag before the lazy lookup for a method is done, the method from your library will be used. The code reference to the method is now resolved; it won't change again. Your main program now officially uses symbols from your dlopened library, so dlclose will no longer close the library - dlclose only drops your explicit handle to it.

In short, you should only ever expect to unload libraries that you only use via explicit calls to dlsym.

What to do instead

What you can do instead is have your library provide a derived class implementation. You'd define your class C as an abstract base class:

class C
{
public:
    virtual void method();
};

In your separately-compiled library, you'd define a derived class and a function that creates an object of that derived class:

class D : public C
{
public:
    virtual void method();
};

void D::method()
{
    // ...
}

extern "C" C* createC()
{
    return new D();
}

Now, in your main program, you'd load the library using dlopen, get a function pointer to createD using dlsym and call it to get an object. When all objects are gone, you can call dlclose, recompile your library, and do the whole thing again:

typedef C* (*creatorFunction)();

int main()
{
    for(;;)
    {
        void *handle = dlopen("mylib.so", 0);
        creatorFunction create = (creatorFunction) dlsym(handle, "createC");

        C *c = (*create)();
        c->method();
        delete c;

        dlclose(handle);

        char pause;
        cin << pause;
    }
    return 0;
}
wolfgang
  • 4,883
  • 22
  • 27
  • Why is this different? The `createC()` function could move between library loads, right? – Andres Jaan Tack Jan 10 '12 at 22:11
  • 2
    @AndresJaanTack Yes, but more importantly: In the original code, the c->method() line is a direct function call to the library, which causes a lazy symbol lookup. Once the lazy symbol lookup has happened, C::method() always refers to that code, and the library cannot be unloaded any more. Here, there are no direct calls; (*create)() goes via a function pointer obtained from dlsym, and c->method() is a virtual function call, so ``dlclose`` will still unload the library. – wolfgang Jan 10 '12 at 22:21
  • @wolfgang: great explanation. And that looks better design to not split the code of an object in separated files. However it would have been convenient in some testing cases, and it seams it is "only" because of "once The code reference to the method is resolved, it won't change again". I am not going to test it out, but if there is a way to undo that, that would make this split of declaration and implementation possible. – Ben Jan 11 '12 at 13:49