为啥在 dlopen'd 函数中传递的 std::any 的 std::any_cast 会引发错误

Posted

技术标签:

【中文标题】为啥在 dlopen\'d 函数中传递的 std::any 的 std::any_cast 会引发错误【英文标题】:Why does an std::any_cast of a passed std::any inside a dlopen'd function raise an error为什么在 dlopen'd 函数中传递的 std::any 的 std::any_cast 会引发错误 【发布时间】:2017-06-10 02:17:43 【问题描述】:

我正在玩弄 c++17 和插件,但遇到了一个我无法解决的错误。在下面的 MWE 中,我可以调用一个带有 std::any 的本地函数,当我尝试读取内容时,一切都按预期工作。当我通过插件(dlopen)加载这个完全相同的功能时,它可以正确地看到任何类型,但它不能std::any_cast 内容。

在找出导致此错误的原因方面的任何帮助将不胜感激。

这是我的环境、MWE 和产生的错误。

>> g++ --version

g++ (GCC) 7.1.1 20170526 (Red Hat 7.1.1-2)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

>> scons --version

SCons by Steven Knight et al.:
    script: v2.5.1.rel_2.5.1:3735:9dc6cee5c168[MODIFIED], 2016/11/03 14:02:02, by bdbaddog on mongodog
    engine: v2.5.1.rel_2.5.1:3735:9dc6cee5c168[MODIFIED], 2016/11/03 14:02:02, by bdbaddog on mongodog
    engine path: ['/usr/lib/scons/SCons']
Copyright (c) 2001 - 2016 The SCons Foundation

>> tree

.
├── SConstruct
└── src
    ├── main.cpp
    ├── plugin.cpp
    └── SConscript

1 directory, 4 files

>> cat SConstruct

SConscript('src/SConscript', variant_dir='build', duplicate=0)

>> cat src/SConscript

env = Environment()
env.Append(CXXFLAGS=['-std=c++17'])
plugin = env.SharedLibrary('plugin', 'plugin.cpp')
Install('../lib', plugin)
driver_env = env.Clone()
driver_env.Append(LIBS=['dl', 'stdc++fs'])
driver = driver_env.Program('driver', 'main.cpp')
Install('../bin', driver)

>> cat src/plugin.cpp

#include <any>
#include <iostream>
using namespace std;
extern "C" 
int plugin(any& context) 
    cout << "    Inside Plugin" << endl;
    cout << "    Has Value? " << context.has_value() << endl;
    cout << "    Type Name: " << context.type().name() << endl;
    cout << "    Value: " << any_cast<double>(context) << endl;


&gt;&gt; cat src/main.cpp

#include <functional>
#include <iostream>
#include <stdexcept>
#include <any>
#include <experimental/filesystem>
#include <dlfcn.h>

using namespace std;
using namespace std::experimental;

function< void(any&) > loadplugin(string filename) 
    function< void(any&) > plugin;
    filesystem::path library_path(filename);
    filesystem::path library_abspath = canonical(library_path);
    void * libraryHandle = dlopen(library_abspath.c_str(), RTLD_NOW);
    if (!libraryHandle) 
        throw runtime_error("ERROR: Could not load the library");
    
    plugin = (int(*) (any&))dlsym(libraryHandle, "plugin");
    if (!plugin) 
        throw runtime_error("ERROR: Could not load the plugin");
    
    return plugin;


int local(any& context) 
    cout << "    Inside Local" << endl;
    cout << "      Has Value? " << context.has_value() << endl;
    cout << "      Type Name: " << context.type().name() << endl;
    cout << "      Value: " << any_cast<double>(context) << endl;


int main(int argc, char* argv[]) 
    cout << "  Resolving Paths..." << endl;
    filesystem::path full_path = filesystem::system_complete( argv[0] ).parent_path();
    filesystem::path plugin_path(full_path/".."/"lib"/"libplugin.so");
    cout << "  Creating Context..." << endl;
    any context(.1);
    cout << "  Loading Plugin..." << endl;
    function<void(any&) > plugin = loadplugin(plugin_path.string());
    cout << "  Calling Local..." << endl;
    local(context);
    cout << "  Calling Plugin..." << endl;
    plugin(context);

&gt;&gt; scons

scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o build/main.o -c -std=c++17 src/main.cpp
g++ -o build/driver build/main.o -ldl -lstdc++fs
Install file: "build/driver" as "bin/driver"
g++ -o build/plugin.os -c -std=c++17 -fPIC src/plugin.cpp
g++ -o build/libplugin.so -shared build/plugin.os
Install file: "build/libplugin.so" as "lib/libplugin.so"
scons: done building targets.

&gt;&gt; tree

.
├── bin
│   └── driver
├── build
│   ├── driver
│   ├── libplugin.so
│   ├── main.o
│   └── plugin.os
├── lib
│   └── libplugin.so
├── SConstruct
└── src
    ├── main.cpp
    ├── plugin.cpp
    └── SConscript

4 directories, 10 files

&gt;&gt; bin/driver

  Resolving Paths...
  Creating Context...
  Loading Plugin...
  Calling Local...
    Inside Local
      Has Value? 1
      Type Name: d
      Value: 0.1
  Calling Plugin...
    Inside Plugin
    Has Value? 1
    Type Name: d
terminate called after throwing an instance of 'std::bad_any_cast'
  what():  bad any_cast
    Value: Aborted (core dumped)

【问题讨论】:

你能让它打印出它试图加载的共享库的绝对路径吗? 另外你不需要克隆环境来添加LIBS,你可以这样做"driver = env.Program('driver', 'main.cpp',LIBS=['dl' , 'stdc++fs'])" 在调试器中运行它并获取到异常发生点的回溯。由于这将在标准库中,因此您需要确保有可用的调试符号。 @bdbaddog,我可以打印出绝对路径,它是正确的,如果它没有解析为实际文件,规范会抛出。感谢您提供有关通过调用构建器对环境进行轻微修改的提示。 @DanielJour,我通过调试器运行它,在 /usr/include/c++/7/any:482 处设置断点(any_cast 在哪里,就在抛出之前),有趣的是是它试图将 any_cast 恢复为 double 的东西看起来不错 (gdb) print __any $5 = std::any containing double = [contained value] = 0.10000000000000001,但是在调用 any_cast 之后,没有任何东西 (gdb) print __p $4 = (double *) 0x0 然后抛出。我不确定下一步该尝试什么,因为我不是 gdb 大师... 【参考方案1】:

libstdc++ 的any 依赖于相同模板实例化的地址在程序中是相同的,这意味着您需要take precautions if you are using dlopen

编译器必须发出 [带有模糊链接的对象,例如模板 实例化] 在任何需要它们存在的翻译单元中, 然后依靠链接和加载过程来确保 只有其中一个在最终的可执行文件中处于活动状态。带静电 链接所有这些符号都在链接时解析,但 动态链接,在加载时会发生进一步的解析。你必须 确保共享库中的对象被解析 可执行文件和其他共享库中的对象。

对于链接到共享库的程序,不需要额外的预防措施。 您无法使用-Bsymbolic 选项创建共享库,因为这会妨碍上述解决方案。 如果您使用dlopen 从共享库中显式加载代码,您必须做几件事。首先,从 通过将其与-E 标志链接来执行(您必须指定 如果您以通常的方式调用链接器,则此为 -Wl,-E 来自编译器驱动程序 g++)。您还必须使外部 已加载库中的符号可用于后续库 将RTLD_GLOBAL 标志提供给dlopen。符号分辨率 可以是即时的或懒惰的。

【讨论】:

以上是关于为啥在 dlopen'd 函数中传递的 std::any 的 std::any_cast 会引发错误的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们不能通过值传递数组来函数?

为啥这个程序会崩溃:在 DLL 之间传递 std::string

为啥不允许将数组按值传递给 C 和 C++ 中的函数?

将右值按值传递给函数时,为啥不调用复制构造函数

为啥 std::vector::resize(n, src) 按值传递?

为啥 C++ 中 std::mutex 的构造函数不抛出?