编译器不可知的运行时类型信息?
Compiler agnostic runtime type information?
如何生成和比较与编译器无关的 运行 时间类型信息?
特别是函数指针类型如何做到这一点?
例如,以下程序包含类型错误。它无法在编译时检测到。如何在 运行 时间检测到它?一般如何检测此类错误?
main.cpp
#include <iostream>
#include <dlfcn.h>
typedef void (*FooPtrType)();
typedef void (*BarPtrType)();
int main()
{
const auto dll = dlopen("functions.dylib", RTLD_LAZY);
if (dll == NULL) {
std::cerr << dlerror() << std::endl;
return 1;
}
const auto foo_ptr = (FooPtrType)dlsym(dll, "bar");
if (foo_ptr == NULL) {
std::cerr << dlerror() << std::endl;
return 1;
}
foo_ptr();
}
functions.cpp,编译为functions.dylib
#include <iostream>
extern "C" void foo() {
std::cout << "foo()" << std::endl;
}
extern "C" int bar() {
std::cout << "bar()" << std::endl;
return 0;
}
您无法在运行时检测到此类类型错误。
由 dlsym
处理的符号只是与给定 dlopen
的 ELF 共享 object 中的某个地址关联的符号。该符号中没有更多类型信息。
(如果 .so
是用 -g
编译的,你可以从 ELF 文件中解析 DWARF 信息——它知道类型。这很痛苦)
阅读 C++ dlopen mini howto, then Drepper's paper: How to write a Shared Library 了解详情。
您可以使用 name mangling 技术(C++ 编译器使用它)。您可能会决定插件中的给定名称具有一些特定的签名(例如,通过声明特定的 dlsym
-ed 名称的插件代码提供 header 为 #include
-d)。
您可以通过数据提供一些自己的输入。例如,您可以决定 every dlsym
-ed 指针具有相同的类型,也许指向公共抽象超类的某个子类的某个实例;或者决定每个 dlsym
-ed 指针都指向一些 struct
,里面有一些 tagged union。你可以使用例如
typedef void void0funsig_t (void);
typedef int int0funsig_t (void);
typedef void void1ifunsig_t (int);
enum funtype_en { void0, int0, void1i; };
struct typefun_st {
enum funtype_en typ;
union {
void0funsig_t* void0funptr; // when typ == void0
int0funsig_t* int0funptr; // when typ == int0
void1ifunsig_t* void1ifunptr; // when typ == void1i
};
};
并决定每个 dlsym
-ed 指针指向这样一个 struct typefun_st
。然后在插件代码中
extern "C" struct typefun_st foofun ={ void0, foo };
并在程序中执行
struct typefun_st *foofunp = dlsym(plugin,"foofun");
等..
您可以使用巧妙的 C 宏来简化此类代码(查看 Emacs 代码中的灵感)。
了解 Qt plugins for inspiration. They use the Qt (runtime) type information provided by the moc. You might perhaps use typeid
and std::type_info...
使用dlsym
时,您总是需要插件约定。在您的情况下,您希望他们在插件加载时 "provide" 一些类型信息。由您来设置所需的基础架构。
多么深刻和发人深省的问题!不错的 OP!
此解决方案背后的基本思想是使用宏生成 class 可以告诉您对象类型名称的模板特化,使用模板编程将这些 class 模板特化组合成函数特化指针,并使用函数重载在专业化中进行搜索。
type_string.h
#ifndef TYPE_STRING_H
#define TYPE_STRING_H
#include <type_traits>
#include <cstdlib>
#include <cstring>
#include <string>
// is_fun_ptr
// Dietmar Kühl
//
template <typename Fun>
struct is_fun_ptr
: std::integral_constant<bool, std::is_pointer<Fun>::value
&& std::is_function<
typename std::remove_pointer<Fun>::type
>::value>
{
};
template<typename T>
struct TypeString;
// type_string<>() overload for non-function pointer objects
// caller must free
template<typename T>
typename std::enable_if<!is_fun_ptr<T>::value, const std::string>::type
type_string()
{
const std::string name = TypeString<T>::value();
char* name_c_str = (char*)malloc(name.length() + 1);
strcpy(name_c_str, name.c_str());
return name_c_str;
}
// getting type name into a template
// atzz and Steve Jessop
//
// There's likely a better way to handle CV qualifiers and pointer/ref
// not all combos covered here
#define ENABLE_TYPE_STRING(TYPE) \
template<> \
struct TypeString<TYPE> { \
static const std::string value() { return #TYPE; } \
}; \
template<> \
struct TypeString<TYPE&> { \
static const std::string value() { return #TYPE "&"; } \
}; \
template<> \
struct TypeString<TYPE*> { \
static const std::string value() { return #TYPE "*"; } \
}; \
template<> \
struct TypeString<const TYPE> { \
static const std::string value() { return "const " #TYPE; } \
}; \
template<> \
struct TypeString<const TYPE&> { \
static const std::string value() { return "const " #TYPE "&"; } \
}; \
template<> \
struct TypeString<const TYPE*> { \
static const std::string value() { return "const " #TYPE "*"; } \
}; \
template<> \
struct TypeString<const TYPE* const> { \
static const std::string value() { return "const " #TYPE "* const"; } \
};
// some builtin types, add others and user-defined types as desired
ENABLE_TYPE_STRING(char)
ENABLE_TYPE_STRING(short)
ENABLE_TYPE_STRING(int)
ENABLE_TYPE_STRING(long)
ENABLE_TYPE_STRING(long long)
ENABLE_TYPE_STRING(signed char)
ENABLE_TYPE_STRING(unsigned char)
ENABLE_TYPE_STRING(unsigned short)
ENABLE_TYPE_STRING(unsigned int)
ENABLE_TYPE_STRING(unsigned long)
ENABLE_TYPE_STRING(unsigned long long)
ENABLE_TYPE_STRING(float)
ENABLE_TYPE_STRING(double)
ENABLE_TYPE_STRING(long double)
ENABLE_TYPE_STRING(std::string)
// void is a special case, no qualifiers, refs
template<>
struct TypeString<void>
{
static const std::string value()
{
return "void";
}
};
template<>
struct TypeString<void*>
{
static const std::string value()
{
return "void*";
}
};
// Function signature to string
// return_type
// angew
//
template <class F>
struct return_type;
template <class R, class... A>
struct return_type<R (*)(A...)>
{
typedef R type;
};
// forward declaration so that this overload may be used in CommaSeparatedNames
template<typename T>
typename std::enable_if<is_fun_ptr<T>::value, const std::string>::type
type_string();
// Concatenating argument types with separating commas
template<typename T, typename... Us>
struct CommaSeparatedNames
{
static const std::string value()
{
return std::string{type_string<T>()}
+ ", " + CommaSeparatedNames<Us...>::value();
}
};
template<typename T>
struct CommaSeparatedNames<T>
{
static const std::string value()
{
return type_string<T>();
}
};
// Arguments to string
template <class F>
struct ArgNames;
template<class R>
struct ArgNames<R (*)()>
{
static const std::string value()
{
return "";
}
};
template<class R, typename A, typename... As>
struct ArgNames<R (*)(A, As...)>
{
static const std::string value()
{
return CommaSeparatedNames<A, As...>::value();
}
};
// overload type_string<>() for function pointers
// caller must free
template<typename T>
typename std::enable_if<is_fun_ptr<T>::value, const std::string>::type
type_string()
{
const auto name =
std::string{type_string<typename return_type<T>::type>()}
+ " (*)(" + ArgNames<T>::value() + ")";
auto name_c_str = (char*)malloc(name.length() + 1);
strcpy(name_c_str, name.c_str());
return name_c_str;
}
// overload type_string<>() to deduce type from an object
// caller must free
template<typename T>
const std::string type_string(T) { return type_string<T>(); }
template<typename T>
const char* type_string_c_str()
{
const auto name = type_string<T>();
char* name_c_str = (char*)malloc(name.length() + 1);
strcpy(name_c_str, name.c_str());
return name_c_str;
}
template<typename T>
const char* type_string_c_str(T) { return type_string_c_str<T>(); }
#endif
用法示例:
#include "type_string.h"
#include <iostream>
void foo() {}
int bar() { return 0; }
float baz(char) { return 0.0f; }
class MyClass;
ENABLE_TYPE_STRING(MyClass)
double quux(const int*, MyClass*) { return 0.0; }
int main()
{
typedef void (*FooTypePtr)();
typedef int (*BarTypePtr)();
typedef float (*BazTypePtr)(char);
typedef double (*QuuxTypePtr)(const int*, MyClass*);
FooTypePtr foo_ptr = foo;
BarTypePtr bar_ptr = bar;
BazTypePtr baz_ptr = baz;
QuuxTypePtr quux_ptr = quux;
QuuxTypePtr (*weird_ptr)(FooTypePtr, BarTypePtr, BazTypePtr) = NULL;
std::cout << type_string(3) << std::endl;
std::cout << type_string('a') << std::endl;
std::cout << type_string(foo_ptr) << std::endl;
std::cout << type_string(bar_ptr) << std::endl;
std::cout << type_string(baz_ptr) << std::endl;
std::cout << type_string(quux_ptr) << std::endl;
std::cout << type_string(weird_ptr) << std::endl;
}
输出:
int
char
void (*)()
int (*)()
float (*)(char)
double (*)(const int*, MyClass*)
double (*)(const int*, MyClass*) (*)(void (*)(), int (*)(), float (*)(char))
需要手动启用对象类型的类型信息,但如果启用了所有相关类型的信息,则可以推断出函数指针的类型。
动态加载示例(又名使用 X 解决 Y)
这是一种在运行时检测此类类型错误的方法。
safe_dl.h
使用 type_string.h
错误检查动态加载。
#ifndef SAFE_DL_H
#define SAFE_DL_H
#include "type_string.h"
#include <exception>
#include <stdexcept>
#include <sstream>
#include <string>
#include <dlfcn.h>
#define ENABLE_RUNTIME_FNC_PTR_TYPE_INFO(F) \
extern "C" const char* F ## _fnc_ptr_type_() { \
return type_string_c_str( F ); }
#define RUNTIME_TYPE_STRING_FNC_NAME(F) \
F ## _fnc_ptr_type_
namespace {
extern "C" void in_dll_free(void* ptr)
{
free(ptr);
}
}
class DynamicLibrary
{
public:
explicit DynamicLibrary(const char* lib_name, const int mode=RTLD_LAZY)
: filename_(lib_name)
{
handle_ = dlopen(lib_name, mode);
if (handle_ == NULL) {
throw std::runtime_error(dlerror());
}
free_ptr_ = find_in_dll_free();
}
DynamicLibrary(const std::string& lib_name)
: DynamicLibrary(lib_name.c_str()) {}
~DynamicLibrary()
{
dlclose(handle_);
}
template<typename T>
T safe_dynamic_load(const std::string& func_name) const
{
// The type of T* tells us the expected type. A cooperating library tells
// us the actual type.
const auto expected_type = type_string<T>();
const auto actual_type = symbol_type(func_name);
if (strcmp(expected_type.c_str(), actual_type))
{
std::ostringstream msg;
msg << "Function pointer type mismatch. Function " << func_name
<< " loaded from file " << filename() << " has expected type "
<< expected_type << " but the actual type is " << actual_type
<< ".";
free_ptr_((void*)actual_type);
throw std::runtime_error(msg.str());
}
free_ptr_((void*)actual_type);
return (T)(symbol(func_name));
}
const std::string& filename() const { return filename_; }
private:
// caller is responsible for freeing returned pointer
const char* symbol_type(const std::string& name) const
{
const auto type_func_name = name + "_fnc_ptr_type_";
typedef const char* (*TypeFuncPtrType)();
TypeFuncPtrType type_func_ptr =
(TypeFuncPtrType)dlsym(handle_, type_func_name.c_str());
if (type_func_ptr == NULL) {
const auto msg = "Safe dynamic loading not enabled for " + name;
throw std::runtime_error(msg.c_str());
}
return type_func_ptr();
}
void* symbol(const std::string& name) const
{
void* p = dlsym(handle_, name.c_str());
if (p == NULL) {
throw(std::runtime_error{dlerror()});
}
return p;
}
// free from within the dll
typedef void (*DllFreePtrType)(void*);
DllFreePtrType find_in_dll_free() const
{
typedef void (*DllFreePtrType)(void*);
DllFreePtrType free_ptr = (DllFreePtrType)dlsym(handle_, "in_dll_free");
if (free_ptr == NULL) {
throw std::runtime_error(dlerror());
}
return free_ptr;
}
private:
const std::string filename_;
void* handle_;
DllFreePtrType free_ptr_;
};
#endif
在原始程序中工作safe_dl.h
:
main.cpp
#include "safe_dl.h"
#include <iostream>
#if defined(__clang__)
#define COMPILER "clang"
#elif defined(__GNUC__)
#define COMPILER "gcc"
#else
#define COMPILER "other"
#endif
typedef void (*FooPtrType)();
typedef int (*BarPtrType)();
int main()
{
std::cout << "main()" << std::endl;
std::cout << "compiler: " COMPILER << std::endl;
const DynamicLibrary dll{"./functions.so"};
// Works fine.
const auto foo_ptr = dll.safe_dynamic_load<FooPtrType>("foo");
foo_ptr();
// Throws exception.
const auto bar_ptr = dll.safe_dynamic_load<FooPtrType>("bar");
bar_ptr();
}
functions.cpp
,编译为functions.dylib
#include "safe_dl.h"
#include <iostream>
#if defined(__clang__)
#define COMPILER "clang"
#elif defined(__GNUC__)
#define COMPILER "gcc"
#else
#define COMPILER "other"
#endif
extern "C" void foo() {
std::cout << "foo()" << std::endl;
std::cout << "compiler: " COMPILER << std::endl;
return;
}
ENABLE_RUNTIME_FNC_PTR_TYPE_INFO(foo)
extern "C" int bar() {
std::cout << "bar()" << std::endl;
return 0;
}
ENABLE_RUNTIME_FNC_PTR_TYPE_INFO(bar)
用 clang 编译的 main.cpp
和用 g++ 编译的 functions.cpp
的输出:
main()
compiler: clang
foo()
compiler: gcc
terminate called after throwing an instance of 'std::runtime_error'
what(): Function pointer type mismatch. Function bar loaded from file
./functions.so has expected type void (*)() but the actual type is int (*)().
Aborted (core dumped)
因为 type_name.h
生成 char *
来编码类型,代码相对编译器不可知,并且 safe_dl.h
将与混合编译器一起工作。 (或者至少该示例在混合 gcc 4.9.2 和 clang 3.5 时有效。)
如何生成和比较与编译器无关的 运行 时间类型信息?
特别是函数指针类型如何做到这一点?
例如,以下程序包含类型错误。它无法在编译时检测到。如何在 运行 时间检测到它?一般如何检测此类错误?
main.cpp
#include <iostream>
#include <dlfcn.h>
typedef void (*FooPtrType)();
typedef void (*BarPtrType)();
int main()
{
const auto dll = dlopen("functions.dylib", RTLD_LAZY);
if (dll == NULL) {
std::cerr << dlerror() << std::endl;
return 1;
}
const auto foo_ptr = (FooPtrType)dlsym(dll, "bar");
if (foo_ptr == NULL) {
std::cerr << dlerror() << std::endl;
return 1;
}
foo_ptr();
}
functions.cpp,编译为functions.dylib
#include <iostream>
extern "C" void foo() {
std::cout << "foo()" << std::endl;
}
extern "C" int bar() {
std::cout << "bar()" << std::endl;
return 0;
}
您无法在运行时检测到此类类型错误。
由 dlsym
处理的符号只是与给定 dlopen
的 ELF 共享 object 中的某个地址关联的符号。该符号中没有更多类型信息。
(如果 .so
是用 -g
编译的,你可以从 ELF 文件中解析 DWARF 信息——它知道类型。这很痛苦)
阅读 C++ dlopen mini howto, then Drepper's paper: How to write a Shared Library 了解详情。
您可以使用 name mangling 技术(C++ 编译器使用它)。您可能会决定插件中的给定名称具有一些特定的签名(例如,通过声明特定的 dlsym
-ed 名称的插件代码提供 header 为 #include
-d)。
您可以通过数据提供一些自己的输入。例如,您可以决定 every dlsym
-ed 指针具有相同的类型,也许指向公共抽象超类的某个子类的某个实例;或者决定每个 dlsym
-ed 指针都指向一些 struct
,里面有一些 tagged union。你可以使用例如
typedef void void0funsig_t (void);
typedef int int0funsig_t (void);
typedef void void1ifunsig_t (int);
enum funtype_en { void0, int0, void1i; };
struct typefun_st {
enum funtype_en typ;
union {
void0funsig_t* void0funptr; // when typ == void0
int0funsig_t* int0funptr; // when typ == int0
void1ifunsig_t* void1ifunptr; // when typ == void1i
};
};
并决定每个 dlsym
-ed 指针指向这样一个 struct typefun_st
。然后在插件代码中
extern "C" struct typefun_st foofun ={ void0, foo };
并在程序中执行
struct typefun_st *foofunp = dlsym(plugin,"foofun");
等..
您可以使用巧妙的 C 宏来简化此类代码(查看 Emacs 代码中的灵感)。
了解 Qt plugins for inspiration. They use the Qt (runtime) type information provided by the moc. You might perhaps use typeid
and std::type_info...
使用dlsym
时,您总是需要插件约定。在您的情况下,您希望他们在插件加载时 "provide" 一些类型信息。由您来设置所需的基础架构。
多么深刻和发人深省的问题!不错的 OP!
此解决方案背后的基本思想是使用宏生成 class 可以告诉您对象类型名称的模板特化,使用模板编程将这些 class 模板特化组合成函数特化指针,并使用函数重载在专业化中进行搜索。
type_string.h
#ifndef TYPE_STRING_H
#define TYPE_STRING_H
#include <type_traits>
#include <cstdlib>
#include <cstring>
#include <string>
// is_fun_ptr
// Dietmar Kühl
//
template <typename Fun>
struct is_fun_ptr
: std::integral_constant<bool, std::is_pointer<Fun>::value
&& std::is_function<
typename std::remove_pointer<Fun>::type
>::value>
{
};
template<typename T>
struct TypeString;
// type_string<>() overload for non-function pointer objects
// caller must free
template<typename T>
typename std::enable_if<!is_fun_ptr<T>::value, const std::string>::type
type_string()
{
const std::string name = TypeString<T>::value();
char* name_c_str = (char*)malloc(name.length() + 1);
strcpy(name_c_str, name.c_str());
return name_c_str;
}
// getting type name into a template
// atzz and Steve Jessop
//
// There's likely a better way to handle CV qualifiers and pointer/ref
// not all combos covered here
#define ENABLE_TYPE_STRING(TYPE) \
template<> \
struct TypeString<TYPE> { \
static const std::string value() { return #TYPE; } \
}; \
template<> \
struct TypeString<TYPE&> { \
static const std::string value() { return #TYPE "&"; } \
}; \
template<> \
struct TypeString<TYPE*> { \
static const std::string value() { return #TYPE "*"; } \
}; \
template<> \
struct TypeString<const TYPE> { \
static const std::string value() { return "const " #TYPE; } \
}; \
template<> \
struct TypeString<const TYPE&> { \
static const std::string value() { return "const " #TYPE "&"; } \
}; \
template<> \
struct TypeString<const TYPE*> { \
static const std::string value() { return "const " #TYPE "*"; } \
}; \
template<> \
struct TypeString<const TYPE* const> { \
static const std::string value() { return "const " #TYPE "* const"; } \
};
// some builtin types, add others and user-defined types as desired
ENABLE_TYPE_STRING(char)
ENABLE_TYPE_STRING(short)
ENABLE_TYPE_STRING(int)
ENABLE_TYPE_STRING(long)
ENABLE_TYPE_STRING(long long)
ENABLE_TYPE_STRING(signed char)
ENABLE_TYPE_STRING(unsigned char)
ENABLE_TYPE_STRING(unsigned short)
ENABLE_TYPE_STRING(unsigned int)
ENABLE_TYPE_STRING(unsigned long)
ENABLE_TYPE_STRING(unsigned long long)
ENABLE_TYPE_STRING(float)
ENABLE_TYPE_STRING(double)
ENABLE_TYPE_STRING(long double)
ENABLE_TYPE_STRING(std::string)
// void is a special case, no qualifiers, refs
template<>
struct TypeString<void>
{
static const std::string value()
{
return "void";
}
};
template<>
struct TypeString<void*>
{
static const std::string value()
{
return "void*";
}
};
// Function signature to string
// return_type
// angew
//
template <class F>
struct return_type;
template <class R, class... A>
struct return_type<R (*)(A...)>
{
typedef R type;
};
// forward declaration so that this overload may be used in CommaSeparatedNames
template<typename T>
typename std::enable_if<is_fun_ptr<T>::value, const std::string>::type
type_string();
// Concatenating argument types with separating commas
template<typename T, typename... Us>
struct CommaSeparatedNames
{
static const std::string value()
{
return std::string{type_string<T>()}
+ ", " + CommaSeparatedNames<Us...>::value();
}
};
template<typename T>
struct CommaSeparatedNames<T>
{
static const std::string value()
{
return type_string<T>();
}
};
// Arguments to string
template <class F>
struct ArgNames;
template<class R>
struct ArgNames<R (*)()>
{
static const std::string value()
{
return "";
}
};
template<class R, typename A, typename... As>
struct ArgNames<R (*)(A, As...)>
{
static const std::string value()
{
return CommaSeparatedNames<A, As...>::value();
}
};
// overload type_string<>() for function pointers
// caller must free
template<typename T>
typename std::enable_if<is_fun_ptr<T>::value, const std::string>::type
type_string()
{
const auto name =
std::string{type_string<typename return_type<T>::type>()}
+ " (*)(" + ArgNames<T>::value() + ")";
auto name_c_str = (char*)malloc(name.length() + 1);
strcpy(name_c_str, name.c_str());
return name_c_str;
}
// overload type_string<>() to deduce type from an object
// caller must free
template<typename T>
const std::string type_string(T) { return type_string<T>(); }
template<typename T>
const char* type_string_c_str()
{
const auto name = type_string<T>();
char* name_c_str = (char*)malloc(name.length() + 1);
strcpy(name_c_str, name.c_str());
return name_c_str;
}
template<typename T>
const char* type_string_c_str(T) { return type_string_c_str<T>(); }
#endif
用法示例:
#include "type_string.h"
#include <iostream>
void foo() {}
int bar() { return 0; }
float baz(char) { return 0.0f; }
class MyClass;
ENABLE_TYPE_STRING(MyClass)
double quux(const int*, MyClass*) { return 0.0; }
int main()
{
typedef void (*FooTypePtr)();
typedef int (*BarTypePtr)();
typedef float (*BazTypePtr)(char);
typedef double (*QuuxTypePtr)(const int*, MyClass*);
FooTypePtr foo_ptr = foo;
BarTypePtr bar_ptr = bar;
BazTypePtr baz_ptr = baz;
QuuxTypePtr quux_ptr = quux;
QuuxTypePtr (*weird_ptr)(FooTypePtr, BarTypePtr, BazTypePtr) = NULL;
std::cout << type_string(3) << std::endl;
std::cout << type_string('a') << std::endl;
std::cout << type_string(foo_ptr) << std::endl;
std::cout << type_string(bar_ptr) << std::endl;
std::cout << type_string(baz_ptr) << std::endl;
std::cout << type_string(quux_ptr) << std::endl;
std::cout << type_string(weird_ptr) << std::endl;
}
输出:
int
char
void (*)()
int (*)()
float (*)(char)
double (*)(const int*, MyClass*)
double (*)(const int*, MyClass*) (*)(void (*)(), int (*)(), float (*)(char))
需要手动启用对象类型的类型信息,但如果启用了所有相关类型的信息,则可以推断出函数指针的类型。
动态加载示例(又名使用 X 解决 Y)
这是一种在运行时检测此类类型错误的方法。
safe_dl.h
使用 type_string.h
错误检查动态加载。
#ifndef SAFE_DL_H
#define SAFE_DL_H
#include "type_string.h"
#include <exception>
#include <stdexcept>
#include <sstream>
#include <string>
#include <dlfcn.h>
#define ENABLE_RUNTIME_FNC_PTR_TYPE_INFO(F) \
extern "C" const char* F ## _fnc_ptr_type_() { \
return type_string_c_str( F ); }
#define RUNTIME_TYPE_STRING_FNC_NAME(F) \
F ## _fnc_ptr_type_
namespace {
extern "C" void in_dll_free(void* ptr)
{
free(ptr);
}
}
class DynamicLibrary
{
public:
explicit DynamicLibrary(const char* lib_name, const int mode=RTLD_LAZY)
: filename_(lib_name)
{
handle_ = dlopen(lib_name, mode);
if (handle_ == NULL) {
throw std::runtime_error(dlerror());
}
free_ptr_ = find_in_dll_free();
}
DynamicLibrary(const std::string& lib_name)
: DynamicLibrary(lib_name.c_str()) {}
~DynamicLibrary()
{
dlclose(handle_);
}
template<typename T>
T safe_dynamic_load(const std::string& func_name) const
{
// The type of T* tells us the expected type. A cooperating library tells
// us the actual type.
const auto expected_type = type_string<T>();
const auto actual_type = symbol_type(func_name);
if (strcmp(expected_type.c_str(), actual_type))
{
std::ostringstream msg;
msg << "Function pointer type mismatch. Function " << func_name
<< " loaded from file " << filename() << " has expected type "
<< expected_type << " but the actual type is " << actual_type
<< ".";
free_ptr_((void*)actual_type);
throw std::runtime_error(msg.str());
}
free_ptr_((void*)actual_type);
return (T)(symbol(func_name));
}
const std::string& filename() const { return filename_; }
private:
// caller is responsible for freeing returned pointer
const char* symbol_type(const std::string& name) const
{
const auto type_func_name = name + "_fnc_ptr_type_";
typedef const char* (*TypeFuncPtrType)();
TypeFuncPtrType type_func_ptr =
(TypeFuncPtrType)dlsym(handle_, type_func_name.c_str());
if (type_func_ptr == NULL) {
const auto msg = "Safe dynamic loading not enabled for " + name;
throw std::runtime_error(msg.c_str());
}
return type_func_ptr();
}
void* symbol(const std::string& name) const
{
void* p = dlsym(handle_, name.c_str());
if (p == NULL) {
throw(std::runtime_error{dlerror()});
}
return p;
}
// free from within the dll
typedef void (*DllFreePtrType)(void*);
DllFreePtrType find_in_dll_free() const
{
typedef void (*DllFreePtrType)(void*);
DllFreePtrType free_ptr = (DllFreePtrType)dlsym(handle_, "in_dll_free");
if (free_ptr == NULL) {
throw std::runtime_error(dlerror());
}
return free_ptr;
}
private:
const std::string filename_;
void* handle_;
DllFreePtrType free_ptr_;
};
#endif
在原始程序中工作safe_dl.h
:
main.cpp
#include "safe_dl.h"
#include <iostream>
#if defined(__clang__)
#define COMPILER "clang"
#elif defined(__GNUC__)
#define COMPILER "gcc"
#else
#define COMPILER "other"
#endif
typedef void (*FooPtrType)();
typedef int (*BarPtrType)();
int main()
{
std::cout << "main()" << std::endl;
std::cout << "compiler: " COMPILER << std::endl;
const DynamicLibrary dll{"./functions.so"};
// Works fine.
const auto foo_ptr = dll.safe_dynamic_load<FooPtrType>("foo");
foo_ptr();
// Throws exception.
const auto bar_ptr = dll.safe_dynamic_load<FooPtrType>("bar");
bar_ptr();
}
functions.cpp
,编译为functions.dylib
#include "safe_dl.h"
#include <iostream>
#if defined(__clang__)
#define COMPILER "clang"
#elif defined(__GNUC__)
#define COMPILER "gcc"
#else
#define COMPILER "other"
#endif
extern "C" void foo() {
std::cout << "foo()" << std::endl;
std::cout << "compiler: " COMPILER << std::endl;
return;
}
ENABLE_RUNTIME_FNC_PTR_TYPE_INFO(foo)
extern "C" int bar() {
std::cout << "bar()" << std::endl;
return 0;
}
ENABLE_RUNTIME_FNC_PTR_TYPE_INFO(bar)
用 clang 编译的 main.cpp
和用 g++ 编译的 functions.cpp
的输出:
main()
compiler: clang
foo()
compiler: gcc
terminate called after throwing an instance of 'std::runtime_error'
what(): Function pointer type mismatch. Function bar loaded from file
./functions.so has expected type void (*)() but the actual type is int (*)().
Aborted (core dumped)
因为 type_name.h
生成 char *
来编码类型,代码相对编译器不可知,并且 safe_dl.h
将与混合编译器一起工作。 (或者至少该示例在混合 gcc 4.9.2 和 clang 3.5 时有效。)