Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to pass go func to jvm #75

Open
proohit opened this issue Sep 28, 2023 · 7 comments
Open

how to pass go func to jvm #75

proohit opened this issue Sep 28, 2023 · 7 comments

Comments

@proohit
Copy link

proohit commented Sep 28, 2023

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:

fn_obj, err := env.NewObject("java/util/function/Function", nil)
fn_obj.SetField(env, "apply", func() {})

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.

@timob
Copy link
Owner

timob commented Sep 30, 2023

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.

@proohit
Copy link
Author

proohit commented Oct 5, 2023

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.

@timob
Copy link
Owner

timob commented Oct 7, 2023

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!

@proohit
Copy link
Author

proohit commented Oct 19, 2023

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?

@proohit
Copy link
Author

proohit commented Oct 23, 2023

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?

@timob
Copy link
Owner

timob commented Nov 6, 2023

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.

@timob
Copy link
Owner

timob commented Dec 8, 2023

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: de58019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants