dolphin

Crates.iodolphin
lib.rsdolphin
version0.3.0
created_at2025-08-09 02:55:19.576158+00
updated_at2025-11-26 03:03:58.509511+00
descriptionA lightweight and safe Rust FFI library for dynamically loading and invoking functions from shared libraries with support for return values
homepagehttps://github.com/Logan-Garrett/dolphin
repositoryhttps://github.com/Logan-Garrett/dolphin
max_upload_size
id1787532
size146,725
Logan (Logan-Garrett)

documentation

README

๐Ÿฌ Dolphin

A lightweight and safe Rust FFI library for dynamically loading and invoking functions from shared libraries (.dll, .so, .dylib). Dolphin provides a simple interface to call native C functions from any compiled library at runtime.

Features

  • ๐Ÿš€ Dynamic Loading - Load functions from any shared library at runtime
  • ๐Ÿ”’ Safe API - Wraps unsafe FFI calls in a safe, ergonomic interface
  • โšก Performance - Pre-load function addresses for zero-overhead repeated calls
  • ๐ŸŽฏ Flexible - Pass any data type including primitives, strings, and complex structs
  • ๐Ÿ”„ Return Values - Call functions that return values (integers, floats, structs, etc.)
  • ๐ŸŒ Cross-Platform - Works on Windows (.dll), Linux (.so), and macOS (.dylib)

Installation

Add Dolphin to your Cargo.toml:

[dependencies]
dolphin = "0.3.0"

Quick Start

Simple Usage

use dolphin::load_and_invoke;

fn main() {
    // Call a C function with a string
    let message = "Hello from Rust!";
    load_and_invoke("./mylib.dylib", "print_message", message.as_bytes())
        .expect("Failed to call function");
    
    // Call with integer array
    let numbers = vec![10, 20, 30, 40, 50];
    load_and_invoke("./mylib.dylib", "calculate_sum", &numbers)
        .expect("Failed to call function");
}

Functions with Return Values

use dolphin::load_and_invoke_with_return;

fn main() {
    // Call a function that returns an integer
    let numbers = vec![10, 20, 30];
    let sum: i32 = load_and_invoke_with_return("./mylib.dylib", "sum_integers", &numbers)
        .expect("Failed to call function");
    println!("Sum: {}", sum); // Prints: Sum: 60
    
    // Call a function that returns a double
    let values = vec![100.0, 200.0, 300.0];
    let avg: f64 = load_and_invoke_with_return("./mylib.dylib", "calculate_average", &values)
        .expect("Failed to call function");
    println!("Average: {}", avg); // Prints: Average: 200.0
}

High-Performance Pattern (Pre-loading)

For repeated calls, pre-load the function once and reuse the address:

use dolphin::{load, invoke};

fn main() {
    // Load once
    let print_addr = load("./mylib.dylib", "print_message")
        .expect("Failed to load function");
    
    // Invoke many times with zero loading overhead
    for i in 0..1000 {
        let msg = format!("Message {}", i);
        invoke(print_addr, msg.as_bytes()).ok();
    }
}

Pre-loading also works with return values:

use dolphin::{load_with_return, invoke_with_return};

fn main() {
    // Load once
    let sum_addr = load_with_return::<i32>("./mylib.dylib", "sum_integers")
        .expect("Failed to load function");
    
    // Invoke many times (1000x faster than repeated loading)
    for i in 0..1000 {
        let numbers = vec![i, i * 2, i * 3];
        let result: i32 = invoke_with_return(sum_addr, &numbers)
            .expect("Failed to invoke");
        println!("Sum: {}", result);
    }
}

Working with Structs

use dolphin::load_and_invoke;

#[repr(C)]
struct User {
    id: i32,
    name: [u8; 64],
    age: i32,
    balance: f64,
}

fn main() {
    let user = User {
        id: 1,
        name: [0; 64], // Initialize with your data
        age: 30,
        balance: 1000.0,
    };
    
    let user_bytes = unsafe {
        std::slice::from_raw_parts(
            &user as *const User as *const u8,
            std::mem::size_of::<User>()
        )
    };
    
    load_and_invoke("./mylib.dylib", "process_user", user_bytes)
        .expect("Failed to process user");
}

API Overview

Core Functions

