ffi

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 18, 2025 License: MIT Imports: 5 Imported by: 8

README

ffi

Go Reference

A purego binding for libffi.

Purpose

You can use purego to call C code without cgo. ffi provides extra functionality (e.g. passing and returning structs by value).

Requirements

OS/Architecture
  • darwin/amd64
  • darwin/arm64
  • freebsd/amd64
  • freebsd/arm64
  • linux/amd64
  • linux/arm64
  • windows/amd64
  • windows/arm64
Software

libffi is preinstalled on most distributions, because it also is a dependency of Python and Ruby. If not, you can install it explicitly:

Arch Linux
sudo pacman -S libffi
Debian 12, Ubuntu 22.04, Ubuntu 24.04
sudo apt install libffi8
FreeBSD
pkg install libffi

Note: Use this -gcflags="github.com/ebitengine/purego/internal/fakecgo=-std" build flag when cross compiling or having CGO_ENABLED set to 0 (FreeBSD only).

Windows

You need a libffi-8.dll next to the executable/root folder of your project or inside C:\Windows\System32. If you don't want to build libffi from source, you can find this dll for example inside the Windows embeddable package of Python.

macOS

You can use Homebrew to install libffi:

brew install libffi

Note: If dlopen can't find the libffi.8.dylib file, you can try setting this environment variable:

export DYLD_FALLBACK_LIBRARY_PATH=$DYLD_FALLBACK_LIBRARY_PATH:/opt/homebrew/opt/libffi/lib

Examples

In this example we create our own library, which consists of two type definitions and one function:

#include <stdbool.h>
#include <string.h>

typedef enum {
    GROCERIES,
    HOUSEHOLD,
    BEAUTY
} Category;

typedef struct {
    const char *name;
    double price;
    Category category;
} Item;

bool IsItemValid(Item item)
{
    if (!item.name || strlen(item.name) == 0)
    {
        return false;
    }

    if (item.price < 0)
    {
        return false;
    }

    if (item.category > BEAUTY)
    {
        return false;
    }

    return true;
}

Compile the code into a shared library:

gcc -shared -o libitem.so -fPIC item.c

The consuming Go code:

package main

import (
	"fmt"

	"github.com/jupiterrider/ffi"
)

type Category uint32

const (
	Groceries Category = iota
	Household
	Beauty
)

type Item struct {
	Name     *byte
	Price    float64
	Category Category
}

func main() {
	// load the library
	lib, err := ffi.Load("./libitem.so")
	if err != nil {
		panic(err)
	}

	// create a new ffi.Type which defines the fields of the Item struct
	typeItem := ffi.NewType(&ffi.TypePointer, &ffi.TypeDouble, &ffi.TypeUint32)

	// get the IsItemValid function and describe its signature
	// (for bool we use ffi.TypeUint8)
	isItemValid, err := lib.Prep("IsItemValid", &ffi.TypeUint8, &typeItem)
	if err != nil {
		panic(err)
	}

	var item Item
	// strings are null-terminated and converted into a byte pointer
	item.Name = &[]byte("Apple\x00")[0]
	item.Price = 0.22
	item.Category = Groceries

	// the return value is stored in a 64-bit integer type, because libffi
	// cannot handle smaller integer types as return value
	var result ffi.Arg

	// call the C function
	// (keep in mind that you have to pass pointers and not the values themselves)
	isItemValid.Call(&result, &item)

	if result.Bool() {
		fmt.Println("Item is valid!")
	} else {
		fmt.Println("Item is not valid!")
	}
}

You can find more examples inside the examples folder of this repository.

Documentation

Index

Examples

Constants

View Source
const (
	Void = iota
	Int
	Float
	Double
	Longdouble
	Uint8
	Sint8
	Uint16
	Sint16
	Uint32
	Sint32
	Uint64
	Sint64
	Struct
	Pointer
	Complex
)

These constants are used for the Type field of Type.

View Source
const TrampolineSize = 32

Variables

View Source
var (
	TypeVoid              = Type{1, 1, Void, nil}
	TypeUint8             = Type{1, 1, Uint8, nil}
	TypeSint8             = Type{1, 1, Sint8, nil}
	TypeUint16            = Type{2, 2, Uint16, nil}
	TypeSint16            = Type{2, 2, Sint16, nil}
	TypeUint32            = Type{4, 4, Uint32, nil}
	TypeSint32            = Type{4, 4, Sint32, nil}
	TypeUint64            = Type{8, 8, Uint64, nil}
	TypeSint64            = Type{8, 8, Sint64, nil}
	TypeFloat             = Type{4, 4, Float, nil}
	TypeDouble            = Type{8, 8, Double, nil}
	TypePointer           = Type{8, 8, Pointer, nil}
	TypeLongdouble        = Type{16, 16, Longdouble, nil}
	TypeComplexFloat      = Type{8, 4, Complex, &[]*Type{&TypeFloat, nil}[0]}
	TypeComplexDouble     = Type{16, 8, Complex, &[]*Type{&TypeDouble, nil}[0]}
	TypeComplexLongdouble = Type{32, 16, Complex, &[]*Type{&TypeLongdouble, nil}[0]}
)

