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

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

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

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

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

Тип C++Тип GoПсевдоним PlugifyПоддержка Ref?
voidvoid (not used in Go)void
boolboolbool
charbytechar8
char16_trunechar16
int8_tint8int8
int16_tint16int16
int32_tint32int32
int64_tint64int64
uint8_tuint8uint8
uint16_tuint16uint16
uint32_tuint32uint32
uint64_tuint64uint64
uintptr_tuintptrptr64
uintptr_tuintptrptr32
floatfloat32float
doublefloat64double
void*unsafe.Pointerfunction
plg::stringstringstring
plg::anyanyany
plg::vector<bool>[]boolbool[]
plg::vector<char>[]bytechar8[]
plg::vector<char16_t>[]runechar16[]
plg::vector<int8_t>[]int8int8[]
plg::vector<int16_t>[]int16int16[]
plg::vector<int32_t>[]int32int32[]
plg::vector<int64_t>[]int64int64[]
plg::vector<uint8_t>[]uint8uint8[]
plg::vector<uint16_t>[]uint16uint16[]
plg::vector<uint32_t>[]uint32uint32[]
plg::vector<uint64_t>[]uint64uint64[]
plg::vector<uintptr_t>[]uintptrptr64[]
plg::vector<uintptr_t>[]uintptrptr32[]
plg::vector<float>[]float32float[]
plg::vector<double>[]float64double[]
plg::vector<plg::string>[]stringstring[]
plg::vector<plg::any>[]anyany[]
plg::vector<plg::vec2>[]Vector2vec2[]
plg::vector<plg::vec3>[]Vector3vec3[]
plg::vector<plg::vec4>[]Vector4vec4[]
plg::vector<plg::mat4x4>[]Matrix4x4mat4x4[]
plg::vec2Vector2vec2
plg::vec3Vector3vec3
plg::vec4Vector4vec4
plg::mat4x4Matrix4x4mat4x4

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

Экспорт функций в Go требует пометки функций для экспорта с помощью директивы //plugify:export. Эти функции затем могут быть вызваны другими плагинами. Языковой модуль Go от Plugify позаботится обо всем остальном.

Использование генератора для упрощения экспорта функций

Инструмент generator.go упрощает процесс экспорта функций Go путем:

  1. Сканирования папки плагина: Он сканирует корневую папку вашего плагина для поиска функций с атрибутом //plugify:export.
  2. Создания манифеста: Он создает файл манифеста .pplugin для экспорта сигнатур функций другим плагинам.
  3. Генерации файлов: Он генерирует файлы autoexport.go и autoexport.h с необходимым кодом для экспорта функций.

Этот инструмент устраняет необходимость в ручном маршалинге, облегчая разработчикам интеграцию их плагинов на Go в экосистему Plugify.

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

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

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

plugin.go
package main

import "C"

//plugify:export AddNumbers
func AddNumbers(a int32, b int32) int32 {
    /**
     * Складывает два целых числа.
     *
     * @param a: Первое целое число.
     * @param b: Второе целое число.
     * @return: Сумма a и b.
     */
    return a + b
}

func main() {} // Требуется для плагинов на Go

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

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

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

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

Запустите инструмент generator.go для генерации файлов autoexport.go и autoexport.h. Эти файлы будут обрабатывать маршалинг типов Plugify в типы Go.

autoexport.go
package main

// #include "autoexports.h"
import "C"
import (
    "github.com/untrustedmodders/go-plugify"
    "reflect"
    "unsafe"
)

//export __AddNumbers
func __AddNumbers(a int32, b int32) int32 {
    return int32(AddNumbers(a, b))
}

Этот сгенерированный код обрабатывает преобразование типов Plugify в типы Go и гарантирует, что функция может быть вызвана из других плагинов.

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

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

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

plugin.go
package main

import "C"
import "unsafe"

func ProcessData(data []float64, prefix string) []string {
    /**
     * Обрабатывает массив чисел double и возвращает отформатированную строку.
     *
     * @param data: Массив значений double.
     * @param length: Длина массива.
     * @param prefix: Префикс для добавления к каждому значению.
     * @return: Отформатированная строка.
     */
    result := ""
    for _, value := range data {
        result += fmt.Sprintf("%s%f", prefix, value)
    }

    return result
}

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

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

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

Запустите инструмент generator.go для генерации файлов autoexport.go и autoexport.h. Эти файлы будут обрабатывать маршалинг типов Plugify в типы Go.

autoexport.go
package main

// #include "autoexports.h"
import "C"
import (
    "github.com/untrustedmodders/go-plugify"
    "reflect"
    "unsafe"
)

//export __ProcessData
func __ProcessData(data *C.PlgVector, prefix *C.PlgString) C.PlgString {
    __result := ProcessData(plugify.GetVectorDataDouble((*plugify.PlgVector)(unsafe.Pointer(data))), plugify.GetStringData((*plugify.PlgString)(unsafe.Pointer(prefix))))
    __return := plugify.ConstructString(__result)
    return *(*C.String)(unsafe.Pointer(&__return))
}

Этот сгенерированный код обрабатывает преобразование типов Plugify в типы Go и гарантирует, что функция может быть вызвана из других плагинов.

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

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

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

plugin.go
package main

import "C"

func ExecuteWithCallback(value int32, inputStr string, callback PlugifyCallback) {
    /**
     * Выполняет функцию обратного вызова с предоставленными параметрами.
     *
     * @param value: Целочисленное значение.
     * @param inputStr: Входная строка.
     * @param callback: Функция обратного вызова для выполнения.
     */
    result := callback(value, inputStr)
    fmt.Printf("Callback result: %s\n", result)
}

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

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

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

Запустите инструмент generator.go для генерации файлов autoexport.go и autoexport.h. Эти файлы будут обрабатывать маршалинг типов Plugify в типы Go.

autoexport.go
package main

// #include "autoexports.h"
import "C"
import (
    "github.com/untrustedmodders/go-plugify"
    "reflect"
    "unsafe"
)

//export __ExecuteWithCallback
func __ExecuteWithCallback(value int32, inputStr *C.PlgString, callback unsafe.Pointer) {
    ExecuteWithCallback(value, plugify.GetStringData((*plugify.PlgString)(unsafe.Pointer(inputStr))), plugify.GetDelegateForFunctionPointer(callback, reflect.TypeOf(PlugifyCallback(nil))).(PlugifyCallback))
}

Этот сгенерированный код обрабатывает преобразование типов Plugify в типы Go и гарантирует, что функция может быть вызвана из других плагинов.

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

Вместо ручного написания JSON манифеста и кода экспорта, вы можете пометить свои функции комментариями //plugify:export ИмяФункции. Генератор проанализирует весь ваш Go проект и автоматически сгенерирует:

  1. Манифест плагина (файл .pplugin) со всеми экспортируемыми методами
  2. Файлы автоэкспорта (autoexport.go и autoexport.h) с кодом маршалинга

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

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

Настройка: Создание вашего генератора

Поскольку генератор является частью пакета go-plugify, но не может быть запущен напрямую как зависимость, вам нужно создать собственный файл generator.go, который вызывает функцию plugify.Generate().

Создайте файл generator.go в корне вашего проекта:

generator.go
//go:generate go run generator.go -package=main -output=.
//go:build ignore

package main

import (
    "flag"
    "fmt"
    "os"
    "strings"

    "github.com/untrustedmodders/go-plugify"
)

func main() {
    var (
        patterns     = flag.String("patterns", "./...", "Package patterns to analyze")
        output       = flag.String("output", "", "Output manifest file (default: <packagename>.pplugin)")
        name         = flag.String("name", "", "Plugin name (default: package name)")
        version      = flag.String("version", "1.0.0", "Plugin version")
        description  = flag.String("description", "", "Plugin description")
        author       = flag.String("author", "", "Plugin author")
        website      = flag.String("website", "", "Plugin website")
        license      = flag.String("license", "", "Plugin license")
        platforms    = flag.String("platforms", "", "Comma-separated list of platforms (e.g., windows,linux,darwin)")
        dependencies = flag.String("dependencies", "", "Comma-separated list of dependencies (e.g., plugin1,plugin2)")
        conflicts    = flag.String("conflicts", "", "Comma-separated list of conflicts (e.g., plugin3,plugin4)")
        entry        = flag.String("entry", "", "Plugin entry point (default: <packagename>)")
        target       = flag.String("package", "main", "Autoexports package (default: main)")
    )

    flag.Parse()

    // Log what we're doing
    fmt.Println("Starting plugin manifest generation...")
    fmt.Printf("Package patterns: %s\n", *patterns)
    if *output != "" {
        fmt.Printf("Output file: %s\n", *output)
    }
    if *name != "" {
        fmt.Printf("Plugin name: %s\n", *name)
    }
    fmt.Printf("Version: %s\n", *version)

    // Parse comma-separated strings
    platformList := parseCommaSeparated(*platforms)
    dependencyList := parseCommaSeparated(*dependencies)
    conflictList := parseCommaSeparated(*conflicts)

    // Call the generator with error handling
    err := plugify.Generate(*patterns, *output, *name, *version, *description, *author, *website, *license, platformList, dependencyList, conflictList, *entry, *target)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error generating plugin manifest: %v\n", err)
        os.Exit(1)
    }
}

// parseCommaSeparated parses a comma-separated string into a slice of trimmed strings
func parseCommaSeparated(input string) []string {
    if input == "" {
        return nil
    }

    parts := strings.Split(input, ",")
    result := make([]string, 0, len(parts))

    for _, part := range parts {
        part = strings.TrimSpace(part)
        if part != "" {
            result = append(result, part)
        }
    }

    return result
}

Использование комментария //plugify:export

Пометьте ваши экспортируемые функции директивой комментария //plugify:export ИмяФункции:

plugin.go
package main

import "C"

//plugify:export AddNumbers
func AddNumbers(a int32, b int32) int32 {
    /**
     * Складывает два целых числа.
     *
     * @param a: Первое целое число.
     * @param b: Второе целое число.
     * @return: Сумма a и b.
     */
    return a + b
}

//plugify:export ProcessData
func ProcessData(data []float64, prefix string) []string {
    /**
     * Обрабатывает массив чисел double и возвращает массив строк.
     *
     * @param data: Массив значений double.
     * @param prefix: Префикс для добавления к каждому значению.
     * @return: Массив отформатированных строк.
     */
    var result []string
    for _, value := range data {
        result = append(result, fmt.Sprintf("%s%f", prefix, value))
    }
    return result
}

func main() {} // Требуется для плагинов на Go

Запуск генератора

Запустите генератор с помощью команды go generate:

go generate

Или запустите его вручную:

go run generator.go -package=main -output=. -name=MyPlugin -version=1.0.0

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

  1. Во время генерации: Парсер Go анализирует весь ваш проект в поисках комментариев //plugify:export
  2. Анализ типов: Он извлекает сигнатуры функций и сопоставляет типы Go с типами Plugify (например, int32int32, []stringstring[])
  3. Генерация манифеста: Создается файл .pplugin со всеми экспортируемыми методами
  4. Генерация кода экспорта: Генерируются файлы autoexport.go и autoexport.h с правильным кодом маршалинга

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

  1. Используйте //plugify:export: Помечайте функции для автоматической генерации с помощью директивы комментария //plugify:export ИмяФункции.
  2. Создайте файл генератора: Настройте собственный файл generator.go, который вызывает plugify.Generate() для автоматической генерации манифеста и экспорта.
  3. Соблюдайте соглашения о типах: Придерживайтесь соглашений о типах Plugify для параметров и возвращаемых значений.
  4. Документируйте ваши функции: Используйте комментарии Go doc для четкого документирования назначения, параметров и возвращаемых значений экспортируемых функций.
  5. Используйте go generate: Запускайте go generate для автоматической перегенерации манифеста и файлов экспорта после внесения изменений.
  6. Тестируйте тщательно: Тестируйте ваши экспортированные функции, чтобы убедиться, что они работают так, как ожидается, при вызове из других плагинов.
  7. Поддерживайте синхронизацию сгенерированных файлов: Перегенерируйте файлы после любых изменений в сигнатурах функций для поддержания консистентности.

Заключение

Экспорт функций в плагинах на Go упрощен благодаря новому автоматическому генератору из go-plugify. Помечая свои функции комментариями //plugify:export и запуская go generate, вы можете автоматически сгенерировать как манифест плагина, так и код экспорта с правильным маршалингом типов. Это устраняет ручное написание JSON и уменьшает количество ошибок, облегчая создание надежных и совместимых плагинов. Генератор анализирует весь ваш Go проект с помощью парсера Go, извлекая сигнатуры функций и автоматически генерируя все необходимые файлы. Для более сложных случаев, таких как обработка обратных вызовов, генератор обрабатывает сложный код маршалинга за вас.