【问题标题】:Compiler agnostic runtime type information?编译器不可知的运行时类型信息?
【发布时间】:2015-02-19 23:25:06
【问题描述】:

如何生成和比较与编译器无关的运行时类型信息?

尤其是函数指针类型如何做到这一点?

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

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

【问题讨论】:

  • 你说的比较是什么意思?
  • 请发布一些示例代码及其使用各种编译器的输出,并解释为什么该输出不适合您。这将使其他人能够理解您的问题。
  • 这个问题的“范围”是多少?编译器不可知,您的意思是您希望使用 gcc/g++ 编译的 Linux 上的代码与 Windows 上的 Microsoft 编译器具有相同的运行时信息?或者只是在一个特定的操作系统中,为单一架构使用不同的编译器变体或版本?我有一种强烈的感觉,第一种选择根本不可能。
  • 为什么需要使用RTTI?
  • 我很确定我们遇到了 XY 问题。我认为您的示例需要包含更多您实际想要实现的目标。自从大约 20 多年前我开始用 C 语言编程以来,我从来没有比较过函数指针的类型——我不完全确定是否有可能做到这一点(因此——你显然可以制作某种包装类,其中包含一些元数据来识别类型)

标签: c++ rtti


【解决方案1】:

您无法在运行时检测到此类类型错误。

dlsym 处理的符号只是与ELF 共享对象中的某个地址关联的符号,该共享对象提供给dlopen。该符号中没有更多类型信息。

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

阅读C++ dlopen mini howto,然后阅读Drepper's paper: How to write a Shared Library了解详情。

您可以使用name mangling 技术(C++ 编译器使用该技术)。您可能会决定插件中的给定名称具有某些特定签名(例如,通过声明特定 dlsym-ed 名称的插件代码提供标头为 #include-d)。

您可以通过数据自己提供一些输入。例如,您可以决定 每个 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 以获得灵感。他们使用moc 提供的Qt(运行时)类型信息。您可能会使用typeid 和std::type_info...

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

【讨论】:

    【解决方案2】:

    这是一个多么深刻而发人深省的问题!不错的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 时有效。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-27
      • 2016-10-04
      • 1970-01-01
      • 2015-03-21
      • 2020-01-19
      • 1970-01-01
      相关资源
      最近更新 更多