Source: fun/ClassLoader.h
|
|
|
|
// ClassLoader - instantiation of classes not known at compile time
// Copyright (C) 2000-2003 bozo & sgbeal @users.sourceforge.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef _FUN_CLASSLOADER_H
#define _FUN_CLASSLOADER_H
#include
#include
#include // you'll need to use qdict, I think, with Qt 1.x
#include
#include // for time_t
namespace fun
{
class LoadableClass;
/**
* A ClassLoader attempts to create an instance of the given class, using a
* dynamically-loaded shared object file. (C++ probably has a better way to
* do this, but I don't know what it is.)
*
* The classes it instantiates must have a constructor which takes no
* arguments, and the shared object which instantiates the class must
* contain a symbol void *new[classname]() which returns a new instance of
* the class. (The macro "INSTANTIATOR(classname)" or
* "NAMESPACE_INSTANTIATOR(namespace, classname)" defined in fun.h will
* do this for you.) Your class must also inherit (at some point in its
* ancestry) from LoadableClass.
*
* By default, a ClassLoader instance just looks in your LD_LIBRARY_PATH
* for a shared object named [classname].so. However, there are
* several ways in which you can change a ClassLoader's behavior.
*
* If there's a specific directory which your application uses for its class
* definitions, or for class definitions of a particular kind of class,
* create a new ClassLoader and call setDSODir("/path/to/dsos"). Each time
* the ClassLoader is asked to instantiate a class, it will look in that
* directory for [classname].so.
*
* If your application uses a single specific DSO which you want to load a
* bunch of classes from, create a new ClassLoader, and call
* setDSO("/path/to/your.so"), or dlopen() the DSO yourself and call
* setDSOHandle(handle). Each time the ClassLoader is asked to instantiate a
* class, it will look in that DSO.
*
* If you want to be able to load classes from multiple directories or DSO's,
* you can create a ClassLoader for each and chain them together with
* setNextLoader(). If the first ClassLoader is unable to instantiate the
* class, the next in line will try. All ClassLoaders share the same cache,
* so subsequent attempts to instantiate the class will not require the list
* to be traversed again.
*
* Also, if a ClassLoader fails to find the shared object it's looking for,
* or fails to find the symbol it expects in the shared object, it will
* attempt to load the symbol from the main program as a last-ditch effort.
* In order for this to work (and you probably do, unless you want to require
* a shared library), your program must allow its own symbols to be resolved
* with dlsym; using automake and libtool, add "-export-dynamic" to your
* program's LDFLAGS.
*
* ClassLoaders cache the results of their class lookups, regardless of
* their outcome; no class is ever searched for twice, unless it's been
* deliberately removed from the cache through uncache() or clearCache().
* As a result, ClassLoader::instantiate() is pretty fast; 1000000 calls to
* "new" and 1000000 calls to instantiate() each take about a second on my
* machine.
*
* All ClassLoaders share the same cache of class information; the
* difference between ClassLoader instances is the list of places they
* look for the shared object to open. This means that if a class has
* been loaded by one ClassLoader, attempts to load another class of the
* same name through another ClassLoader will return the first class,
* which might not be what we want.
*
*/
class ClassLoader
{
public:
/**
* Set this eminently intuitive API feature to true to make this
* ClassLoader more talkative about what it's doing.
*/
bool debug;
/**
* Creates a ClassLoader with the default class-finding behavior.
*/
ClassLoader();
/**
* Cleans up the ClassLoader; if this ClassLoader opened its own
* shared object, it will dlclose() it here.
*/
virtual ~ClassLoader();
/**
* Cause the ClassLoader to look for class DSO's in the given directory
* during subsequent calls to the ClassLoader's instantiate() method.
* For namespace-qualified class names like foo::Bar, ClassLoader really
* will look for a file named "foo::Bar.so".
*/
void setDSODir(const char *path);
const char * getDSODir();
/**
* Cause the ClassLoader to open the given shared object and look in it
* for classes during subsequent calls to the ClassLoader's
* instantiate() method. This is passed on to dlopen(), so it will be
* searched for in the LD_LIBRARY_PATH if it doesn't start with a "/".
* (or a . or something... not sure what the rule is.)
*/
void setDSO(const char *path);
/**
* Cause the ClassLoader to use the given DSO handle during subsequent calls to the ClassLoader's
* instantiate() method. Because this does not cause the ClassLoader to
* open the DSO, the DSO will not be closed by ClassLoader::emptyCache(),
* and will continue to be used by the ClassLoader instance to which it
* was passed.
*/
void setDSOHandle(void *handle);
/**
* If this ClassLoader fails to locate an instantiator, it will pass
* the request on to the next ClassLoader in line. next may be NULL.
*
* Note that because all ClassLoaders share the same class cache,
* instantiate() will only recurse the first time a given class is
* instantiated, if at all. Subsequent instantiations can be handled
* by "this" ClassLoader, not the next one in line.
*
* If deleteWhenDeleted is true, this ClassLoader is responsible for
* the next ClassLoader's memory, and will delete the next ClassLoader
* when it is itself deleted. This lets you create a chain of
* ClassLoaders, and keep a pointer to only the head of the chain.
*/
void setNextLoader(ClassLoader *next, bool deleteWhenDeleted = false);
/**
* Passes the given ClassLoader to setNextLoader() on the last
* ClassLoader in the chain.
*
* If you're reading the names of DSO's or directories from the command
* line or an environment variable, and want to search them in the order
* given, you can keep just the head of the list, and pass subsequent
* ClassLoaders to its setLastLoader() to append them to the end of the
* list. See setNextLoader() for more information.
*/
void setLastLoader(ClassLoader *last, bool deleteWhenDeleted = false);
/**
* The moment ClassLoader lives for. Attempts to create an instance
* of the given class name. XXX what about error handling? Add an
* equivalent to dlerror()?
*/
virtual LoadableClass *instantiate(const char *className);
/**
* A convenience version of instantiate() which makes sure the
* instantiated object is of the given type, and assigns it to the
* given pointer if so. (Otherwise it deletes it again.)
*/
template
void instantiate(const char *className, LC *&);
/**
* Causes future calls to this ClassLoader's instantiate("from") method
* to return instantiate("to") instead. This can be useful if you have
* a case where you always want a specific subclass to be returned
* instead of a base class, or if you want to associate some symbolic
* name with a class.
*
* Passing NULL as the "to" argument causes the translation to stop
* being performed.
*
* Note that translations are performed on the given class names,
* but in translate(), not instantiate(). For example, if you
* translate("foo", "bar"), and then translate("bar", "baz"), subsequent
* attempts to instantiate "foo" will give you "baz", as you would
* expect, not "bar". However, because the translation was done in
* translate(), not instantiate(), a later call to translate("bar", NULL)
* will leave "foo" mapped to "baz", instead of causing it to revert to
* "bar". While not exactly what you might expect, it makes instantiate()
* faster, which is most important.
*/
virtual void translate(const char *from, const char *to);
/**
* Returns the class name which will be substituted for the given
* name, or NULL if the given class name is not mapped to another.
*/
virtual const char *getTranslation(const char *className);
/**
* Returns all of the translated class names. You'll have to call
* getTranslation() for each element in the list to find out what it's
* mapped to.
*/
virtual QStringList getTranslations();
/**
* Undoes the effect of all translate() calls on this ClassLoader.
*/
virtual void clearTranslations();
/**
registerObjectFactory() registers a classname with the
classloader, mapping it to a function which is capable of
creating objects of that class name (which may be any key -
not necessarily a formal class name). Note that this is
essentialy useless for mapping to DLL-loaded factories,
since the factory function cannot be pointed to at
link-time. However, perhaps it can be used by the
classloader to determine which symbol in a DLL to use for
purposes of creating a new class (instead of hard-coding
the value set via the INSTANTIATOR() family of macros, for
example).
It's original intention is to allow dynamic loading of
template classes or other types with "odd" names.
It's experimental, in any case.
Justification for it being static is: C++ would not allow
the same class with different definitions, so it would be
useless to be able to store such conflicting info in two
classloaders in the same app. It could be argued that
specific classloaders should not know about specific
classes, or may use different paths, may use the same key
for logically-unrelated classes, etc., so there are
certainly several valid arguments for not making this
static. The fact is, however, that having it static greatly
simplifies it's usage in libGCom ;).
Note that this function uses the same internal class table
as the "classic" methods, so it can be used to override a
particular class instantiator function. e.g., if you want
to use a custom loader for Foo, instead of relying on
Foo.so being found:
ClassLoader::registerObjectFactory( "Foo", myFooFunction );
Sample theoretical usage:
LoadableClass * myFactory() { return new Foo(); }
ClassLoader::registerObjectFactory( "MagicMysteryFoo", myFactory );
...
Foo *foo = dynamic_cast( myclassloader->instantiate( "MagicMysteryFoo" ) );
This code is largely taken from/inspired by Andrei Alexandrescu's "Modern C++ Design",
chapter 8.
*/
typedef LoadableClass * (*ObjectFactory)();
static void registerObjectFactory( const QString &classname, ClassLoader::ObjectFactory fp );
/**
* Gets the list of class names which this ClassLoader believes it
* can instantiate.
*
* ClassLoaders for whom setDSODir() has been called look at the
* list of files in their DSO directory.
*
* ClassLoaders who load multiple classes from a single DSO or open
* DSO handle look for a symbol, classLoaderClassListFP, and treat it
* as a pointer to a function which returns a const char * containing
* a whitespace-delimited list. (Remember such a symbol should be
* declared extern "C".) For example, if you put your list of classes
* in the Makefile variable foo_classlist, and add
* -DFOO_CLASSLIST="\"$(foo_classlist)\"" to the list of compile-time
* flags, your classLoaderClassListFP could be:
*
* extern "C" const char *classLoaderClassListFP()
* {
* return FOO_CLASSLIST;
* }
*
* ClassLoaders who will be loading DSO's from the default path
* don't attempt to determine which DSO's in the path might contain
* loadable classes.
*
* If openFiles is true, the ClassLoader will attempt to actually
* dlopen() files which it believes contain loadable classes, and
* retrieve their instantiators. This should yeild more accurate
* (but slower) results. (Err, scratch that; that's not implemented.)
*
* If setNextClassLoader has been called on this ClassLoader, it
* will append its list to the list returned by the "next" ClassLoader,
* and will not attempt to remove duplicates. (I didn't see a
* QStringList/QValueList member for getting a sorted unique list,
* so see ClassLoader::sortUniq(QStringList &).)
*/
QStringList getClassNames(bool openFiles = false);
/**
* This removes the given class from the cache; the next attempt to
* instantiate() the class will cause its shared object to be reopened
* and reexamined.
*/
static void uncache(const char *className);
/**
* This destroys the cache of class info; subsequent calls to any
* instantiate() method will cause shared objects to be reopened
* and reexamined as needed.
*/
static void emptyCache();
/**
* Utility function for the missing (?) "sort unique" QValueList member
* function.
*/
static void sortUniq(QStringList &list);
/**
* "With the version of GCC on my box," typeid(*foo).name() comes out
* as "N3fun3FooE" instead of the more aesthetically pleasing "fun::Foo".
* This attempts to return the demangled name. (Well, it's not exactly
* mangled... I don't know what it is. c++filt, for example, doesn't
* know what to do with it.)
*
* If this isn't able to figure out how to decode the given name, it
* just returns the original.
*/
static QString decodeTypeIDName(const char *);
private:
void *loadClassInfo(const char *);
QAsciiDict classTrans;
void *soh;
QString path;
bool isdir, tried;
ClassLoader *next;
int nextRefCount; // how many other ClassLoaders have us "next"?
QStringList *getClassNamesBuf; // buffered results from getClassNames()
time_t getClassNamesMod; // when were results buffered?
bool deleteNext; // we own next's memory
};
template
void
ClassLoader::instantiate(const char *className, LC *&p)
{
LoadableClass *tp = instantiate(className);
p = dynamic_cast(tp);
if (tp && !p)
{
//errkk, warn that life is bad?
delete tp;
p = NULL;
}
}
} // namespace fun
#endif // _FUN_CLASSLOADER_H
| Generated by: stephan on cheyenne on Mon Aug 11 14:06:52 2003, using kdoc 2.0a54. |