【问题标题】:Call main executable's functions from plugin compiled using Clang从使用 Clang 编译的插件调用主要可执行文件的函数
【发布时间】:2021-11-19 13:56:02
【问题描述】:

我正在编写一个程序(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

Mangled __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的存在。

我试图研究如何做到这一点,查阅手册页,阅读大量堆栈溢出答案,我发现唯一可行的是将这些标志添加到链接器:

-Wl,-undefined,dynamic_lookup

它工作,库编译并且应用程序按预期工作。但我真的不想使用dynamic_lookup,因为它会将库中的每个未定义符号标记为已解决,这可能会导致一些不良后果。我只想告诉链接器主程序的公共符号的存在。

我错过了什么?有没有比dynamic_lookup更好的解决方案?

【问题讨论】:

  • 加载后,我会亲自手动交出指向插件的函数指针表。这将依赖图保持为 DAG,通常更易于使用。
  • 当你将插件编译为动态库时,你只是在编译plugin.cpp..那么test.cpp呢?
  • 可以解决,如果您将main 与标志-rdynamic 链接并在链接plugin.dylib 时允许未定义的符号(或者如果您将main 作为依赖项添加到plugin.dylib 中--导致另一个问题。)最好的选择是遵循@Frank的建议。

标签: c++ clang clang++


【解决方案1】:

最好的办法是手动完成由库加载器完成的工作。即:填充函数指针。毕竟 plugin->main 绑定已经手动完成了,所以反过来做同样的事情是有意义的。

您可以通过精心设计插件和主应用程序共享的标头来使此过程基本透明。唯一棘手的部分是处理由多个源文件组成的插件的 ODR。

由于这是一个 C++ 问题,以下是使用 RAII 包装器时的样子。 ODR 难题通过 PLUGIN_MAIN 宏处理,该宏只应在插件源之一中定义。

test_plugin.hpp

using pluginCall_fn = void(*)();
using testCall_fn = void(*)();

#if !defined(MAIN_APPLICATION)

#if defined(PLUGIN_MAIN)
#define EXPORTED_FROM_MAIN __attribute__((visibility("default")))
#else 
#define EXPORTED_FROM_MAIN __attribute__((visibility("default"))) extern
#endif

extern "C" {
  // Declare symbols provided by the plugin
  __attribute__((visibility("default"))) void pluginCall();
  // etc...

  // Declare/define pointers that will be populated by the main application
  EXPORTED_FROM_MAIN testCall_fn testCall;
  // etc...
}

#undef EXPORTED_FROM_MAIN
#else // In the main app.

#include <stdexcept>

// Declare "symbols" provided by the main application
void testCall();

// Utility class to load/unload a dynamic library.
// Probably belongs in its own header...
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(const loaded_library&) = delete;
  loaded_library& operator=(const loaded_library&) = delete;
  loaded_library(loaded_library&& rhs) : handle_(rhs.handle_) {
    rhs.handle_ = nullptr;
  }

  loaded_library& operator=(loaded_library&& rhs) {
    handle_ = rhs.handle_;
    rhs.handle_ = nullptr;
    return *this;
  }

  ~loaded_library() {
    if(handle_) {
      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_;
};

// Plugin interface.
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

plugin.cpp

#define PLUGIN_MAIN
#include "test_plugin.hpp"

#include <stdio.h>

void pluginCall() {
    printf("Plugin call\n");
    testCall();
}

test.cpp

#define MAIN_APPLICATION
#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");
}

您可能会发现这段代码有点脆弱,因为test_plugin.hpp 的几个不同部分需要保持同步。

这可以通过使用 X-Macros 来解决,但代价是混淆 IDE 并损害代码的易读性。在相关 API 变得不情愿地大之前,我不会走这条路。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2013-09-07
  • 1970-01-01
  • 1970-01-01
  • 2013-03-05
  • 1970-01-01
  • 1970-01-01
  • 2012-10-17
  • 2022-10-21
相关资源
最近更新 更多