Predefined variables for primitive data types.

For bool you can use TypeUint8 and check its value:

byte(returnValue) != 0

A string is just an array of characters in C (often seen as char * or const char *). Use TypePointer for that.

To convert strings between C and Go take a look at golang.org/x/sys/unix.BytePtrFromString and golang.org/x/sys/unix.BytePtrToString. The Windows counterparts are golang.org/x/sys/windows.BytePtrFromString and golang.org/x/sys/windows.BytePtrToString.

Slices are treated as pointers as well. You can use unsafe.Slice to convert a pointer into a slice.

Functions

func Call

func Call(cif *Cif, fn uintptr, rValue unsafe.Pointer, aValues ...unsafe.Pointer)

Call calls the function fn according to the description given in cif. cif must have already been prepared using PrepCif.

  • fn is the address of the desired function. Use purego.Dlsym to get one.
  • rValue is a pointer to a variable that will hold the result of the function call. Provide nil if the function has no return value. You cannot use integer types smaller than 8 bytes here (float32 and structs are not affected). Use Arg instead and typecast afterwards.
  • aValues are pointers to the argument values. Leave empty or provide nil if the function takes none.

Example:

// C function:
// int ilogb(double x);

var result ffi.Arg
x := 1.0
ffi.Call(&cif, ilogb, unsafe.Pointer(&result), unsafe.Pointer(&x))
fmt.Printf("%d\n", int32(result))

func ClosureFree added in v0.3.0

func ClosureFree(writable *Closure)

ClosureFree is used to free memory allocated by ClosureAlloc.

func NewCallback added in v0.3.0

func NewCallback(fn Callback) uintptr

NewCallback converts the Go function fn into a C function pointer. The returned value can be used as parameter in PrepClosureLoc.

Types

type Abi

type Abi uint32
const (
	DefaultAbi Abi = 2
)

type Arg

type Arg uint64

Arg can be used as a return value for functions, which return integers smaller than 8 bytes.

See Call.

func (Arg) Bool added in v0.3.0

func (a Arg) Bool() bool

Bool converts Arg into a Boolean.

type Callback added in v0.3.0

type Callback func(cif *Cif, ret unsafe.Pointer, args *unsafe.Pointer, userData unsafe.Pointer) uintptr

Callback has the following parameters:

  • cif is the same object that was passed to PrepClosureLoc.
  • ret is a pointer to the memory used for the function's return value. If the function is declared as returning void, then this value is garbage and should not be used.
  • args is a C array of pointers to the arguments. You can use unsafe.Slice to convert it.
  • userData is the same data that was passed to PrepClosureLoc.

The returned uintptr can be ignored. It's just there for compatibility reasons.

Example:

// We assume that the closure has the following signature:
// int64_t Add(int64_t a, int64_t b);

cb := ffi.NewCallback(func(cif *ffi.Cif, ret unsafe.Pointer, args *unsafe.Pointer, userData unsafe.Pointer) uintptr {
	arguments := unsafe.Slice(args, cif.NArgs)
	*(*int64)(ret) = *(*int64)(arguments[0]) + *(*int64)(arguments[1])
	return 0
})

type Cif

type Cif struct {
	Abi      uint32
	NArgs    uint32
	ArgTypes **Type
	RType    *Type
	Bytes    uint32
	Flags    uint32
}

Cif stands for "Call InterFace". It describes the signature of a function.

Use PrepCif to initialize it.

type Closure added in v0.3.0

type Closure struct {
	Tramp    [TrampolineSize]byte
	Cif      *Cif
	Fun      unsafe.Pointer
	UserData unsafe.Pointer
}

Closure can be used to create callbacks (function pointers) at runtime.

Use ClosureAlloc for allocation and PrepClosureLoc for preparation.

func ClosureAlloc added in v0.3.0

func ClosureAlloc(size uintptr, code *unsafe.Pointer) *Closure

ClosureAlloc allocates a new Closure.

  • size should be big enough to hold a Closure object.
  • code is the corresponding executable address (function pointer).

The Closure is not managed by Go's garbage collector. It can be deallocated by using ClosureFree.

Example:

