[英]Call main executable's functions from plugin compiled using Clang
我正在编写一个程序(macOS,clang++ 编译器,目前只有 AppleSilicon),稍后我可以通过提供使用主程序公共接口的自定义插件(动态库,在运行时加载)来扩展它。
test.hpp - 公共接口:
#if defined(MAGIC_PLUGIN)
# define MAGIC_EXPORT /* nothing */
#else
# define MAGIC_EXPORT __attribute__((visibility("default")))
#endif
MAGIC_EXPORT
void testCall();
test.cpp - 主程序:
#include <stdio.h>
#include <dlfcn.h>
#include "test.hpp"
// Declare a function to call from a loaded plugin
typedef void (* plugin_call_func)(void);
int main(int argc, char** argv) {
// Load library
const char* libraryName = "plugin.dylib";
void* library = dlopen(libraryName, RTLD_NOW);
if (library == nullptr) {
printf("Cannot open library\n");
return 1;
}
// Get function from loaded library
plugin_call_func pluginCall = reinterpret_cast<plugin_call_func>(
dlsym(library, "pluginCall"));
if (pluginCall == nullptr) {
printf("Cannot find the pluginCall function\n");
return 2;
}
// Execute loaded function
pluginCall();
// Forget function and close library
pluginCall = nullptr;
auto libraryCloseResult = dlclose(library);
if (libraryCloseResult != 0) {
printf("Cannot close library\n");
return 3;
}
return 0;
}
// Public function, should be called from a plugin
void testCall() {
printf("Test call\n");
}
plugin.cpp - 插件的来源:
#define MAGIC_PLUGIN
#include <stdio.h>
#include "test.hpp"
__attribute__((visibility("default")))
extern "C" void pluginCall(void) {
printf("Plugin call\n");
testCall();
}
首先,我编译主应用程序:
clang++ -std=c++20 -fvisibility=hidden -target arm64-apple-macos12 test.cpp -o test
nm --defined-only test
显示以下符号:
0000000100003ee4 T __Z8testCallv
0000000100000000 T __mh_execute_header
0000000100003dcc t _main
损坏的 __Z8testCallv 是我需要的。 到目前为止一切看起来都不错。 但后来我尝试将插件编译为动态库......
clang++ -std=c++20 -fvisibility=hidden -dynamiclib -g -current_version 0.1 -target arm64-apple-macos12 plugin.cpp -o plugin.dylib
并得到这个错误:
Undefined symbols for architecture arm64:
"testCall()", referenced from:
_pluginCall in plugin-38422c.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
好吧,这有点公平,我可以理解这一点,因为动态库不知道testCall
是在某个地方实现的。 所以我想说它不必担心testCall
的存在。
我试图研究如何做到这一点,查找手册页,阅读大量堆栈溢出答案,而我只发现有效的方法是将这些标志添加到 linker:
-Wl,-undefined,dynamic_lookup
它工作,库编译并且应用程序按预期工作。 但我真的不想使用dynamic_lookup
因为它会将库中的每个未定义符号标记为已解决,这可能会导致一些不良后果。 我只想告诉 linker 关于主程序公共符号的存在。
我错过了什么? 有没有比dynamic_lookup
更好的解决方案?
最好的办法是手动完成动态库加载器完成的工作。 即:填充 function 指针。 毕竟,plugin->main 绑定已经手动完成,所以反过来做同样的事情是有意义的。
您可以通过精心制作插件和主应用程序共享的 header 来使此过程基本透明。 唯一棘手的部分是处理由多个源文件组成的插件的 ODR。
由于这是一个 C++ 问题,因此这是使用 RAII 包装器的样子。 ODR 难题是通过PLUGIN_MAIN
宏处理的,该宏只应在插件源之一中定义。
test_plugin.hpp
using pluginCall_fn = void(*)();
using testCall_fn = void(*)();
#if defined(COMPILING_PLUGIN) || defined(PLUGIN_MAIN)
extern "C" {
// Declare symbols provided by the plugin
__attribute__((visibility("default"))) void pluginCall();
// Declare pointers that will be populated by the main application
__attribute__((visibility("default"))) extern testCall_fn testCall;
}
#ifdef PLUGIN_MAIN
// Only define the pointers in the TU with PLUGIN_MAIN defined.
testCall_fn testCall;
#endif
#else // In the main app.
#include <stdexcept>
// Declare "symbols" provided by the main application
void testCall();
struct loaded_library final {
loaded_library(const char* libName)
: handle_(dlopen(libName, RTLD_NOW)) {
if(!handle_) {
throw std::runtime_error("failed to load plugin");
}
}
~loaded_library() {
dlclose(handle_);
}
template<typename T>
T get_symbol(const char* symbol) {
T result = reinterpret_cast<T>(dlsym(handle_, symbol));
if(!result) {
throw std::runtime_error("missing symbol");
}
return result;
}
private:
void* handle_;
};
struct loaded_plugin final {
loaded_plugin(const char* libName)
: lib_(libName) {
// Load functions from plugin
pluginCall = lib_.get_symbol<pluginCall_fn>("pluginCall");
// ...
// Assign callbacks to plugin
*lib_.get_symbol<testCall_fn*>("testCall") = &testCall;
// Call the plugin's init function here if applicable.
}
pluginCall_fn pluginCall;
private:
loaded_library lib_;
};
#endif
插件.cpp
#define PLUGIN_MAIN
#include "test_plugin.hpp"
#include <stdio.h>
void pluginCall() {
printf("Plugin call\n");
testCall();
}
测试.cpp
#include "test_plugin.hpp"
int main(int argc, char** argv) {
const char* libraryName = "plugin.dylib";
loaded_plugin plugin(libraryName);
plugin.pluginCall();
}
// Public function, should be called from a plugin
void testCall() {
printf("Test call\n");
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.