Import Functions

Learn how to import functions from other plugins written in different languages and use them in your own.

To use functions from another plugin in your C# plugin, you need to generate language-specific header files. These headers provide the necessary wrappers to call functions exported by other plugins. This guide explains how to generate these headers and how to use them in your C# plugin.

Generating Header Files

Plugify provides a unified generator tool to automatically generate header files for importing functions from other plugins. These headers include wrapper functions that handle the function calls and parameter passing.

Steps to Generate Bindings

Using the Online Generator:

Visit plugify-gen tool to generate bindings through a user-friendly web interface. Simply upload your plugin manifest file (.pplugin) and select C# as the target language to generate the corresponding C# module.

Using the Command-Line Tool:

You can also download and use the generator tool locally from the plugify-gen repository.

Example usage:

plugify-gen -manifest ./plugins/plugin_from_another_language/plugin_from_another_language.pplugin -output ./output/ -lang dotnet

Integrate the Generated Module:

  • The tool generates the folder imported/plugin_from_another_language/ with core.cs, enums.cs, aliases.cs and delegates.cs in the specified output directory.
  • Copy the generated folder to your project directory.
  • Declare the plugin in the dependencies of your manifest, so that it loads before yours.

Project Integration

After generating the module, integrate it into your C# project:

Copy Module to Project

Copy the generated module folder to your project directory:

cp -r ./output/imported/plugin_from_another_language ./MyPlugin/

Add to .csproj

SDK-style projects compile every .cs file in the project folder, so the copied files are picked up automatically. If your project lists files explicitly, add them to your .csproj:

<ItemGroup>
    <Compile Include="plugin_from_another_language\**\*.cs" />
</ItemGroup>

Or manually add each file:

<ItemGroup>
    <Compile Include="plugin_from_another_language\core.cs" />
    <Compile Include="plugin_from_another_language\enums.cs" />
    <Compile Include="plugin_from_another_language\aliases.cs" />
    <Compile Include="plugin_from_another_language\delegates.cs" />
</ItemGroup>

Using Namespace

Use the generated namespace in your C# files:

using plugin_from_another_language;

Using Generated Wrapper Functions

The generated module contains wrapper functions that allow you to call functions from the other plugin. These wrappers handle the function address lookup and parameter passing.

Example Generated Header

Here’s an example of a generated header file for a plugin named plugin_from_another_language:

imported/plugin_from_another_language/core.cs
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Plugify;

// Generated from plugin_from_another_language.pplugin (group: core)

namespace plugin_from_another_language {
#pragma warning disable CS0649

    internal static unsafe partial class plugin_from_another_language {

#region ParamCallback
        private static delegate*<int, float, double, Vector4, long[], Char8, string, Char16, short, void> _ParamCallback = &___ParamCallback;
        private static delegate* unmanaged[Cdecl]<int, float, double, Vector4*, Vector192*, Char8, String192*, Char16, short, void> __ParamCallback;
        private static void ___ParamCallback(int a, float b, double c, Vector4 d, long[] e, Char8 f, string g, Char16 h, short k)
        {
            var __e = NativeMethods.ConstructVectorInt64(e, e.Length);
            var __g = NativeMethods.ConstructString(g);
            try {
                __ParamCallback(a, b, c, &d, &__e, f, &__g, h, k);
            }
            finally {
                // Perform cleanup.
                NativeMethods.DestroyVectorInt64(&__e);
                NativeMethods.DestroyString(&__g);
            }
        }
#endregion ParamCallback

        internal static void ParamCallback(int a, float b, double c, Vector4 d, long[] e, Char8 f, string g, Char16 h, short k)
        {
            _ParamCallback(a, b, c, d, e, f, g, h, k);
        }
    }
}

How It Works

  • ParamCallback is the method you call in your C# code.
  • The wrapper (___ParamCallback) converts strings and arrays to Plugify types and cleans them up after the call.
  • The function pointer __ParamCallback is set by the language module when your plugin loads. If the other plugin is also written in C#, the module points _ParamCallback straight at its method, without the conversion.

Example: Using the Generated Header

Here’s how you can use the generated header in your C# plugin:

plugin.cs
using System.Numerics;
using plugin_from_another_language;

public class MyPlugin {
    public void MyFunction() {
        // Call the exported function from the other plugin
        plugin_from_another_language.plugin_from_another_language.ParamCallback(
            42,                // int a
            3.14f,             // float b
            2.718,             // double c
            new Vector4(1, 2, 3, 4), // Vector4 d
            new long[] { 100, 200 }, // long[] e
            'x',               // Char8 f
            "Hello, Plugify!", // string g
            '✓',               // Char16 h
            10                 // short k
        );
    }
}

When is Header Generation Necessary?

In C#, you need generated bindings for every plugin you import, whatever language that plugin is written in. Without them, the compiler cannot reference the exported functions. Plugins written in dynamically-typed languages like Python do not need this step, because method binding happens at runtime.

Best Practices

  1. Use the Generator Tool: Always use the Plugify generator tool (online or command-line) to generate headers for imported functions.
  2. Include Generated Headers: Include the generated headers in your plugin source files to access the exported functions.
  3. Test Thoroughly: Test the imported functions to ensure they work as expected.
  4. Document Dependencies: Clearly document the plugins and functions your plugin depends on.

Conclusion

Importing functions from another plugin in C# is straightforward when you use the Plugify generator tool to generate the necessary headers. These headers provide wrapper functions that handle function address lookup and parameter passing, making it easy to integrate functionality from other plugins. By following the steps and best practices outlined in this guide, you can create robust and interoperable plugins in the Plugify ecosystem.