Skip to content

C/C++ Bindings

C/C++ bindings let your Rust library be used from C and C++ applications.

The template uses cbindgen to automatically generate header files, handling cross-language calling conventions and platform details so you can focus on writing Rust code.

Note

Generating C bindings makes it possible to then generate bindings for further languages, such as C#.

Many language binding tools work best with C-compatible interfaces.

Overview

  • C Bindings: C-compatible headers generated by cbindgen
  • C++ Bindings: C++ headers with namespaces and additional type safety

Both are generated automatically when the c-exports feature is enabled.

Example Bindings

C Header
#ifdef _MSC_VER
#define PACKED
    #pragma pack(push, 1)
#else
#define PACKED __attribute__((packed))
#endif


#ifndef prs_rs
#define prs_rs

/* Generated with cbindgen:0.29.0 */

/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */

#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

/**
 * Compresses the given data in `source`, placing it in `destimation`.
 *
 * Parameters
 *
 * - `src`: A pointer to the compressed data.
 * - `src_len`: Length of the compressed data.
 * - `destination`: A pointer to the decompressed data to be written.
 *
 * # Returns
 *
 * Number of bytes written to `destination`.
 *
 * # Safety
 *
 * It's safe as long as `dest` has sufficient length (max length: [`prs_calculate_max_compressed_size`])
 * and the remaining parameters are valid.
 */
 uintptr_t prs_compress(const unsigned char *src, unsigned char *dest, uintptr_t srcLen) ;

/**
 * Decodes the maximum possible compressed size after compressing a file with provided
 * `source_len` length.
 *
 * # Parameters
 *
 * - `source_len`: Length of the compressed data.
 *
 * # Returns
 *
 * The length of the decompressed data at `source`.
 *
 * # Remarks
 *
 * A properly compressed PRS file has a theoretical maximum size of 1.125 times the size of the
 * original input. i.e. (1 byte for every 8 bytes of input).
 *
 * Up to 2 bytes may be added to that in addition, namely via:
 * - Rounding file to next byte
 * - Having to write 00 opcode after a compressed sequence of bytes to terminate.
 */
 uintptr_t prs_calculate_max_compressed_size(uintptr_t sourceLen) ;

/**
 * Decodes the compressed data at `source` without performing the actual decompression.
 *
 * You can use this operation to determine the size of the data to decompress
 * without actually decompressing the data to a buffer.
 *
 * # Parameters
 *
 * - `source`: A pointer to the compressed data.
 *
 * # Returns
 *
 * The length of the decompressed data at `source`.
 *
 * # Safety
 *
 * Function is safe as long as the pointer points to valid PRS compressed data with
 * a terminator byte.
 */
 uintptr_t prs_calculate_decompressed_size(const unsigned char *src) ;

/**
 * Decompresses PRS compressed data, in an unsafe manner, without any error handling.
 *
 * # Parameters
 *
 * - `source`: A pointer to the compressed data.
 * - `destination`: A pointer to the decompressed data.
 *
 * # Returns
 *
 * - The length of the decompressed data.
 *
 * # Remarks
 *
 * The length of the decompressed data at `destination` should be sufficient to store the decompressed data.
 *
 * If you know the length of the compressed data (i.e. amount of bytes until end of compressed data),
 * call [`prs_calculate_max_compressed_size`] to get the length of the decompressed data
 * buffer.
 *
 * If you are unsure of the length, you use the [`prs_calculate_decompressed_size`]
 * function to determine the length of the decompressed data (at expense of some additional overhead).
 *
 * # Safety
 *
 * Function is safe as long as the source points to valid PRS compressed data with
 * a terminator byte. The destination should be large enough to store the decompressed data.
 */
 uintptr_t prs_decompress(const unsigned char *src, unsigned char *dest) ;

#endif  /* prs_rs */
C++ Header
#ifdef _MSC_VER
#define PACKED
    #pragma pack(push, 1)
#else
#define PACKED __attribute__((packed))
#endif


#ifndef prs_rs
#define prs_rs

/* Generated with cbindgen:0.29.0 */

/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */

#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>

namespace prs_rs {


extern "C" {

/// Compresses the given data in `source`, placing it in `destimation`.
///
/// Parameters
///
/// - `src`: A pointer to the compressed data.
/// - `src_len`: Length of the compressed data.
/// - `destination`: A pointer to the decompressed data to be written.
///
/// # Returns
///
/// Number of bytes written to `destination`.
///
/// # Safety
///
/// It's safe as long as `dest` has sufficient length (max length: [`prs_calculate_max_compressed_size`])
/// and the remaining parameters are valid.
 uintptr_t prs_compress(const unsigned char *src, unsigned char *dest, uintptr_t srcLen) ;

/// Decodes the maximum possible compressed size after compressing a file with provided
/// `source_len` length.
///
/// # Parameters
///
/// - `source_len`: Length of the compressed data.
///
/// # Returns
///
/// The length of the decompressed data at `source`.
///
/// # Remarks
///
/// A properly compressed PRS file has a theoretical maximum size of 1.125 times the size of the
/// original input. i.e. (1 byte for every 8 bytes of input).
///
/// Up to 2 bytes may be added to that in addition, namely via:
/// - Rounding file to next byte
/// - Having to write 00 opcode after a compressed sequence of bytes to terminate.
 uintptr_t prs_calculate_max_compressed_size(uintptr_t sourceLen) ;

/// Decodes the compressed data at `source` without performing the actual decompression.
///
/// You can use this operation to determine the size of the data to decompress
/// without actually decompressing the data to a buffer.
///
/// # Parameters
///
/// - `source`: A pointer to the compressed data.
///
/// # Returns
///
/// The length of the decompressed data at `source`.
///
/// # Safety
///
/// Function is safe as long as the pointer points to valid PRS compressed data with
/// a terminator byte.
 uintptr_t prs_calculate_decompressed_size(const unsigned char *src) ;

/// Decompresses PRS compressed data, in an unsafe manner, without any error handling.
///
/// # Parameters
///
/// - `source`: A pointer to the compressed data.
/// - `destination`: A pointer to the decompressed data.
///
/// # Returns
///
/// - The length of the decompressed data.
///
/// # Remarks
///
/// The length of the decompressed data at `destination` should be sufficient to store the decompressed data.
///
/// If you know the length of the compressed data (i.e. amount of bytes until end of compressed data),
/// call [`prs_calculate_max_compressed_size`] to get the length of the decompressed data
/// buffer.
///
/// If you are unsure of the length, you use the [`prs_calculate_decompressed_size`]
/// function to determine the length of the decompressed data (at expense of some additional overhead).
///
/// # Safety
///
/// Function is safe as long as the source points to valid PRS compressed data with
/// a terminator byte. The destination should be large enough to store the decompressed data.
 uintptr_t prs_decompress(const unsigned char *src, unsigned char *dest) ;

}  // extern "C"

}  // namespace prs_rs

#endif  // prs_rs

The C++ version adds namespaces and uses C++-style includes for better type safety.

How to Export Functions

Info

These examples demonstrate good practices for exporting Rust functions to C.

Use these patterns when writing your own export functions.

Basic Function Exports

Use #[no_mangle] and extern "C" to export functions:

#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
    a + b
}

Error Handling with Result Types

When you have a Rust function that returns Result<T, E>, use this pattern to handle errors safely in C.

use alloc::ffi::CString;
use core::ffi::c_char;
use core::ptr::null;

/// Result type for C exports
#[repr(C)]
pub struct OperationResult {
    pub is_ok: bool,
    pub ok: u32,
    pub err: *const c_char,
}

#[no_mangle]
pub extern "C" fn perform_operation(value: u32) -> OperationResult {
    match your_rust_function(value) {
        Ok(result) => OperationResult {
            is_ok: true,
            ok: result,
            err: null(),
        },
        Err(err) => OperationResult {
            is_ok: false,
            ok: 0,
            err: CString::new(err.to_string()).unwrap().into_raw(),
        },
    }
}

Handling Optional Values

When you have a Rust function that returns Option<T>, use this pattern to handle optional values in C.

/// Optional value wrapper for C
#[repr(C)]
pub struct OptionalInt {
    pub has_value: bool,
    pub value: i32,
}

#[no_mangle]
pub extern "C" fn get_optional_value() -> OptionalInt {
    match your_optional_function() {
        Some(value) => OptionalInt {
            has_value: true,
            value,
        },
        None => OptionalInt {
            has_value: false,
            value: 0,
        },
    }
}

Memory Management

Always provide free functions for allocated memory:

#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
    unsafe {
        if !s.is_null() {
            let _ = CString::from_raw(s);
        }
    }
}

#[no_mangle]
pub extern "C" fn free_operation_result(result: OperationResult) {
    if !result.is_ok {
        free_string(result.err as *mut c_char);
    }
}

The user will be required to call these to dispose of the result. Here's a usage example:

// This allocates result
OperationResult result = perform_operation(42);
if (result.is_ok) {
    // do something with valid result
} else {
    printf("Error: %s\n", result.err);
}
// result must always be freed
free_operation_result(result);

Alternative Error Handling Approach

Info

For a more efficient approach, more similar to classic C programming, use negative values as errors with constants for readability.

// Alternative approach using constants for error codes
#[repr(C)]
pub struct OperationResultAlt {
    pub value: i32, // Negative values indicate errors
}

// Constants for error codes
pub const OPERATION_SUCCESS: i32 = 0;
pub const OPERATION_ERROR_INVALID_INPUT: i32 = -1;
pub const OPERATION_ERROR_NETWORK: i32 = -2;
pub const OPERATION_ERROR_UNKNOWN: i32 = -3;

#[no_mangle]
pub extern "C" fn perform_operation_alt(value: i32) -> OperationResultAlt {
    match your_rust_function(value) {
        Ok(result) => OperationResultAlt { 
            value: result 
        },
        Err(_) => OperationResultAlt { 
            value: OPERATION_ERROR_UNKNOWN 
        },
    }
}

#[no_mangle]
pub extern "C" fn perform_operation_is_error(result: OperationResultAlt) -> bool {
    result.value < OPERATION_SUCCESS
}

Manual Header Generation

Manual generation is only needed when adjusting configuration.

Headers are auto-generated in automated builds and published in releases.

The template uses cbindgen to generate C and C++ header files from your Rust code.

Using VSCode, press Ctrl+Shift+P → "Run Task" → Select one of:

  • Generate C Bindings - Generate C headers only
  • Generate C++ Bindings - Generate C++ headers only

Or from command line:

cd src

# Install cbindgen (one-time setup)
cargo install cbindgen

# Generate C bindings
cbindgen --config ../.github/cbindgen_c.toml --output bindings/c/your-project.h your-project

# Generate C++ bindings
cbindgen --config ../.github/cbindgen_cpp.toml --output bindings/cpp/your-project.hpp your-project

Replace your-project with your actual project name. Configuration files are located in .github/:

  • .github/cbindgen_c.toml - C bindings configuration
  • .github/cbindgen_cpp.toml - C++ bindings configuration

Automatic Header Generation

Info

Your C/C++ headers are automatically generated when you push a tag to make a release.

When you push a release tag, headers are generated with cbindgen, and attached to your GitHub release. See Automated Testing & Publishing for details.

Generate headers locally:

cd src
cargo build --features c-exports  # Generate C/C++ headers

Headers are placed in target/headers/ directory.

C Bindings Releases

Headers in GitHub Releases

You can also find the headers in 'Artifacts' of 'GitHub Actions' runs for regular builds.

Integration with Existing Projects

Info

Add C/C++ bindings to existing projects by copying configuration files from the template.

1. Generate Template Files

First, generate a fresh template to get the latest configuration files:

cargo generate --git https://github.com/Reloaded-Project/reloaded-templates-rust.git

2. Copy Configuration Files

Copy these key files from the generated template to your existing project:

  • cbindgen_c.toml and cbindgen_cpp.toml - cbindgen configuration
  • src/exports.rs - template for your export functions
  • Relevant sections from Cargo.toml for the c-exports feature

3. Update Project Configuration

Add the c-exports feature to your Cargo.toml:

[features]
default = ["std"]
std = []
c-exports = []

Include the exports module in your src/lib.rs:

#[cfg(feature = "c-exports")]
pub mod exports;

See the main documentation for more details.