-
-
Notifications
You must be signed in to change notification settings - Fork 53
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
Call methods of a class generated in runtime #166
Comments
Could you clarify which version of Note that there is https://github.com/JuliaInterop/JavaCall.jl/releases/tag/v0.8.0rc-1 |
I tested it on v0.7.8, but v0.8.0rc-1 gives the same result anyway: julia> jcall(obj, "hello", JString, (JString,), "Bob")
Exception in thread "main" java.lang.NoSuchMethodError: hello
ERROR: JavaCall.JavaCallError("Error calling Java: java.lang.NoSuchMethodError: hello")
Stacktrace:
[1] geterror()
@ JavaCall ~/.julia/packages/JavaCall/KFY5m/src/core.jl:542
[2] get_method_id(jnifun::typeof(JavaCall.JNI.GetMethodID), obj::JObject, method::String, rettype::Type, argtypes::Tuple{DataType})
@ JavaCall ~/.julia/packages/JavaCall/KFY5m/src/core.jl:255
[3] get_method_id
@ ~/.julia/packages/JavaCall/KFY5m/src/core.jl:393 [inlined]
[4] jcall(ref::JObject, method::String, rettype::Type, argtypes::Tuple{DataType}, args::String)
@ JavaCall ~/.julia/packages/JavaCall/KFY5m/src/core.jl:370
[5] top-level scope
@ REPL[9]:1 And just in case: julia> versioninfo()
Julia Version 1.7.2
Commit bf53498635 (2022-02-06 15:21 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-12.0.1 (ORCJIT, skylake)
Environment:
JULIA_COPY_STACKS = 1
JULIA_EDITOR = code
JULIA_NUM_THREADS = |
I notice that they use a dynamic class loader. Do they mention anything regarding the Java Native Interface? |
Not really. I will try it with the standard |
Nope, the same error. Self-containing exampleusing JavaCall
const JFile = @jimport java.io.File
const JToolProvider = @jimport javax.tools.ToolProvider
const JJavaCompiler = @jimport javax.tools.JavaCompiler
const JInputStream = @jimport java.io.InputStream
const JOutputStream = @jimport java.io.OutputStream
const JClassLoader = @jimport java.lang.ClassLoader
const JURLClassLoader = @jimport java.net.URLClassLoader
const JURI = @jimport java.net.URI
const JURL = @jimport java.net.URL
function JavaCall.classforname(name::String, loader)
return jcall(JClass, "forName", JClass, (JString, jboolean, JClassLoader),
name, true, loader)
end
function mkclass(name::String, src::String)
# based on https://stackoverflow.com/a/2946402
cls = mktempdir(; prefix="jj-") do root
# write source to a file
elems = split(name, ".")
pkg_path = joinpath(root, elems[1:end-1]...)
mkpath(pkg_path)
src_path = joinpath(pkg_path, elems[end] * ".java")
open(src_path, "w") do f
write(f, src)
end
# compile
jcompiler = jcall(JToolProvider, "getSystemJavaCompiler", JJavaCompiler)
jcall(jcompiler, "run", jint,
(JInputStream, JOutputStream, JOutputStream, Vector{JString}),
nothing, nothing, nothing, [src_path])
# load class
jfile = JFile((JString,), root)
juri = jcall(jfile, "toURI", JURI)
jurl = jcall(juri, "toURL", JURL)
jloader = jcall(JURLClassLoader, "newInstance", JURLClassLoader, (Vector{JURL},), [jurl])
classforname(name, jloader)
end
return cls
end
function instantiate(name::String, src::String)
jclass = mkclass(name, src)
return jcall(jclass, "newInstance", JObject, ())
end
JavaCall.init()
name = "julia.compiled.Hello"
src = """
package julia.compiled;
public class Hello {
public String hello(String name) {
return "Hello, " + name;
}
public void goodbye() {
System.out.println("Goodbye!");
}
}
"""
obj = instantiate(name, src)
jcall(obj, "hello", JString, (JString,), "Bob") # NoSuchMethodError:
jcall(obj, "goodbye", Nothing, ()) # NoSuchMethodError:
jcall(obj, "equals", jboolean, (JObject,), obj) # ok |
Well my point was the opposite actually. We need to use |
But isn't Would it help if we introduce a proxy class loaded using the system class loader (and thus JNI-friendly), but in runtime loading the new generated class? |
This is failing at https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetMethodID. The question then is what are the correct arguments to pass to Since reflection seems to be working, perhaps you should work off this result: julia> listmethods(obj)
11-element Vector{JMethod}:
java.lang.String hello(java.lang.String)
void goodbye()
void wait(long)
void wait(long, int)
void wait()
boolean equals(java.lang.Object)
java.lang.String toString()
int hashCode()
java.lang.Class getClass()
void notify()
void notifyAll() v0.8.0rc-1 offers this new way of using Lines 374 to 380 in 63026e4
where Line 396 in 63026e4
Combined with the two argument form of Lines 111 to 114 in 63026e4
|
Just wanted to post an updated on this. I tried the suggested method, but it turns out due to Also, I test it on OpenJDK 11, which I already had problems with in the past. Oracle download site for their JDK 11 is broken right now, once it's fixed, I'll check if this problem exists there too. |
Can you try to use |
Concretely, is there any other JNI method other than |
I tried Generate object (copy from above)# copy from one of the previous comments
using JavaCall
const JFile = @jimport java.io.File
const JToolProvider = @jimport javax.tools.ToolProvider
const JJavaCompiler = @jimport javax.tools.JavaCompiler
const JInputStream = @jimport java.io.InputStream
const JOutputStream = @jimport java.io.OutputStream
const JClassLoader = @jimport java.lang.ClassLoader
const JURLClassLoader = @jimport java.net.URLClassLoader
const JURI = @jimport java.net.URI
const JURL = @jimport java.net.URL
function JavaCall.classforname(name::String, loader)
return jcall(JClass, "forName", JClass, (JString, jboolean, JClassLoader),
name, true, loader)
end
function mkclass(name::String, src::String)
# based on https://stackoverflow.com/a/2946402
cls = mktempdir(; prefix="jj-") do root
# write source to a file
elems = split(name, ".")
pkg_path = joinpath(root, elems[1:end-1]...)
mkpath(pkg_path)
src_path = joinpath(pkg_path, elems[end] * ".java")
open(src_path, "w") do f
write(f, src)
end
# compile
jcompiler = jcall(JToolProvider, "getSystemJavaCompiler", JJavaCompiler)
jcall(jcompiler, "run", jint,
(JInputStream, JOutputStream, JOutputStream, Vector{JString}),
nothing, nothing, nothing, [src_path])
# load class
jfile = JFile((JString,), root)
juri = jcall(jfile, "toURI", JURI)
jurl = jcall(juri, "toURL", JURL)
jloader = jcall(JURLClassLoader, "newInstance", JURLClassLoader, (Vector{JURL},), [jurl])
classforname(name, jloader)
end
return cls
end
function instantiate(name::String, src::String)
jclass = mkclass(name, src)
return jcall(jclass, "newInstance", JObject, ())
end
JavaCall.init()
name = "julia.compiled.Hello"
src = """
package julia.compiled;
public class Hello {
public String hello(String name) {
return "Hello, " + name;
}
public void goodbye() {
System.out.println("Goodbye!");
}
}
"""
obj = instantiate(name, src) import JavaCall.JNI
sig = JavaCall.method_signature(JString, JString)
ptr = Ptr(JavaCall.metaclass(obj))
# methods
JNI.GetMethodID(ptr, "call", sig) # ==> Ptr{Nothing} @0x0000000000000000
JNI.GetStaticMethodID(ptr, "call", sig) # ==> Ptr{Nothing} @0x0000000000000000
# fields, just in case, with method signature
JNI.GetFieldID(ptr, "call", sig) # ==> Ptr{Nothing} @0x0000000000000000
JNI.GetStaticFieldID(ptr, "call", sig) # ==> Ptr{Nothing} @0x0000000000000000
# reflected method
meth = listmethods(obj, "hello")[1]
JNI.FromReflectedMethod(meth) # ==> Ptr{Nothing} @0x00000000029d26a0 |
jcall
fails withNoSuchMethodError
when calling methods of a dynamically compiled class.I use InMemoryJavaCompiler to compile a new class and create its instance. (
InMemoryJavaCompiler
is based onjavax.tools.*
, so quite standard approach).The resulting object looks like the ordinary one and I can see the methods I defined:
However, calling them with
jcall
fails, even though calling the inherited methods works fine:What's even more confusing, these new methods can actually be called using the reflection:
Although it fails with
NoSuchMethodException
for the inherited method ¯_(ツ)_/¯:The code above is executable from the
Spark#new-api
branch, but honestly I hope that I'm just doing some stupid mistake that a keen eye can catch just from the description.The text was updated successfully, but these errors were encountered: