为什么 dlopen 函数中传递的 std::any 的 std::any_cast 会引发错误
Why does an std::any_cast of a passed std::any inside a dlopen'd function raise an error
我在玩弄 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;
}
}
>> 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);
}
>> 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.
>> 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
>> 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)
libstdc++ 的 any
依赖于程序内部相同模板实例化的地址,这意味着您需要 take precautions if you are using dlopen
:
The compiler has to emit [objects with vague linkage, like template
instantiations] in any translation unit that requires their presence,
and then rely on the linking and loading process to make sure that
only one of them is active in the final executable. With static
linking all of these symbols are resolved at link time, but with
dynamic linking, further resolution occurs at load time. You have to
ensure that objects within a shared library are resolved against
objects in the executable and other shared libraries.
- For a program which is linked against a shared library, no additional precautions are needed.
- You cannot create a shared library with the
-Bsymbolic
option, as that prevents the resolution described above.
- If you use
dlopen
to explicitly load code from a shared library, you must do several things. First, export global symbols from the
executable by linking it with the -E
flag (you will have to specify
this as -Wl,-E
if you are invoking the linker in the usual manner
from the compiler driver, g++). You must also make the external
symbols in the loaded library available for subsequent libraries by
providing the RTLD_GLOBAL
flag to dlopen
. The symbol resolution
can be immediate or lazy.
我在玩弄 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;
}
}
>> 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);
}
>> 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.
>> 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
>> 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)
libstdc++ 的 any
依赖于程序内部相同模板实例化的地址,这意味着您需要 take precautions if you are using dlopen
:
The compiler has to emit [objects with vague linkage, like template instantiations] in any translation unit that requires their presence, and then rely on the linking and loading process to make sure that only one of them is active in the final executable. With static linking all of these symbols are resolved at link time, but with dynamic linking, further resolution occurs at load time. You have to ensure that objects within a shared library are resolved against objects in the executable and other shared libraries.
- For a program which is linked against a shared library, no additional precautions are needed.
- You cannot create a shared library with the
-Bsymbolic
option, as that prevents the resolution described above.- If you use
dlopen
to explicitly load code from a shared library, you must do several things. First, export global symbols from the executable by linking it with the-E
flag (you will have to specify this as-Wl,-E
if you are invoking the linker in the usual manner from the compiler driver, g++). You must also make the external symbols in the loaded library available for subsequent libraries by providing theRTLD_GLOBAL
flag todlopen
. The symbol resolution can be immediate or lazy.