[英]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.