diff --git a/inc/libfunctionality/Common.h b/inc/libfunctionality/Common.h index b2f85d0..aa81ec3 100644 --- a/inc/libfunctionality/Common.h +++ b/inc/libfunctionality/Common.h @@ -64,9 +64,24 @@ Copyright 2014 Sensics, Inc. /** @brief Utility macro for token pasting aka concatenation */ #define LIBFUNC_DETAIL_CAT(A, B) LIBFUNC_DETAIL_CAT_IMPL(A##B) +/** @brief Utility macro used in stringification of macros. */ +#define LIBFUNC_DETAIL_STRINGIFY_IMPL(X) #X + +/** @brief Utility macro for stringification of macro expansions. */ +#define LIBFUNC_DETAIL_STRINGIFY(X) LIBFUNC_DETAIL_STRINGIFY_IMPL(X) + +/** @brief The prefix appended to a plugin name to generate a unique entry point + * name. */ +#define LIBFUNC_DETAIL_EP_PREFIX libfunc_ep_ + +/** @brief Utility macro (second-level expansion) for unique entry point + * generation */ +#define LIBFUNC_DETAIL_EP_NAME_IMPL(X, PLUGINNAME) \ + LIBFUNC_DETAIL_CAT(X, PLUGINNAME) + /** @brief Generate the unique entry point name for each plugin. */ #define LIBFUNC_DETAIL_EP_NAME(PLUGINNAME) \ - LIBFUNC_DETAIL_CAT(libfunc_ep_, PLUGINNAME) + LIBFUNC_DETAIL_EP_NAME_IMPL(LIBFUNC_DETAIL_EP_PREFIX, PLUGINNAME) /** @brief Return type of the entry point function. */ typedef char libfunc_ep_return_t; @@ -90,7 +105,8 @@ typedef char libfunc_ep_return_t; /** @brief The string name of the common entry point. @todo make this use c preproc stringize while dodging portability problems. */ -#define LIBFUNC_DETAIL_EP_COMMON_NAME_STRING "libfunc_entry_point" +#define LIBFUNC_DETAIL_EP_COMMON_NAME_STRING \ + LIBFUNC_DETAIL_STRINGIFY(LIBFUNC_DETAIL_EP_COMMON_NAME) /** @brief Declaration of the common entry point used in dynamic mode. */ #define LIBFUNC_DETAIL_EP_COMMON_DECLARATION \ diff --git a/inc/libfunctionality/PluginInterface.h b/inc/libfunctionality/PluginInterface.h index 840cfae..b04ee17 100644 --- a/inc/libfunctionality/PluginInterface.h +++ b/inc/libfunctionality/PluginInterface.h @@ -59,7 +59,7 @@ Copyright 2014 Sensics, Inc. * In dynamic mode, we have to create the common entry point as a trampoline to * the unique one. */ #define LIBFUNC_DETAIL_PLUGIN(PLUGINNAME) \ - LIBFUNC_DETAIL_EP_DECLARATION(PLUGINNAME); \ + LIBFUNC_DETAIL_EP_DECORATION LIBFUNC_DETAIL_EP_DECLARATION(PLUGINNAME); \ LIBFUNC_DETAIL_EP_DECORATION LIBFUNC_DETAIL_EP_COMMON_DECLARATION { \ return LIBFUNC_DETAIL_EP_NAME(PLUGINNAME)(LIBFUNC_DETAIL_PARAM_NAME); \ } diff --git a/src/libfunctionality/LoadPluginLibdl.h b/src/libfunctionality/LoadPluginLibdl.h index 40fae60..c6e2be4 100644 --- a/src/libfunctionality/LoadPluginLibdl.h +++ b/src/libfunctionality/LoadPluginLibdl.h @@ -45,46 +45,71 @@ PluginHandle loadPluginByName(const char *n, void *opaque) { return loadPluginByName(std::string(n), opaque); } -PluginHandle loadPluginByName(std::string const &n, void *opaque) { - if (n.empty()) { - throw exceptions::BadPluginName(); - } - - LibraryHandle lib(RAIILoadLibrary(n + LIBFUNC_MODULE_SUFFIX)); - - if (!lib) { - throw exceptions::CannotLoadPlugin(n); - } +typedef void *(*DlsymReturn)(); +/// Mini helper wrapper around dlsym, because that cast is nasty. +static inline DlsymReturn retrieveEntryPoint(void *handle, const char *name) { + DlsymReturn raw_ep; // Appropriate, but odd, syntax per the dlopen(3) man page. // and // http://stackoverflow.com/questions/1354537/dlsym-dlopen-with-runtime-arguments - dlerror(); // clear errors + dlerror(); // clear the error + /// @todo This is more C than C++, but it's Posix-recommended, + *(void **)(&raw_ep) = dlsym(handle, name); + return raw_ep; +} -/// @todo Pick one of these two implementations - one is more C and -/// Posix-recommended, -/// the other is simpler C++. -#if 1 - typedef void *(*DlsymReturn)(); - DlsymReturn raw_ep; - *(void **)(&raw_ep) = - dlsym(lib.get(), LIBFUNC_DETAIL_EP_COMMON_NAME_STRING); - if (dlerror() != NULL || raw_ep == NULL) { - throw exceptions::CannotLoadEntryPoint(n); - } +/// internal helper function used by both the global symbol table entry point +/// path as well as the load from the dlopened handle path +static inline entry_point_t convertAndCallEntryPoint(std::string const &n, + DlsymReturn raw_ep, + void *opaque) { entry_point_t ep = reinterpret_cast(raw_ep); -#else - entry_point_t ep = reinterpret_cast( - dlsym(lib.get(), LIBFUNC_DETAIL_EP_COMMON_NAME_STRING)); -#endif - - if (dlerror() != NULL || ep == NULL) { + if (ep == NULL) { throw exceptions::CannotLoadEntryPoint(n); } libfunc_ep_return_t result = (*ep)(opaque); if (result != LIBFUNC_RETURN_SUCCESS) { throw exceptions::PluginEntryPointFailed(n); } + return ep; +} + +PluginHandle loadPluginByName(std::string const &n, void *opaque) { + if (n.empty()) { + throw exceptions::BadPluginName(); + } + + // attempt to load the symbol from the global symbol table. If successful, + // plugin is already pre-loaded + { + std::string ep_name = + std::string(LIBFUNC_DETAIL_STRINGIFY(LIBFUNC_DETAIL_EP_PREFIX)) + n; + DlsymReturn raw_ep = retrieveEntryPoint(RTLD_DEFAULT, ep_name.c_str()); + + const char *err = dlerror(); + if (err == NULL && raw_ep != NULL) { + convertAndCallEntryPoint(n, raw_ep, opaque); + // Yes, returning an empty PluginHandle here works fine, since it's + // just for lifetime management. + return PluginHandle(); + } + } + + LibraryHandle lib(RAIILoadLibrary(n + LIBFUNC_MODULE_SUFFIX)); + + if (!lib) { + throw exceptions::CannotLoadPlugin(n); + } + + { + DlsymReturn raw_ep = + retrieveEntryPoint(lib.get(), LIBFUNC_DETAIL_EP_COMMON_NAME_STRING); + if (dlerror() != NULL || raw_ep == NULL) { + throw exceptions::CannotLoadEntryPoint(n); + } + convertAndCallEntryPoint(n, raw_ep, opaque); + } return PluginHandle(lib); } diff --git a/tests/cplusplus/LoadTest.cpp b/tests/cplusplus/LoadTest.cpp index 4122cc5..0804f2f 100644 --- a/tests/cplusplus/LoadTest.cpp +++ b/tests/cplusplus/LoadTest.cpp @@ -26,6 +26,7 @@ // Internal Includes #include +#include // Library/third-party includes #include "gtest/gtest.h" @@ -35,6 +36,20 @@ using std::string; +// loading from the global symbol table is only implemented currently in the libdl path +// @todo implement global symbol table plugin loading in the win32 path and remove this ifdef +#ifdef LIBFUNC_DL_LIBDL + +LIBFUNC_PLUGIN_NO_PARAM(com_sensics_libfunc_tests_staticplugin) { + return LIBFUNC_RETURN_SUCCESS; +} + +TEST(load_static_plugin, load_from_global) { + ASSERT_NO_THROW((libfunc::loadPluginByName("com_sensics_libfunc_tests_staticplugin", NULL))); +} + +#endif + TEST(load_dummy_plugin, cstr_name_null_data) { ASSERT_NO_THROW((libfunc::loadPluginByName("DummyPlugin", NULL))); }