timob / jnigi

Golang Java JNI library
BSD 2-Clause "Simplified" License
166 stars 44 forks source link
go java jvm linux

JNIGI

JNI Go Interface.

A package to access Java from Go code. Can be used from a Go executable or shared library. This allows for Go to initiate the JVM or Java to start a Go runtime respectively.

Go Reference Actions

Module name change

The go module name is renamed to github.com/timob/jnigi in the branch. Checkout v2 if you want to retain the old name.

Install

# In your apps Go module directory
go get github.com/timob/jnigi

# Add flags needed to include JNI header files, change this as appropriate for your JDK and OS
export CGO_CFLAGS="-I/usr/lib/jvm/default-java/include -I/usr/lib/jvm/default-java/include/linux"

# build your app
go build

The compilevars.sh (compilevars.bat on Windows) script can help setting the CGO_CFLAGS environment variable.

Finding JVM at Runtime

The JVM library is dynamically linked at run time. Use the LoadJVMLib(jvmLibPath string) error function to load the shared library at run time. There is a function AttemptToFindJVMLibPath() string to help to find the library path.

Status

Testing

Most of the code has tests. To run the tests using docker:

# get source
git clone https://github.com/timob/jnigi.git

cd jnigi

# build image
docker build -t jnigi_test .

# run tests
docker run jnigi_test

Uses

Has been used on Linux/Windows/MacOS (amd64) Android (arm64) multi threaded apps.

Note about using on Windows

Because of the way the JVM triggers OS exceptions during CreateJavaVM, which the Go runtime treats as unhandled exceptions, the code for the Go runtime needs to be changed to allow the JVM to handle the exceptions. See https://github.com/timob/jnigi/issues/31#issuecomment-1668914368 for how to do this.

Example

package main

import (
    "fmt"
    "github.com/timob/jnigi"
    "log"
    "runtime"
)

func main() {
    if err := jnigi.LoadJVMLib(jnigi.AttemptToFindJVMLibPath()); err != nil {
        log.Fatal(err)
    }

    runtime.LockOSThread()
    jvm, env, err := jnigi.CreateJVM(jnigi.NewJVMInitArgs(false, true, jnigi.DEFAULT_VERSION, []string{"-Xcheck:jni"}))
    if err != nil {
        log.Fatal(err)
    }

    hello, err := env.NewObject("java/lang/String", []byte("Hello "))
    if err != nil {
        log.Fatal(err)
    }

    world, err := env.NewObject("java/lang/String", []byte("World!"))
    if err != nil {
        log.Fatal(err)
    }

    greeting := jnigi.NewObjectRef("java/lang/String")
    err = hello.CallMethod(env, "concat", greeting, world)
    if err != nil {
        log.Fatal(err)
    }

    var goGreeting []byte
    err = greeting.CallMethod(env, "getBytes", &goGreeting)
    if err != nil {
        log.Fatal(err)
    }

    // Prints "Hello World!"
    fmt.Printf("%s\n", goGreeting)

    if err := jvm.Destroy(); err != nil {
        log.Fatal(err)
    }
}