var code unsafe.Pointer
closure := ffi.ClosureAlloc(unsafe.Sizeof(ffi.Closure{}), &code)
defer ffi.ClosureFree(closure)

type Fun added in v0.3.0

type Fun struct {
	Addr uintptr
	Cif  *Cif
}

Fun is used to group a function's address and the related call interface together.

You can use Lib.Prep or Lib.PrepVar to create one.

func (Fun) Call added in v0.3.0

func (f Fun) Call(ret any, args ...any)

Call calls the function's address according to the description given in Cif.

  • ret is a pointer to a variable that will hold the result of the function call. Provide nil if the function has no return value. You cannot use integer types smaller than 8 bytes here (float32 and structs are not affected). Use Arg instead and typecast afterwards.
  • args are pointers to the argument values. Leave empty or provide nil if the function takes none.

Example:

// C function:
// int ilogb(double x);

var result ffi.Arg
x := 1.0
ilogb.Call(&result, &x)
fmt.Printf("%d\n", int32(result))

type Lib added in v0.3.0

type Lib struct {
	Addr uintptr
}

Lib holds the address to a shared library.

Use Load to initialize one.

func Load added in v0.3.0

func Load(name string) (l Lib, err error)

Load loads a shared library at runtime.

The name can be an absolute path, relative path or just the filename. If just the filename is passed, it will use the OS specific search paths.

Example:

var filename string

switch runtime.GOOS {
case "freebsd", "linux":
	filename = "libraylib.so"
case "windows":
	filename = "raylib.dll"
case "darwin":
	filename = "libraylib.dylib"
}

raylib, err := Load(filename)

func (Lib) Close added in v0.3.0

func (l Lib) Close() error

Close deletes a reference to the library. If the reference count is zero, the library gets unloaded.

func (Lib) Get added in v0.3.0

func (l Lib) Get(name string) (addr uintptr, err error)

Get retrieves the address of an exported function or variable.

Example:

// C code:
// int magic_number = 42;

magicNumber, err := lib.Get("magic_number")
if err != nil {
	panic(err)
}

// prints 42
fmt.Println(*(*int32)(unsafe.Pointer(magicNumber)))

// if go vet yells "possible misuse of unsafe.Pointer",
// you can do the following workaround:
fmt.Println(**(**int32)(unsafe.Pointer(&magicNumber)))

func (Lib) Prep added in v0.3.0

func (l Lib) Prep(name string, ret *Type, args ...*Type) (f Fun, err error)

Prep is used to get and describe a library's function.

  • name is the name of the function.
  • ret is the return type. Use TypeVoid if the function has none.
  • args are the arguments. Leave empty or provide nil if the function has none.

For variadic functions use Lib.PrepVar instead.

Example:

// C function:
// double cos(double x);

cos, err := libm.Prep("cos", &ffi.TypeDouble, &ffi.TypeDouble)
if err != nil {
	panic(err)
}

func (Lib) PrepVar added in v0.3.0

func (l Lib) PrepVar(name string, nFixedArgs int, ret *Type, args ...*Type) (f Fun, err error)

PrepVar is used to get and describe a variadic function.

In general its operation is the same as for Lib.Prep except that:

  • nFixedArgs is the number of fixed arguments, prior to any variadic arguments. It must be greater than zero.

This function will return an error if any of the variable argument types is TypeFloat. Same goes for integer types smaller than 4 bytes. See issue 608.

Note that, different cif's must be prepped for calls to the same function when different numbers of arguments are passed.

Also note that a call to this function with nFixedArgs = len(args) is NOT equivalent to a call to Lib.Prep.

Example:

// C function:
// int printf(const char *restrict format, ...);

printf, err := libc.PrepVar("printf", 1, &ffi.TypeSint32, &ffi.TypePointer, &ffi.TypeDouble)
if err != nil {
	panic(err)
}

text, _ := unix.BytePtrFromString("Pi is %f\n")
pi := math.Pi
var nCharsPrinted int32
printf.Call(&nCharsPrinted, &text, &pi)

type Status

type Status uint32
const (
	OK Status = iota
	BadTypedef
	BadAbi
	BadArgType
)

func PrepCif

func PrepCif(cif *Cif, abi Abi, nArgs uint32, rType *Type, aTypes ...*Type) Status

PrepCif initializes cif.

  • abi is the ABI to use. Normally DefaultAbi is what you want.
  • nArgs is the number of arguments. Use 0 if the function has none.
  • rType is the return type. Use TypeVoid if the function has none.
  • aTypes are the arguments. Leave empty or provide nil if the function has none.

The returned status code will be OK, if everything worked properly.

Example:

// C function:
// double cos(double x);

