Open proohit opened 1 year ago
They way I've done this in tha past is to create a class in java that implements the interface and declare all the methods native (or at least the ones you are interested in). Then provide the go implementations using registernatives. Please see Oracle's JNI documentation on how to do this.
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html
The implementing funnctionns are passed the object so you can keep track of this on the Go side.
Hey, thanks for your reply and sorry for my late response. Can registerNatives
be used for objects as well? The JNI interface expects clazz: a Java class object
, but the jnigi api declares a className
parameter. I'm not sure, if registerNatives
is used for whole classes or objects of that class.
As an example:
I have the following class, which, as you suggested, implements the said function interface:
package shared.server;
import java.util.function.Function;
public class RouteHandlerNative implements Function<String, String> {
private String path;
public RouteHandlerNative(String path) {
this.path = path;
}
public native String apply(String arg0);
}
Now can I do the following, but for an instance of RouteHandlerNative
instead?
func applyTest(arg0 string) string {
return ""
}
rhn_obj, err := env.NewObject("shared/server/RouteHandlerNative", []string{"/test"})
env.RegisterNative("shared/server/RouteHandlerNative", "apply", jnigi.Object, []interface{}{}, applyTest) // <-- use for rhn_obj instead?
Here's a little context for what I am trying: I'm trying to provide a Java library that can be used from go (and other languages), but accepts custom function implementations from the host language (go, and other languages). The domain is a webserver (java shared lib) for which route handlers can be registered from host languages. The poc includes handlers that handle requests and respond with a response, in this case a string, hence the interface Function<String, String>
. For reference, there is a Map<String, RouteHandlerNative>
somewhere in the java lib that matches incoming request paths to RouteHandlerNative#path
and invokes RouteHandlerNative#apply
, which then is called from the host language.
I hope that clarifies my needs and request.
Hi, in your example you are on the right direction but you can't use go types in your native function call back, JNI uses C, so you need to convert from the C types to go types in your call back. So something like (just going off old code of mine):
/*
#include<stdint.h>
extern uintptr_t go_callback_RouteHandlerNativeApply(void *env, uintptr_t obj, uintptr_t arg0)
*/
import"C"
//export go_callback_RouteHandlerNativeApply
func go_callback_RouteHandlerNativeApply(env unsafe.Pointer, obj uintptr, arg0 uintptr) uintptr
Then you can use the C function in the last argument to JNIGI RegisterNative.
In your callback you need to convert the argument to a go string, call your go function and convert the go string returned to a java string and return.
You need a good understanding of how Cgo works https://pkg.go.dev/cmd/cgo . Maybe if you get it working you could submit an example to the project!
Hi and thanks for the reference. I followed your advice using RegisterNative
and was able to accomplish what I'm looking for in Rust, now I'm trying the same in go.
This is the jni c signature:
JNIEXPORT jstring JNICALL Java_shared_server_Server_handle_1request_1external
(JNIEnv *, jclass, jint, jstring);
This is the corresponding go function:
func handle_request_external(env unsafe.Pointer, obj uintptr, fn_id uintptr, raw_request_ptr uintptr) *jnigi.ObjectRef {
println(fn_id, raw_request_ptr)
req_ptr := unsafe.Pointer(raw_request_ptr)
req_string_ptr := (*string)(req_ptr)
request := *req_string_ptr
println(request)
var env_val *jnigi.Env = (*jnigi.Env)(env)
resp, _ := env_val.NewObject("java/lang/String", []byte("test"))
return resp
}
I'm now trying to register the function as a native method:
env.RegisterNative("shared/server/Server", "Java_shared_server_Server_handle_1request_1external", jnigi.Object, []interface{}{}, handle_request_external)
//or
env.RegisterNative("shared/server/Server", "handle_request_external", jnigi.Object, []interface{}{}, handle_request_external)
Both variants result in the following error:
panic: interface conversion: interface {} is func(unsafe.Pointer, uintptr, uintptr, uintptr) *jnigi.ObjectRef, not unsafe.Pointer
goroutine 1 [running, locked to thread]:
tekao.net/jnigi.(*Env).RegisterNative(0x4000062040, {0x4bf675?, 0x400004ff08?}, {0x4c4a7a, 0x33}, {0x4df338?, 0x4ded5c}, {0x400004fee0, 0x0, 0x200000003?}, ...)
I assume the signature do not match somehow, but do you have any directions from here?
Okay so following up, I researched some more and came a little further. Here's the current state and I feel like I'm close:
With headers:
#include<jni.h>
#include <stdint.h>
extern uintptr_t handle_request_external(void *env, uintptr_t obj, uintptr_t fn_id, uintptr_t raw_request_ptr);
/*
#include<server.h>
*/
import "C"
//export handle_request_external
func handle_request_external(env unsafe.Pointer, obj uintptr, raw_fn_id uintptr, raw_request_ptr uintptr) uintptr {
req_ptr := unsafe.Pointer(raw_request_ptr)
request := *(*string)(req_ptr)
fn_id := int32(raw_fn_id)
route := routes[fn_id]
response := route.Handler(request)
println(response)
var env_val *jnigi.Env = jnigi.WrapEnv(env)
resp, _ := env_val.NewObject("java/lang/String", []byte(response))
return uintptr(unsafe.Pointer(resp))
}
...
env.RegisterNative("shared/server/Server", "handle_request_external", jnigi.ObjectType("java/lang/String"), []interface{}{jnigi.Int, "java/lang/String"}, C.handle_request_external)
Everything works, so the native handle_request_external
function is being found and executed natively. The only problem I'm having is the return value of the native function, which should be a java/lang/String
. For that, I'm trying to create a new object in the JVM and pass that as a reference, but the []byte(response)
part seems to be erroneous. I've also tried a simple env_val.NewObject("java/lang/String", []byte("test"))
, but I'm still getting the following error:
signal: segmentation fault (core dumped)
with some gibbery core dumps I cannot quiet understand (cannot find the c program in order to use gdb
). I feel like the JVM is trying to access some memory (the go byte slice) and fails at that for some reason. @timob, do you have some directions I could take from here?
Hi! Sorry about the delay real life things... The first problem I see is you are trying to derefernce a pointer from Java as a Go string request := *(*string)(req_ptr)
. Given the case that this is actually a Java String, you need to use the getBytes
method to get the bytes and convert that into a Go string. See the readme example line 37
It might also be better for the handle_request_external
function to take a String object that gets set by the function instead of returning a new object, so that JVM can manage the object reference.
Hi! I'm currently doing a PR to increase test coverage of the module.
There is an test for RegisterNative that does what you were asking above if you are still interested. See commit: https://github.com/timob/jnigi/pull/78/commits/de5801930ad50846681542e819f951b8ce869b48
Hi I wanted to ask how it is possible to pass a go func as an implementation to an object in JVM that implements the
java.util.function.Function<String,String>
interface.Is there a way and if yes, what steps do I need for that?
I have tried the following attempt:
This of course fails, because there is no
<init>
ctor for an interface. My goal is to provide an implementation in go for that interface.