Export Functions

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

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

Basic Type Mapping

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

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

Exporting Functions in C#

Exporting functions in C# is straightforward because C# is a statically-typed language. You need to define the function and specify it in the plugin manifest. Plugify's C# Language Module handles the rest.

Basic Example

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

Function Definition

plugin.cs
namespace ExampleCSharpPlugin
{
    public static class ExportedFunctions
    {
        /// <summary>
        /// Adds two integers.
        /// </summary>
        /// <param name="a">First integer.</param>
        /// <param name="b">Second integer.</param>
        /// <returns>Sum of a and b.</returns>
        public static int AddNumbers_Exported(int a, int b)
        {
            return a + b;
        }
    }
}

Plugin Manifest

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

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

Advanced Example: Exporting Complex Functions

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

Function Definition

plugin.cs
namespace ExampleCSharpPlugin
{
    public static class ExportedFunctions
    {
        /// <summary>
        /// Processes an array of doubles and returns an array of strings.
        /// </summary>
        /// <param name="data">Array of double values.</param>
        /// <param name="prefix">Prefix to add to each value.</param>
        /// <returns>Array of formatted strings.</returns>
        public static string[] ProcessData_Exported(double[] data, string prefix)
        {
            return data.Select(value => $"{prefix}{value}").ToArray();
        }
    }
}

Plugin Manifest

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

Exporting Functions with References

Plugify supports reference parameters (also known as "out" or "ref" parameters) that allow functions to modify values and return them to the caller. In C#, reference parameters are declared using the ref keyword.

Function Definition with Reference Parameters

plugin.cs
namespace ExampleCSharpPlugin
{
    public static class ExportedFunctions
    {
        /// <summary>
        /// Increments an integer value by reference.
        /// </summary>
        /// <param name="value">The value to increment (passed by reference).</param>
        public static void IncrementValue_Exported(ref int value)
        {
            value += 1;
        }

        /// <summary>
        /// Calculates sum and product of two numbers, returning both via reference.
        /// </summary>
        /// <param name="a">First number.</param>
        /// <param name="b">Second number.</param>
        /// <param name="sum">Output parameter for sum.</param>
        /// <param name="product">Output parameter for product.</param>
        public static void Calculate_Exported(int a, int b, ref int sum, ref int product)
        {
            sum = a + b;
            product = a * b;
        }
    }
}

Plugin Manifest with Reference Parameters

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

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

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, arrays, and structs support reference parameters.

Handling Callbacks

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

Function Definition

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

    public static class ExportedFunctions
    {
        /// <summary>
        /// Executes a callback function with the provided parameters.
        /// </summary>
        /// <param name="value">Integer value.</param>
        /// <param name="inputStr">Input string.</param>
        /// <param name="callback">Callback function to execute.</param>
        public static void ExecuteWithCallback_Exported(int value, string inputStr, ExampleCallback callback)
        {
            string result = callback(value, inputStr);
            Console.WriteLine($"Callback result: {result}");
        }
    }
}

Plugin Manifest

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

Automating Manifest Generation

Instead of manually writing the manifest JSON for each exported function, you can use the Plugify.Generator NuGet package to automatically generate manifest entries for functions marked with the [NativeExport] attribute.

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. Reduced Errors: Eliminates typos and type mismatches between code and manifest
  4. Easy Maintenance: Changes to function signatures are automatically reflected in the manifest
  5. Build-Time Generation: Manifest is generated during compilation, keeping it always in sync

Setup: Installing Plugify.Generator

Add the Plugify.Generator NuGet package to your C# plugin project:

dotnet add package Plugify.Generator

Or add it directly to your .csproj file:

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

The Plugify package provides the [NativeExport] attribute, while Plugify.Generator is the Roslyn source generator that processes these attributes during compilation.

Using the NativeExport Attribute

Mark your exported functions with the [NativeExport("FunctionName")] attribute:

plugin.cs
using Plugify;

namespace ExampleCSharpPlugin
{
    public static class ExportedFunctions
    {
        /// <summary>
        /// Adds two integers.
        /// </summary>
        /// <param name="a">First integer.</param>
        /// <param name="b">Second integer.</param>
        /// <returns>Sum of a and b.</returns>
        [NativeExport("AddNumbers")]
        public static int AddNumbers_Exported(int a, int b)
        {
            return a + b;
        }

        /// <summary>
        /// Processes an array of doubles and returns an array of strings.
        /// </summary>
        /// <param name="data">Array of double values.</param>
        /// <param name="prefix">Prefix to add to each value.</param>
        /// <returns>Array of formatted strings.</returns>
        [NativeExport("ProcessData")]
        public static string[] ProcessData_Exported(double[] data, string prefix)
        {
            return data.Select(value => $"{prefix}{value}").ToArray();
        }
    }
}

How It Works

  1. During Compilation: The Roslyn source generator scans your code for methods marked with [NativeExport]
  2. Type Mapping: It automatically maps C# types to Plugify types (e.g., intint32, string[]string[])
  3. Manifest Generation: A JSON file containing the methods array is generated in your build output
  4. Integration: Copy the generated manifest entries into your .pplugin file

Generated Output

When you build your project, the generator creates a file (e.g., ExampleCSharpPlugin.methods.json) containing:

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

Simply copy this array into your plugin manifest's methods section.

Advanced: Working with Delegates

The generator also supports delegate types for callback parameters:

plugin.cs
using Plugify;

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

    public static class ExportedFunctions
    {
        /// <summary>
        /// Executes a callback function with the provided parameters.
        /// </summary>
        /// <param name="value">Integer value.</param>
        /// <param name="inputStr">Input string.</param>
        /// <param name="callback">Callback function to execute.</param>
        [NativeExport("ExecuteWithCallback")]
        public static void ExecuteWithCallback_Exported(int value, string inputStr, ExampleCallback callback)
        {
            string result = callback(value, inputStr);
            Console.WriteLine($"Callback result: {result}");
        }
    }
}

The generator will automatically create the complete function prototype structure:

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

Best Practices

  1. Define Functions Clearly: Ensure your functions are well-documented and easy to understand.
  2. Follow Type Conventions: Adhere to Plugify's type conventions for parameters and return values.
  3. Use XML Documentation: Add XML doc comments (/// <summary>, /// <param>, /// <returns>) to provide descriptions in the generated manifest.
  4. Use NativeExport Attribute: When possible, use the [NativeExport] attribute with Plugify.Generator to automate manifest generation.
  5. Test Thoroughly: Test your exported functions to ensure they work as expected when called by other plugins.
  6. Keep Manifests Updated: If using automated generation, regenerate the manifest after making changes to function signatures.

Conclusion

Exporting functions in C# plugins is simple and straightforward. By defining your functions and describing them in the plugin manifest, you can create robust and interoperable plugins. With the new Plugify.Generator Roslyn source generator, you can automate manifest generation by simply marking functions with the [NativeExport] attribute, reducing manual effort and keeping your manifest synchronized with your code. For more advanced use cases, such as handling callbacks, use the techniques outlined in this guide.