I am working on a C++ library consisting of many plugins, which can be included independently of each other. The set of plugins is only dependent on the users requirements at compile time.
The plugins are source code only, they are not standalone binaries.
For this purpose, the main (and only) CMakeLists.txt of the library has a predefined plugins list, and every plugin found in a plugins directory is added to the binary target.
In addition, a preprocessor #define with the name of the plugin is set:
set (plugins
plugin1
plugin2
plugin3
...)
#optional plugins
foreach(library ${plugins})
file(TO_CMAKE_PATH "plugins/${library}" librarypath)
get_filename_component(librarypath ${librarypath} ABSOLUTE)
if(EXISTS ${librarypath})
message("--> found ${library}")
include_directories(${librarypath}/include)
file(GLOB libsources "${librarypath}/src/*.cpp" "${librarypath}/src/*.f90")
set(sources ${sources} ${libsources})
string(TOUPPER ${library} LIBRARY)
add_definitions(-D${LIBRARY})
endif()
endforeach(library)
Now in my main library, what I basically do is the following:
#ifdef PLUGIN1
# include "plugin1.h"
#endif
#ifdef PLUGIN2
# include "plugin2.h"
#endif
#ifdef PLUGIN3
# include "plugin3.h"
#endif
...
// each plugin has a unique id:
enum PluginID : int {
Plugin1 = 1,
Plugin2 = 2,
Plugin3 = 3,
};
// the name of each plugin is associated with its ID,
PluginID getPluginIDFromName( const std::string& PluginName )
{
static std::map<std::string, PluginID> PluginIDMap = {
{"PLUGIN1", Plugin1},
{"PLUGIN2", Plugin2},
{"PLUGIN3", Plugin3},
};
return PluginIDMap[PluginName];
}
// Load a plugin by its ID
PluginBaseClass* pluginFactory( PluginID pluginID)
{
switch ( pluginID ) {
#ifdef PLUGIN1
case Plugin1: { return new class Plugin1();}
#endif
#ifdef PLUGIN2
case Plugin2: { return new class Plugin2();}
#endif
#ifdef PLUGIN3
case Plugin3: { return new class Plugin3();}
#endif
}}
So the result is that in the main source I can load the plugin via:
PluginBaseClass* thePlugin1 = pluginFactory ( getPluginIDFromName ("PLUGIN1") );
Everything works as intended, but I feel that what I do is some kind of abusing cmake and preprocessor macros. Is there any better way to achieve my goals?
In addition, manually updating the map and switch for each possible plugin is rather cumbersome.
The requirement I have is that the user should not need to modify CMakeLists.txt manually. Thank you in advance!
Edit: I want to make the plugins available both via their IDs or their names, hence the two functions. In addition, static linking is preferred; I see no reason for dynamic loading.
Instead of manually creating the mapping from id to plugin name and the factory function by hand you can use what is known as "self-registration" and have the compiler do most of the work for you.
A static plugin factory
First, we need a factory class where the individual plugins can register themselves. The declaration might look something like this:
class PluginFactory
{
public:
using PluginCreationFunctionT = PluginBaseClass(*)();
PluginFactory() = delete;
static bool Register(std::string_view plugin name,
PluginID id,
PluginCreationFunctionT creation_function);
static PluginBaseClass* Create(std::string_view name);
static PluginBaseClass* Create(PluginID id);
private:
static std::map<std::string_view, PluginCreationFunctionT> s_CreationFunctionByName;
static std::map<PluginID, PluginCreationFunctionT> s_CreationFunctionById;
};
The corresponding source file then contains
std::map<std::string_view, PluginFactory::PluginCreationFunctionT>
PluginFactory::s_CreationFunctionByName;
std::map<PluginID, PluginFactory::PluginCreationFunctionT>
PluginFactory::s_CreationFunctionById;
bool PluginFactory::Register(std::string_view const plugin name,
PluginId const id,
PluginCreationFunctionT const creation_function)
{
// assert that no two plugins accidentally try to register
// with the same name or id
assert(s_CreationFunctionByName.find(name) == s_CreationFunctionByName.end());
assert(s_CreationFunctionById.find(id) == s_CreationFunctionById.end());
s_CreateFunctionByName.insert(name, creation_function);
s_CreateFunctionById.insert(id, creation_function);
return true;
}
PluginBaseClass* PluginFactory::Create(std::string_view const name)
{
auto const it = s_CreationFunctionByName.find(name);
return it != s_CreationFunctionByName.end() ? it->second() : nullptr;
}
PluginBaseClass* PluginFactory::Create(std::string_view const id)
{
auto const it = s_CreationFunctionById.find(name);
return it != s_CreationFunctionById.end() ? it->second() : nullptr;
}
Note that Register always returns true - we'll need it to return a value in order to use the Register function as an initializer for a global variable. Being an initializer of a global variable causes the compiler to emit code to call the Register function during program startup.
In your main function you can now obtain an instance of a particular plugin via
PluginBaseClass* thePlugin1 = PluginFactory::Create("PLUGIN1");
or via
PluginBaseClass* thePlugin1 = PluginFactory::Create(PluginID::Plugin1);
Modifying the plugins
The plugins themselves now need to be modified so that they register themselves. In theory any global variable would to, but to avoid name clashes between different plugins it is easiest to just add a static data member to each plugin class, e.g.
class Plugin1 : public PluginBaseClass {
public:
...
private:
static bool s_IsRegistered;
};
and then add the following to the source file for the plugin:
namespace {
PluginBaseClass* create_plugin1()
{
return new Plugin1{};
}
}
bool Plugin1::s_IsRegistered
= PluginFactory::Register("PLUGIN1", PluginID::Plugin1, create_plugin1);
Simplifying the CMake code
Now that the compiler generates the mapping you no longer need the preprocessor definitions. All that your CMake-code now needs to do is add the correct include directories and sources. But that doesn't need to be part of the main CMake file. Instead, you can put a CMakeLists.txt into each of the plugin folders and then include them either via add_subdirectory or include into the main CMakefile:
foreach(library ${plugins})
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/plugins/${library}/CMakeLists.txt)
message(STATUS "--> found ${library}"
include(${CMAKE_CURRENT_LIST_DIR}/plugins/${library}/CMakeLists.txt)
else()
message(FATAL "Unknown plugin ${library} requested!")
endif()
endforeach()
The CMakeLists.txt for plugin1 in the plugins/plugin1 folder then contains just
include_directories(${CMAKE_CURRENT_LIST_DIR}/include)
file(GLOB sources_plugin1 "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/*.f90")
list(APPEND sources ${sources_plugin1})
It might not look like much of an improvement in this particular case, but having these separate CMakeLists.txt files now also allows conditionally adding dependencies.
For example, assume that Plugin2 is the only plugin that uses boost. With the separate CMakeLists.txt you can add everything you need to find and use boost to the CMakeLists.txt of Plugin2 without polluting the main CMakeLists.txt file.
read plugin list from external file. read here. Basically move
set (plugins
plugin1
plugin2
plugin3
...)
into plugins.cmake file, and use
include(plugins.cmake) in your main cmake file.
In order to avoid the need to manually modify the map and switch: if it is OK to assume e.g. max number of plugins is 64, you may use bit masks in the following manner:
auto plugin_mask = PLUGINS;
auto id = 1;
while (plugin_mask)
{
if (plugin_mask & 1)
{
// found plugin
// add to map
std::pair<std::string, PluginID> p;
p.first = "PLUGIN" + std::to_string(id);
p.second = static_cast<PluginID>(id++);
PluginIDMap.insert(p);
} plugin_mask >> 1;
}
Related
I am converting a large Windows C++ application from a large set of source files to
a smaller core application linked to several static libraries (to which many
of the original source files are moved).
Each library requires access to a 'registration method' in the core application.
Each library should call that method during global initialization, but that is
not happening. That is my problem.
The code works fine in the original form where libraries are not used.
I guess I am omitting a necessary link option for the libraries, but
I don't know which.
I have created a minimal, workable example. I developed this
on Windows 10 using:
CMake 3.14.5
MSVC 2019
Here's CMakeLists.txt:
cmake_minimum_required(VERSION 2.8.9)
project (CMakeLinkTest)
add_library(myLibrary STATIC MyStar.cpp)
add_executable(CMakeLinkTest StarFactory.cpp main.cpp)
target_link_libraries(CMakeLinkTest myLibrary)
The application contains main.cpp:
#include <iostream>
int main(int argc, char *argv[]){
std::cout << "Hello World!" << std::endl;
return 0;
}
and a singleton class called StarFactory.
StarFactory.h:
#include<string>
class StarFactory
{
public:
static StarFactory* instance();
~StarFactory() {};
std::string registerStarType(std::string a_type);
private:
StarFactory() {};
static StarFactory* mp_instance; // Singleton instance
};
StarFactory.cpp:
#include <iostream>
#include "StarFactory.h"
StarFactory* StarFactory::mp_instance = 0;
StarFactory* StarFactory::instance()
{
if ( mp_instance==0 )
mp_instance = new StarFactory;
return mp_instance;
}
std::string StarFactory::registerStarType(std::string a_type)
{
std::cout << "registerStarType: " << a_type << std::endl;
return a_type;
}
Finally, a static library contains class MyStar which registers itself with
the singleton at global initialisation.
MyStar.cpp:
#include<string>
#include "StarFactory.h"
class MyStar
{
public:
MyStar() {
StarFactory* s = StarFactory::instance();
//s->registerStarType("MyStar");
};
};
MyStar myStar;
std::string starName = StarFactory::instance()->registerStarType("MyStar");
Now for what happens. If I link MyStar.cpp directly into the application I see:
>CMakeLinkTest.exe
registerStarType: MyStar
Hello World!
If link MyStar.cpp into MyLibrary.lib and link that to the application I see:
>CMakeLinkTest.exe
Hello World!
So, the library's call (last line of MyStar.cpp) to the application's singleton is not working.
Can anyone explain this please?
As stated by engf-010, if a symbol defined in your static library is not used, the linker won't put it in the final binary.
One way to solve the problem using CMake would be to use an OBJECT library instead of a STATIC library.
The default behavior for linker is to not include static library that is not referenced.
You can either:
force linker to include the library anyway - you can use cmake's add_link_options or equivalent
not use a static library - just link the object like as in your first example
reference the code in static library
use a shared object (dynamic library) instead
I'm trying to use Gradle (5.6.2) to build a basic C++ library, and cannot figure out what is going wrong here. I started out using Gradle init to create the basic structure... here's my build.gradle:
plugins {
// Apply the cpp-library plugin to add support for building C++ libraries
id 'cpp-library'
// Apply the cpp-unit-test plugin to add support for building and running C++ test executables
id 'cpp-unit-test'
}
// Set the target operating system and architecture for this library
library {
targetMachines.add(machines.macOS.x86_64)
dependencies {
implementation files('/usr/local/lib/libjsoncpp.a') // used by classA
}
}
tasks.withType(CppCompile).configureEach {
compilerArgs.add "-std=c++11"
compilerArgs.add "-w"
}
The source tree looks like this:
src/main/cpp -> classA.cpp classB.cpp classB.hpp hello.cpp
src/main/public -> classA.hpp cppd.h cpplib.h
src/test/cpp -> classATest.cpp hello_test.cpp
hello.cpp, cppd.h, cpplib.h, and hello_test.cpp all came from the 'gradle init' and aren't actually used.
classA calls a few methods in classB. classB only depends on standard libraries.
classB has a public method classB::method1() that calls two private methods classB::method2() and classB::method3()
When I build, I get a linker error that it can't find classB::method2() or classB::method3(). I checked the method signatures and they all match up (same number and type of arguments in classB.hpp, classB.cpp, and in the linker error message).
I've scoured the Gradle documentation and Googled everything I can think of, tried several variations on the build.gradle file, and... I don't understand why the linker can't find methods in the same CPP file??
Building with Clang 11.0 on MacOS 10.14.6 in case it matters...
Also for reference, here's the relevant bits of the header file:
class classB {
public:
method1();
private:
string& method2(const string& s, bool b);
int method3(uint16_t* b, const string& s);
}
And the methods from the cpp file:
string& method2(const string& s, bool b) {
// blah
}
int method3(uint16_t* b, const string& s) {
// blah
}
OH MY GOODNESS! Just... Nevermind. You know how sometimes posting the question itself is enough to make the problem obvious?
For the record, of course what's missing is the class identifyer-thing (sorry, can't recall the term, I'm coming back from Java) on the front of the method names in the CPP file. They should be:
string& classB::method2(const string& s, bool b) {
// blah
}
int classB::method3(uint16_t* b, const string& s) {
// blah
}
Without the class identifyer-things, the linker doesn't realize they are member functions, and doesn't make the connection.
I'd like make a dynamic library by creating a DLL and import it into my main program.
But I can't run my program correctly since I switch from LIB to DLL.
This is my DLL .h file :
class Connector
{
public:
Connector(std::string _apiKey,
std::string _masterCode,
std::string _masterSystem,
std::string _masterVersion,
int INTERNAL_PARAMETER = -1);
virtual ~Connector();
std::string query(std::string method,
std::map<std::string,
std::string> params);
[...]
}
And this is the link code in my mainApp :
typedef std::string (CALLBACK* kcDLLFUNC_QUERY)(
std::string, std::map<std::string, std::string>, std::string);
HINSTANCE kcDLL = LoadLibrary(_T("Connect"));
kcDLLFUNC_QUERY kcDLLFUNC_query = (kcDLLFUNC_QUERY)GetProcAddress(kcDLL, "query");
std::map<std::string, std::string> params;
params["amount"] = "50";
std::string RES = kcDLLFUNC_query("de", params, "");
std::cout << RES << std::endl;
FreeLibrary(kcDLL);
Have I forgotten anything?
The main issue is that GetProcAddress() only works with extern "C" functions. The function you want to call is a member of a class, and you haven't exported either the function or the entire class.
I typically implement this by adding a define to the DLL project, and then create a header in the DLL project that defines a macro that indicates if the function/class is exported or imported. Something like this:
// Assumes IS_DLL is defined somewhere in the project for your DLL
// (such as in the project's Properties: C/C++ -> Preprocessor)
#ifdef IS_DLL
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
And then modify your class like this:
#include "DllExport.h" // name of the header file defined above
class DLL_API Connector
{
public:
Connector(std::string _apiKey, std::string _masterCode, std::string _masterSystem, std::string _masterVersion, int INTERNAL_PARAMETER = -1);
virtual ~Connector();
std::string query(std::string method, std::map<std::string, std::string> params);
[...]
}
In your .exe, include the header for your class, and use it as usual. You also need to link to the DLL. In recent versions of Visual Studio, this is done as follows:
In the Solution Explorer, expand the project for the .exe.
Right click References, and select Add Reference....
In the dialog, select Solution in the list on the left.
Select the checkbox next to the DLL's project, and press OK.
If you end up creating multiple DLLs for your program, you'll need to change the name of the defines so they don't clash (I typically include the name of the DLL in the name of each define).
c++ (not c++11)
Say i have 100 .cpp files in my project, which currently do some work.
All of those files currently include some globals.h file which i can edit easily.
I want each of those files to have its own instance of some object, and I also want that instance to have some unique ID of the file in which it is instantiated.
In addition, I want those objects to be created by a factory method, and I need the instances to have some way for a user to handle them - meaning they can't be anonymous.
In short - I need a way to generate unique IDs for all of the files in my project, i need the file to be able to access its own ID using the same name in all files, but also to be able to access the ID of a file externally in another "manager" file.
Here are options that don't work:
1.
Enum:
If I use an enum and give every file an enum ID, now I can't do this in globals.h:
static thePrivateInstanceInThisFile = theFactory.makeInstance(fileID);
because I need a different fileID in every file, and that was defined statically, and uniquely named using my enum.
2.
Class that counts its own instances
Define in globals.h:
class FileIDGiver{
private:
static int currentID;//initialize to 0 in cpp
int myID;
public:
FileIDGiver(){
myID = currentID++;
}
int getFileID(){
return myID;
}
}
static FileIDGiver theFileId;
static thePrivateInstanceInThisFile = theFactory.makeInstance(theFileId.getFileID());
This will give an ID to each static file instace which is unique to the file, but now it is not manageable externally to the file.
I thought about doing something like
globals.cpp
int file1ID;
int file2ID;
...
globals.h
extern file1ID;
extern file2ID;
...
file1.cpp
file1ID = theFileId.getFileID();
file2.cpp
file2ID = theFileId.getFileID();
...
and whenever a user needs to manage a file he would either use the file's ID variable, or create a new one in the above manner.
This would allow me to access each uniquely and automatically file ID externally.
The only problem I have with this is the line file1ID = theFileId.getFileID(); only executes in runtime, AFTER the line static thePrivateInstanceInThisFile = theFactory.makeInstance(theFileId.getFileID());.
which executes at compile time.
I can't figure out a good way to reverse this order, or maybe do a whole other mechanic.
Again - I need:
Automatically created file IDs
Unique file IDs (which are very very preferably numbers)
Usage of those IDs by the same variable name in all files (automatically, using a static variable definition in the globals.h file)
Ability to access a specific file ID manually by using another manually defined variable.
Please advise some good way to accomplish this
Thanks.
This sounds like a bad case of the static initialization order fiasco.
Here is a solution which uniquely assigns integer ids to each file, then generates a unique Instance by calling a factory function with the file's id, while ensuring that the Instance factory is initialized before its first use:
idgiver.h:
class IdGiver
{
int id;
public:
IdGiver() : id(0) {}
int getId() {return id++;}
};
IdGiver &getTheIdGiver();
idgiver.cpp:
#include "idgiver.h"
IdGiver &getTheIdGiver()
{
static IdGiver theIdGiver;
return theIdGiver;
}
factory.h:
class Instance
{
// ...
};
class Factory
{
// ...
public:
Factory() : {/*...*/}
Instance getInstance(int id) {/*...*/}
};
Factory &getTheFactory();
factory.cpp:
#include "factory.h"
Factory &getTheFactory()
{
static Factory theFactory;
return theFactory;
}
globals.h:
#include "idgiver.h"
#include "factory.h"
static int thisFileId = getTheIdGiver().getId();
static Instance thisFileInstance = getTheFactory().getInstance(thisFileId);
If you want to be able to access the static Instances of other files, then this cannot be done via an automatically generated id because the id generated for a file could change each time a new file is added, or every time it is compiled, or even on each execution. Therefore in this solution, each file manually defines its own persistent id similarly to example 1 in the question.
ids.h
enum FileId
{
File1, File2, File3
};
factory.h
#include "ids.h"
#include "instance.h"
class Factory
{
// ...
public:
Factory() {/*...*/}
Instance createInstance(FileId fileid) {/*...*/}
};
Factory &getTheFactory();
factory.cpp
#include "factory.h"
Factory &getTheFactory()
{
static Factory theFactory;
return theFactory;
}
idmanager.h
#include "ids.h"
#include "instance.h"
template<FileId id>
struct Manager
{
static Instance &getInstance(); // not defined
};
global.h
#include "idmanager.h"
#include "factory.h"
template <>
Instance &Manager<FILEID>::getInstance()
{
static Instance theInstance = getTheFactory().getInstance(FILEID);
return theInstance;
};
static Instance &getThisFileInstance()
{
return Manager<FILEID>::getInstance();
}
Usage is as follows: for each file requiring a static Instance object, place at the start
#define FILEID File1 // The FileId corresponding to this file
#include "global.h"
Then in any file,
The unique id is given by FILEID. (sorry it's a macro)
The static Instance of this file is obtained by getThisFileInstance().
The static Instance of any file is obtained by Manager<any_file_id>::getInstance().
This works by placing the implementation for an instantiation of the template Manager<FileId> in each file, each of which creates and returns that file's static Instance.
Advantages are persistence of ids, and zero run-time overhead: no need to dynamically assign ids, and the calls to Manager<file_id>::getInstance() are resolved at compile-time.
Also, the ids.h header can easily be generated by a script which scans the first line of each file for #define FILEID fileid, so the only maintenance left is remembering to write #define FILEID fileid.
You might modify your building procedure (e.g. your Makefile) to define some unique thing. E.g. you could compile your foo23.cpp file with something like (assuming GCC on some Linux system; adapt this to your compiler and OS and builder)
g++ -Wall -c -DBASENAME="$(basename foo23.cpp)" -DUNIQUEID=23
You could get the 23 for UNIQUEID using some shell script or whatever, e.g. an ad-hoc rule in your Makefile. Details depend upon your file naming conventions.
then use appropriately BASENAME and UNIQUEID in your C or C++ code (perhaps with dirty #if UNIQUEID==23 preprocessor tricks...).
So the idea is to generate the UNIQUEID in your build system and to pass it thru some preprocessor symbols. Details are OS, compiler, build-system specific.
You might also do some crude meta-programming by generating some C or C++ source or header file (perhaps using some awk script or some GPP or m4 preprocessing) in your building procedure.
I've run into a confusing problem with CMake-generated DLL files on Windows. In my library, I use Curiously Recurring Template Pattern to give a certain classes a unique ID number:
// da/Attribute.h:
#ifndef DA_ATTRIBUTE_H
#define DA_ATTRIBUTE_H
namespace da {
typedef unsigned int AttributeId;
class AttributeBase {
public:
virtual AttributeId getTypeId() const=0;
protected:
/** Static ID counter. Every class that derives da::AttributeBase is
assigned an increment of this counter as its type ID number */
static AttributeId sNextId;
};
template <class Derived>
class Attribute : public AttributeBase {
private:
static AttributeId msTypeId;
public:
Attribute() {
if (msTypeId == 0) {
msTypeId = ++sNextId;
}
}
virtual ~Attribute() {
}
/** For static contexts */
static AttributeId typeId() {
if (msTypeId == 0) {
msTypeId = ++sNextId;
}
return msTypeId;
}
AttributeId getTypeId() const {
return typeId();
}
};
template <class Derived> AttributeId Attribute<Derived>::msTypeId = 0;
}
#endif
Problem is, when I link the DLL to an executable project, there appears to be some inconsistencies with the different ID methods. For example:
// Foo.h
struct Foo : public da::Attribute<Foo> {
Foo() { }
};
...
// main.cpp
Foo *foo = new Foo;
Foo->getTypeId() == 1 // True
Foo::typeId() == 1 // Should be true, but isn't. Foo::typeId() == 2
Running through with GDB, with a break in Foo::getTypeID(), I found that "msTypeId" and "Foo::msTypeId" had different memory addresses. What the hell.
This only happens when Foo is defined in the DLL, though. (And only in Windows 7, apparently--I don't have this problem in my Debian build) If I create the derived class inside main.cpp, or if I just compile all the code from the library into the executable, skipping the DLL step entirely, it works with no problems.
Everything was compiled using MSYS and MinGW, with GCC 4.7 on Windows 7 Home Premium.
Here's the CMakeLists.txt for the library, in case I messed something up there:
cmake_minimum_required(VERSION 2.6)
project(foo)
add_definitions(-std=c++0x)
set(CMAKE_BUILD_TYPE Debug)
set(sources
Foo.cpp
)
add_library(foo SHARED ${sources})
You have to export the types from the shared library. This is done using the __declspec(dllexport) and __declspec(dllimport) decorators. Read through the MSDN documentation; it's rather involved.
Since the header needs to have __declspec(dllexport) when building the library and __declspec(dllimport) when compiling the code that uses it, one usually defines a symbol, customarily called LIBRARYNAME_EXPORT and #ifdefs it depending on whether LIBRARYNAME_EXPORTS is defined.
CMake automatically defines target_EXPORTS when building (shared) library. It can be overridden by setting DEFINE_SYMBOL target property.
Unix chooses a different path and by default exports and also imports all symbols from shared libraries (except for static and explicitly hidden ones). This incurs a little bit of performance penalty as more symbols need to be resolved, but it much easier to use (no changes are needed to switch from static to shared library) and much more flexible (i.e. you can override symbols from shared libraries, which you can't do in Windows).