purego
repository·main·Indexed 25 days ago
https://github.com/ebitengine/puregoA library for calling C functions from Go without requiring Cgo. It facilitates easier cross-compilation, faster build times, and smaller binaries by using dynamic loading and a foreign function interface (FFI) approach. It provides tools for loading shared libraries via Dlopen, mapping C functions to Go using RegisterFunc and RegisterLibFunc, and creating C-compatible callbacks with NewCallback.
What's inside purego
- purego is a library that allows calling C functions from Go without using Cgo. This enables easier cross-compilation, faster build times, and smaller binaries by avoiding the C wrapper functions generated by Cgo. It also supports dynamic linking and acting as a foreign function interface (FFI) for shared objects.
Handle C-created strings in Go
mainWhen a C function returns a
char*(null-terminated pointer to a string):- Automatic Copy: If you use a Go
stringreturn type,puregoallocates a new Go string and copies the data. This string is managed by the Go garbage collector. - Manual Management: If the pointer is not null-terminated, or if you need the pointer to continue pointing to C memory (e.g., a buffer), use a pointer to a byte (
*byte) and convert it to a slice usingunsafe.Slice. In this case, you are responsible for managing the lifetime of the pointer.
// Case 1: Go manages the string var foo func(s string) string goString := foo("copied") // Case 2: Manual management (caller must free) var foo2 func(b string) *byte mustFree := foo2("not copied\x00") defer free(mustFree)- Automatic Copy: If you use a Go
Call C functions from Go using purego
mainTo call C functions, use
purego.Dlopento load a shared library andpurego.RegisterLibFuncto bind a Go function variable to a symbol in that library.Note: The example below is specific to macOS and Linux. For Windows or FreeBSD support, refer to the complete example in the repository's
examples/libcdirectory.```go 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!") }To run this example with Cgo disabled:
CGO_ENABLED=0 go run main.goSupported Platforms for purego
mainPureGo supports various platforms categorized into Tier 1 (officially supported, bugs are release blockers) and Tier 2 (best-effort support).
Tier 1
- Android: amd64, arm64 (requires
CGO_ENABLED=1) - iOS: amd64, arm64 (requires
CGO_ENABLED=1) - Linux: amd64, arm64
- macOS: amd64, arm64
- Windows: amd64, arm64 (requires
CGO_ENABLED=1)
Tier 2
- Android: 386, arm (requires
CGO_ENABLED=1; supports structs by value in arguments/returns but not inNewCallbackcallbacks) - FreeBSD: amd64, arm64 (requires
CGO_ENABLED=1; supports structs by value in arguments/returns but not inNewCallbackcallbacks) - Linux: 386, arm, loong64, ppc64le, riscv64, s390x
- NetBSD: amd64, arm64
- Windows: 386, arm
Compilation Notes
- For certain Tier 2 architectures, you must use the flag
-gcflags="github.com/ebitengine/purego/internal/fakecgo=-std"to compile withCGO_ENABLED=0.
- Android: amd64, arm64 (requires
Handle dynamic linking errors with Dlerror
mainWhen performing dynamic linking operations such as
Dlopen,Dlsym, orDlcloseon supported Unix-like platforms (Darwin, FreeBSD, Linux, or NetBSD), errors may be returned as aDlerrortype. This type implements the standard Goerrorinterface and provides the error message string via the.Error()method.Note:
Dlerroris not available on Windows because Windows does not have a direct counterpart to these specific dynamic linking error mechanisms.Retrieve symbols with Dlsym
mainUse
Dlsymto find the memory address of a symbol (function or variable) within a dynamic library. It requires ahandleobtained from a previousDlopencall and thenameof the symbol.Note: This function is not available on Windows. For Windows, use
golang.org/x/sys/windows.GetProcAddressinstead.Map C functions by name using RegisterLibFunc
mainUse
RegisterLibFuncto find a symbol by name within a loaded library handle and map it to a Go function. This is a wrapper aroundRegisterFuncthat usesDlsyminternally.Warning: This function will panic if the symbol name cannot be found in the provided library handle.
Load dynamic libraries with Dlopen
mainUse
Dlopento examine and load a dynamic library or bundle file specified bypath. If the library is compatible and not already loaded, it is linked and any initializer functions are called.Note: This function is not available on Windows. For Windows, use
golang.org/x/sys/windowsfunctions likeLoadLibraryorNewLazyDLLinstead.Call C functions using SyscallN
mainUse
SyscallNto call a C function pointer (fn) with a variable number of arguments. The function returns up to three return values (r1,r2, anderr) asuintptr.Constraints and Limitations:
- Argument Limit: You can pass a maximum of 32 arguments. Passing more will cause a panic.
- Nil Pointer: Passing a
fnvalue of0will cause a panic. - Float Parameters:
SyscallNdoes not properly support functions that have both integer and float parameters. Onamd64, if there are more than 8 floats, subsequent floats will be placed incorrectly on the stack. - Safety: When using
uintptrarguments that point to memory, you must follow all rules specified in theunsafe.Pointerdocumentation (specifically regarding pointer lifetime and stack management).
Unload dynamic libraries with Dlclose
mainUse
Dlcloseto decrement the reference count of a dynamic library handle. If the reference count reaches zero and no other loaded libraries depend on it, the library is unloaded.Note: This function is not available on Windows. For Windows, use
golang.org/x/sys/windows.FreeLibraryinstead.Map C functions to Go using RegisterFunc
mainUse
RegisterFuncto bind a Go function pointer to a C function address. The Go function (fptr) must have a signature that matches the C function's calling convention.Important Constraints:
fptrmust be a pointer to a function.- The function can return at most one value.
- There is no automatic verification that the Go signature matches the C function; incorrect signatures will cause undefined behavior or crashes.
- Memory Safety: For arguments, ensure the C code does not hold onto Go memory references. For strings, if the string is not null-terminated,
puregocopies it into temporary memory valid only for the duration of the call. If the string is already null-terminated,puregodoes not copy it, and you must ensure it stays alive (e.g., usingruntime.KeepAlive). - Structs: You must manually ensure that Go struct padding matches the C struct padding. On Apple ARM64 (macOS/iOS),
puregohandles stack alignment for struct arguments automatically.
Convert a Go function to a Windows stdcall callback with NewCallback
mainUse
NewCallbackto convert a Go function into a function pointer that conforms to the Windowsstdcallcalling convention. This is required when interoperating with Windows code that expects callbacks.Requirements and Constraints:
- The provided function
fnmust return exactly oneuintptr-sized result. - Arguments must not have a size larger than
uintptr. - A limited number of callbacks can be created in a single Go process, and the memory allocated for them is never released. However, at least 1024 callbacks can be created between calls to
NewCallbackandNewCallbackCDecl. - If the function includes a
CDecltype as its first argument,NewCallbackwill automatically usesyscall.NewCallbackCDeclinstead of the standardstdcallversion.
- The provided function