Passing and returning integers

Integers are the “hello world!” of FFI, as they are generally much easier to pass across the boundary. Let’s create a library that adds two unsigned 32-bit numbers.

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

Compile this with cargo build, which will produce a library in target/debug/. The exact filename depends on your platform:

Platform Pattern
Windows *.dll
OS X lib*.dylib
Linux lib*.so

C

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

extern uint32_t
addition(uint32_t, uint32_t);

int main(void) {
  uint32_t sum = addition(1, 2);
  printf("%" PRIu32 "\n", sum);
}

We start by declaring an extern function with the proper argument and return types. This can then be compiled and linked against the Rust library using gcc --std=c11 -o c-example src/main.c -L target/debug/ -lintegers.

As noted in the basics section, this can be run on macOS and Linux with LD_LIBRARY_PATH=target/debug/ ./c-example, and on Windows by copying target\debug\integers.dll to the current directory and running .\c-example.

Ruby

require 'ffi'

module Integers
  extend FFI::Library
  ffi_lib 'integers'
  attach_function :addition, [:uint32, :uint32], :uint32
end

puts Integers.addition(1, 2)

This can be run with LD_LIBRARY_PATH=target/debug/ ruby ./src/main.rb

Python

#!/usr/bin/env python3

import sys, ctypes
from ctypes import c_uint32

prefix = {'win32': ''}.get(sys.platform, 'lib')
extension = {'darwin': '.dylib', 'win32': '.dll'}.get(sys.platform, '.so')
lib = ctypes.cdll.LoadLibrary(prefix + "integers" + extension)

lib.addition.argtypes = (c_uint32, c_uint32)
lib.addition.restype = c_uint32

print(lib.addition(1, 2))

As noted in the basics section, this can be run on macOS and Linux with LD_LIBRARY_PATH=target/debug/ python src/main.py, and on Windows by copying target\debug\integers.dll to the current directory and running py src\main.py.

Haskell

{-# LANGUAGE ForeignFunctionInterface #-}

import Data.Word (Word32)

foreign import ccall "addition"
  addition :: Word32 -> Word32 -> Word32

main :: IO ()
main = print (addition 1 2)

We have to enable the ForeignFunctionInterface language extension and import the relevant low-level types before we can include a foreign import declaration. This includes the calling convention (ccall), the symbol name ("addition"), the corresponding Haskell name (addition), and the type of the function. This function is effectively pure, so we don’t include IO in the type, but an observably impure function would want to return an IO value to indicate that it has side-effects.

This can be compiled using ghc src/main.hs target/debug/libintegers.so -o haskell-example.

Node.js

const ffi = require('ffi-napi');

const lib = ffi.Library('libintegers', {
  addition: ['uint32', ['uint32', 'uint32']],
});

console.log(lib.addition(1, 2));

The Library function specifies the name of a dynamic library to link with, along with a list of exported functions’ signatures (in the form of function_name: [return_type, [argument_types]]). These functions are then available as methods of the object returned by Library.

This can be run with LD_LIBRARY_PATH=target/debug node src/main.js.

C#

using System;
using System.Runtime.InteropServices;

class Integers
{
    [DllImport("integers", EntryPoint="addition")]
    public static extern uint Addition(uint a, uint b);

    static public void Main()
    {
        var sum = Integers.Addition(1, 2);
        Console.WriteLine(sum);
    }
}

We use the Platform Invoke functionality to access functions in a dynamic library. The DllImport attribute lists the name of the library that the function may be found in. These functions are then available as static methods of the class. To adhere to C# naming standards, we use the EntryPoint property to use a capitalized name for the exposed function.

This can be compiled with mcs -out:csharp-example src/main.cs and executed with LD_LIBRARY_PATH=target/debug mono csharp-example.

Julia

#!/usr/bin/env julia
using Libdl

libname = "integers"
if !Sys.iswindows()
    libname = "lib$(libname)"
end

libintegers = Libdl.dlopen(libname)
addition_sym = Libdl.dlsym(libintegers, :addition)

addition(a, b) = ccall(
    addition_sym,
    UInt32, (UInt32, UInt32), 
    a, b)

println(addition(1, 2))

Foreign function calls are made with the ccall primitive. If the library’s name is known in advance, we can also skip fetching the function pointer through dlsym, by passing a (func, lib) literal tuple:

addition(a, b) = ccall(
    (:addition, "libintegers"), # ← must be a constant expression!
    UInt32,
    (UInt32, UInt32), 
    a, b)

As noted in the basics section, this can be run on macOS and Linux with LD_LIBRARY_PATH=target/debug/ julia src/main.jl, and on Windows by copying target\debug\integers.dll to the current directory and running julia src\main.jl.