Update of /cvsroot/libfunutil/libfunutil/lib/cl In directory sc8-pr-cvs1:/tmp/cvs-serv25688/lib/cl Added Files: LoadableClass.cpp LoadableClass.h LoadableSubClass.cpp Makefile ns.class_loader.cpp ns.class_loader.h ns.classname_transformer.h ns.cl_demo.cpp ns.context_singleton.h ns.debuggering_macros.h ns.dll_loader.h ns.instantiator.h ns.path_finder.cpp ns.path_finder.h Log Message: egg --- NEW FILE: LoadableClass.cpp --- #include "LoadableClass.h" --- NEW FILE: LoadableClass.h --- #ifndef LOADABLECLASS_H_INCLUDED #define LOADABLECLASS_H_INCLUDED 1 // License: Public Domain // Author: st...@wa... #include "class_loader.h" #include "debuggering_macros.h" /** A base class for testing and demonstrating dll_loader. */ class LoadableClass { public: LoadableClass() { CERR << "LoadableClass()"<<std::endl; } virtual ~LoadableClass() { CERR << "~LoadableClass()"<<std::endl; } }; CLASSLOADER_REGISTER(LoadableClass,LoadableClass); #endif // LOADABLECLASS_H_INCLUDED --- NEW FILE: LoadableSubClass.cpp --- // License: Public Domain // Author: st...@wa... #include "class_loader.h" #include "debuggering_macros.h" #include "LoadableClass.h" /** A class for demonstrating dll_loader, intended to be compiled to a standalone DLL. */ class LoadableSubClass : public LoadableClass { public: LoadableSubClass() { CERR << "LoadableSubClass()"<<std::endl; } virtual ~LoadableSubClass() { CERR << "~LoadableSubClass()"<<std::endl; } }; CLASSLOADER_REGISTER(LoadableClass,LoadableSubClass); CLASSLOADER_REGISTER4(LoadableClass,LoadableSubClass,int,7); --- NEW FILE: Makefile --- include toc.make # maintenance notes: # expected via toc.make: # $(CL_NAMESPACE) # $(LIBCL_LIBNAME) # $(LIBCL_VERSION) # $(LIBCL_CLIENT_LDADD) CL_NAMESPACE ?= cl LIBCL_VERSION ?= $(PACKAGE_VERSION) LIBCL_LIBNAME ?= $(CL_NAMESPACE)_class_loader NS_SOURCES = ns.class_loader.cpp \ ns.path_finder.cpp \ ns.cl_demo.cpp # ns.classname_transformer.cpp NS_HEADERS = \ ns.class_loader.h \ ns.classname_transformer.h \ ns.context_singleton.h \ ns.dll_loader.h \ ns.instantiator.h \ ns.path_finder.h \ ns.debuggering_macros.h DIST_FILES += $(NS_SOURCES) $(NS_HEADERS) # Be careful with SOURCES/HEADERS, lest they be cleaned up when you don't # want them to be. Simplest is to put everything into NS_{HEADERS/SOURCES} and expect # them to be filtered by sed. SOURCES = \ $(patsubst ns.%,%,$(NS_SOURCES)) HEADERS = \ $(patsubst ns.%,%,$(NS_HEADERS)) CLEAN_FILES += $(SOURCES) $(HEADERS) NAMESPACE = $(CL_NAMESPACE) NAMESPACE_TOKEN = CL_NAMESPACE NAMESPACE_PREFIX = ns. NAMESPACE_FILTERED_FILES = $(SOURCES) $(HEADERS) include $(toc_makesdir)/NAMESPACE.make INSTALL_PACKAGE_HEADERS_DEST = $(prefix)/include/$(CL_NAMESPACE) INSTALL_PACKAGE_HEADERS = $(HEADERS) SYMLINK_HEADERS = $(INSTALL_PACKAGE_HEADERS) SYMLINK_HEADERS_DEST = $(top_srcdir)/include/$(CL_NAMESPACE) include $(toc_makesdir)/symlink_headers.make OBJECTS = $(addsuffix .o,class_loader path_finder) # DIST_FILES += LoadableClass.cpp LoadableClass.h LoadableSubClass.cpp SHARED_LIBS = lib$(LIBCL_LIBNAME) LoadableSubClass STATIC_LIBS = lib$(LIBCL_LIBNAME) lib$(LIBCL_LIBNAME)_so_OBJECTS = class_loader.o path_finder.o lib$(LIBCL_LIBNAME)_a_OBJECTS = $(lib$(LIBCL_LIBNAME)_so_OBJECTS) lib$(LIBCL_LIBNAME)_so_VERSION = $(LIBCL_VERSION) # LoadableClass_so_OBJECTS = LoadableClass.o # LoadableClass_so_LDADD = $(LIBCL_CLIENT_LDADD) LoadableSubClass_so_OBJECTS = LoadableSubClass.o LoadableSubClass_so_LDADD = $(LIBCL_CLIENT_LDADD) DIST_FILES += LoadableSubClass.cpp LoadableClass.h LoadableClass.cpp include $(toc_makesdir)/SHARED_LIBS.make include $(toc_makesdir)/STATIC_LIBS.make INSTALL_LIBEXECS = lib$(LIBCL_LIBNAME).so.$(lib$(LIBCL_LIBNAME)_so_VERSION) # ^^^ re-set the list to remove Loadable*.*, which are automatically added by SHARED_LIBS BIN_PROGRAMS = test_cl test_cl_bin_OBJECTS = cl_demo.o LoadableClass.o cl_demo_cpp_CPPFLAGS += -DCLASSLOADER_DEBUG=1 test_cl_bin_LDADD = $(LIBCL_CLIENT_LDADD) include $(toc_makesdir)/BIN_PROGRAMS.make INSTALL_BINS = # ^^^ we don't want cl_demo installed, and BIN_PROGRAMS does that by default all: $(SOURCES) $(HEADERS) symlink-headers deps $(OBJECTS) SHARED_LIBS STATIC_LIBS BIN_PROGRAMS @echo "When running the test_cl binary be sure to set your LD_LIBRARY_PATH to include ${PWD}!" @echo "Running the 'test' target does this for you." # $(ENM_NAMESFILE) RUNTEST = LD_LIBRARY_PATH=${PWD} ./test_cl # demonstrate a failed lookup for a DLL: faildll: mv LoadableSubClass.so tmp.foo -$(RUNTEST) mv tmp.foo LoadableSubClass.so # demo app: test-.: $(RUNTEST) test: test-. # for compatibility with toc's tests.make :/ --- NEW FILE: ns.class_loader.cpp --- #include <map> #include <typeinfo> #include <stdlib.h> // getenv() #if HAVE_CONFIG_H # include "config.h" // might have CLASS_LOADER_DEFAULT_PATH #endif #include "class_loader.h" #ifdef HAVE_LTDL // prefer libltdl, but if it's not available... # include <ltdl.h> #else // use libdl instead: # include <dlfcn.h> #endif // Clients may define CLASS_LOADER_DEFAULT_PATH when compiling. Be sure to escape the quotes: // -DCLASS_LOADER_DEFAULT_PATH=\"/some/path:/another/path\" #ifndef CLASS_LOADER_DEFAULT_PATH # define CLASS_LOADER_DEFAULT_PATH "." #endif #define CLASS_LOADER_PATH CLASS_LOADER_DEFAULT_PATH namespace CL_NAMESPACE { int m_CL_debug_level = CLASSLOADER_DEBUG; int class_loader_debug_level() { return m_CL_debug_level; } void class_loader_debug_level( int dlevel ) { m_CL_debug_level = dlevel; } std::string open_dll( const std::string & basename, const path_finder & finder ) { static bool donethat = false; if( !donethat ) { // About these dlopen(0) calls: // They are here for client-side convenience, to open the main() app. // In theory, that really isn't necessary any more, but that needs // to be tested. donethat = true; #if HAVE_LTDL lt_dlinit(); lt_dlopen( 0 ); #else dlopen( 0, RTLD_NOW | RTLD_GLOBAL ); #endif } std::string where = finder.find( basename ); if( where.empty() ) return where; void * soh = 0; #if HAVE_LTDL soh = lt_dlopen( where.c_str() ); #else soh = dlopen( where.c_str(), RTLD_NOW | RTLD_GLOBAL ); #endif return soh ? where : ""; // todo: add a helper class to dlclose(soh) post-main(). This has proven problematic // (crashes) in some cases, so we currently rely on the OS to clean everything up :/. } void class_loader_path_init() { static bool donethat = false; if( donethat ) return; donethat = true; path_finder & path = CL_NAMESPACE::context_singleton<path_finder,class_loader_sharing_context>::instance(); path.add_extension( ".so:.SO" ); path.add_path( CLASS_LOADER_PATH ); const char * p = getenv( "LD_LIBRARY_PATH" ); if( p ) { path.add_path( p ); } CL_DEBUG_STATIC << "Initialized shared class_loader path: ["<<path<<"] extensions=["<<path.extensions_string()<<"]"<<std::endl; return; } static const int class_loader_path_init_bogus = (class_loader_path_init(),0); } // namespace --- NEW FILE: ns.class_loader.h --- #ifndef CLASSLOADER_H_INCLUDED #define CLASSLOADER_H_INCLUDED 1 // Author: stephan beal <st...@s1...> // License: Public Domain #include <string> #include <iostream> #include <sstream> #include <fstream> #include <cassert> #include <map> #ifndef CLASSLOADER_DEBUG #define CLASSLOADER_DEBUG 0 #endif #if HAVE_CONFIG_H # include "config.h" // might have CL_NAMESPACE #endif #include "instantiator.h" #include "path_finder.h" #include "context_singleton.h" #include "debuggering_macros.h" // COUT/CERR #if CLASSLOADER_DEBUG #include <typeinfo> #endif #define CL_TYPENAME typeid(ThisType()).name() #define CL_DEBUG if( 0 != CL_NAMESPACE::class_loader_debug_level() ) CERR << CL_TYPENAME <<" " #define CL_DEBUG_STATIC if( 0 != CL_NAMESPACE::class_loader_debug_level() ) CERR << " " namespace CL_NAMESPACE { /** Internal helper class. */ struct class_loader_sharing_context {}; /** Utility function to help keep an instance count of T. Should ONLY be called from ctors and dtors. */ template <typename T> unsigned long & inst_count() { static unsigned long c = 0; return c; } /** Sets the framework-wide debug level. This is shared by all class_loaders, regardless of their templatized types. Currently only two values are supported: zero (no debugging output) and non-zero (all debugging output). Note that this uses a compile-time default, which means that if compiled without debugging, some debug messages will not show up because they happen pre-main(), before a client can set the debug level. */ void class_loader_debug_level( int dlevel ); /** Returns the framework-wide debug level. */ int class_loader_debug_level(); // /** // "Conceptually experimental" and currently unused. // */ // struct dll // { // void * handle; // handle returned by dlopen() // std::string path; // path where DLL was opened from // std::string key; // lookup key used to find DLL // dll(void *h,const std::string &p,const std::string &k) : handle(h),path(p),key(k) {}; // dll() : handle(0),path(""),key("") {}; // mainly to make this class useful for containers. // ~dll(){}; // }; /** UNTESTED Does exactly what it's name says: it searches for a DLL using the given basename and path_finder. If it finds a DLL it opens it, which triggers any class_loader registrations in the DLL. Returns the path to the DLL, if it was found/opened, else an empty string. This function is intended to be used by class_loader subclasses, and was written to reduce the need for DLL-capable loaders to rely on the dll_loader class. By convention basename is expected to be a class name, and a namespace part is acceptable. Namespaces are currently removed before doing the file search, but a different algorithm may be applied as some point, or an additional parameter to define a name-translation functor may be added. */ std::string find_and_open_dll( const std::string & basename, const path_finder & finder ); /** A basic templates-based classloader implementation, intended to work in conjunction with CL_NAMESPACE::instantiator for loading classes via registered object factories. Templatized on: BaseType - this is the type to which all loaded classes should be cast. This is analogous to libfun's fun::LoadableClass. KeyType - the key type to be used to search for classes. Historically this is a string, but any comparable type should work. It is important that KeyType support less-than ordering. UseSharedInstances - determines the ownership policy for pointers returned from load(). See load() and the library manual for full details. This parameter should be left as-is for most use-cases. To make any given class classloadable by this class, call this macro one time (preferably from the class' header file): <pre> CLASSLOADER_REGISTER(BaseType,SubType); CLASSLOADER_REGISTER(SubType,SubType); // optional, and sometimes useful </pre> BaseType need not be ClassLoadable, but SubType must derive from (or be) BaseType. The exact meanings of the parameters are discussed at length in the documentation for that macro, in the class_loader.h header file and in the libs11n manual. As a side-effect of it's template-ness, usage of this class is compile-time type-safe without the use of any casts. Please see cl_demo.cpp for a documented, runnable example of using this class. See s11n::dll_loader for a subclass which can load classes from DLLs. */ template < class BaseType, class KeyType = std::string, bool UseSharedInstances = false /* experimental */ > class class_loader { public: /** Highly experimental and largely untested. */ static const bool use_shared_instances = UseSharedInstances; /** value_type is the base-most class type to be used for casting. Contrary to usage in some std::-namespace CL_NAMESPACEasses, value_type is not a pointer type, even though this class deals only with pointers to value_type objects. This is intended to ease the use of value_type in many common contexts. */ typedef BaseType value_type; /** The key type used for class lookups. The default is std::string. */ typedef KeyType key_type; /** Convenience typedef. It's only public for documentation reasons. */ typedef class_loader < value_type, key_type, use_shared_instances > ThisType; /** factory_type is a function pointer which takes no arguments and returns a value_type pointer. todo: add proper functor support. */ typedef value_type *( *factory_type ) (); // todo: investigate utility of: BaseType *(*factory_type)( key_type ) class_loader() { ++inst_count<ThisType>(); //CL_DEBUG << "instance #" << inst_count<ThisType>()<<std::endl; } virtual ~class_loader() { if( use_shared_instances ) { // might be optimized away by the compiler :) if( 0 == --inst_count<ThisType>() ) { CL_DEBUG << "Cleaning up shared objects..." << std::endl; map_type & bob = context_singleton<map_type,ThisType>::instance(); typename map_type::iterator it = bob.begin(); typename map_type::iterator et = bob.end(); for( ; it != et; ++it ) { delete( (*it).second ); } } } } /** Returns a reference to a shared instance of this classloader type. */ static ThisType & shared() { return context_singleton<ThisType,ThisType>::instance(); } /** Tries to instantiate an object using a factory which has been mapped to the given key. Returns a non-NULL pointer on success and NULL on error. The caller takes ownership of the returned pointer... UNLESS: If ThisType::use_shared_instances is true then this function will always return the same object for any given key, and the client <b>DOES NOT</b> take ownership of the returned pointer! The objects will be deleted when the last class_loader of this type destructs, almost certainly post-main(). Subclasses should override this function to customize classloading behaviour. The default implementation loads classes which have been registered via: - ThisType::register_factory() - ThisType::register_subtype() (Remember, the CLASSLOADER_REGISTER macro normally calls those for you.) See CL_NAMESPACE::dll_loader for a DLL-capable implementation. */ virtual value_type * load( const key_type & key ) const { CL_DEBUG << ": load("<<key<<")"<<std::endl; if( ! ThisType::use_shared_instances ) { return ThisType::instantiator_type::instantiate( key ); } value_type * ch = NULL; map_type & bob = context_singleton<map_type,ThisType>::instance(); // we use this instance in other code, to delete the pointers. typename map_type::iterator it = bob.find( key ); // WTF does this require typename in this context? if( bob.end() == it ) { ch = instantiator_type::instantiate( key ); bob[key] = ch; } else { ch = (*it).second; } return ch; } /** Convenience static version of load( key ), which uses the class_loader returned by shared(). Avoid using this, as the following (quasi-incorrectly) have different behaviour: <pre> typedef CL_NAMESPACE::dll_loader<s11n::Serializable> CL; CL cl; s11n::Serializable * sable = NULL; sable = cl.load( "foo::FooClass" ); // works properly sable = CL::load_class( "foo::FooClass" ); // ends up calling class_loader<>::load() <pre> */ static value_type * load_class( const key_type & key ) { return shared().load( key ); } /** register_factory() associates key with a factory function. If fp == NULL then a default factory is used (normally this behaviour is just fine). register_factory() probably should not be static, but i'm not fully convinced that a non-static implementation is useful, and a static implementation eases use of this class. Since real classloaders must(?) use the same definitions for all like-types, anyway, it seems like a moot point. In any case, static appears to ease client usage, so static it is. */ static void register_factory( const key_type & key, factory_type fp = NULL ) { CL_DEBUG << "register_factory("<<key<<","<<std::hex<< fp<<")"<<std::endl; instantiator_type::register_factory( key, fp ); } /** Convenience function which registers a factory which creates SubT pointers but returns value_type pointers. */ template <typename SubT> static void register_subtype( const key_type & key ) { CL_DEBUG << " register_subtype("<<key<<")" <<std::endl; register_factory( key, CL_NAMESPACE::object_factory<value_type,SubT>::new_instance ); } /** path() returns a path_finder, which is useful for searching for files in a known set of directories and/or having a known set of file extensions. Note that path() has no use in the default class_loader implementation, but it is useful for subclasses which work with files. If use_global is false (the default) it returns a path shared by all class_loader<T> types sharing the same T. If use_global is true it returns a path() which is shared by ALL class_loaders. It is still up to the class_loader implementations to do something useful with the path(), however. */ path_finder & path( bool use_global = false ) const { return use_global ? CL_NAMESPACE::context_singleton<path_finder,class_loader_sharing_context>::instance() : CL_NAMESPACE::context_singleton<path_finder,ThisType>::instance(); } private: /** Factories registered with this instantiator_type are inherently loadable by this class. */ typedef CL_NAMESPACE::instantiator < BaseType, KeyType > instantiator_type; typedef std::map<key_type,value_type *> map_type; }; // class_loader } // namespace // ---------------------------------------------------------------------- namespace { // anonymous ns, important for linking reasons. // ---------------------------------------------------------------------- /** A helper object for registering classes when a DLL or application is opened (i.e., when static objects are initialized). Normally clients only need to run the CLASSLOADER_REGISTER macro somewhere in their impl code: <pre> CLASSLOADER_REGISTER(BaseType,ImplType); </pre> This class is a side-effect of the implementation of the CLASSLOADER_REGISTER macro (but, IMO, a pretty interesting side-effect ;). */ template <class BaseT,class SubT = BaseT, class KeyT = std::string, bool CLSharedInstances = false> struct classloader_registerer { typedef BaseT base_t; typedef SubT sub_t; typedef KeyT key_t; typedef classloader_registerer<base_t,sub_t> this_t; typedef CL_NAMESPACE::class_loader<base_t,key_t,CLSharedInstances> loader_t; typedef CL_NAMESPACE::object_factory<base_t,sub_t> factory_t; static const char * class_name() { assert( 0 && "this classloader_registerer<> is unspecialized!" ); return 0; } static void initialize() { assert( 0 && "this classloader_registerer<> is unspecialized!" ); return 0; } }; // ---------------------------------------------------------------------- } // anonymous namespace // ---------------------------------------------------------------------- /** Call this macro one time to make your classes loadable by all class_loader<BaseT> instances. Parameters: - BaseT, the base-most class type. i.e., that which will be used by clients when they call: <pre> BaseType * foo = class_loader<BaseType>( "mynamespace::SubtypeOfBaseT" ); </pre> The libs11n documentation goes into much more detail about this. - SubT, an implementation class derived from BaseT. Example: <pre> CLASSLOADER_REGISTER(mynamespace::MyDocumentBase,mynamespace::MyDocument); </pre> i'd like to call CLASS_NAME(SubT) from this macro, but doing so causes template specialization collisions when CLASSLOADER_REGISTER is called, e.g., like so: <pre> CLASSLOADER_REGISTER(BaseT,SubT); CLASSLOADER_REGISTER(SubT,SubT); </pre> This is valid usage, however, and necessary in some client cases, so i can't kill it's usage with an automatic CLASS_NAME() :(. See CLASSLOADER_REGISTER[1-5] if you want to customize the registration behaviour or register one class with multiple KeyT's. */ #define CLASSLOADER_REGISTER(BaseT,SubT) CLASSLOADER_REGISTER2(BaseT,SubT) // CLASSLOADER_REGISTER[1-5]: // Pneumonic for remembering which macro to call: the suffix is the number of args to pass. // 1-3 only known to work for (KeyT = std::string). // Example: // CLASSLOADER_REGISTER4(BaseType,SubType,double,42.42) // registers SubType via class_loader<BaseType>::register_factory( 42.42 ). #define CLASSLOADER_REGISTER1(BaseT) CLASSLOADER_REGISTER2(BaseT,BaseT) #define CLASSLOADER_REGISTER2(BaseT,SubT) CLASSLOADER_REGISTER3(BaseT,SubT,std::string) #define CLASSLOADER_REGISTER3(BaseT,SubT,KeyT) CLASSLOADER_REGISTER4(BaseT,SubT,KeyT,# SubT) #define CLASSLOADER_REGISTER4(BaseT,SubT,KeyT,ThekeY) CLASSLOADER_REGISTER5(BaseT,SubT,KeyT,ThekeY,false) #define CLASSLOADER_REGISTER5(BaseT,SubT,KeyT,ThekeY,UseshareD) \ namespace { template<> struct classloader_registerer<BaseT,SubT,KeyT,UseshareD> {\ typedef BaseT base_t; \ typedef SubT sub_t; \ typedef KeyT key_t; \ typedef classloader_registerer<base_t,sub_t> this_t;\ typedef CL_NAMESPACE::class_loader<base_t,key_t,UseshareD> loader_t; \ typedef CL_NAMESPACE::object_factory<base_t,sub_t> factory_t; \ static int placeholder; \ static KeyT key(){ return ThekeY; } \ static void initialize( const key_t & k = key() ) { \ loader_t::register_factory( k, factory_t::new_instance ); \ }\ }; \ int classloader_registerer<BaseT,SubT,KeyT,UseshareD>::placeholder = (classloader_registerer<BaseT,SubT,KeyT,UseshareD>::initialize(),0);\ } // many thanks to tom, from c.l.c++, for the (...,0) trick! // kill this some day: // if( CLASSLOADER_DEBUG ) CERR << typeid(this_t()).name()<<" initialize("<<k<<")"<<std::endl; // static factory_t factory(){ return CL_NAMESPACE::object_factory< base_t, sub_t >::new_instance; } // static bool donethat = false; if(donethat) return; donethat=true; // loader_t::register_factory( key(), CL_NAMESPACE::object_factory<base_t,sub_t>::new_instance ); #endif // CLASSLOADER_H_INCLUDED --- NEW FILE: ns.classname_transformer.h --- #ifndef CLASSNAME_TRANSFORMER_H #define CLASSNAME_TRANSFORMER_H // License: Do As You Damned Well Please // Author: st...@s1... #include <string> namespace CL_NAMESPACE { /** An interface for transforming class names into filesystem-friendly strings, for doing class-to-DLL lookups. It is intended for use by DLL-aware classloaders used in the libclass_loader framework. */ class classname_transformer { public: classname_transformer() {}; virtual ~classname_transformer() {}; /** Default implementation simply strips the namespace part from classname and returns the rest. e.g.: foo::Foo == Foo foo::bar::Foo == Foo Foo == Foo */ virtual std::string translate( const std::string & classname ); }; } // namespace CL_NAMESPACE #endif // CLASSNAME_TRANSFORMER_H --- NEW FILE: ns.cl_demo.cpp --- #define CLASSLOADER_DEBUG 1 #include "class_loader.h" /** It is important to understand that any given class_loader<T> instantiation works only for types which are exactly of type T, not for subtypes of T. As this essentially kills any chance of using polymorphic types, this is obviously a problem! That said, we can coerce the classloader into using sub-types of T by feeding it factories which creates (SubT*) and returns (T*). class_loader<T>::register_subtype<X>() provides a very simple way to register a default factory for type X, returning pointers to T (where X must be a subclass of T). The sample code below exemplifies the requirements placed on clients of class_loader: 1. Classes to be loaded from a given classoader type must derive from a common base type. 2. Clients calling load() must use a class_loader<T> instance, where T is the base type defined in (1). 3. Proper lookup keys must be registered with the appopriate classloader. **/ #include "debuggering_macros.h" struct A { A(){ CERR << "A()"<<std::endl; } virtual ~A(){ CERR << "~A()"<<std::endl; } }; struct B : public A { B(){ CERR << "B()"<<std::endl; } virtual ~B(){ CERR << "~B()"<<std::endl; } }; struct C : public B { C(){ CERR << "C()"<<std::endl; } virtual ~C(){ CERR << "~C()"<<std::endl; } }; // Define USE_REGISTER to 0 to disable the use of CLASSLOADER_REGISTER // and use manual factory registration instead. This is for testing // both approaches, and they should function identically. #define USE_REGISTER 1 #if USE_REGISTER // Corresponds to the ACL classloader in the example below: CLASSLOADER_REGISTER(A,A); CLASSLOADER_REGISTER(A,B); CLASSLOADER_REGISTER(A,C); // ^^^ normally clients would only use these 3, but the ones coming up // next are useful in some cases: // Corresponds to the BCL classloader in the example below: CLASSLOADER_REGISTER(B,B); CLASSLOADER_REGISTER(B,C); // Corresponds to the CCL classloader in the example below: CLASSLOADER_REGISTER(C,C); #endif // USE_REGISTER #include "LoadableClass.h" // sample BaseType #include "dll_loader.h" // a DLL-aware class_loader implementation int main() { using namespace CL_NAMESPACE; // Define some shortcuts: typedef class_loader<A,std::string,true> ACL; // demonstation of shared objects. CERR << "Note: class_loader<A> uses shared objects." << std::endl; typedef class_loader<B> BCL; typedef class_loader<C> CCL; #if ! USE_REGISTER /****************************** If we do not use CLASSLOADER_REGISTER the following code is necessary to register A, B and C. If you'll look closely you'll notice that these calls correspond one-to-one to the above macro calls. ****************************/ // Set up a loader for class A: ACL::register_factory( "A" ); // factory creating/returning A* ACL::register_subtype<B>( "B" ); // factory creating B*, returning A* ACL::register_subtype<C>( "C" ); // factory creating C*, returning A* // Set up a loader for class B: BCL::register_factory( "B" ); // factory creating B*, returning B* BCL::register_subtype<C>( "C" ); // factory creating C*, returning B* // BCL::register_subtype<A>( "A" ); // ERROR: there is no conversion for A* to B* // Set up a loader for class C: CCL::register_factory( "C" ); // factory creating C*, returning C* // CCL::register_subtype<A>( "A" ); // ERROR: there is no conversion from A* to C* // CCL::register_subtype<B>( "B" ); // ERROR: there is no conversion from B* to C* #endif // let's register an alias for class C: CCL::register_factory( "bogo::C" ); // it may be useful to define configurable classes types: ACL::register_subtype<C>( "default_document_class" ); #define TESTLOAD(OBJ,CL,NAME,SHOULDPASS) \ CERR << # OBJ << " = "<<#CL<<".load("<<NAME<<");"<<std::endl; \ OBJ = CL.load( NAME ); \ std::cerr << " = " << std::hex<<OBJ<<std::endl; \ if( SHOULDPASS && (!OBJ) ) { CERR << "TEST FAILED! :(" << std::endl; exit(1); } \ else CERR << "TEST PASSED :)" << std::endl; \ if( OBJ && ! CL.use_shared_instances ) delete( OBJ ); ACL acl; A * a = 0; TESTLOAD(a,acl,"A",true); // creates A* TESTLOAD(a,acl,"B",true); // creates B*, disguised as A* TESTLOAD(a,acl,"B",true); // test the shared object support. Should be the same as previous call. TESTLOAD(a,acl,"C",true); // creates C*, disguised as A* TESTLOAD(a,acl,"C",true); // test shared again... TESTLOAD(a,acl,"X",false); // NULL: key "X" was never registered with ACL TESTLOAD(a,acl,"default_document_class",true); // creates C* BCL bcl; B * b = 0; TESTLOAD(b,bcl,"B",true); // creates B* TESTLOAD(b,bcl,"A",false); // NULL: key "A" was never registered with BCL TESTLOAD(b,bcl,"C",true); // creates a C*, disguised as a B* CCL ccl; C * c = 0; TESTLOAD(c,ccl,"C",true); // creates a C* TESTLOAD(c,ccl,"bogo::C",true); // creates a C* TESTLOAD(c,ccl,"default_document_class",false); // NULL typedef dll_loader<LoadableClass> LCL; LCL lcl; LoadableClass * lc = 0; TESTLOAD(lc,lcl,"LoadableClass",true); TESTLOAD(lc,lcl,"LoadableSubClass",true); // a DLL, we hope typedef class_loader<LoadableClass,int> INTCL; INTCL intcl; TESTLOAD(lc,intcl,7,true); // see LoadableSubClass.cpp for why this works TESTLOAD(lc,intcl,1,false); // and why this doesn't. CERR << "Exiting main(). Any loaders using shared object instances should clean up now..." << std::endl; return 0; } --- NEW FILE: ns.context_singleton.h --- #ifndef CONTEXT_SINGLETON_H_INCLUDED #define CONTEXT_SINGLETON_H_INCLUDED 1 // Author: stephan beal <st...@s1...> // License: Public Domain namespace CL_NAMESPACE { /** A helper class to help avoid the usage of static data members in template classes, as they are often awkward to initialize. Templatized on: - SharedType: the type of object to share. - SharingType: a "context" in which this object is shared. example: <pre> struct sharing_context {}; context_singleton<std::string,sharing_context>::instance() = "foo"; std::string bar = context_singleton<std::string,sharing_context>::instance(); // "foo" </pre> */ template <class SharedType, class ContextType> class context_singleton { public: /** Returns an instance which is unique for the combination of SharedType and ContextType. Always returns the same instance. */ static SharedType & instance() { static SharedType meyers; return meyers; }; }; } // namespace #endif // CONTEXT_SINGLETON_H_INCLUDED --- NEW FILE: ns.debuggering_macros.h --- #ifndef DEBUGGERING_MACROS_H #define DEBUGGERING_MACROS_H 1 // CERR is a drop-in replacement for std::cerr, but slightly more // decorative. #ifndef CERR #define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : " #define CERRL(A) CERR << A << std::endl; #endif #ifndef COUT #define COUT std::cout << __FILE__ << ":" << std::dec << __LINE__ << " : " #define COUTL(A) COUT << A << std::endl; #endif #endif // DEBUGGERING_MACROS_H --- NEW FILE: ns.dll_loader.h --- #ifndef DLLLOADER_H_INCLUDED #define DLLLOADER_H_INCLUDED 1 // Author: stephan beal <st...@s1...> // License: Public Domain #include <stdlib.h> // abort() #include <typeinfo> #include <map> #include <string> #include <cassert> #ifdef NDEBUG #undef NDEBUG // i want to force an assert in a couple of cases. #endif #include "class_loader.h" // parent class, plus CL_DEBUG macro #include "debuggering_macros.h" // CERR/COUT #ifdef HAVE_LTDL // prefer libltdl, but if it's not available... # include <ltdl.h> #else // use libdl instead: # include <dlfcn.h> #endif namespace CL_NAMESPACE { /** A classloader for loading BaseType classes from DLLs. This class represents my very first usage of dlopen(), and there may be bugs related to how i use the dlXXX() functions. Conventions: Classes to be dynamically loaded must be registered with a classloader. How do we make the classloader aware of a class which doesn't exist yet? It's actually straightforward: We need to initialize the DLL when it is opened, and the simplest way to do this is for the client to simply assign a no-meaning value to a static const variable. Here's one way to register your class: <pre> static const int bogus_var = CL_NAMESPACE::class_loader<SerializableType>::register_subtype<myns::MyClass>( "myns::MyClass" ); </pre> a simpler approach is to use call the CLASSLOADER_REGISTER macro one time from your <i>header</i> files (for complex linking reasons): <pre> CLASSLOADER_REGISTER(BaseType,my_ns::MyClass); </pre> Please read the docs for that macro (class_loader.h)! However you do it, having code which is run when the DLL is opened allows you to feed your class to the classloader, instead of it trying to eat your class (so to speak). This is not a conventional approach to loading classes, but it's <b>compile-time type safe</b> and allows us to <b>avoid all types of casts</b> in the implementation. Classes must be saved in a file called ClassName.so and installed in one of these paths: - LD_LIBRARY_PATH (the run-time, not compile-time, value) - current directory - a path selected by client code, assuming that the client also modifies this class_loader's path to match. This class can also load objects which have been statically registered via dll_loader<BaseType>::registerXXX(). That means that any factories registered with class_loader<BaseType> are inherently loadable by this class as well. Factories registered from within DLLs are available to all like-typed class_loader<T> instances. When this document mentions dlopen(), dlclose() or dlerror() it is really refering to either dlXXX() or ltdlXXX(), depending on which one this library was built with. Their conventions are the same for our purposes, so this should not make a difference to client code, but is useful to know when low-level DLL opening problems arise. */ template <typename BaseType> class dll_loader : public CL_NAMESPACE::class_loader < BaseType, std::string, false > { public: /** value_type is for conformance with our parent class' interface. */ typedef BaseType value_type; /** A convenience typedef. */ typedef dll_loader<BaseType> ThisType; /** Convenience typedef. */ typedef CL_NAMESPACE::class_loader < value_type, std::string > ParentClass; /** Constructs a default dll_loader. */ dll_loader() { ++inst_count<ThisType>(); } /** */ virtual ~dll_loader() { } /** load() first tries to load key via the ancestor's load(), and returns that pointer if it is non-NULL. Secondly, it searches for a DLL named 'key.so', using this object's path(). If it is not found in this object's path then the global path() is searched. To make a very long story very short, if it succeeds it returns a pointer to a new object, otherwise it returns NULL. The internals are covered in more detail in the libclass_loader library manual. This function caches lookups it's finds and will refuse to try a second time to look up a failed key. This no-two-tries policy is very arguable, and may be made toggleable at some point. It cannot be done via adding a new argument to this function because that violates the class_loader interface, making this a special case, and i hate special cases. */ virtual ThisType::value_type * load( const std::string & key ) const { // todo: go through and clean all this up: value_type * ch = NULL; ch = this->ParentClass::load( key ); // if the parent can handle it, great. if ( ch ) { CL_DEBUG << "Parent class handled '"<<key<<"'"<<std::endl; return ch; } // Only first-time and EVIL (see below) entries will run past this point, // since all successful lookups are cached via the parent class. void * soh = 0; // shared object handle std::string path; // potentially a path to a DLL std::string xlated = key; // key, translated to remove namespace part (because :: is not filesystem-friendly). // strip namespace part from the file std::string::size_type colon = xlated.rfind( ":" ); if( std::string::npos != colon ) { xlated = xlated.substr( colon + 1 ); } path = this->path().find( xlated ); // search for DLL if( path.empty() ) { // try harder! path = this->path( true ).find( xlated ); } if ( path.empty() ) { // Backup plan: try the main application space: CL_DEBUG << "No DLL found for key '"<<key<<"' in paths ["<<this->path(false)<<"] ["<<this->path(true)<<"]"<<std::endl; soh = NULL; } else { // Freu! :) CL_DEBUG << "Found DLL for '"<<key<<"': " << path << std::endl; soh = this->open_dll( path.c_str() ); if( ! soh ) { // Trauer! :`( CERR << "open_dll("<<path<<") failed. DLERROR says: " << this->dll_error()<<std::endl; return NULL; } } if( ! soh ) { // no findie... CL_DEBUG << ":( Class '"<<key<<"' not found (maybe not registered with *this exact* class_loader type?)." << std::endl; return NULL; } // reminder: just because we got this far // DOES NOT inherently mean that that // the DLL contains a class for this // specific class_loader type! return this->ParentClass::load( key ); // ^^^^^ in theory it's registered with our // parent class by now, because that's how the // CLASSLOADER_REGISTER process works. This // assumes that we opened the correct DLL, // which might not actually be the case. :/ } // load() protected: /** Maps strings to dlopen() handles. */ typedef std::map<std::string,void *> soh_map_type; /** The internal map of keys (class names) and dlopen() handles. */ static ThisType::soh_map_type & ThisType::soh_map() { static ThisType::soh_map_type bob; return bob; } /** Tried to open the given DLL, following the semantics provided by the underlying dlopen() implementation. Returns a handle for the DLL, as per dlopen() conventions. If path.empty() then the main application is opened (i.e., the same as if NULL is passed to dlopen()). This method does not do any path() searching. The result of the lookup (perhaps NULL) is stored in soh_map(). */ void * open_dll( const std::string & path ) const { void * soh = 0; #if HAVE_LTDL soh = lt_dlopen( path.empty() ? 0 : path.c_str() ); #else soh = dlopen( path.empty() ? 0 : path.c_str(), RTLD_NOW | RTLD_GLOBAL ); #endif soh_map()[path] = soh; // that's okay if it's NULL return soh; } /** Close the given shared object handle, following the semantics provided by the underlying dlclose() implementation. */ void close_dll( void * soh ) { #if HAVE_LTDL lt_dlclose( soh ); #else dlclose( soh ); #endif // todo: remove entry from soh_map(). // The future of soh_map() is in limbo, // so that will wait. } /** Returns the same thing as the underlying dlerror() implementation. */ const char * dll_error() const { #if HAVE_LTDL return lt_dlerror(); #else return dlerror(); #endif } }; }; // namespace CL_NAMESPACE #endif // DLLLOADER_H_INCLUDED --- NEW FILE: ns.instantiator.h --- #ifndef INSTANTIATOR_H_INCLUDED #define INSTANTIATOR_H_INCLUDED 1 // Author: stephan beal <st...@s1...> // License: Public Domain #include <string> #include <map> namespace CL_NAMESPACE { /** object_factory is a helper object factory for the classes instantiator and class_loader . Classloaders, at least in my experience, need to be able to load all classes which derive from some given type. Without a common base class, one can't safely attempt to cast from an arbitrary pointer to the type we want to load. That's where the BaseType parameter comes in. All objects instantiated via this loader must inherit from BaseType. KeyType is a type which specifies the type of key used to look up classes, defaulting to std::string. Both BaseType and KeyType must be Default Constructable on the heap (e.g., via new BaseType()). This class holds no state: it only provides typedefs and one method. Sample usage: <pre> typedef instantiator<MyClass> CL; CL::register_factory( "my_key" ); // ^^^ uses a default factory creating MyClass objects: you can // pass your own as the second arg. MyClass *foo = CL::load( "some_key" ); // == NULL foo = CL::instantiate( "my_key" ); // == a new object </pre> */ template < class T, class SubT = T > struct object_factory { /** A typedef for the first template parameter for this type. */ typedef T result_type; /** A typedef for the second template parameter for this type. */ typedef SubT actual_type; /** This creates a new SubT, which is assumed to be a subclass of T. It can be used as a factory for instantiator & class_loader. */ static result_type *new_instance() { return new actual_type; } // result_type * operator()() const { return new_instance(); } // untested! }; /** instantiator is essentially a static classloader, capable of loading classes by using registered factories for a given set of keys (class names). */ template < class BaseType, class KeyType = std::string > class instantiator { public: /** Convenience typedef. */ typedef instantiator< BaseType, KeyType > ThisType; // i can't believe this works. /** A typedef for the BaseType used by this class. */ typedef BaseType value_type; /** A typedef for the KeyType used by this class. */ typedef KeyType key_type; /** The type of factories used by this class: a function taking void and returning (value_type *). todo: implement proper functor support. */ typedef value_type *( *factory_type ) (); /** Tries to instantiate an instance of value_type using the given key. Returns NULL if no class could be loaded for the given key. The caller takes responsibility for the returned pointer. */ static ThisType::value_type * instantiate( const ThisType::key_type & key ) { typename object_factory_map::const_iterator it = factory_map().find( key ); if ( it != factory_map().end() ) // found a factory? { return ( it->second ) (); // run our factory. } return NULL; } /** Registers a factory using the given key. If fp is NULL then ThisType::new_instance is used as the factory. Note that fp may not return a type other than ThisType::value_type *, but the actual object it creates may be a polymorphic subclass of value_type. See the object_factory class for a factory which does this subtype-to-base conversion. */ static void register_factory( const ThisType::key_type & key, ThisType::factory_type fp = NULL ) { ThisType::factory_type fac = (NULL==fp) ? object_factory<ThisType::value_type>::new_instance : fp; factory_map().insert( object_factory_map::value_type( key, fac ) ); } /** Convenience wrapper around register_factory( key, factory_for_SubOfBaseType ). Registers a factory for ThisType::value_type, using the given key and a default factory for producing SubOfBaseType objects. SubOfBaseType must be a subtype of ThisType::value_type. */ template <typename SubOfBaseType> static void register_subtype( const ThisType::key_type & key ) { register_factory( key, object_factory<ThisType::value_type,SubOfBaseType>::new_instance ); } // /** // Convenience factory which returns a default-constructed // instance of ThisType::value_type. The caller takes // responsibility for the returned pointer. // */ // static ThisType::value_type * new_instance() // { // return new ThisType::value_type(); // } /** Internal container type used for mapping keys to factories. */ typedef std::map < key_type, factory_type > object_factory_map; /** Returns the internal key-to-factory map. It is safe for clients to modify this except in multi-threaded environments, and then all guarantees go out the window. That said, it should never be necessary for clients to use this. */ static object_factory_map & factory_map() { static object_factory_map fac; return fac; } private: // intentionally unimplemented: instantiator(); ~instantiator(); instantiator( const ThisType & ); instantiator & operator=( const ThisType & ); }; // class instantiator } // namespace #endif // INSTANTIATOR_H_INCLUDED --- NEW FILE: ns.path_finder.cpp --- // Author: stephan beal <st...@s1...> // License: Public Domain #include <iostream> #include <unistd.h> #include <stdlib.h> // getenv() #if HAVE_CONFIG_H # include "config.h" // CONFIG_HAVE_CYGWIN #endif #include "path_finder.h" #include "debuggering_macros.h" using std::string; using std::ostream; namespace CL_NAMESPACE { using std::ostream; std::ostream & operator<<( ostream & os, const CL_NAMESPACE::path_finder & obj ) { os << obj.path_string(); return os; } string & operator<<( string & os, const CL_NAMESPACE::path_finder & obj ) { os += obj.path_string(); return os; } path_finder::~path_finder() { } CL_NAMESPACE::path_finder & operator +=( CL_NAMESPACE::path_finder & obj, const string & os ) { obj.add_path( os ); return obj; } path_finder::path_finder( const string & p, const string & e, const string & pathsep ) { this->path_separator( pathsep ); this->path( p ); this->extensions( e ); } const string & path_finder::path_separator() const { return ( ( path_finder * ) this )->pathseparator; } void path_finder::path_separator( const string & sep ) { this->pathseparator = sep; } std::string path_finder::join_list( const string_list & list, const std::string & separator ) const { std::string ret; unsigned long count = list.size(); unsigned long at = 0; string_list::const_iterator it = list.begin(); string_list::const_iterator et = list.end(); for(; it != et; ++it ) { ret += (*it); if( ++at != count ) ret += separator; } return ret; } string path_finder::path_string() const { return this->join_list( this->paths, this->pathseparator ); } const path_finder::string_list & path_finder::path() const { return this->paths; } string path_finder::extensions_string() const { return this->join_list( this->exts, this->pathseparator ); } const path_finder::string_list & path_finder::extensions() const { return this->exts; } unsigned int tokenize_to_list( const std::string & str, std::list<std::string> & li, const std::string & sep ) { // internal helper function if( str.empty() ) return 0; unsigned int c = 0; std::string token; std::string::size_type sz = str.size(); for( std::string::size_type i = 0; i < sz; i++ ) { if( sz-1 == i ) token += str[i]; if( str.find( sep, i ) == i || (sz-1 == i) ) { //CERR << "token="<<token<<std::endl; li.push_back( token ); token = ""; i += sep.size() - 1; continue; } token += str[i]; } return c; } unsigned int path_finder::path( const string & p ) { this->paths.erase( this->paths.begin(), this->paths.end() ); return tokenize_to_list( p, this->paths, this->pathseparator ); } unsigned int path_finder::path( const path_finder::string_list & p ) { this->paths = p; return this->paths.size(); } void path_finder::add_path( const string & p ) { tokenize_to_list( p, this->paths, this->pathseparator ); } unsigned int path_finder::extensions( const string & p ) { this->exts.erase( this->exts.begin(), this->exts.end() ); return tokenize_to_list( p, this->exts, this->pathseparator ); } unsigned int path_finder::extensions( const path_finder::string_list & e ) { this->exts = e; return this->exts.size(); } void path_finder::add_extension( const string & p ) { tokenize_to_list( p, this->exts, this->pathseparator ); } // static bool path_finder::is_accessible( const string & path ) { int err = access( path.c_str(), F_OK ); if ( err != 0 ) { // LIBE_VERBOSE << "exists("<<path<<"): access() returned error: "<<err<<endl; } return err == 0; } string path_finder::basename( const std::string & name ) { string::size_type slashat = name.find_last_of( path_finder::dir_separator() ); if ( slashat == string::npos ) return name; return name.substr( slashat + 1 ); } string path_finder::find( const string & resource, bool check_cache ) const { static const std::string NOT_FOUND = "path_finder::find() : no findie"; if ( resource.empty() ) return string(); string checkat; #define CHECKPATH(CHECKAT) checkat = CHECKAT; \ if( ! checkat.empty() && path_finder::is_accessible( checkat ) ) \ { this->hitcache[resource] = checkat; return checkat; } //CERR << "find( " << resource << " )" << std::endl; if( check_cache ) { checkat = this->hitcache[resource]; if ( checkat == NOT_FOUND ) return string(); if ( !checkat.empty() ) return checkat; } CHECKPATH( resource ); string_list::const_iterator piter = this->paths.begin(); string_list::const_iterator eiter = this->exts.begin(); string path; string ext; if ( path_finder::is_accessible( resource ) ) return resource; piter = this->paths.begin(); string checkhere; while ( piter != this->paths.end() ) { path = ( *piter ); if ( !path.empty() ) { path += path_finder::dir_separator(); } ++piter; checkhere = path + resource; //CERR << "find( " << resource << " ) checking " << checkhere << std::endl; CHECKPATH( checkhere ); eiter = this->exts.begin(); while ( eiter != this->exts.end() ) { ext = ( *eiter ); ++eiter; checkhere = path + resource + ext; //CERR << "find( " << resource << " ) checking " << checkhere << std::endl; CHECKPATH( checkhere ); } } //CERR << "find( "<<resource<<" ): not found :(" << std::endl; this->hitcache[resource] = NOT_FOUND; return string(); } // bin_path_finder::bin_path_finder() : path_finder( ::getenv( "PATH" ) ) // { // this->extensions( ".sh" ); // #if CONFIG_HAVE_CYGWIN // this->extensions(".exe:.bat"); // #endif // } // bin_path_finder::~bin_path_finder() // { // } }... [truncated message content] |