purego

package module
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: May 24, 2023 License: Apache-2.0 Imports: 7 Imported by: 0

README

purego

Go Reference

A library for calling C functions from Go without Cgo.

Motivation

The Ebitengine game engine was ported to use only Go on Windows. This enabled cross-compiling to Windows from any other operating system simply by setting GOOS=windows. The purego project was born to bring that same vision to the other platforms supported by Ebitengine.

Benefits

  • Simple Cross-Compilation: No C means you can build for other platforms easily without a C compiler.
  • Faster Compilation: Efficiently cache your entirely Go builds.
  • Smaller Binaries: Using Cgo generates a C wrapper function for each C function called. Purego doesn't!
  • Dynamic Linking: Load symbols at runtime and use it as a plugin system.
  • Foreign Function Interface: Call into other languages that are compiled into shared objects.

Example

This example only works on macOS and Linux. For a complete example look at libc which supports Windows.

package main

import (
	"fmt"
	"runtime"

	"github.com/ebitengine/purego"
)

func getSystemLibrary() string {
	switch runtime.GOOS {
	case "darwin":
		return "/usr/lib/libSystem.B.dylib"
	case "linux":
		return "libc.so.6"
	default:
		panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS))
	}
}

func main() {
	libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL)
	if err != nil {
		panic(err)
	}
	var puts func(string)
	purego.RegisterLibFunc(&puts, libc, "puts")
	puts("Calling C from Go without Cgo!")
}

Then to run: CGO_ENABLED=0 go run main.go

External Code

Purego uses code that originates from the Go runtime. These files are under the BSD-3 License that can be found in the Go Source. This is a list of the copied files:

  • zcallback_darwin_*.s from package runtime
  • internal/abi/abi_*.h from package runtime/cgo
  • internal/fakecgo/asm_GOARCH.s from package runtime/cgo
  • internal/fakecgo/callbacks.go from package runtime/cgo
  • internal/fakecgo/go_GOOS_GOARCH.go from package runtime/cgo
  • internal/fakecgo/iscgo.go from package runtime/cgo
  • internal/fakecgo/setenv.go from package runtime/cgo

Documentation

Index

Examples

Constants

View Source
const (
	RTLD_DEFAULT = 0x00000 // Pseudo-handle for dlsym so search for any loaded symbol
	RTLD_LAZY    = 0x00001 // Relocations are performed at an implementation-dependent time.
	RTLD_NOW     = 0x00002 // Relocations are performed when the object is loaded.
	RTLD_LOCAL   = 0x00000 // All symbols are not made available for relocation processing by other modules.
	RTLD_GLOBAL  = 0x00100 // All symbols are available for relocation processing of other modules.
)

Variables

This section is empty.

Functions

func Dlclose

func Dlclose(handle uintptr) error

Dlclose decrements the reference count on the dynamic library handle. If the reference count drops to zero and no other loaded libraries use symbols in it, then the dynamic library is unloaded.

func Dlopen

func Dlopen(path string, mode int) (uintptr, error)

Dlopen examines the dynamic library or bundle file specified by path. If the file is compatible with the current process and has not already been loaded into the current process, it is loaded and linked. After being linked, if it contains any initializer functions, they are called, before Dlopen returns. It returns a handle that can be used with Dlsym and Dlclose. A second call to Dlopen with the same path will return the same handle, but the internal reference count for the handle will be incremented. Therefore, all Dlopen calls should be balanced with a Dlclose call.

func Dlsym

func Dlsym(handle uintptr, name string) (uintptr, error)

Dlsym takes a "handle" of a dynamic library returned by Dlopen and the symbol name. It returns the address where that symbol is loaded into memory. If the symbol is not found, in the specified library or any of the libraries that were automatically loaded by Dlopen when that library was loaded, Dlsym returns zero.

func NewCallback

func NewCallback(_ interface{}) uintptr
Example
package main

import (
	"fmt"
	"runtime"

	"github.com/ebitengine/purego"
)

func main() {
	if runtime.GOOS == "linux" {
		// TODO: enable once callbacks are working properly on Linux
		fmt.Println("1 2 3 4 5 6 7 8 9\n45")
		return
	}

	cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int {
		fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9)
		return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9
	})

	var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int) int
	purego.RegisterFunc(&fn, cb)

	ret := fn(1, 2, 3, 4, 5, 6, 7, 8, 9)
	fmt.Println(ret)

}
Output:

1 2 3 4 5 6 7 8 9
45

func RegisterFunc

func RegisterFunc(fptr interface{}, cfn uintptr)

RegisterFunc takes a pointer to a Go function representing the calling convention of the C function. fptr will be set to a function that when called will call the C function given by cfn with the parameters passed in the correct registers and stack.

A panic is produced if the type is not a function pointer or if the function returns more than 1 value.

These conversions describe how a Go type in the fptr will be used to call the C function. It is important to note that there is no way to verify that fptr matches the C function. This also holds true for struct types where the padding needs to be ensured to match that of C; RegisterFunc does not verify this.

Type Conversions (Go => C)

string <=> char*
bool <=> _Bool
uintptr <=> uintptr_t
uint <=> uint32_t or uint64_t
uint8 <=> uint8_t
uint16 <=> uint16_t
uint32 <=> uint32_t
uint64 <=> uint64_t
int <=> int32_t or int64_t
int8 <=> int8_t
int16 <=> int16_t
int32 <=> int32_t
int64 <=> int64_t
float32 <=> float (WIP)
float64 <=> double (WIP)
struct <=> struct (WIP)
func <=> C function
unsafe.Pointer, *T <=> void*
[]T => void*

There is a special case when the last argument of fptr is a variadic interface (or []interface} it will be expanded into a call to the C function as if it had the arguments in that slice. This means that using arg ...interface{} is like a cast to the function with the arguments inside arg. This is not the same as C variadic.

There are some limitations when using RegisterFunc on Linux. First, there is no support for function arguments. Second, float32 and float64 arguments and return values do not work when CGO_ENABLED=1. Otherwise, Linux has the same feature parity as Darwin.

func RegisterLibFunc

func RegisterLibFunc(fptr interface{}, handle uintptr, name string)

RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). It panics if it can't find the name symbol.

func SyscallN

func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr)

SyscallN takes fn, a C function pointer and a list of arguments as uintptr. There is an internal maximum number of arguments that SyscallN can take. It panics when the maximum is exceeded. It returns the result and the libc error code if there is one.

NOTE: SyscallN does not properly call functions that have both integer and float parameters. See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 for an explanation of why that is.

On amd64, if there are more than 8 floats the 9th and so on will be placed incorrectly on the stack.

The pragma go:nosplit is not needed at this function declaration because it uses go:uintptrescapes which forces all the objects that the uintptrs point to onto the heap where a stack split won't affect their memory location.

Types

type Dlerror

type Dlerror struct {
	// contains filtered or unexported fields
}

Dlerror represents an error value returned from Dlopen, Dlsym, or Dlclose.

func (Dlerror) Error

func (e Dlerror) Error() string

Directories

Path Synopsis
examples
internal
cgo
Package objc is a low-level pure Go objective-c runtime.
Package objc is a low-level pure Go objective-c runtime.

Jump to

Keyboard shortcuts

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