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 Python script (generator.py) 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 Header Files

Locate the Generator Script:

  • The generator.py script is located in the generator folder of the C# Language Module.

Run the Generator Script:

  • Open a terminal or command prompt and navigate to the folder containing generator.py.
  • Run the script with the following command:
    python generator.py "path_to_plugin.pplugin" "output_folder"
    
    • path_to_plugin.pplugin: The path to the plugin manifest file (.pplugin) of the plugin you want to import functions from.
    • output_folder: The directory where the generated header file will be saved.

Example:

python generator.py ./plugins/MyPlugin/MyPlugin.pplugin ./output/

Include the Generated Header:

  • The script will generate a header file (e.g., MyPlugin.cs) in the specified output folder.
  • Include this header in your C# plugin source files to use the exported functions.

Using Generated Wrapper Functions

The generated header file 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:

plugin_from_another_language.cs
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Plugify;

// Generated from plugin_from_another_language.pplugin by https://github.com/untrustedmodders/plugify-module-dotnet/blob/main/generator/generator.py

namespace plugin_from_another_language {
    internal static unsafe class plugin_from_another_language {
        internal static delegate*<int, float, double, Vector4, long[], Char8, string, Char16, short, void> ParamCallback = &___ParamCallback; // Use this to call the function
        internal static delegate* unmanaged[Cdecl]<int, float, double, Vector4*, Vector192*, Char8, String192*, Char16, short, void> __ParamCallback; // Internal call (original function)
        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);
            }
        }
    }
}

How It Works

  • The wrapper function (___ParamCallback) handles parameter marshaling and cleanup.
  • The __ParamCallback delegate is set by the language module during plugin load.
  • The ParamCallback delegate is the public-facing function that you can call in your C# code.

Example: Using the Generated Header

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

plugin.cs
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
            new Char8('x'),    // Char8 f
            "Hello, Plugify!", // string g
            new Char16('✓'),   // Char16 h
            10                 // short k
        );
    }
}

When is Header Generation Necessary?

Header generation is essential when importing functions from plugins written in statically-typed languages like C++ or C#. Without these headers, the compiler cannot reference the exported functions. For dynamically-typed languages like Python, header generation is not necessary because method binding happens at runtime.

Best Practices

  1. Use the Generator Script: Always use the generator.py script 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 generator.py script 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.