为了说明情况:我有一个Java项目,其中部分使用了两个JNI库。以libbar.so
和libfoo.so
为例。如果这些是系统库,
System.loadLibrary("bar");
如果只是使用已有的库,那么问题就迎刃而解了。但由于这些库是我自己打包到JAR文件中的,所以我需要做类似于下面这样的操作:
System.load("/path/to/libfoo.so");
System.load("/path/to/libbar.so");
需要先安装libfoo,否则libbar
无法找到它,因为它不在系统库搜索路径中。
这样做已经运作良好一段时间了,但我现在遇到了一个问题,即使类型正确,std::any_cast
仍然会抛出std::bad_any_cast
。 我追踪了一下发现,这两个库对该类型的typeinfo有不同的定义,并且它们在运行时没有被合并。 这似乎是因为System.load()
最终使用RTLD_LOCAL
而不是RTLD_GLOBAL
调用dlopen()
。
我写了这个示例来演示这种行为,而不需要JNI:
在我的系统上,我得到了:foo.hpp
class foo { }; extern "C" const void* libfoo_foo_typeinfo();
foo.cpp
#include "foo.hpp" #include <typeinfo> extern "C" const void* libfoo_foo_typeinfo() { return &typeid(foo); }
bar.cpp
#include "foo.hpp" #include <typeinfo> extern "C" const void* libbar_foo_typeinfo() { return &typeid(foo); }
main.cpp
#include <iostream> #include <typeinfo> #include <dlfcn.h> int main() { void* libfoo = dlopen("./libfoo.so", RTLD_NOW | RTLD_LOCAL); void* libbar = dlopen("./libbar.so", RTLD_NOW | RTLD_LOCAL); auto libfoo_fn = reinterpret_cast<const void* (*)()>( dlsym(libfoo, "libfoo_foo_typeinfo")); auto libbar_fn = reinterpret_cast<const void* (*)()>( dlsym(libbar, "libbar_foo_typeinfo")); auto libfoo_ti = static_cast<const std::type_info*>(libfoo_fn()); auto libbar_ti = static_cast<const std::type_info*>(libbar_fn()); std::cout << std::boolalpha << (libfoo_ti == libbar_ti) << "\n" << (*libfoo_ti == *libbar_ti) << "\n"; return 0; }
Makefile
all: libfoo.so libbar.so main libfoo.so: foo.cpp $(CXX) -fpic -shared -Wl,-soname=$@ $^ -o $@ libbar.so: bar.cpp $(CXX) -fpic -shared -Wl,-soname=$@ $^ -L. -lfoo -o $@ main: main.cpp $(CXX) $^ -ldl -o $@
$ make
...
$ ./main
false
true
这是因为尽管类型信息地址不同,但GCC的libstdc++使用了编码后的名称来确定相等性。例如,在LLVM的libc++中,相等性基于类型信息地址本身,这样就会得到:
$ make CXX="clang++ -stdlib=libc++"
$ ./main
false
false
如果我传递
RTLD_GLOBAL
,那么我就会看到...true
true
如果我先编辑 main.cpp
以先加载 libbar.so
,只需告诉它可以找到 libfoo.so
,它也可以正常工作:
$ LD_LIBRARY_PATH=. ./main
true
true
但出于本篇文章开头所述的原因,这两种方法都不是可行的解决方案。
这与https://github.com/android-ndk/ndk/issues/533非常相似,但是它涉及到非动态类型,因此没有办法添加一个“关键函数”来强制 typeinfo 成为一个强符号。我碰巧在 Android 上首先复现了这个问题,但它并不特定于 Android。