简体   繁体   English

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

[英]Call main executable's functions from plugin compiled using Clang

I'm writing a program (macOS, clang++ compiler, only AppleSilicon at the moment) that I can extend later by providing custom plugins (dynamic library, loaded at runtime) which use main program's public interface.我正在编写一个程序(macOS,clang++ 编译器,目前只有 AppleSilicon),稍后我可以通过提供使用主程序公共接口的自定义插件(动态库,在运行时加载)来扩展它。

test.hpp - public interface: test.hpp - 公共接口:

#if defined(MAGIC_PLUGIN)
#  define MAGIC_EXPORT /* nothing */
#else
#  define MAGIC_EXPORT __attribute__((visibility("default")))
#endif

MAGIC_EXPORT
void testCall();

test.cpp - main programm: 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 - plugin's source: plugin.cpp - 插件的来源:

#define MAGIC_PLUGIN

#include <stdio.h>
#include "test.hpp"

__attribute__((visibility("default")))
extern "C" void pluginCall(void) {
    printf("Plugin call\n");
    testCall();
}

First, I compile main app:首先,我编译主应用程序:

clang++ -std=c++20 -fvisibility=hidden -target arm64-apple-macos12 test.cpp -o test

The nm --defined-only test shows these symbols: nm --defined-only test显示以下符号:

0000000100003ee4 T __Z8testCallv
0000000100000000 T __mh_execute_header
0000000100003dcc t _main

Mangled __Z8testCallv is what I need.损坏的 __Z8testCallv 是我需要的。 Everything looks good so far.到目前为止一切看起来都不错。 But then I try to compile the plugin as dynamic library...但后来我尝试将插件编译为动态库......

clang++ -std=c++20 -fvisibility=hidden -dynamiclib -g -current_version 0.1 -target arm64-apple-macos12 plugin.cpp -o plugin.dylib

and get this error:并得到这个错误:

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)

Well, it's kind of fair, I can understand this, because the dynamic library does not know that testCall is somewhere implemented.好吧,这有点公平,我可以理解这一点,因为动态库不知道testCall是在某个地方实现的。 So I want to say it that it does not have to worry about testCall 's existence.所以我想说它不必担心testCall的存在。

I tried to research how to do this, looked up man pages, read tons of stack overflow answers, and what I only found that works is adding these flags to linker:我试图研究如何做到这一点,查找手册页,阅读大量堆栈溢出答案,而我只发现有效的方法是将这些标志添加到 linker:

-Wl,-undefined,dynamic_lookup

It works, the library compiles and the app works as expected.它工作,库编译并且应用程序按预期工作。 But I don't really want to use dynamic_lookup because it will mark every undefined symbol in the library as resolved, which may lead to some bad consequences.但我真的不想使用dynamic_lookup因为它会将库中的每个未定义符号标记为已解决,这可能会导致一些不良后果。 I want to tell the linker only about existence of the main program's public symbols.我只想告诉 linker 关于主程序公共符号的存在。

What am I missing?我错过了什么? Is there any better solution than dynamic_lookup ?有没有比dynamic_lookup更好的解决方案?

Your best bet is to manually do the work that's done by the dynamic library loader.最好的办法是手动完成动态库加载器完成的工作。 That is: populating function pointers.即:填充 function 指针。 After all, the plugin->main binding is already done manually, so doing the same thing the other way around makes sense.毕竟,plugin->main 绑定已经手动完成,所以反过来做同样的事情是有意义的。

You can make this process essentially transparent by carefully crafting the header shared by the plugin and main application.您可以通过精心制作插件和主应用程序共享的 header 来使此过程基本透明。 The only tricky part is handling ODR for plugins that are composed of multiple source files.唯一棘手的部分是处理由多个源文件组成的插件的 ODR。

Since this is a C++ question, here's what it could look like with a RAII wrapper.由于这是一个 C++ 问题,因此这是使用 RAII 包装器的样子。 The ODR conundrum is handled via the PLUGIN_MAIN macro that should only be defined in one of a plugin's sources. ODR 难题是通过PLUGIN_MAIN宏处理的,该宏只应在插件源之一中定义。

test_plugin.hpp 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

plugin.cpp插件.cpp

#define PLUGIN_MAIN
#include "test_plugin.hpp"

#include <stdio.h>

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

test.cpp测试.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