Loading classes

Loading classes

C++ compilers mangle the symbols it puts in object file, so it's generally not possible to get the right name to look for to get a reference to a C++ function, not to mention a member function. The application programmer must therefore employ some trickery for the application to load classes and class instances dynamically.

Abstract Interface

At the heart of the solution, is to have a class hierarchy that's rooted in one base class, and the application will deal only with that interface. The base class is abstract, and serves as the applications `point of recognintion':

class object 
{
public:
  object() {}
  virtual ~object() {}
  virtual void print() const = 0;
};

This class does not define a very interesting interface, but it serves to outline the idea. A concrete library should define sub-class(es) of this base class, and implement the methods there.

Now, as the class member functions are not readily avaliable dynamically (due to mangling), and hence the constructor isn't either, the application should use a factory pattern to create objects of the base class:

class factory
{
public:
  /** Constructor. */
  factory() {}
  /** Destructor. */
  virtual ~factory() {}
  /** Create an object, returning the address. */
  virtual object* create() = 0;
};

This factory only defines abstract interfaces. A concrete library must define a sub-class, and implement the interface. Note, that the above factory assumes that the object constructors does not take any arguments.

Concrete implementation

In a concrete class library, the developer should sub-class the abstract base class, as well as the abstract factory. Here's one example of a concrete descendent of the object base class

class myobject : public object
{
public:
  myobject() {}
  virtual ~myobject() {}
  void print() const {
    std::cout << "Hello from myobject " << ltmm_util::basename(__FILE__) 
              << " line "  << __LINE__ << std::endl;
  }
};

The library should also implement the factory base class. Here's one such example

class myfactory : public factory 
{
private:
  static factory* _instance;
  myfactory() {}
  virtual ~myfactory() {}  
public:
  static factory* instance() {
    if (!_instance) _instance = new myfactory;
    return _instance;
  }
  object* create(){ return new myobject; }
};

Now, due to mangling, the application can not create an object of either class directly, so instead, the library must provide a C function (use of extern "C" linkage specification) that instantices a factory sub-class and returns it to the caller. Here, the singleton pattern was used to insure that only one factory of a given kind may exist:

extern "C" 
{
  factory DLLEXPORT *myfactory_init() 
  {
    return myfactory::instance();
  }
}

//____________________________________________________________________

Usage of the dynamic classes

Usage of the dynamic classes is now quite simple. The application should make a ltmm::loader instance, open the dynamic module, find the C function that returns the factory, and call it.

using namespace ltmm;

typedef function0<factory*> factory_init;

Having obtained an object of the concrete factory, the application can put it to use, by calling the factory::create member function. That returns a new pointer to a object object pointing to an object of the concrete class. The application can then use the interface defined by object

typedef function0<arbitrary_factory*> arbitrary_init;

int main(int argc, char** argv) 
{
  try {
    std::map<std::string,symbol_list>& preloaded = loader<>::preloaded();
    loader<>& l              = loader<>::instance();
    l.addto_search_path(".libs");

    handle<>&      myhandle  = l.load("myobject.la");
    factory_init   myinit(myhandle.find_symbol("myfactory_init"));
    factory*       myfactory = myinit();
    object*        myobject  = myfactory->create();
    myobject->print();

Calling arbitrary member functions

Suppose the application does not know the interface that a class provide a priori. It would be nice to be able to call those interfaces, even if the class is completely unknown.

First, there should be some abstract factory class that the application can use.

class arbitrary_factory 
{
public:
  virtual void* call(void* o, const std::string& c, 
                     const std::string& m, std::vector<void*>& a) = 0;
};

This class only defines one member function. arbitrary_factory::call takes 4 arguments, a pointer to some memory were some sort of object may live, the name of the class, the name of a member function of that class, and an argument list, encoded as a void* vector. It returns the return value of the member function encoded as a void*.

Now, suppose there's some class that the application would like to interface:

class fancy 
{
private:
public:
  fancy() {}
  ~fancy() {}
  int foo(int a, int b) {
    std::cout << "Hello from fancy::foo in " << ltmm_util::basename(__FILE__)
              << " line " << __LINE__
              << ": a=" << a << " b=" << b << std::endl;
    return a+b;
  }
};

What the library then needs is a sub class of the arbitrary_factory class that can interface objects of that kind.

class fancy_factory : public arbitrary_factory 
{
  static arbitrary_factory* _instance;
  fancy_factory() {}
  virtual ~fancy_factory() {}  
public:
  static arbitrary_factory* instance(); 
  void* call(void* o, const std::string& c, const std::string& m, 
             std::vector<void*>& a);
#ifdef HAVE_TEMPLATE_MEMFUNC
  template<int c, int m> void* callit(void*,std::vector<void*>& a);
#endif
};

Again, this class uses the singletion pattern for exactly the same reasons as before.

Here, the fancy_factory class implements the arbitrary_factory::call member function, as well as having an internal templated member function of the same name. The default instantation of the member function does nothing. However, the library defines two specialisations that does something; one for the constructor of fancy:

template<int c, int m> 
void* FANCY_FACTORY callit(void*,std::vector<void*>& a) 
{
  return 0;
}

and one for the member function foo:

template <>
void* FANCY_FACTORY callit<1,1>(void* o, std::vector<void*>& a) 
{
  fancy* m = new fancy;
  return m;
}

Note how these two templated member functions decode the memory address, and argument list, and encode the return value.

Finally, the non-templated arbitrary_factory::call member function is implemeted to call the appropiate templated member function, based on it's 2nd and 3rd arguments:

void* FANCY_FACTORY callit<1,2>(void* o, std::vector<void*>& a) 
{
  static int retval;
  if (!o) return 0;
  int aa = *((int*)a[0]);
  int bb = *((int*)a[1]);
  fancy* m = static_cast<fancy*>(o);
  retval = m->foo(aa, bb);
  return static_cast<void*>(&retval);
}
//____________________________________________________________________

The application can then use the fancy_factory interface to create and manipulate objects of the class fancy

    handle<>&         fancyhandle = l.load("fancy.la");
    arbitrary_init    fancyinit(fancyhandle.find_symbol("fancy_factory_init"));
    arbitrary_factory* fancyfactory = fancyinit();
    
    std::vector<void*> args;
    void* ret = 0;
    void* fancyobject = fancyfactory->call(0, "fancy", "fancy", args);
    
    int a = 10;
    int b = 20;
    int c;
    args.push_back(&a);
    args.push_back(&b);
    ret = fancyfactory->call(fancyobject, "fancy", "foo", args);
    c = *((int*)ret);
    std::cout << "Return value: " << c << std::endl;

See also:
object.hh

myobject.cc

factory.cc

arbitrary.hh

fancy.cc

Top of page
Last update Mon Jun 27 13:25:17 2005
Christian Holm
Created by DoxyGen 1.4.3-20050530