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

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

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

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

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

Тип C++Тип C#Псевдоним PlugifyПоддержка Ref?
voidvoidvoid
boolBool8bool
charChar8char8
char16_tChar16char16
int8_tsbyteint8
int16_tshortint16
int32_tintint32
int64_tlongint64
uint8_tbyteuint8
uint16_tushortuint16
uint32_tuintuint32
uint64_tulonguint64
uintptr_tnintptr64
uintptr_tnintptr32
floatfloatfloat
doubledoubledouble
void*Delegatefunction
plg::stringstringstring
plg::anyobjectany
plg::vector<bool>Bool8[]bool[]
plg::vector<char>Char8[]char8[]
plg::vector<char16_t>Char16[]char16[]
plg::vector<int8_t>sbyte[]int8[]
plg::vector<int16_t>short[]int16[]
plg::vector<int32_t>int[]int32[]
plg::vector<int64_t>long[]int64[]
plg::vector<uint8_t>byte[]uint8[]
plg::vector<uint16_t>ushort[]uint16[]
plg::vector<uint32_t>uint[]uint32[]
plg::vector<uint64_t>ulong[]uint64[]
plg::vector<uintptr_t>nint[]ptr64[]
plg::vector<uintptr_t>nint[]ptr32[]
plg::vector<float>float[]float[]
plg::vector<double>double[]double[]
plg::vector<plg::string>string[]string[]
plg::vector<plg::any>object[]any[]
plg::vector<plg::vec2>Vector2[]vec2[]
plg::vector<plg::vec3>Vector3[]vec3[]
plg::vector<plg::vec4>Vector4[]vec4[]
plg::vector<plg::mat4x4>Matrix4x4[]mat4x4[]
plg::vec2Vector2vec2
plg::vec3Vector3vec3
plg::vec4Vector4vec4
plg::mat4x4Matrix4x4mat4x4

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

Экспорт функций в C# прост, поскольку C# - это статически типизированный язык. Вам нужно определить функцию и указать ее в манифесте плагина. Языковой модуль C# в Plugify позаботится обо всем остальном.

Простой пример

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

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

plugin.cs
namespace ExampleCSharpPlugin
{
    public static class ExportedFunctions
    {
        /// <summary>
        /// Складывает два целых числа.
        /// </summary>
        /// <param name="a">Первое целое число.</param>
        /// <param name="b">Второе целое число.</param>
        /// <returns>Сумма a и b.</returns>
        public static int AddNumbers_Exported(int a, int b)
        {
            return a + b;
        }
    }
}

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

Чтобы экспортировать функцию, опишите ее в манифесте плагина в разделе methods:

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

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

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

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

plugin.cs
namespace ExampleCSharpPlugin
{
    public static class ExportedFunctions
    {
        /// <summary>
        /// Обрабатывает массив чисел double и возвращает массив строк.
        /// </summary>
        /// <param name="data">Массив значений double.</param>
        /// <param name="prefix">Префикс для добавления к каждому значению.</param>
        /// <returns>Массив отформатированных строк.</returns>
        public static string[] ProcessData_Exported(double[] data, string prefix)
        {
            return data.Select(value => $"{prefix}{value}").ToArray();
        }
    }
}

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

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

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

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

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

plugin.cs
namespace ExampleCSharpPlugin
{
    public delegate string ExampleCallback(int a, string b);

    public static class ExportedFunctions
    {
        /// <summary>
        /// Выполняет функцию обратного вызова с предоставленными параметрами.
        /// </summary>
        /// <param name="value">Целочисленное значение.</param>
        /// <param name="inputStr">Входная строка.</param>
        /// <param name="callback">Функция обратного вызова для выполнения.</param>
        public static void ExecuteWithCallback_Exported(int value, string inputStr, ExampleCallback callback)
        {
            string result = callback(value, inputStr);
            Console.WriteLine($"Результат обратного вызова: {result}");
        }
    }
}

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

plugin_name.pplugin
{
  "name": "ExampleCSharpPlugin",
  "version": "1.0.0",
  "methods": [
    {
      "name": "ExecuteWithCallback",
      "funcName": "ExampleCSharpPlugin.ExportedFunctions.ExecuteWithCallback_Exported",
      "paramTypes": [
        {
          "type": "int32",
          "name": "value"
        },
        {
          "type": "string",
          "name": "inputStr"
        },
        {
          "type": "function",
          "name": "callback",
          "prototype": {
            "name": "ExampleCallback",
            "funcName": "ExampleCallback_Exported",
            "paramTypes": [
              {
                "type": "int32",
                "name": "value"
              },
              {
                "type": "string",
                "name": "inputStr"
              }
            ],
            "retType": {
              "type": "string"
            }
          }
        }
      ],
      "retType": {
        "type": "void"
      }
    }
  ]
}

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

Вместо ручного написания JSON манифеста для каждой экспортируемой функции, вы можете использовать NuGet-пакет Plugify.Generator для автоматической генерации записей манифеста для функций, помеченных атрибутом [NativeExport].

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

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

Настройка: Установка Plugify.Generator

Добавьте NuGet-пакет Plugify.Generator в ваш проект плагина на C#:

dotnet add package Plugify.Generator

Или добавьте его напрямую в ваш файл .csproj:

<ItemGroup>
  <PackageReference Include="Plugify" Version="*" />
  <PackageReference Include="Plugify.Generator" Version="*" />
</ItemGroup>

Пакет Plugify предоставляет атрибут [NativeExport], а Plugify.Generator - это генератор источников Roslyn, который обрабатывает эти атрибуты во время компиляции.

Использование атрибута NativeExport

Пометьте ваши экспортируемые функции атрибутом [NativeExport("ИмяФункции")]:

plugin.cs
using Plugify;

namespace ExampleCSharpPlugin
{
    public static class ExportedFunctions
    {
        /// <summary>
        /// Складывает два целых числа.
        /// </summary>
        /// <param name="a">Первое целое число.</param>
        /// <param name="b">Второе целое число.</param>
        /// <returns>Сумма a и b.</returns>
        [NativeExport("AddNumbers")]
        public static int AddNumbers_Exported(int a, int b)
        {
            return a + b;
        }

        /// <summary>
        /// Обрабатывает массив чисел double и возвращает массив строк.
        /// </summary>
        /// <param name="data">Массив значений double.</param>
        /// <param name="prefix">Префикс для добавления к каждому значению.</param>
        /// <returns>Массив отформатированных строк.</returns>
        [NativeExport("ProcessData")]
        public static string[] ProcessData_Exported(double[] data, string prefix)
        {
            return data.Select(value => $"{prefix}{value}").ToArray();
        }
    }
}

Как это работает

  1. Во время компиляции: Генератор источников Roslyn сканирует ваш код на наличие методов, помеченных [NativeExport]
  2. Сопоставление типов: Он автоматически сопоставляет типы C# с типами Plugify (например, intint32, string[]string[])
  3. Генерация манифеста: JSON-файл, содержащий массив methods, генерируется в выходных данных сборки
  4. Интеграция: Скопируйте сгенерированные записи манифеста в ваш файл .pplugin

Сгенерированный результат

При сборке вашего проекта генератор создает файл (например, ExampleCSharpPlugin.methods.json), содержащий:

[
  {
    "name": "AddNumbers",
    "funcName": "ExampleCSharpPlugin.ExportedFunctions.AddNumbers_Exported",
    "paramTypes": [
      {
        "type": "int32",
        "name": "a"
      },
      {
        "type": "int32",
        "name": "b"
      }
    ],
    "retType": {
      "type": "int32"
    }
  },
  {
    "name": "ProcessData",
    "funcName": "ExampleCSharpPlugin.ExportedFunctions.ProcessData_Exported",
    "paramTypes": [
      {
        "type": "double[]",
        "name": "data"
      },
      {
        "type": "string",
        "name": "prefix"
      }
    ],
    "retType": {
      "type": "string[]"
    }
  }
]

Просто скопируйте этот массив в раздел methods вашего манифеста плагина.

Продвинутое использование: Работа с делегатами

Генератор также поддерживает типы делегатов для параметров обратного вызова:

plugin.cs
using Plugify;

namespace ExampleCSharpPlugin
{
    public delegate string ExampleCallback(int a, string b);

    public static class ExportedFunctions
    {
        /// <summary>
        /// Выполняет функцию обратного вызова с предоставленными параметрами.
        /// </summary>
        /// <param name="value">Целочисленное значение.</param>
        /// <param name="inputStr">Входная строка.</param>
        /// <param name="callback">Функция обратного вызова для выполнения.</param>
        [NativeExport("ExecuteWithCallback")]
        public static void ExecuteWithCallback_Exported(int value, string inputStr, ExampleCallback callback)
        {
            string result = callback(value, inputStr);
            Console.WriteLine($"Результат обратного вызова: {result}");
        }
    }
}

Генератор автоматически создаст полную структуру прототипа функции:

{
  "name": "ExecuteWithCallback",
  "funcName": "ExampleCSharpPlugin.ExportedFunctions.ExecuteWithCallback_Exported",
  "paramTypes": [
    {
      "type": "int32",
      "name": "value"
    },
    {
      "type": "string",
      "name": "inputStr"
    },
    {
      "type": "function",
      "name": "callback",
      "prototype": {
        "name": "ExampleCallback",
        "funcName": "ExampleCallback_Exported",
        "paramTypes": [
          {
            "type": "int32",
            "name": "a"
          },
          {
            "type": "string",
            "name": "b"
          }
        ],
        "retType": {
          "type": "string"
        }
      }
    }
  ],
  "retType": {
    "type": "void"
  }
}

Лучшие практики

  1. Определяйте функции четко: Убедитесь, что ваши функции хорошо документированы и просты для понимания.
  2. Соблюдайте соглашения о типах: Придерживайтесь соглашений о типах Plugify для параметров и возвращаемых значений.
  3. Используйте XML-документацию: Добавляйте комментарии XML-документации (/// <summary>, /// <param>, /// <returns>) для предоставления описаний в сгенерированном манифесте.
  4. Используйте атрибут NativeExport: По возможности используйте атрибут [NativeExport] с Plugify.Generator для автоматизации генерации манифеста.
  5. Тестируйте тщательно: Тестируйте ваши экспортированные функции, чтобы убедиться, что они работают так, как ожидается, при вызове из других плагинов.
  6. Поддерживайте манифесты в актуальном состоянии: Если используете автоматическую генерацию, перегенерируйте манифест после внесения изменений в сигнатуры функций.

Заключение

Экспорт функций в плагинах на C# прост и понятен. Определяя свои функции и описывая их в манифесте плагина, вы можете создавать надежные и совместимые плагины. С новым генератором источников Roslyn - Plugify.Generator вы можете автоматизировать генерацию манифеста, просто помечая функции атрибутом [NativeExport], что снижает ручные усилия и поддерживает ваш манифест синхронизированным с кодом. Для более сложных случаев, таких как обработка обратных вызовов, используйте методы, описанные в этом руководстве.