For Void Functions (no return value):

  • load(library_path, function_name) - Load a function and return its address

    • Returns: Option<usize> - The function address if found
  • invoke(address, arguments) - Call a pre-loaded function

    • Returns: Result<(), String> - Success or error message
  • load_and_invoke(library_path, function_name, arguments) - Load and call in one step

    • Returns: Result<(), String> - Success or error message

For Functions with Return Values:

  • load_with_return::<R>(library_path, function_name) - Load a function that returns type R

    • Returns: Result<usize, String> - The function address or error message
  • invoke_with_return::<T, R>(address, arguments) - Call a pre-loaded function that returns type R

    • Returns: Result<R, String> - The return value or error message
  • load_and_invoke_with_return::<T, R>(library_path, function_name, arguments) - Load and call in one step

    • Returns: Result<R, String> - The return value or error message

C Function Requirements

Void Functions

C functions that don't return values must follow this signature:

void your_function(const uint8_t* data, size_t len) {
    // Your implementation
}

Functions with Return Values

C functions that return values must follow this signature:

ReturnType your_function(const uint8_t* data, size_t len) {
    // Your implementation
    return value;
}

Where ReturnType can be any C type (int, double, struct, etc.).

The data pointer contains the serialized arguments, and len is the byte count.

Example C Library

#include <stdio.h>
#include <stdint.h>

// Void function (no return)
void print_message(const uint8_t* data, size_t len) {
    printf("Message: %.*s\n", (int)len, (char*)data);
}

// Function returning an integer
int32_t sum_integers(const uint8_t* data, size_t len) {
    int count = len / sizeof(int32_t);
    const int32_t* numbers = (const int32_t*)data;
    int32_t sum = 0;
    for (int i = 0; i < count; i++) {
        sum += numbers[i];
    }
    return sum;
}

// Function returning a double
double calculate_average(const uint8_t* data, size_t len) {
    int count = len / sizeof(double);
    const double* values = (const double*)data;
    double sum = 0.0;
    for (int i = 0; i < count; i++) {
        sum += values[i];
    }
    return count > 0 ? sum / count : 0.0;
}

Compile it:

# macOS
gcc -shared -fPIC mylib.c -o libmylib.dylib

# Linux
gcc -shared -fPIC mylib.c -o libmylib.so

# Windows
gcc -shared mylib.c -o mylib.dll

Examples

The repository includes comprehensive examples:

# Clone the repository
git clone https://github.com/Logan-Garrett/dolphin.git
cd dolphin/dolphin

# Build C examples
cd examples && make && cd ..

# Run examples
cargo run --example usage_example         # Basic usage patterns
cargo run --example preload_example       # Pre-loading pattern
cargo run --example return_values_example # Return value examples

C# Interop: See examples/CSHARP.md for documentation on C# interop via NativeAOT (advanced).

Use Cases

  • Plugin Systems - Dynamically load and execute plugins at runtime
  • C Library Integration - Call C libraries without compile-time linking
  • C# Interop - Call .NET/C# functions via NativeAOT (see examples/CSHARP.md for details)
  • Hot Reloading - Reload functions without restarting your application
  • Language Interop - Bridge Rust with C, C++, or any C-compatible library
  • Legacy Code - Interface with existing native libraries

Performance

Dolphin is designed for performance:

  • Zero overhead for pre-loaded functions
  • No runtime dependencies beyond libloading
  • Minimal allocations - Direct memory operations
  • Efficient - Function addresses are simple integers

Benchmark comparison:

  • load_and_invoke: ~1-2ยตs per call (includes library loading)
  • load + invoke: ~50-100ns per call (pre-loaded)

Safety

While FFI is inherently unsafe, Dolphin provides guardrails:

  • โœ… Validates function addresses before calling
  • โœ… Returns Result types for error handling
  • โœ… Safe wrapper API around unsafe operations
  • โš ๏ธ User must ensure correct function signatures
  • โš ๏ธ User must ensure data layout matches C expectations

Platform Support

Platform Library Format Tested
macOS .dylib โœ…
Linux .so โœ…
Windows .dll โœ…

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

Built with:

  • libloading - Cross-platform dynamic library loading

Links


Made with ๐Ÿฌ by Logan Garrett

Testing

cargo test # Run basic tests

cargo test -- --ignored # Run integration tests (requires gcc)

cargo test -- --include-ignored # Run all tests

Commit count: 0

cargo fmt