繁体   English   中英

从使用 Clang 编译的插件调用主要可执行文件的功能

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM