Экспорт функций

Руководство по экспорту функций из вашего плагина на C++ для использования другими языковыми модулями в Plugify.

В экосистеме Plugify плагины на C++ могут экспортировать функции, чтобы сделать их доступными для других плагинов. Это руководство объясняет, как экспортировать функции в C++, и предоставляет примеры, которые помогут вам беспрепятственно интегрировать ваши плагины.

Базовое сопоставление типов

В следующей таблице перечислены способы представления типов в C++ API:

Тип C++Псевдоним PlugifyПоддержка ссылок ?
voidvoid
boolbool
charchar8
char16_tchar16
int8_tint8
int16_tint16
int32_tint32
int64_tint64
uint8_tuint8
uint16_tuint16
uint32_tuint32
uint64_tuint64
uintptr_tptr64
uintptr_tptr32
floatfloat
doubledouble
void*function
plg::stringstring
plg::anyany
plg::vector<bool>bool[]
plg::vector<char>char8[]
plg::vector<char16_t>char16[]
plg::vector<int8_t>int8[]
plg::vector<int16_t>int16[]
plg::vector<int32_t>int32[]
plg::vector<int64_t>int64[]
plg::vector<uint8_t>uint8[]
plg::vector<uint16_t>uint16[]
plg::vector<uint32_t>uint32[]
plg::vector<uint64_t>uint64[]
plg::vector<uintptr_t>ptr64[]
plg::vector<uintptr_t>ptr32[]
plg::vector<float>float[]
plg::vector<double>double[]
plg::vector<plg::string>string[]
plg::vector<plg::any>any[]
plg::vector<plg::vec2>vec2[]
plg::vector<plg::vec3>vec3[]
plg::vector<plg::vec4>vec4[]
plg::vector<plg::mat4x4>mat4x4[]
plg::vec2vec2
plg::vec3vec3
plg::vec4vec4
plg::mat4x4mat4x4

Экспорт функций в C++

Чтобы экспортировать функцию в плагине на C++, вам необходимо убедиться, что функция видна другим плагинам. Обычно это делается путем пометки функции макросом PLUGIN_API, который обеспечивает экспорт функции при компиляции плагина в виде динамически подключаемой библиотеки (DLL).

Ключевые моменты

  • Статические функции: Экспортируемые функции, как правило, должны быть static, чтобы избежать необходимости в экземпляре объекта для вызова.
  • С-компоновка: Макрос PLUGIN_API не включает автоматически extern "C" для предотвращения искажения имен (name mangling). Мы должны убедиться, что функцию можно найти просто по имени, добавив extern "C".
  • Типы параметров и возвращаемых значений: Используйте нативные типы Plugify (например, plg::string, plg::vector) для бесшовной интеграции.

Генерация макроса PLUGIN_API

Макрос PLUGIN_API генерируется с помощью CMake. Вот как вы можете настроить его в вашем файле CMakeLists.txt:

include(GenerateExportHeader)
generate_export_header(${PROJECT_NAME}
    EXPORT_MACRO_NAME CPPLM_EXPORT
    EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/exports/module_export.h
)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR}/exports)

Это генерирует заголовочный файл (module_export.h), который определяет макрос PLUGIN_API. Включите этот заголовочный файл в исходные файлы вашего плагина, чтобы помечать функции для экспорта.

Базовый пример

Вот простой пример экспорта функции в плагине на C++:

Определение функции

plugin.cpp
#include <plugify/plugify.h>
#include "module_export.h" // Include the generated export header

extern "C" PLUGIN_API int32_t AddNumbers_Exported_Name(int32_t a, int32_t b) {
    return a + b;
}

Экспорт функции

Когда плагин будет загружен, функция AddNumbers будет экспортирована и сможет вызываться другими плагинами.

Пример манифеста плагина

Все экспортированные функции должны быть описаны в файле манифеста плагина в разделе methods. Вот пример манифеста для плагина, который экспортирует функцию AddNumbers:

plugin_name.pplugin
{
  "name": "ExamplePlugin",
  "version": "1.0.0",
  "methods": [
    {
      "name": "AddNumbers",
      "funcName": "AddNumbers_Exported_Name",
      "paramTypes": [
        {
          "type": "int32",
          "name": "a"
        },
        {
          "type": "int32",
          "name": "b"
        }
      ],
      "retType": {
        "type": "int32"
      }
    }
  ]
}

Продвинутый пример: Экспорт сложных функций

Вот пример экспорта функции со сложными типами параметров и возвращаемого значения:

Определение функции

plugin.cpp
#include <plugify/plugify.h>
#include "module_export.h"

extern "C" PLUGIN_API plg::vector<plg::string> ProcessData_Exported_Name(const plg::vector<double>& data, const plg::string& prefix) {
    plg::vector<plg::string> result;
    for (double value : data) {
        result.push_back(prefix + std::to_string(value));
    }
    return result;
}

Манифест плагина

plugin_name.pplugin
{
  "name": "ExamplePlugin",
  "version": "1.0.0",
  "methods": [
    {
      "name": "ProcessData",
      "funcName": "ProcessData_Exported_Name",
      "paramTypes": [
        {
          "type": "double[]",
          "name": "data"
        },
        {
          "type": "string",
          "name": "prefix"
        }
      ],
      "retType": {
        "type": "string[]"
      }
    }
  ]
}

Обработка обратных вызовов (callbacks)

Plugify позволяет экспортировать функции, которые принимают обратные вызовы в качестве параметров. Вот пример:

Определение функции

#include <plugify/plugify.h>
#include "module_export.h"

using CallbackFunction = plg::string(*)(int32_t, const plg::string&);

extern "C" PLUGIN_API void ExecuteWithCallback_Exported_Name(int32_t value, const plg::string& input, CallbackFunction callback) {
    plg::string result = callback(value, input);
    // Process the result
}

Манифест плагина

plugin_name.pplugin
{
  "name": "ExamplePlugin",
  "version": "1.0.0",
  "methods": [
    {
      "name": "ExecuteWithCallback",
      "funcName": "ExecuteWithCallback_Exported_Name",
      "paramTypes": [
        {
          "type": "int32",
          "name": "value"
        },
        {
          "type": "string",
          "name": "input"
        },
        {
          "type": "function",
          "name": "callback",
          "prototype": {
            "name": "ExampleCallback",
            "funcName": "ExampleCallback_Exported_Name",
            "paramTypes": [
              {
                "type": "int32",
                "name": "value"
              },
              {
                "type": "string",
                "name": "input"
              }
            ],
            "retType": {
              "type": "string"
            }
          }
        }
      ],
      "retType": {
        "type": "void"
      }
    }
  ]
}

Автоматизация генерации манифеста

Вместо ручного написания JSON-манифеста для каждой экспортируемой функции, вы можете использовать автоматизированные инструменты для извлечения сигнатур функций, типов, описаний параметров и даже определений перечислений непосредственно из вашего исходного кода C++.

Преимущества автоматической генерации

  1. Не требуется ручное написание JSON: Сигнатуры функций автоматически извлекаются из вашего кода
  2. Полная информация о типах: Структуры перечислений и typedef функций включаются автоматически
  3. Интеграция документации: Комментарии Doxygen парсятся и включаются в манифест
  4. Меньше ошибок: Устраняются опечатки и несоответствия типов между кодом и манифестом
  5. Простое обслуживание: Изменения в сигнатурах функций автоматически отражаются

Настройка: Добавление генерации документации в CMake

Добавьте следующее в ваш CMakeLists.txt для генерации документации и JSON-манифеста:

# Найти clang-doc (обычно поставляется с установкой LLVM/Clang)
find_program(CLANG_DOC clang-doc)

if(CLANG_DOC)
    # Генерация YAML-документации из ваших экспортируемых функций
    add_custom_target(docs
        COMMAND ${CLANG_DOC}
            --executor=all-TUs
            -p ${CMAKE_CURRENT_BINARY_DIR}
            --output=${CMAKE_CURRENT_SOURCE_DIR}/docs
            --extra-arg=-Wno-error
            --format=yaml
            ${CMAKE_SOURCE_DIR}/src/export/*.cpp  # Путь к вашим экспортируемым функциям
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
        COMMENT "Generating documentation with clang-doc"
    )

    # Опционально: Автоматическое преобразование YAML в JSON после генерации документации
    find_package(Python3 COMPONENTS Interpreter)
    if(Python3_FOUND)
        add_custom_command(TARGET docs POST_BUILD
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_SOURCE_DIR}/parser/parser.py
                    ${CMAKE_SOURCE_DIR}/docs/index.yaml
                    ${CMAKE_SOURCE_DIR}/exported_methods.json
            COMMENT "Parsing documentation to JSON"
        )
    endif()
endif()

Использование парсера

Установка парсера

Скачайте parser.py и поместите его в ваш проект (например, в директорию tools/).

Установите зависимости:

pip install pyyaml

Документируйте ваши функции

Добавьте комментарии Doxygen к вашим экспортируемым функциям:

/**
 * @brief Создает консольную команду как административную команду.
 * Если команда не существует, она создается. Когда эта команда вызывается,
 * права доступа игрока автоматически проверяются перед разрешением продолжения.
 *
 * @param name Имя консольной команды.
 * @param adminFlags Флаги администратора, указывающие, какой уровень администратора может использовать эту команду.
 * @param description Краткое описание того, что делает команда.
 * @param flags Флаги команды, определяющие поведение команды.
 * @param callback Функция обратного вызова, которая вызывается при выполнении команды.
 * @param mode Был ли хук в режиме post (после обработки) или в режиме pre (до обработки).
 * @return Логическое значение, указывающее, была ли команда успешно добавлена.
 */
extern "C" PLUGIN_API bool AddAdminCommand(
    const plg::string& name,
    int64_t adminFlags,
    const plg::string& description,
    ConVarFlag flags,
    CommandCallback callback,
    HookMode mode
);

Документируйте перечисления и typedef

Документируйте ваши перечисления и typedef функций:

/**
 * @brief Перечисление, представляющее тип обратного вызова.
 */
enum class HookMode : uint8_t {
    /** Обратный вызов будет выполнен перед оригинальной функцией */
    Pre = 0,
    /** Обратный вызов будет выполнен после оригинальной функции */
    Post = 1
};

/**
 * @brief Обрабатывает выполнение команды, вызванной вызывающей стороной.
 * Эта функция обрабатывает команду, интерпретирует ее контекст и обрабатывает любые предоставленные аргументы.
 */
using CommandCallback = ResultType (*)(int32_t caller, CommandCallingContext context, const plg::vector<plg::string>& arguments);

Генерация документации

Соберите цель документации:

cmake --build . --target docs

Это генерирует docs/index.yaml со всей информацией о ваших функциях.

Преобразование в JSON

Запустите парсер для преобразования YAML в формат манифеста:

python3 parser/parser.py docs/index.yaml exported_methods.json

Или с фильтрацией:

# Только функции из файлов, начинающихся с "commands"
python3 parser/parser.py docs/index.yaml exported_methods.json --file-prefix "commands"

# Только функции с "Admin" в имени
python3 parser/parser.py docs/index.yaml exported_methods.json --name-filter "Admin"

Использование в манифесте

Скопируйте сгенерированный массив methods из exported_methods.json в манифест вашего плагина:

{
  "name": "ExamplePlugin",
  "version": "1.0.0",
  "methods": [
    // Вставьте содержимое из exported_methods.json сюда
  ]
}

Продвинутое: Структуры перечислений

Парсер автоматически включает полные определения перечислений, когда параметр использует тип перечисления:

Сгенерированный вывод:

{
  "name": "mode",
  "type": "uint8",
  "description": "Был ли хук в режиме post или в режиме pre.",
  "enum": {
    "name": "HookMode",
    "description": "Перечисление, представляющее тип обратного вызова.",
    "values": [
      {
        "name": "Pre",
        "value": 0,
        "description": "Обратный вызов будет выполнен перед оригинальной функцией"
      },
      {
        "name": "Post",
        "value": 1,
        "description": "Обратный вызов будет выполнен после оригинальной функции"
      }
    ]
  }
}

Продвинутое: Typedef функций

Typedef указателей на функции автоматически парсятся в полные прототипы:

Сгенерированный вывод:

{
  "name": "callback",
  "type": "function",
  "description": "Функция обратного вызова, которая вызывается при выполнении команды.",
  "prototype": {
    "name": "CommandCallback",
    "funcName": "CommandCallback",
    "description": "Обрабатывает выполнение команды, вызванной вызывающей стороной.",
    "paramTypes": [
      {
        "name": "param1",
        "type": "int32"
      },
      {
        "name": "param2",
        "type": "int32",
        "enum": {
          "name": "CommandCallingContext",
          "values": [...]
        }
      },
      {
        "name": "param3",
        "type": "string[]"
      }
    ],
    "retType": {
      "type": "int32",
      "enum": {
        "name": "ResultType",
        "values": [...]
      }
    }
  }
}

Рекомендации

  1. Используйте PLUGIN_API: Всегда используйте макрос PLUGIN_API для пометки функций для экспорта.
  2. Следуйте соглашениям о типах: Придерживайтесь соглашений о типах Plugify для параметров и возвращаемых значений.
  3. Документируйте ваши функции: Четко документируйте назначение, параметры и возвращаемые значения экспортируемых функций.
  4. Тщательно тестируйте: Тестируйте ваши экспортированные функции, чтобы убедиться, что они работают как ожидалось при вызове из других плагинов.
  5. Обновляйте манифест: Всегда описывайте экспортированные функции в манифесте плагина в разделе methods.

Заключение

Экспорт функций в плагинах на C++ прост, если вы следуете соглашениям и лучшим практикам Plugify. Используя макрос PLUGIN_API, придерживаясь соглашений о типах и описывая функции в манифесте плагина, вы можете создавать надежные и совместимые плагины. Для более сложных сценариев использования, таких как обработка обратных вызовов или возврат объектов C++, используйте методы, изложенные в этом руководстве.