DEV Community

Cover image for Bun-ffi - Getting started with C++ and Bun
Calum Knott
Calum Knott

Posted on

Bun-ffi - Getting started with C++ and Bun

This post is intended to be a little intro guide on how to call C++ code from Bun using FFI.

Repo is at the bottom!

I found some more complicated examples online, but I wanted to make a minimal example that just works out of the box.

Bun's Foreign Function Interface (FFI) lets you call native C/C++ code directly from JavaScript with near-zero overhead. This is perfect when you need performance-critical operations or want to leverage existing C++ libraries.

In this little post, we'll build a minimal example that calls C++ functions from Bun.

Why Use FFI?

  • Performance: C++ sometimes runs faster than JavaScript for compute-intensive tasks
  • Existing Libraries: Tap into unique or required C/C++ libraries
  • System-Level Access: Work with low-level APIs not directly available in JavaScript

Project Structure

├── cpp/
│   └── cpp_math.cpp      # C++ source code
├── bun/
│   └── index.js          # Bun FFI wrapper
├── dist/
│   └── cpp_math.dylib    # Compiled shared library
├── build_cpp.sh          # Build script for the C++ code
└── build_bun.sh          # Build script for the Bun code

Enter fullscreen mode Exit fullscreen mode

Step 1: Write Your C++ Code

First, create a C++ file with functions you want to export.

cpp_math.cpp

#include <stdint.h>

// Export function with C linkage to avoid name mangling
extern "C" {
    int32_t add(int32_t a, int32_t b) {
        return a + b;
    }

    int32_t multiply(int32_t a, int32_t b) {
        return a * b;
    }
}
Enter fullscreen mode Exit fullscreen mode

Important: Use extern "C" so Bun can find your functions by their simple names (add, multiply) instead of mangled C++ names.

Step 2: Compile to a Shared Library

Create a build script that compiles your C++ code into a shared library (.dylib on macOS, .so on Linux, .dll on Windows):

build_cpp.sh

#!/bin/bash
clang++ -shared -fPIC -O3 -o dist/cpp_math.dylib cpp/cpp_math.cpp
Enter fullscreen mode Exit fullscreen mode

Run it:

chmod +x build_cpp.sh
./build_cpp.sh
Enter fullscreen mode Exit fullscreen mode

The flags:

  • -shared: Create a shared library
  • -fPIC: Generate position-independent code (required for shared libraries)
  • -O3: Optimize for performance? (Co-pilot told me to add this! :D)

Step 3: Call from Bun using FFI

Now use Bun's dlopen to load and call your C++ functions:

import { dlopen, FFIType, suffix } from "bun:ffi";

// Import the dylib as an embedded asset (for compiled binaries)
import cpp_math from "../dist/cpp_math.dylib" with { type: "file" };

// Find the library (works in dev and when compiled)
const libName = `cpp_math.${suffix}`;
const libPath = cpp_math || [
    `${import.meta.dir}/${libName}`,
    `${import.meta.dir}/../dist/${libName}`,
].find(p => Bun.file(p).exists());

// Load the library and define function signatures
const cpp_math_lib = dlopen(libPath, {
    add: {
        args: [FFIType.i32, FFIType.i32],
        returns: FFIType.i32,
    },
    multiply: {
        args: [FFIType.i32, FFIType.i32],
        returns: FFIType.i32,
    }
});

// Call the C++ functions!
console.log("C++ Add:", cpp_math_lib.symbols.add(10, 20));      // 30
console.log("C++ Multiply:", cpp_math_lib.symbols.multiply(10, 20)); // 200
Enter fullscreen mode Exit fullscreen mode

How It Works

  1. dlopen(path, symbols): Opens the shared library
  2. Function signatures: Tell Bun the argument and return types
  3. FFIType: Maps JavaScript types to C types (i32, f64, ptr, etc.)
  4. suffix: Platform-specific extension (dylib/so/dll)
  5. Import with type: "file": Embeds the library for standalone compilation

Bonus: Compile to a Single Binary

You can bundle everything into a single executable:

build_bun.sh

bun build ./bun/index.js --compile --outfile dist/bun_binary
Enter fullscreen mode Exit fullscreen mode

The import ... with { type: "file" } statement embeds the .dylib into the compiled binary, making it truly self-contained!

Available FFI Types

Common types you'll use:

  • FFIType.i32 / FFIType.i64 - Integers (32/64-bit)
  • FFIType.u32 / FFIType.u64 - Unsigned integers
  • FFIType.f32 / FFIType.f64 - Floats (32/64-bit)
  • FFIType.ptr - Pointers
  • FFIType.void - No return value
  • FFIType.cstring - C string (null-terminated)

When to Use FFI

Good for:

  • CPU-intensive calculations (image processing, cryptography, physics)
  • Wrapping existing C/C++ libraries
  • Hardware/system-level operations

Conclusion

Bun's FFI makes it trivially easy to call C++ code from JavaScript. With just three files (C++ source, build script, and JS wrapper), you can achieve native performance while keeping the convenience of JavaScript.

The ability to compile everything into a single binary makes distribution simple—no need to worry about shipping separate .dylib files!


Full code: Check out this repo for the complete working example.
https://github.com/calumk/example-repo-bun-ffi-cpp

GitHub logo calumk / example-repo-bun-ffi-cpp

Bun's Foreign Function Interface (FFI) lets you call native C/C++ code directly from JavaScript with near-zero overhead. This is perfect when you need performance-critical operations or want to leverage existing C++ libraries.

Bun FFI Experiment

A minimal example of calling C++ functions from Bun using FFI.

Structure

  • cpp/cpp_math.cpp - C++ source with a simple add() function
  • build_cpp.sh - Compiles C++ to a shared library
  • bun/index.js - Bun script that calls the C++ function via FFI
  • build_bun.sh - Compiles the Bun script into a standalone executable, including the C++ shared library

Usage

  1. Build the C++ library:
./build_cpp.sh
Enter fullscreen mode Exit fullscreen mode
  1. Run the Bun script:
bun run bun/index.js
Enter fullscreen mode Exit fullscreen mode
  1. Build the Bun executable:
./build_bun.sh
Enter fullscreen mode Exit fullscreen mode

Run the Bun executable:

./dist/bun_binary
Enter fullscreen mode Exit fullscreen mode

Expected output:

C++ Add: 10 + 20 = 30
C++ Multiply: 10 * 20 = 200
JS Add: 10 + 20 = 30
JS Multiply: 10 * 20 = 200
Enter fullscreen mode Exit fullscreen mode

Top comments (0)