var cif ffi.Cif
status := ffi.PrepCif(&cif, ffi.DefaultAbi, 1, &ffi.TypeDouble, &ffi.TypeDouble)
if status != ffi.OK {
	panic(status)
}

func PrepCifVar

func PrepCifVar(cif *Cif, abi Abi, nFixedArgs, nTotalArgs uint32, rType *Type, aTypes ...*Type) Status

PrepCifVar initializes cif for a call to a variadic function.

In general its operation is the same as for PrepCif except that:

  • nFixedArgs is the number of fixed arguments, prior to any variadic arguments. It must be greater than zero.
  • nTotalArgs is the total number of arguments, including variadic and fixed arguments. aTypes must have this many elements.

This function will return BadArgType if any of the variable argument types is TypeFloat. Same goes for integer types smaller than 4 bytes. See issue 608.

Note that, different cif's must be prepped for calls to the same function when different numbers of arguments are passed.

Also note that a call to this function with nFixedArgs = nTotalArgs is NOT equivalent to a call to PrepCif.

Example:

// C function:
// int printf(const char *restrict format, ...);

var cif ffi.Cif
status := ffi.PrepCifVar(&cif, ffi.DefaultAbi, 1, 2, &ffi.TypeSint32, &ffi.TypePointer, &ffi.TypeDouble)
if status != ffi.OK {
	panic(status)
}

text, _ := unix.BytePtrFromString("Pi is %f\n")
pi := math.Pi
var nCharsPrinted int32
ffi.Call(&cif, printf, unsafe.Pointer(&nCharsPrinted), unsafe.Pointer(&text), unsafe.Pointer(&pi))

func PrepClosureLoc added in v0.3.0

func PrepClosureLoc(closure *Closure, cif *Cif, fun uintptr, userData, codeLoc unsafe.Pointer) Status

PrepClosureLoc creates a C function (so-called closure) at runtime.

  • closure is the object return by ClosureAlloc.
  • cif describes the signature of the function to be created. Use PrepCif for initialization.
  • fun is a pointer to a C function which will be called when the closure is invoked. You can use NewCallback to create one.
  • userData is arbitrary and optional (can be nil) data passed to your closure function fun.
  • codeLoc is the executable address allocated by ClosureAlloc.
Example
package main

import (
	"fmt"
	"math"
	"unsafe"

	"github.com/jupiterrider/ffi"
)

func main() {
	var sin unsafe.Pointer
	closure := ffi.ClosureAlloc(unsafe.Sizeof(ffi.Closure{}), &sin)
	defer ffi.ClosureFree(closure)

	fun := ffi.NewCallback(func(cif *ffi.Cif, ret unsafe.Pointer, args *unsafe.Pointer, userData unsafe.Pointer) uintptr {
		arg := unsafe.Slice(args, cif.NArgs)[0]
		sine := math.Sin(*(*float64)(arg))
		*(*float64)(ret) = sine
		return 0
	})

	var cif ffi.Cif
	if status := ffi.PrepCif(&cif, ffi.DefaultAbi, 1, &ffi.TypeDouble, &ffi.TypeDouble); status != ffi.OK {
		panic(status)
	}

	if status := ffi.PrepClosureLoc(closure, &cif, fun, nil, sin); status != ffi.OK {
		panic(status)
	}

	var sine float64
	var x float64 = 1
	ffi.Call(&cif, uintptr(sin), unsafe.Pointer(&sine), unsafe.Pointer(&x))
	fmt.Println(sine)
}
Output:

0.8414709848078965

func (Status) String

func (s Status) String() string

type Type

type Type struct {
	Size      uint64 // Initialize to 0 (automatically set by libffi as needed).
	Alignment uint16 // Initialize to 0 (automatically set by libffi as needed).
	Type      uint16 // Use ffi.Struct for struct types.
	Elements  **Type // Pointer to the first element of a nil-terminated slice.
}

Type is used to describe the structure of a data type.

Example:

// C struct:
// typedef struct Point {
//     int x;
//     int y;
// } Point;

typePoint := ffi.Type{Type: ffi.Struct, Elements: &[]*ffi.Type{&ffi.TypeSint32, &ffi.TypeSint32, nil}[0]}

Primitive data types are already defined (e.g. TypeDouble for float64).

func NewType added in v0.2.2

func NewType(elements ...*Type) Type

NewType can by used to create a new struct Type.

Example:

ffi.NewType(&ffi.TypeFloat, &ffi.TypeFloat)
// is equivalent to
ffi.Type{Type: ffi.Struct, Elements: &[]*ffi.Type{&ffi.TypeFloat, &ffi.TypeFloat, nil}[0]}

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL