Description of HelloWorld Sample¶
This document describes the implementation of “HellowWorldPlugin”, which is one of sample plugins.
Contents
- Source code
- Headers
- Namespace using directive
- Definition of the plugin class
- Constructor
- The “initialize” function
- Adding a menu item
- Connecting a function to the menu item
- Coding the behavior when the menu item is activated
- Defining the plugin entry
- How to compile
- Using Choreonoid SDK installed in the system
- Using the build environment for the main Choreonoid source
Source code¶
The source code of this sample is as follows:
#include <cnoid/Plugin>
#include <cnoid/MenuManager>
#include <cnoid/MessageView>
#include <boost/bind.hpp>
using namespace cnoid;
using namespace boost;
class HelloWorldPlugin : public Plugin
{
public:
HelloWorldPlugin() : Plugin("HelloWorld")
{
}
virtual bool initialize()
{
Action* menuItem = menuManager().setPath("/View").addItem("Hello World");
menuItem->sigTriggered().connect(bind(&HelloWorldPlugin::onHelloWorldTriggered, this));
return true;
}
private:
void onHelloWorldTriggered()
{
MessageView::mainInstance()->putln("Hello World !");
}
};
CNOID_IMPLEMENT_PLUGIN_ENTRY(HelloWorldPlugin)
This code is included in a source archive as “HellowWorldPlugin.cpp” in the directory “share/sampleplugins/HelloWorldPlugin”. Please use that file for checking and testing this sample. Note that there may be some slightly different part between the above code and one attached in a source archive depending on Choreonoid versions or the convenience for description.
When you build and install this sample, it adds a sub menu item “Hello World” to the “View” item of the main menu. Selecting this menu item puts “Hello World!” in the message view. This plugin just does it, but this is useful for learning the basics of the plugin development on Choreonoid.
Headers¶
First we describe the headers this sample is including.
#include <cnoid/Plugin>
#include <cnoid/MenuManager>
#include <cnoid/MessageView>
These three headers are provided by the Choreonoid framework. The provided headers are basically contained in sub directory “cnoid” of the include path of the framework, and the headers are usually included with the prefix “cnoid/”.
As header files to include do not have a extension, the headers are specified without extension like the above code. The form of header files without a extension is a standard way in C++ because this is employed in the standard C++ libraries, and the C++ libraries such as Eigen, Qt and OpenSceneGraph, which Choreonoid uses as its base, also employ this form. Choreonoid also employs this form to achieve a coding style unified with those libraries.
The three headers included here have the following functions.
- cnoid/Plugin
This header defines the Plugin class. When you make a plugin, this header must be included and a plugin is defined as a class which inherits the Plugin class.
- cnoid/MenuManager
This header defines the MenuManager class which manages menus. When you need to add some menu items, include this header to make the MenuManager available.
- cnoid/MessageView
This header defines the MessageView class which corresponds the view object to put text messages. Include this header when you need to put some text messages to the message view.
The actual implementation of these headers are in “src/Base” of the source tree. See those files directly to know the actual class definitions. (Note that filenames of the actual implementation files have .h extension.) Although the reference manual of the class definitions can be generated by using a tool “Doxygen”, sorry, current source do not have sufficient comments for producing a good reference manuals. We will increase the comments for generating reference manuals.
#include <boost/bind.hpp>
This includes the header of the “Bind” library provided by Boost C++ libraries. Bind is a library for generating function objects in a flexible way, and it is often used in the Choreonoid framework to connect event handlers with the mechanism called “signal”. The details of it are described later.
Choreonoid also uses several other libraries provided by Boost, and plugin developers should know the overview of those libraries. Concretely, the libraries such as Smart Ptr, Signals, Function, Format, Dynamic Bitset and Multi-Array as well as Bind are closely related with the plugin development. To know the details of these libraries, please refer to documents in the official Boost site .
Note that the header files of Boost basically have .hpp extension in contrast to headers provided by the framework and other libraries. How difficult it is to unify this kind of coding format in C++!
Namespace using directive¶
Following directives allow namespace “cnoid” and “boost” to be omitted in subsequent code.
using namespace cnoid;
using namespace boost;
“cnoid” is the namespace of Choreonoid and classes and functions provided by Choreonoid are basically defined in this namespace. For example, the “Plugin” class used in this sample should be written as “cnoid::Plugin” so that it can distinguish the namespace, but you can simply write “Plugin” without the namespace prefix “cnoid::” if you write the above directives in advance.
Note that you should not use the using directives more than is necessary because they make nonsense of namespace’s original purpose to avoid the name conflicts. In principle, the using directive should not be used and all the namespaces should be explicitly specified in header files. On the other hand, you can use them in implementation files (.cpp) to make their codes simpler as long as name conflicts do not happen.
Here “boost”, the namespace of Boost libraries, is also the target of the using directive. As libraries of Boost are often used in the Choreonoid’s plugin development, omitting the boost namespace can make the coding a little simpler. However boost namespace include a lot of functions and classes and possibility of the name conflicts or confusion would be increased by omitting the boost namespace. Thus whether the namespace omitting should be used or not should be selected depending on the situation. Or you can use the “using declaration” to only omit the namespace for particular classes and using it may be better for some situations.
Definition of the plugin class¶
Next, the class corresponding to the HellowWorld plugin is defined:
class HelloWorldPlugin : public Plugin
{
...
};
A plugin of Choreonoid is defined as a class which inherits “cnoid::Plugin” (here “cnoid::” is omitted). You can give an arbitrary name to an inherited plugin class, but making the name end with “Plugin” is our convention to make the name clearly be recognized as a plugin class. It is also important to give a name which would not cause the name conflicts with other plugins.
At least the following functions must be defined in a plugin class:
- Constructor
- “initialize” function
In the following, we describe how to write these functions.
Constructor¶
The constructor is written as follows:
HelloWorldPlugin() : Plugin("HelloWorld")
{
}
In the constructor, the constructor of the base Plugin class must be called with a name of the plugin. Usually the name which removes the last “Plugin” string from the class name is given to this parameter.
Although this sample has nothing in the constructor, the dependency relationship on other plugins should be informed by using the “require” function when the plugin requires other plugins. This will be described in the description of “Sample1Plugin”.
The “initialize” function¶
The initialization of a plugin is written in the “initialize” function as follows:
virtual bool initialize()
{
...
}
The initialize function is a virtual function defined in the base Plugin class, and overriding this allows a plugin to implement the actual initialization code. Such virtual functions also include “finalize”, “description”, etc.
While plugins are being loaded into the memory, each initialize function is called in order considering the dependency relationship between plugins. When necessary objects are created and the initialization succeeds, this function should returns true, or return false if the initialization failed. The return value is used by the system to know whether the plugin is successfully initialized or not.
Defining the plugin entry¶
Finally, you must write the following code for each plugin class:
CNOID_IMPLEMENT_PLUGIN_ENTRY(HelloWorldPlugin)
This is a macro defined in the header “cnoid/Plugin”, and giving the name of a plugin to this macro defines the function with which the system obtains the plugin instance from the DLL of the plugin. Please don’t forget to write this macro because the plugin DLL cannot be recognized as a plugin without this macro.
Note that each plugin must be implemented in a DLL which implements only one plugin and a single DLL cannot implement multiple plugins. In other words, the above macro cannot be written more than twice.
That’s all of the description of the source code. Next we describe how to compile this source.
How to compile¶
Basically following conditions must be satisfied to compile and use a plugin:
- The header files and binaries of the libraries such as Boost, Eigen, Qt, OpenSeneGraph, etc., on which Choreonoid depends, must be available to the build tools.
- The header files and library files provided by Choreonoid must be also available to the build tools.
- The build environment and options must be the same as those used for building the dependency libraries and the main Choreonoid modules. (Basically using the same compiler on the same OS and PC architecture will meet this condition.)
- The binary for the plugin must be built as a shared (dynamic-link) library.
- The name of the binary must be “libCnoidXXXPlugin.so” (XXX corresponds to the plugin name) for Linux, or “CnoidXXXPlugin.dll” for Windows.
- The binary must be installed in the plugin directory of Choreonoid. The plugin directory is “lib/choreonoid-x.x/” (x.x corresponds to the version number) of the Choreonoid installed directory.
As long as the above conditions are satisfied, basically any ways to compile a plugin can be employed. In this document, we describe the following two ways which are standard ones to compile a plugin.
- Using Choreonoid SDK installed in the system
- Using the build environment for the main Choreonoid source
Using Choreonoid SDK installed in the system¶
You can compile a plugin with Choreonoid SDK which has been installed by doing “make install”. In this case Choreonoid is used as an external library.
To do this, please turn on the “INSTALL_SDK” option in the CMake setting in advance, and do “make install”. It installs not only the execution files but also header files and library files.
The installed SDK can be used as a usual library and you can compile a plugin in arbitrary ways. As one way to compile the plugin, here we present the following Makefile. This Makefile is contained in the HelloWorldPlugin source directory. (Since version 1.2, the makefile is contained in “extplugin/sample/HellowWorldPlugin” with name “ManualMakfile”.)
CXXFLAGS += `pkg-config --cflags choreonoid`
PLUGIN = libCnoidHelloWorldPlugin.so
$(PLUGIN): HelloWorldPlugin.o
g++ -shared `pkg-config --libs choreonoid` -o $(PLUGIN) HelloWorldPlugin.o
install: $(PLUGIN)
install -s $(PLUGIN) `pkg-config --variable=plugindir choreonoid`
clean:
rm -f *.o *.so
Doing “make” with this Makefile will produce the binary of the plugin. (Please type “make -f ManualMakfile” to use ManualMakefile.) Then “make install” copies the binary into the plugin directory of the installed Choreonoid. As a result, the plugin is loaded when Choreonoid is executed.
This makefile uses a tool called “pkg-config” to detect parameters such as include path, link path, and libraries to link. “pkg-config” is a standard tool used in Unix-like systems for this purpose. When a library name and some options are given to this command like the above makefile, it produces the strings such as include path, link path and libraries to link for the supported library. By passing the output strings to the compiler arguments, you can compile a program using the library without considering the details of it. Please see the manual of pkg-config to know the details.
Note that if you modified “CMAKE_INSTALL_PREFIX” of CMake from the default “/usr/local” setting, pkg-config cannot find the configuration file of Choreonoid. In this case, you must set the full path of the sub directory “lib/pkgconfig” of the installed Choreonoid directory to the environmental variable “PKG_CONFIG_PATH”.
Note
There is a mistake in the pkg-config configuration in the source archive of the version 1.1.0 and earlier versions. We will fix it from the next version (1.2). Before that, please replace the part “-lCnoidGuiBase” at line 14 of “misc/pkgconfig/choreonoid.pc.in” with “-lCnoidBase” to fix this problem.
For Windows, you might be able to compile a plugin with this kind of Makefile and pkg-config, but it is more usual to make a project on VisualC++ IDE. In that case, you have to manually specify include path, library path, and libraries in the project configuration dialog to compile the plugin. Or it may be better to use the next way.
Using the build environment for the main Choreonoid source¶
If you compiled Choreonoid from the source, you can use the environment for it to compile plugins. That’s actually very simple idea where your sources of plugins are added to the Choreonoid source and they are compiled together.
If the main Choreonoid source has successfully been compiled, the dependency libraries as well as Choreonoid’s headers and libraries are to be correctly configured and compiling a plugin can be done without newly considering those things. This also leads to using build system CMake, which is employed by Choreonoid. If you know the syntax of CMake files, you can write build settings of your plugins more easily than writing Makefiles. For Windows, CMake can generate project files of Visual C++. This enables you to build your plugins on the Visual C++ IDE without doing complicated settings using Visual C++’s dialogs.
Consequently we recommend this method for users who use Choreonoid which was compiled from the source.
Let’s begin to describe concrete procedure. First, the Choreonoid source provides the “extplugin” directory, where the sources of plugins should be placed. Please make the sub directory for a plugin and put the source files of the plugin in it. CMakeLists.txt, which describes build settings for CMake, should be also contained in the directory. Note that the last part of the directory must be ended with “Plugin” because directories with such names are recognized as additional plugins sources.
For the HelloWorldPlugin sample, sub directory “HelloWorldPlugin” is contained in directory “extplugin/sample” as a sample of this compile method. Sample plugins are placed in “sample” sub directory to distinguish them from user’s plugins. In addition, for the versions earlier than 1.2, source file “HelloWorldPlugin.cpp” is placed in “share/sampleplugins/HelloWorldPlugin”, and the CMakeLists.txt is written to use it. In this way, the source file placement of samples is a little different from usual cases, where you can just put all source files in the corresponding sub directory in “extplugin”.
CMakeLists.txt for HelloWorldPlugin is as follows.
option(BUILD_HELLO_WORLD_SAMPLE "Building a Hello World sample plugin" OFF)
if(NOT BUILD_HELLO_WORLD_SAMPLE)
return()
endif()
set(target CnoidHelloWorldPlugin)
set(srcdir ${PROJECT_SOURCE_DIR}/share/sampleplugins/HelloWorldPlugin)
add_library(${target} SHARED ${srcdir}/HelloWorldPlugin.cpp)
target_link_libraries(${target} CnoidBase)
apply_common_setting_for_plugin(${target})
First, the following code is written to enable a user to select whether this plugin is compiled or not.
option(BUILD_HELLO_WORLD_SAMPLE "Building a Hello World sample plugin" OFF)
if(NOT BUILD_HELLO_WORLD_SAMPLE)
return()
endif()
This value is off by default, and this can be switched with a CMake command such as “ccmake”. It is recommended to write this kind of code for your additional plugins.
Next let’s see the subsequent part, which is applied when BUILD_HELLO_WORLD_SAMPLE is set to ON.
set(target CnoidHelloWorldPlugin)
This code is to replace a long plugin name with variable “target”.
set(srcdir ${PROJECT_SOURCE_DIR}/share/sampleplugins/HelloWorldPlugin)
add_library(${target} SHARED ${srcdir}/HelloWorldPlugin.cpp)
This code is to build a shared library corresponding the plugin. Although this code is a little complicated for using the source file contained in another directory, usually you can pass source files to the “add_library” command.
target_link_libraries(${target} CnoidBase)
This code describes the dependency libraries to link. For the libraries and plugins compiled in the Choreonoid source, you can just write their names like this. “CnoidBase” is a library which corresponds to the base GUI framework of Choreonoid. If your plugin only uses the base functions of framework, you only have to specify this library. Note that the libraries which the specified library depends on are automatically linked. Thus usually it is sufficient to specify the libraries of Choreonoid unless the plugin uses other external libraries which are not originally used in Choreonoid.
apply_common_setting_for_plugin(${target})
This function is defined in the CMakeLists.txt which is placed in the top directory of the Choreonoid source. It applies the build settings which should be commonly applied to plugins. Writing this code do the processes such as installing binary files when “make install” is done.
Please read the manual of CMake to know the details of how to write CMakeLists.txt. In addition, you will be able to know more about how to write CMakeLists for plugins by reading those for other libraries and plugins in the Choreonoid source.