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 Go API:

C++ TypeGo TypePlugify AliasRef Support ?
voidvoid (not used in Go)void❌
boolboolbool✅
charint8char8✅
char16_tuint16char16✅
int8_tint8int8✅
int16_tint16int16✅
int32_tint32int32✅
int64_tint64int64✅
uint8_tuint8uint8✅
uint16_tuint16uint16✅
uint32_tuint32uint32✅
uint64_tuint64uint64✅
uintptr_tuintptrptr64✅
uintptr_tuintptrptr32✅
floatfloat32float✅
doublefloat64double✅
void*named func typefunction❌
plg::stringstringstring✅
plg::anyanyany✅
plg::vector<bool>[]boolbool[]✅
plg::vector<char>[]int8char8[]✅
plg::vector<char16_t>[]uint16char16[]✅
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>[]plugify.Vector2vec2[]✅
plg::vector<plg::vec3>[]plugify.Vector3vec3[]✅
plg::vector<plg::vec4>[]plugify.Vector4vec4[]✅
plg::vector<plg::mat4x4>[]plugify.Matrix4x4mat4x4[]✅
plg::vec2plugify.Vector2vec2✅
plg::vec3plugify.Vector3vec3✅
plg::vec4plugify.Vector4vec4✅
plg::mat4x4plugify.Matrix4x4mat4x4✅

Exporting Functions in Go

To export a function, mark it with a //plugify:export Name comment. The function must be exported in Go terms (start with a capital letter). The go-plugify generator then writes a cgo wrapper __Name for it and describes the function in the plugin manifest, where funcName is the name of that wrapper.

Using Generator to Simplify Function Export

The generator from go-plugify (called from your generator.go, see below) simplifies the process of exporting Go functions by:

  1. Scanning the Plugin Package: It finds functions with the //plugify:export comment.
  2. Creating the Manifest: It writes the .pplugin manifest with the signatures of these functions.
  3. Generating Files: It writes autoexports.go and autoexports.h with the cgo wrappers that convert Plugify types to Go types.

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

exports.go
package main

// AddNumbers adds two integers.
//
//plugify:export AddNumbers
func AddNumbers(a int32, b int32) int32 {
    return a + b
}

Plugin Manifest

The generator describes the function in the methods section of the manifest:

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

Generated Code

The generated autoexports.go contains the wrapper that other plugins call:

autoexports.go
package main

//#include "autoexports.h"
import "C"

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

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

exports.go
package main

import "fmt"

// ProcessData formats each value with a prefix and joins the results.
//
//plugify:export ProcessData
func ProcessData(data []float64, prefix string) string {
    result := ""
    for _, value := range data {
        result += fmt.Sprintf("%s%f", prefix, value)
    }
    return result
}

Plugin Manifest

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

Generated Code

Strings and slices arrive as Plugify containers; the wrapper converts them to Go values and the result back:

autoexports.go
//export __ProcessData
func __ProcessData(data *C.Vector, prefix *C.String) C.String {
    __result := ProcessData(plugify.GetVectorDataDouble[float64]((*plugify.PlgVector)(unsafe.Pointer(data))), plugify.GetStringData[string]((*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

exports.go
package main

// IncrementValue increments an integer value by reference.
//
//plugify:export IncrementValue
func IncrementValue(value *int32) {
    *value += 1
}

// Calculate returns the sum and the product of two numbers via pointers.
//
//plugify:export Calculate
func Calculate(a int32, b int32, sum *int32, product *int32) {
    *sum = a + b
    *product = a * b
}

Plugin Manifest with Reference Parameters

In the manifest, parameters passed by reference are marked with "ref": true:

go_example_plugin.pplugin
{
  "name": "go_example_plugin",
  "version": "0.1.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:

autoexports.go
//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. The callback type must be a named Go function type:

Function Definition

exports.go
package main

import "fmt"

// ExampleCallback is the callback signature.
type ExampleCallback func(value int32, inputStr string) string

// ExecuteWithCallback calls the callback and prints its result.
//
//plugify:export ExecuteWithCallback
func ExecuteWithCallback(value int32, inputStr string, callback ExampleCallback) {
    result := callback(value, inputStr)
    fmt.Printf("Callback result: %s\n", result)
}

Plugin Manifest

The generator puts the callback signature into prototypes and refers to it by name:

go_example_plugin.pplugin
{
  "name": "go_example_plugin",
  "version": "0.1.0",
  "methods": [
    {
      "name": "ExecuteWithCallback",
      "funcName": "__ExecuteWithCallback",
      "paramTypes": [
        {
          "type": "int32",
          "name": "value"
        },
        {
          "type": "string",
          "name": "inputStr"
        },
        {
          "type": "function",
          "name": "callback",
          "prototype": "ExampleCallback"
        }
      ],
      "retType": {
        "type": "void"
      }
    }
  ],
  "prototypes": [
    {
      "name": "ExampleCallback",
      "paramTypes": [
        {
          "type": "int32",
          "name": "value"
        },
        {
          "type": "string",
          "name": "inputStr"
        }
      ],
      "retType": {
        "type": "string"
      }
    }
  ]
}

Generated Code

The wrapper turns the native function pointer into a value of your Go function type:

autoexports.go
//export __ExecuteWithCallback
func __ExecuteWithCallback(value int32, inputStr *C.String, callback unsafe.Pointer) {
    ExecuteWithCallback(value, plugify.GetStringData[string]((*plugify.PlgString)(unsafe.Pointer(inputStr))), plugify.GetDelegateForFunctionPointer(callback, reflect.TypeOf(ExampleCallback(nil))).(ExampleCallback))
}

Automating Manifest and Export Generation

The manifest and the export code are not written by hand: you mark functions with //plugify:export Name comments and run go generate. The generator parses your package and writes:

  1. Plugin Manifest (.pplugin file) with all exported methods
  2. Autoexport Files (autoexports.go and autoexports.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

The generator is part of the go-plugify package, but it is started from your own generator.go, which calls plugify.Generate(). The Go plugin template already contains this file:

generator.go
//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>)")
    )

    flag.Parse()

    err := plugify.Generate(*output, *name, *entry, true, plugify.GenerateParams{
        Version:      *version,
        Description:  *description,
        Author:       *author,
        Website:      *website,
        License:      *license,
        Platforms:    parseCommaSeparated(*platforms),
        Dependencies: parseCommaSeparated(*dependencies),
        Conflicts:    parseCommaSeparated(*conflicts),
    }, &plugify.LoadPackageFilter{
        PathPatterns: []string{*patterns},
    })
    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
}

The //go:build ignore line keeps generator.go out of the plugin build. The //go:generate directive that starts it goes into one of your plugin's source files, for example main.go:

main.go
//go:generate go run generator.go -output go_example_plugin.pplugin -name go_example_plugin -version 0.1.0 -entry bin/go_example_plugin -author untrustedmodders -website https://github.com/untrustedmodders/ -license MIT -description "An example of a Go plugin."

package main

description, author, website and license are required fields of the manifest: if you leave these flags out, Plugify rejects the generated manifest.

Using the //plugify:export Comment

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

exports.go
package main

import "fmt"

// AddNumbers adds two integers.
//
//plugify:export AddNumbers
func AddNumbers(a int32, b int32) int32 {
    return a + b
}

// ProcessDataList formats each value with a prefix.
//
//plugify:export ProcessDataList
func ProcessDataList(data []float64, prefix string) []string {
    var result []string
    for _, value := range data {
        result = append(result, fmt.Sprintf("%s%f", prefix, value))
    }
    return result
}

Running the Generator

Run the generator using the go generate command:

go generate

Or run it manually with the same flags:

go run generator.go -output my_plugin.pplugin -name my_plugin -version 1.0.0 -entry bin/my_plugin -author "Your Name" -website https://example.com -license MIT -description "My plugin"

Then build the plugin as usual (go build -buildmode=c-shared).

How It Works

  1. During Generation: The Go parser analyzes your package looking for //plugify:export comments
  2. Type Analysis: It extracts function signatures and maps Go types to Plugify types (e.g., int32 → int32, []string → string[])
  3. Manifest Generation: A .pplugin file is created with all exported methods
  4. Export Code Generation: autoexports.go and autoexports.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 generator from go-plugify. By marking your functions with //plugify:export comments and running go generate, you get both the plugin manifest and the export code with proper type marshalling. This eliminates manual JSON writing and reduces errors, making it easier to create robust and interoperable plugins. For callbacks, the generator handles the complex marshalling code for you.