[Documentation] [TitleIndex] [WordIndex

New in groovy


class_loader is a ROS-independent package that allows one to dynamically load exported C++ classes during runtime from a runtime library (i.e. .so/.dll file) and create objects of those classes. What makes a class loaded through class_loader different from just linking against a runtime library and using classes from it is that your code does not require the definition of the class (i.e. the header file for the class) in your client code. Classes loaded in this fashion are also often called plugins.

class_loader VS pluginlib

class_loader is used in the implementation of the higher-level ROS package pluginlib which is the encouraged method for loading plugins in the ROS ecosystem. You should use class_loader when creating plugins intended for non-ROS packages and pluginlib when exporting plugins to ROS packages.


class_loader is simple to use and requires linking against a single library (libclass_loader).


Example: Basic Workflow for ClassLoader

#include <class_loader/class_loader.h>
#include "MyBase.h" //Defines class MyBase

int main()
  class_loader::ClassLoader loader("libMyLibrary.so");
  std::vector<std::string> classes = loader.getAvailableClasses<MyBase>();
  for(unsigned int c = 0; c < classes.size(); ++c)
    boost::shared_ptr<MyBase> plugin = loader.createInstance<MyBase>(classes[c]);

Making Classes Exportable and Common Base Class

It is important to note that the ClassLoader can only inspect classes and create objects of those classes if those classes are registered to be exportable. For any classes you want to export, make sure to have a declaration of the following macro for each class within your source (.cpp) files:

where Derived refers to the name of the class to be exported and Base refers to the name of the class from which it is derived. Though you do not need the definition of the Derived class in the code that will load the class, you still need to have a definition to its base class so as to be able to use the plugin. Notice in the code examples that when introspecting the library via getAvailableClasses() or creating a plugin via createInstance() that both methods require a template type argument indicating the base class. If the correct base class is not given, the class will not be seen or instantiable by the ClassLoader. It is ok to have classes with different base classes registered or even register the same class multiple times with different bases in the same library and transitively the same ClassLoader. Just note the base class argument must be provided at compile time via a template argument for methods where it matters.

Once you have built all your source files using the macro, you can package the object files into a runtime library and then load that library via the ClassLoader.

Thread-Safety and Type Correctness

All methods to class_loader::ClassLoader and class_loader::MultiLibraryClassLoader are thread safe. The use of templates allows for all types to be statically verified and it is impossible to load an invalid plugin with an incompatible interface. This is a powerful concept in guaranteeing the integrity of plugins at both compile and runtime which is of course very important in robotics.

Understanding Loading and Unloading

When one uses the ClassLoader to create and destroy plugins, it results in the opening and closing of the runtime libraries containing those plugins. When a runtime library is loaded by an operating system, symbols in the host executable that are stubs to the runtime library are resolved. When the library is unloaded, the symbols at those corresponding memory addresses are unavailable as the code has been removed from executable's address space and [possibly] unloaded from memory. If one attempts to use symbols that are unresolved, it leads to a runtime link error. This means if I create an object from a class defined within a runtime library, I then unload the library, and then I try to use the object...things will go bad.

loadLibrary() and unloadLibrary()

In the example code above, the library is loaded and unloaded by ClassLoader automatically, though class_loader::ClassLoader provides methods for explicitly loading and unloading the underlying library (loadLibrary() and unloadLibrary() respectively). The ClassLoader is smart in that it will automatically load a library when created and unload it when the ClassLoader goes out of scope. One may wonder why the load and unload methods are then exposed. In order to understand that, we must first understand the ClassLoader's on-demand (lazy) load/unload mode.

On-Demand (Lazy) Library Loading/Unloading

The ClassLoader constructor provides an optional boolean flag (ondemand_loadunload) to indicate if the ClassLoader is to perform on-demand (lazy) loading of a library as needed and automatically closing it after the last plugin in created by it is destroyed. By default, this flag is set to false. When set to false, the ClassLoader loads the library at construction time and unloads it at destruction time. In on-demand mode, the library is only opened when the first plugin is created via createInstance() and unloaded when the only remaining plugin created by the ClassLoader is destroyed. This mode is helpful when we want to minimize the number of libraries loaded in memory.

Load/Unload Call Counting

It is often useful, particularly in on-demand mode, to control when the library is loaded and unloaded. One can force this by calling loadLibrary. When one calls loadLibary and unloadLibrary, the library is forcefully loaded into memory and cannot be unloaded automatically by the system until unloadLibrary is called. This allows multiple threads sharing the same ClassLoader to force a shared library, even if it's open in on-demand mode, to stay in memory until it's done. This is not a thread safety issue, but rather one for performance to prevent a library from continuously being loaded/unloaded if a thread is going to need it heavily for some period of time and wants to guarantee the library stays in memory.

Managed vs Unmanaged Objects

A class_loader::ClassLoader allows one to create objects in the form of boost::shared_ptr values so as to provide automatic cleanup of objects. Another one of the important reasons this is implemented is so that the user cannot unload the library prematurely. The user is free to create unmanaged objects via the alternate class_loader::ClassLoader::createUnmanagedInstance method, but this will prevent the ClassLoader from stopping the user on unloading the library when there are still objects in memory. This is because the ClassLoader cannot be aware of the state of unmanaged instances.

It is encourage that the shared_ptr version class_loader::ClassLoader::createInstance() be used instead to allow for safe usage of the class.


Error Handling Through Exceptions

Many ClassLoader methods will raise an exception of base class type class_loader::ClassLoaderException if an error occurs. The subclasses of class_loader::ClassLoaderException are defined in class_loader/class_loader_exceptions.h and indicate the various problems. These include:

Multi-library Class Loader

The ClassLoader is designed to be bound only to a single runtime library. Often it is convenient to have multiple libraries open and be able to load/unload classes from them through a uniform interface. That is why the class class_loader::MultiLibraryClassLoader is provided. This class provides as an almost identical interface to class_loader::ClassLoader but allows one to bind several libraries to a single loader object. Internally, the class_loader::MultiLibraryClassLoader is just a manager for a collection of class_loader::ClassLoader objects.

Caution of Linking Directly Against Plugin Libraries

pluginlib as of version 1.9 and class_loader HIGHLY discourage linking applications directly against libraries containing plugins. Often times users will place plugins into libraries along side code intended to directly be linked. Other times they want to be able to use classes as plugins as well as directly use them without a ClassLoader. This was fine in previous versions of pluginlib, but as version 1.9, pluginlib sits on top of class_loader for plugin loading which cannot handle this.

The Problem - Orphan Class Factories.

The issue is that the way class_loader implements plugin introspection is by having plugin class factories automatically register themselves when the library is opened. This is not how pluginlib prior to version 1.9 operated. However, the problem is is that when you directly link an executable against a library with plugins, when that program starts up, all the plugin factories will be created outside the scope of a class_loader::ClassLoader. See class_loader/Design for more details.

Backwards Compatible with a Caveat

class_loader can compensate and still operate so legacy code will function correctly. However, the underlying issue is the factories are prematurely loaded and class_loader has no idea where those factories came from and there's no class_loader bound to them. If you attempt to create a plugin from a library directly linked in, the class_loader essentially is guessing the factory it has is the one corresponding to the class you want.

Name Collisions

However, the problem occurs if you have two different libraries that define the same class. This can lead to a name collision and there's no way to tell which factory came from which library. In this case, class_loader will generating a warning message.

Inability to Unload Plugin Libraries

Also class_loader can no longer shutdown libraries properly as it cannot tell if the client executable is still using the non-plugin code from the same library. Attempting to close the library prematurely will result in the program crashing, hence it is not done. Often times this is not an issue as the OS will do cleanup on program shutdown, but for programs where the library truly needs to be shutdown, unlinked, and removed from memory in execution, you cannot and must not directly link.

Example: Basic Workflow for MultiLibraryClassLoader

#include <class_loader/multi_library_class_loader.h>
#include "MyBase.h" //Defines class MyBase

int main()
  class_loader::MultiLibraryClassLoader loader;
  std::vector<std::string> classes = loader.getAvailableClasses<MyBase>();
  for(unsigned int c = 0; c < classes.size(); ++c)
    boost::shared_ptr plugin<MyBase> = loader.createInstance<MyBase>(classes[c]);

Design Details

For those interested in understanding the internals of class_loader, go to the class_loader/Design page.

Report a Bug


2024-07-13 13:14