Export Functions

Guide to export functions from your plugin to be used by other language modules within Plugify.

In the Plugify ecosystem, Go plugins can export functions to make them accessible to other plugins. This guide explains how to define and export functions in Go and provides examples to help you integrate your plugins seamlessly.

Basic Type Mapping

The following table lists how types are exposed to the JavaScript API:

C++ TypeGo TypePlugify AliasRef Support ?
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

Exporting Functions in Go

Exporting functions in Go requires marking the functions for export using the //plugify:export directive. These functions can then be called by other plugins. Plugify's Go Language Module handles the rest.

Using Generator to Simplify Function Export

The generator.go tool simplifies the process of exporting Go functions by:

  1. Scanning the Plugin Folder: It scans your plugin's root folder to find functions with //plugify:export attribute.
  2. Creating the Manifest: It creates the .pplugin manifest file to export the function signatures to other plugins.
  3. Generating Files: It generates autoexport.go and autoexport.h files with the necessary code to export functions.

This tool eliminates the need for manual marshalling, making it easier for developers to integrate their Go plugins into the Plugify ecosystem.

Basic Example

Here’s a simple example of exporting a function in a Go plugin:

Function Definition

plugin.go
package main

import "C"

//plugify:export AddNumbers
func AddNumbers(a int32, b int32) int32 {
    /**
     * Adds two integers.
     *
     * @param a: First integer.
     * @param b: Second integer.
     * @return: Sum of a and b.
     */
    return a + b
}

func main() {} // Required for Go plugins

Plugin Manifest

To export the function, describe it in the plugin manifest under the methods section:

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"
      }
    }
  ]
}

Generated Code

Run the generator.go tool to generate the autoexport.go and autoexport.h files. These files will handle the marshalling of Plugify types to Go types.

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))
}

This generated code handles the conversion of Plugify types to Go types and ensures the function can be called from other plugins.

Advanced Example: Exporting Complex Functions

Here’s an example of exporting a function with complex parameter and return types:

Function Definition

plugin.go
package main

import "C"
import "unsafe"

//plugify:export ProcessData
func ProcessData(data []float64, prefix string) []string {
    /**
     * Processes an array of doubles and returns a formatted string.
     *
     * @param data: Array of double values.
     * @param length: Length of the array.
     * @param prefix: Prefix to add to each value.
     * @return: Formatted string.
     */
    result := ""
    for _, value := range data {
        result += fmt.Sprintf("%s%f", prefix, value)
    }

    return result
}

Plugin Manifest

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"
      }
    }
  ]
}

Generated Code

Run the generator.go tool to generate the autoexport.go and autoexport.h files. These files will handle the marshalling of Plugify types to Go types.

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))
}

This generated code handles the conversion of Plugify types to Go types and ensures the function can be called from other plugins.

Exporting Functions with References

Plugify supports reference parameters (also known as "out" or "inout" parameters) that allow functions to modify values and return them to the caller. In Go, reference parameters are implemented using pointers.

Function Definition with Reference Parameters

plugin.go
package main

import "C"

//plugify:export IncrementValue
func IncrementValue(value *int32) {
    /**
     * Increments an integer value by reference.
     *
     * @param value: Pointer to the value to increment.
     */
    *value += 1
}

//plugify:export Calculate
func Calculate(a int32, b int32, sum *int32, product *int32) {
    /**
     * Calculates sum and product of two numbers, returning both via pointers.
     *
     * @param a: First number.
     * @param b: Second number.
     * @param sum: Pointer to store the sum.
     * @param product: Pointer to store the product.
     */
    *sum = a + b
    *product = a * b
}

func main() {} // Required for Go plugins

Plugin Manifest with Reference Parameters

In the manifest, mark parameters that are passed by reference using "ref": true:

plugin_name.pplugin
{
  "name": "ExampleGoPlugin",
  "version": "1.0.0",
  "methods": [
    {
      "name": "IncrementValue",
      "funcName": "__IncrementValue",
      "paramTypes": [
        {
          "type": "int32",
          "name": "value",
          "ref": true
        }
      ],
      "retType": {
        "type": "void"
      }
    },
    {
      "name": "Calculate",
      "funcName": "__Calculate",
      "paramTypes": [
        {
          "type": "int32",
          "name": "a"
        },
        {
          "type": "int32",
          "name": "b"
        },
        {
          "type": "int32",
          "name": "sum",
          "ref": true
        },
        {
          "type": "int32",
          "name": "product",
          "ref": true
        }
      ],
      "retType": {
        "type": "void"
      }
    }
  ]
}

Generated Code for Reference Parameters

The generator will create wrapper functions that handle pointer marshalling:

autoexport.go
package main

// #include "autoexports.h"
import "C"
import (
    "unsafe"
)

//export __IncrementValue
func __IncrementValue(value *int32) {
    IncrementValue(value)
}

//export __Calculate
func __Calculate(a int32, b int32, sum *int32, product *int32) {
    Calculate(a, b, sum, product)
}

Reference Parameter Support

Reference parameters work with most Plugify types as shown in the "Ref Support" column of the type mapping table. The following types do not support references:

  • void (cannot be passed by reference)
  • function (callback/delegate types)

All other types including primitives, strings, slices, and structs support reference parameters via pointers.

Handling Callbacks

Plugify allows you to export functions that accept callbacks as parameters. Here’s an example:

Function Definition

plugin.go
package main

import "C"

//plugify:export ExecuteWithCallback
func ExecuteWithCallback(value int32, inputStr string, callback PlugifyCallback) {
    /**
     * Executes a callback function with the provided parameters.
     *
     * @param value: Integer value.
     * @param inputStr: Input string.
     * @param callback: Callback function to execute.
     */
    result := callback(value, inputStr)
    fmt.Printf("Callback result: %s\n", result)
}

Plugin Manifest

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"
      }
    }
  ]
}

Automating Manifest and Export Generation

Instead of manually writing the manifest JSON and export code, you can mark your functions with //plugify:export FuncName comments. The generator will parse your entire Go project and automatically generate:

  1. Plugin Manifest (.pplugin file) with all exported methods
  2. Autoexport Files (autoexport.go and autoexport.h) with marshalling code

Benefits of Automated Generation

  1. No Manual JSON Writing: Function signatures are automatically extracted from your code
  2. Complete Type Information: Parameter types and return types are automatically mapped
  3. Automatic Marshalling: Export wrappers with proper type conversion are generated
  4. Reduced Errors: Eliminates typos and type mismatches between code and manifest
  5. Easy Maintenance: Changes to function signatures are automatically reflected

Setup: Creating Your Generator

Since the generator is part of the go-plugify package but cannot be run directly as a dependency, you need to create your own generator.go file that calls the plugify.Generate() function.

Create a generator.go file in your project root:

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
}

Using the //plugify:export Comment

Mark your exported functions with the //plugify:export FuncName comment directive:

plugin.go
package main

import "C"

//plugify:export AddNumbers
func AddNumbers(a int32, b int32) int32 {
    /**
     * Adds two integers.
     *
     * @param a: First integer.
     * @param b: Second integer.
     * @return: Sum of a and b.
     */
    return a + b
}

//plugify:export ProcessData
func ProcessData(data []float64, prefix string) []string {
    /**
     * Processes an array of doubles and returns an array of strings.
     *
     * @param data: Array of double values.
     * @param prefix: Prefix to add to each value.
     * @return: Array of formatted strings.
     */
    var result []string
    for _, value := range data {
        result = append(result, fmt.Sprintf("%s%f", prefix, value))
    }
    return result
}

func main() {} // Required for Go plugins

Running the Generator

Run the generator using the go generate command:

go generate

Or run it manually:

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

How It Works

  1. During Generation: The Go parser analyzes your entire project looking for //plugify:export comments
  2. Type Analysis: It extracts function signatures and maps Go types to Plugify types (e.g., int32int32, []stringstring[])
  3. Manifest Generation: A .pplugin file is created with all exported methods
  4. Export Code Generation: autoexport.go and autoexport.h files are generated with proper marshalling code

Best Practices

  1. Use //plugify:export: Mark functions for automated generation with the //plugify:export FuncName comment directive.
  2. Create a Generator File: Set up your own generator.go file that calls plugify.Generate() for automated manifest and export generation.
  3. Follow Type Conventions: Adhere to Plugify's type conventions for parameters and return values.
  4. Document Your Functions: Use Go doc comments to clearly document the purpose, parameters, and return values of exported functions.
  5. Use go generate: Run go generate to automatically regenerate manifest and export files after making changes.
  6. Test Thoroughly: Test your exported functions to ensure they work as expected when called by other plugins.
  7. Keep Generated Files in Sync: Regenerate after any changes to function signatures to maintain consistency.

Conclusion

Exporting functions in Go plugins is streamlined with the new automated generator from go-plugify. By marking your functions with //plugify:export comments and running go generate, you can automatically generate both the plugin manifest and export code with proper type marshalling. This eliminates manual JSON writing and reduces errors, making it easier to create robust and interoperable plugins. The generator analyzes your entire Go project using the Go parser, extracting function signatures and generating all necessary files automatically. For more advanced use cases, such as handling callbacks, the generator handles the complex marshalling code for you.