简体中文 English

插件概述

插件是一种扩展现有程序的机制。作为一个例子,一个计算器程序可以通过加载额外的插件来扩展它所支持的操作的列表。插件允许第三方开发者在无需访问计算器程序源代码的情况下来扩展该程序。

创建插件

在C中,插件通过下列步骤被创建:

  • 应用程序为插件定义接口。这是一个应用程序期待插件来实现的函数的列表。
  • 插件实现了接口界面并且代码被编译为共享对象。
  • 应用程序发现插件,动态加载插件,解析插件中的符号/函数并调用接口中定义的方法。

让我们以一个可以使用插件扩展的计算器程序作为例子。

  • 计算器程序的接口定义为:
    1. // operatorinterface.h
    2. double operation(double arg1, double arg2);
    3. typedef double (*operation_pointer)(double, double);
  • 额外的插件将接口实现为:
    1. // addition.c
    2. #include "operatorinterface.h"
    3.  
    4. double operation(double arg1, double arg2)
    5. {
    6.     return arg1 + arg2;
    7. }

    该插件使用 ‘cc -shared -fPIC addition.c -o addition.so’ 编译成共享对象。
  • 应用程序在运行时通过搜索预配置的路径中的共享对象来发现插件。
    1. // application.c
    2. #include <dlfcn.h>
    3. #include "operatorinterface.h"
    4.  
    5. int main()
    6. {
    7.     const char *plugin_path = "/path/to/plugin.so";
    8.     void *plugin = dlopen(plugin_path, RTLD_LAZY); // plugin_path 是一个指向任意一个支持 operatorinterface.h 的插件路径的指针
    9.     operation_pointer ptr = (operation_pointer) dlsym(plugin, "operation"); // 获得 'operation' 函数的指针
    10.     ptr(10, 20); // 调用插件中的函数
    11. }

导出 C 中的符号

一个共享对象可能会包含很多函数,但是它们中只有一部分想暴露给外部程序。编译器提供了标记函数可见性的机制。在gcc中,这是通过添加前缀 attribute((visibility(“default”))) 实现的。而在MSVC中 __declspec(dllexport) 被添加到函数或类前面。

额外的插件应该这样写:

  1. // addition.c
  2. #include "operatorinterface.h"
  3.  
  4. __attribute__((visibility("default"))) double operation(double arg1, double arg2)
  5. {
  6.     return arg1 + arg2;
  7. }

导出 C++ 中的符号

在C中,函数名到符号的映射是标准化的。但是,在C ++,不存在这样的标准,而且每个编译器会为相同的函数名生成不同的符号名。为了支持C++中很多名字相同但签名不同的函数的重载, 名字改编(name mangling) 是必须的。

因此,在C++中,

  • 比如上面的例子中如果插件编译使用C++编译器,使用dlsym的符号解析“操作”将会失败。程序员需要用改编以后的名称来解析。
  • 改编后名称的解析是编译器相关的。如果插件是使用不同的编译器编译的话,即使使用改编后的名称解析也将无法工作。

解决上述问题的技巧是在C++中定义这个接口,但用一个单独的C函数返回一个指向该接口的指针。这个C函数使用 C-linkage 编译(通过使用_extern“C”_)。例如,上面的插件可以用C++实现如下:

  1. // operatorinterface.h
  2. class OperatorInterface {
  3. public:
  4.     virtual double operation(double arg1, double arg2) = 0;
  5. };
  6. extern "C" __attribute__((visibility("default"))) OperatorInterface *getInterface();
  7. typedef OperatorInterface *(*GetInterfacePointer)();

  1. // addition.cpp
  2. #include "operatorinterface.h"
  3.  
  4. class AdditionOperator : public OperatorInterface
  5. {
  6. public:
  7.     double operation(double arg1, double arg2) { return arg1 + arg2; }
  8. };
  9.  
  10. extern "C" OperatorInterface *getInterface() { return new AdditionOperator; }

  1. // application.cpp
  2. #include "operatorinterface.h"
  3. #include <dlfcn.h>
  4.  
  5. int main() {
  6.     void *plugin = dlopen(plugin_path, RTLD_LAZY); // plugin_path 是一个指向任意一个支持 operatorinterface.h 的插件路径的指针
  7.     GetInterfacePointer getInterfacePointer = (GetInterfacePointer) dlsym(plugin, "getInterface"); // 解析 'C' 函数
  8.     OperatorInterface *interface = getInterfacePointer(); // 使用 'C' 函数获取C++接口
  9.     interface->operation(10, 20); // 调用带参数的插件接口中的函数
  10. }

Categories: