|
Loading classesLoading classesC++ 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 InterfaceAt 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 implementationIn 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 theobject 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
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" { factory DLLEXPORT *myfactory_init() { return myfactory::instance(); } } //____________________________________________________________________ Usage of the dynamic classesUsage 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
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 functionsSuppose 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. 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
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
template<int c, int m> void* FANCY_FACTORY callit(void*,std::vector<void*>& a) { return 0; }
and one for the member function
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
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
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;
Last update Mon Jun 27 13:25:17 2005 Christian Holm Created by DoxyGen 1.4.3-20050530 |