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
.