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 Rust plugin, you need to generate language-specific bindings. These bindings provide the necessary wrappers to call functions exported by other plugins. This guide explains how to generate these bindings and how to use them in your Rust plugin.

Generating Rust Bindings

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

Steps to Generate Rust 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 Rust as the target language to generate the corresponding .rs file.

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 rust

Integrate the Generated Module:

  • The tool will generate a module folder (e.g., plugin_from_another_language/) in the specified output directory.
  • Copy the entire generated module folder to your project's src/ directory.
  • Declare the module in your src/lib.rs.

Project Integration

After generating the module, integrate it into your Rust project:

Copy Module to Project

Copy the generated module folder to your project's src/ directory:

cp -r ./output/plugin_from_another_language ./my_plugin/src/

Declare Module

Declare the module in your src/lib.rs:

mod plugin_from_another_language;

Import in Source Files

Import and use the generated module in your Rust files:

use crate::plugin_from_another_language;
// Or bring specific items into scope
use crate::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 Bindings

Here's an example of a generated Rust binding file for a plugin named plugin_from_another_language:

plugin_from_another_language.rs
// Generated from plugin_from_another_language.pplugin by https://github.com/untrustedmodders/plugify-gen

use plugify::*;

pub mod plugin_from_another_language {
    pub fn ParamCallback(a: i32, b: f32, c: f64, d: &Vec4, e: &Arr<i64>, f: i8, g: &Str, h: u16, k: i16) {
        unsafe { __plugin_from_another_language_ParamCallback.expect("ParamCallback function was not found")(a, b, c, d, e, f, g, h, k) }
    }
    pub type _ParamCallback = unsafe extern "C" fn(i32, f32, f64, &Vec4, &Arr<i64>, i8, &Str, u16, i16);
    #[allow(dead_code, non_upper_case_globals)]
    #[unsafe(no_mangle)]
    pub static mut __plugin_from_another_language_ParamCallback: Option<_ParamCallback> = None;
}

How It Works

  • The wrapper function (ParamCallback) retrieves the address of the exported function using the Plugify API.
  • The ____plugin_from_another_language_ParamCallback delegate is set by the language module during plugin load.
  • The wrapper function directly passes the parameters to the exported function.

Example: Using the Generated Bindings

Here's how you can use the generated bindings in your Rust plugin:

src/lib.rs
mod plugin_from_another_language;

use plugify::*;
use plugin_from_another_language::*;

fn on_plugin_start() {
    // Call the exported function from the other plugin
    let data = Arr::from(vec![100i64, 200i64]);
    let message = Str::from("Hello, Plugify!");
    let vec4 = Vec4::new(1.0, 2.0, 3.0, 4.0);

    plugin_from_another_language::param_callback(
        42,          // i32 a
        3.14,        // f32 b
        2.718,       // f64 c
        &vec4,       // &Vec4 d
        &data,       // &Arr<i64> e
        'x' as i8,   // i8 f
        &message,    // &Str g
        '✓' as u16,  // u16 h
        10           // i16 k
    );
}

register_plugin!(
    start: on_plugin_start
);

Working with PlgTypes

Plugify uses special types for cross-language compatibility:

Str

use plugify::Str;

// Create from Rust string
let msg = Str::from("Hello");

// Create from String
let msg = Str::from(String::from("Hello"));

// Convert to Rust &str
let s: &str = msg.as_str();

// Convert to String
let string: String = msg.to_string();

Arr<T>

use plugify::Arr;

// Create from Vec
let numbers = Arr::from(vec![1, 2, 3, 4, 5]);

// Create empty
let mut data: Arr<i32> = Arr::new();
data.push(42);

// Access elements
let first = data[0];

// Iterate
for num in &numbers {
    println!("{}", num);
}

// Convert to Vec
let vec: Vec<i32> = numbers.into();

Var

use plugify::Var;

// Create from different types
let var1 = Var::from(Any::Int32(42));
let var2 = Var::from(Any::Float(3.14);
let var3 = Var::from(Any::String("text"));

// Extract value
match var1.get() {
    Any::Int32(num) => println!("Number: {}", num),
    _ => println!("NA"),
}

When is Binding Generation Necessary?

Binding generation is essential when importing functions from plugins written in other languages. Without these bindings, you cannot safely call the exported functions. The generator ensures type safety and proper ABI compatibility.

Best Practices

  1. Use the Generator Tool: Always use the Plugify generator tool (online or command-line) to generate bindings for imported functions.
  2. Include Generated Modules: Place generated files in a modules directory and include them in your plugin.
  3. Handle Errors Safely: Check for null function pointers before calling imported functions.
  4. Document Dependencies: Clearly document the plugins and functions your plugin depends on.
  5. Use PlgTypes: Always use Str, Arr<T>, and Var for cross-language compatibility.
  6. Respect Ownership: Be careful with borrowed references vs owned values.

Conclusion

Importing functions from another plugin in Rust is straightforward when you use the Plugify generator tool to generate the necessary bindings. These bindings provide safe 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 with Rust's safety guarantees.