簡體   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