dolphin 0.2.0

A lightweight and safe Rust FFI library for dynamically loading and invoking functions from shared libraries (.dll, .so, .dylib)
Documentation
  • Coverage
  • 66.67%
    4 out of 6 items documented4 out of 4 items with examples
  • Size
  • Source code size: 51.52 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 2.13 MB This is the summed size of all files generated by rustdoc for all configured targets
  • ร˜ build duration
  • this release: 17s Average build duration of successful builds.
  • all releases: 17s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • Repository
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • Logan-Garrett

๐Ÿฌ 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
  • ๐ŸŒ Cross-Platform - Works on Windows (.dll), Linux (.so), and macOS (.dylib)

Installation

Add Dolphin to your Cargo.toml:

[dependencies]
dolphin = "0.1.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");
}

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();
    }
}

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

  • 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

C Function Requirements

C functions must follow this signature:

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

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

Example C Library

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

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

void calculate_sum(const uint8_t* data, size_t len) {
    int count = len / sizeof(int);
    const int* numbers = (const int*)data;
    int sum = 0;
    for (int i = 0; i < count; i++) {
        sum += numbers[i];
    }
    printf("Sum: %d\n", sum);
}

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

Use Cases

  • Plugin Systems - Dynamically load and execute plugins at runtime
  • C Library Integration - Call C libraries without compile-time linking
  • 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

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