views:

223

answers:

2

I've successfully loaded a C++ plugin using a custom plugin loader class. Each plugin has an extern "C" create_instance function that returns a new instance using "new".

A plugin is an abstract class with a few non-virtual functions and several protected variables(std::vector refList being one of them).

The plugin_loader class successfully loads and even calls a virtual method on the loaded class (namely "std::string plugin::getName()".

The main function creates an instance of "host" which contains a vector of reference counted smart pointers, refptr, to the class "plugin". Then, main creates an instance of plugin_loader which actually does the dlopen/dlsym, and creates an instance of refptr passing create_instance() to it. Finally, it passes the created refptr back to host's addPlugin function. host::addPlugin successfully calls several functions on the passed plugin instance and finally adds it to a vector<refptr<plugin> >.

The main function then subscribes to several Apple events and calls RunApplicationEventLoop(). The event callback decodes the result and then calls a function in host, host::sendToPlugin, that identifies the plugin the event is intended for and then calls the handler in the plugin. It's at this point that things stop working.

host::sendToPlugin reads the result and determines the plugin to send the event off to.

I'm using an extremely basic plugin created as a debugging plugin that returns static values for every non-void function.

Any call on any virtual function in plugin in the vector causes a bad access exception. I've tried replacing the refptrs with regular pointers and also boost::shared_ptrs and I keep getting the same exception. I know that the plugin instance is valid as I can examine the instance in Xcode's debugger and even view the items in the plugin's refList.

I think it might be a threading problem because the plugins were created in the main thread while the callback is operating in a seperate thread. I think things are still running in the main thread judging by the backtrace when the program hits the error but I don't know Apple's implementation of RunApplicationEventLoop so I can't be sure.

Any ideas as to why this is happening?

class plugin
{
public:
    virtual std::string getName(); 

protected:
    std::vector<std::string> refList;
};

and the pluginLoader class:

template<typename T> class pluginLoader 
{
    public: pluginLoader(std::string path);
    // initializes private mPath string with path to dylib

    bool open(); 
    // opens the dylib and looks up the createInstance function. Returns true if successful, false otherwise

    T * create_instance(); 
    // Returns a new instance of T, NULL if unsuccessful
}; 

class host
{
public:
      addPlugin(int id, plugin * plug);
      sendToPlugin(); // this is the problem method
      static host * me;

private:
     std::vector<plugin *> plugins; // or vector<shared_ptr<plugin> > or vector<refptr<plugin> >
};

apple event code from host.cpp;

host * host::me;
pascal OSErr HandleSpeechDoneAppleEvent(const AppleEvent *theAEevt, AppleEvent *reply, SRefCon refcon) {
         // this is all boilerplate taken straight from an apple sample except for the host::me->ae_callback line
         OSErr status = 0;
         Result result = 0;
         // get the result
         if (!status) {
             host::me->ae_callback(result);
         }
         return status;
}
void host::ae_callback(Result result) {
       OSErr err;
       // again, boilerplate apple code
   // grab information from result
       if (!err)
         sendToPlugin();
}
void host::sendToPlugin() {
       // calling *any* method in plugin results in failure regardless of what I do
}

EDIT: This is being run on OSX 10.5.8 and I'm using GCC 4.0 with Xcode. This is not designed to be a cross platform app.

EDIT: To be clear, the plugin works up until the Apple-supplied event loop calls my callback function. When the callback function calls back into host is when things stop working. This is the problem I'm having, everything else up to that point works.

+1  A: 

Without seeing all of your code it isn't going to be easy to work out exactly what is going wrong. Some things to look at:

  • Make sure that the linker isn't throwing anything away. On gcc try the compile options -Wl -E -- we use this on Linux, but don't seem to have found a need for it on the Macs.
  • Make sure that you're not accidentally unloading the dynamic library before you've finished with it. RAII doesn't work for unloading dynamic libraries unless you also stop exceptions at the dynamic library border.

You may want to examine our plug in library which works on Linux, Macs and Windows. The dynamic loading code (along with a load of other library stuff) is available at http://svn.felspar.com/public/fost-base/trunk/

We don't use the dlsym mechanism -- it's kind of hard to use properly (and portably). Instead we create a library of plugins by name and put what are basically factories in there. You can examine how this works by looking at the way that .so's with test suites can be dynamically loaded. An example loader is at http://svn.felspar.com/public/fost-base/trunk/fost-base/Cpp/fost-ftest/ftest.cpp and the test suite registration is in http://svn.felspar.com/public/fost-base/trunk/fost-base/Cpp/fost-test/testsuite.cpp The threadsafe_store holds the factories by name and the suite constructor registers the factory.

KayEss
I'm not actually unloading it as plugins stay live for the entire lifetime of the application right now.I'm using the factory pattern and it's working, everything works perfect on the main thread. As soon as the main thread starts RunApplicationEventLoop and an event starts to get handled in a seperate thread things stop working. I've edited the question with a little more code related to Apple events, maybe that'll help.
A: 

I completely missed the fact that I was calling dlclose in my plugin_loader's dtor and for some reason the plugins were getting destructed between the RunApplicatoinEventLoop call and the call to sendToPlugin. I removed dlclose and things work now.

If you look at the implementation we have there's an extra fostlib::atexit() implementation that allows the dlclose to be scheduled for execution when the application terminates. That way everything still gets destructed cleanly. You can see the code at http://svn.felspar.com/public/fost-base/trunk/fost-base/Cpp/fost-core/dynlib-linux.cpp -- all it does is to defer the delete on the internal pimpl pointer until the applicattion terminates (the file name implies that it is Linux, but the same code is also used on the Mac).
KayEss
BTW, isn't that exactly the second point I made in my answer? "Make sure that you're not accidentally unloading the dynamic library before you've finished with it."
KayEss