编译器不可知的运行时类型信息

Compiler agnostic runtime type information?

本文关键字:类型 信息 运行时 不可知 编译器      更新时间:2023-10-16

如何生成和比较编译器不可知的运行时类型信息?

特别是,对于函数指针类型,如何做到这一点?

例如,以下程序包含一个类型错误。在编译时无法检测到它。如何在运行时检测到它?一般来说,如何检测到此类错误?

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共享对象中的某个地址相关联的符号。该符号中没有更多类型信息。

(如果.so是用-g编译的,你可以从ELF文件中解析DWARF信息,它知道类型。这很痛苦)

阅读C++dlopen mini howto,然后阅读Drepper的论文:如何编写共享库了解详细信息。

您可以使用名称篡改技术(C++编译器也使用这种技术)。您可能会决定插件中的给定名称具有特定的签名(例如,通过插件代码提供一个#include-d的头,该代码声明特定的dlsym-ed名称)。

您可以通过数据提供一些自己键入的内容。例如,您可以决定每个dlsym-ed指针都具有相同的类型,可能指向公共抽象超类的某个子类的某个实例;或者决定每个CCD_ 8-ed指针指向某个CCD_。例如,你可以使用

 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
    };
   };

并且确定每个CCD_ 10-ed指针都指向这样的CCD_。然后在插件代码

 extern "C" struct typefun_st foofun ={ void0, foo };

在程序中做

 struct typefun_st *foofunp = dlsym(plugin,"foofun");

等等。。

您可以使用聪明的C宏来简化这些代码(从Emacs代码中寻找灵感)。

阅读Qt插件以获得灵感。它们使用moc提供的Qt(运行时)类型信息。您可能会使用typeid和std::type_info。。。

当使用dlsym时,总是需要插件约定。在您的情况下,您希望他们在插件加载时"提供"一些类型信息。由您来设置所需的基础架构。

这是一个多么深刻和发人深省的问题!不错的一个OP!

该解决方案背后的基本思想是使用宏生成可以告诉您对象类型名称的类模板专业化,使用模板编程将这些类模板专业化成函数指针的专业化,以及使用函数重载在专业化之间进行搜索。

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
// http://stackoverflow.com/a/18667268/1128289
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
// http://stackoverflow.com/a/4485051/1128289
// 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
// http://stackoverflow.com/a/18695701/1128289
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时有效。)