# ๐ฌ 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`:
```toml
[dependencies]
dolphin = "0.3.0"
```
## Quick Start
### Simple Usage
```rust
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
```rust
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:
```rust
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:
```rust
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
```rust
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:
```c
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:
```c
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
```c
#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:
```bash
# 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:
```bash
# 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](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
| 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](../LICENSE) file for details.
## Acknowledgments
Built with:
- [`libloading`](https://crates.io/crates/libloading) - Cross-platform dynamic library loading
## Links
- **Repository**: https://github.com/Logan-Garrett/dolphin
- **Documentation**: https://docs.rs/dolphin
- **Crates.io**: https://crates.io/crates/dolphin
---
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