purego

repository·main·Indexed 25 days ago

https://github.com/ebitengine/purego

A 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.

Tokens
2.9K
Snippets
2
Records
17
Agent score
85%

What's inside purego

  1. Overview of purego

    main
    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.
  2. Handle C-created strings in Go

    main

    When a C function returns a char* (null-terminated pointer to a string):

    1. Automatic Copy: If you use a Go string return type, purego allocates a new Go string and copies the data. This string is managed by the Go garbage collector.
    2. 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 using unsafe.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)
  3. Call C functions from Go using purego

    main

    To call C functions, use purego.Dlopen to load a shared library and purego.RegisterLibFunc to 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/libc directory.

    ```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.go

  4. Supported Platforms for purego

    main

    PureGo 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 in NewCallback callbacks)
    • FreeBSD: amd64, arm64 (requires CGO_ENABLED=1; supports structs by value in arguments/returns but not in NewCallback callbacks)
    • 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 with CGO_ENABLED=0.
  5. Handle dynamic linking errors with Dlerror

    main

    When performing dynamic linking operations such as Dlopen, Dlsym, or Dlclose on supported Unix-like platforms (Darwin, FreeBSD, Linux, or NetBSD), errors may be returned as a Dlerror type. This type implements the standard Go error interface and provides the error message string via the .Error() method.

    Note: Dlerror is not available on Windows because Windows does not have a direct counterpart to these specific dynamic linking error mechanisms.

  6. Retrieve symbols with Dlsym

    main

    Use Dlsym to find the memory address of a symbol (function or variable) within a dynamic library. It requires a handle obtained from a previous Dlopen call and the name of the symbol.

    Note: This function is not available on Windows. For Windows, use golang.org/x/sys/windows.GetProcAddress instead.

  7. Map C functions by name using RegisterLibFunc

    main

    Use RegisterLibFunc to find a symbol by name within a loaded library handle and map it to a Go function. This is a wrapper around RegisterFunc that uses Dlsym internally.

    Warning: This function will panic if the symbol name cannot be found in the provided library handle.

  8. Load dynamic libraries with Dlopen

    main

    Use Dlopen to examine and load a dynamic library or bundle file specified by path. 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/windows functions like LoadLibrary or NewLazyDLL instead.

  9. Call C functions using SyscallN

    main

    Use SyscallN to call a C function pointer (fn) with a variable number of arguments. The function returns up to three return values (r1, r2, and err) as uintptr.

    Constraints and Limitations:

    • Argument Limit: You can pass a maximum of 32 arguments. Passing more will cause a panic.
    • Nil Pointer: Passing a fn value of 0 will cause a panic.
    • Float Parameters: SyscallN does not properly support functions that have both integer and float parameters. On amd64, if there are more than 8 floats, subsequent floats will be placed incorrectly on the stack.
    • Safety: When using uintptr arguments that point to memory, you must follow all rules specified in the unsafe.Pointer documentation (specifically regarding pointer lifetime and stack management).
  10. Unload dynamic libraries with Dlclose

    main

    Use Dlclose to 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.FreeLibrary instead.

  11. Map C functions to Go using RegisterFunc

    main

    Use RegisterFunc to 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:

    • fptr must 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, purego copies it into temporary memory valid only for the duration of the call. If the string is already null-terminated, purego does not copy it, and you must ensure it stays alive (e.g., using runtime.KeepAlive).
    • Structs: You must manually ensure that Go struct padding matches the C struct padding. On Apple ARM64 (macOS/iOS), purego handles stack alignment for struct arguments automatically.
  12. Convert a Go function to a Windows stdcall callback with NewCallback

    main

    Use NewCallback to convert a Go function into a function pointer that conforms to the Windows stdcall calling convention. This is required when interoperating with Windows code that expects callbacks.

    Requirements and Constraints:

    • The provided function fn must return exactly one uintptr-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 NewCallback and NewCallbackCDecl.
    • If the function includes a CDecl type as its first argument, NewCallback will automatically use syscall.NewCallbackCDecl instead of the standard stdcall version.