beginnings of Java @Import support

This commit is contained in:
Adam D. Ruppe 2019-12-10 18:24:22 -05:00
parent 708307d885
commit d04c6806e3
1 changed files with 307 additions and 21 deletions

328
jni.d
View File

@ -1,6 +1,7 @@
/++ /++
Provides easy interoperability with Java code through JNI. Provides easy interoperability with Java code through JNI.
Given this Java:
```java ```java
class Hello { class Hello {
public native void hi(String s); public native void hi(String s);
@ -23,6 +24,7 @@
} }
``` ```
And this D:
--- ---
import arsd.jni; import arsd.jni;
@ -49,6 +51,7 @@
} }
--- ---
We can:
$(CONSOLE $(CONSOLE
$ javac Hello.java $ javac Hello.java
$ dmd -shared myjni.d jni.d # compile into a shared lib $ dmd -shared myjni.d jni.d # compile into a shared lib
@ -78,6 +81,8 @@
Calling Java methods from D coming later. Calling Java methods from D coming later.
While you can write pretty ordinary looking D code, there's some things to keep in mind for safety and efficiency.
$(WARNING $(WARNING
ALL references passed to you through Java, including ALL references passed to you through Java, including
arrays, objects, and even the `this` pointer, MUST NOT arrays, objects, and even the `this` pointer, MUST NOT
@ -108,10 +113,15 @@
You may choose to only import JavaClass from here to minimize the You may choose to only import JavaClass from here to minimize the
namespace pollution. namespace pollution.
Constructing Java objects works and it will pin it. Just remember
that `this` inside a method is still subject to escaping restrictions!
+/ +/
module arsd.jni; module arsd.jni;
// FIXME: in general i didn't handle overloads at all
// see: https://developer.android.com/training/articles/perf-jni.html // see: https://developer.android.com/training/articles/perf-jni.html
// FIXME: do these work on Windows? // FIXME: do these work on Windows?
@ -132,21 +142,262 @@ void uninitializeDRuntime() {
Runtime.terminate(); Runtime.terminate();
} }
// FIXME make a start JVM function /+
extern(C)
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// can also return JNI_ERR
/+
private mixin template JavaImportImpl(T, alias method) { JNIEnv* env;
static assert(0, "@Import not yet implemented"); // FIXME if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
import std.traits; return JNI_ERR;
pragma(mangle, method.mangleof) }
private static ReturnType!method implementation(Parameters!method args, T this_) {
// FIXME. need to get the jni env to this somehow, remembering it gets invalidated easily // Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
static if(is(typeof(return) == void)) jclass c = env->FindClass("com/example/app/package/MyClass");
{} if (c == nullptr) return JNI_ERR;
else
return typeof(return); // Register your class' native methods.
static const JNINativeMethod methods[] = {
{"nativeFoo", "()V", reinterpret_cast(nativeFoo)},
{"nativeBar", "(Ljava/lang/String;I)Z", reinterpret_cast(nativeBar)},
};
int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));
if (rc != JNI_OK) return rc;
+/
return JNI_VERSION_1_6;
}
extern(C)
void JNI_OnUnload(JavaVM* vm, void* reserved) {
// FIXME: the cached _jmethodIDs need to all be cleared out too
}
+/
// need this for Import functions
JNIEnv* activeEnv;
struct ActivateJniEnv {
// this will put it on a call stack so it will be
// sane through re-entrant situations
JNIEnv* old;
this(JNIEnv* e) {
old = activeEnv;
activeEnv = e;
}
~this() {
activeEnv = old;
} }
} }
// FIXME make a start JVM function and figure out threads...
private void exceptionCheck(JNIEnv* env) {
if((*env).ExceptionCheck(env)) {
// ExceptionDescribe // prints it to stderr, not that interesting
jthrowable thrown = (*env).ExceptionOccurred(env);
// do I need to free thrown?
(*env).ExceptionClear(env);
throw new Exception("Java threw");
}
}
private enum ImportImplementationString = q{
static if(is(typeof(return) == void)) {
(*env).CallSTATICVoidMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
} else static if(is(typeof(return) == int)) {
auto ret = (*env).CallSTATICIntMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
return ret;
} else static if(is(typeof(return) == long)) {
auto ret = (*env).CallSTATICLongMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
return ret;
} else static if(is(typeof(return) == float)) {
auto ret = (*env).CallSTATICFloatMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
return ret;
} else static if(is(typeof(return) == double)) {
auto ret = (*env).CallSTATICDoubleMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
return ret;
} else static if(is(typeof(return) == bool)) {
auto ret = (*env).CallSTATICBooleanMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
return ret;
} else static if(is(typeof(return) == byte)) {
auto ret = (*env).CallSTATICByteMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
return ret;
} else static if(is(typeof(return) == wchar)) {
auto ret = (*env).CallSTATICCharMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
return ret;
} else {
static assert(0, "Unsupported return type for JNI " ~ typeof(return).stringof);
//return DDataToJni(env, __traits(getMember, dobj, __traits(identifier, method))(JavaParamsToD!(Parameters!method)(env, args).args));
}
};
private mixin template JavaImportImpl(T, alias method) {
import std.traits;
private static jmethodID _jmethodID;
static if(__traits(identifier, method) == "__ctor")
pragma(mangle, method.mangleof)
private static T implementation(Parameters!method args, T this_) {
auto env = activeEnv;
if(env is null)
throw new Exception("JNI not active in this thread");
if(!_jmethodID) {
jclass jc;
if(!internalJavaClassHandle_) {
jc = (*env).FindClass(env, (T._javaParameterString[1 .. $-1] ~ "\0").ptr);
if(!jc)
throw new Exception("Cannot find Java class " ~ T._javaParameterString[1 .. $-1]);
internalJavaClassHandle_ = jc;
} else {
jc = internalJavaClassHandle_;
}
_jmethodID = (*env).GetMethodID(env, jc,
"<init>",
// java method string is (args)ret
("(" ~ DTypesToJniString!(typeof(args)) ~ ")V\0").ptr
);
if(!_jmethodID)
throw new Exception("Cannot find static Java method " ~ T.stringof ~ "." ~ __traits(identifier, method));
}
auto o = (*env).NewObject(env, internalJavaClassHandle_, _jmethodID, DDataToJni(env, args).args);
this_.internalJavaHandle_ = o;
return this_;
}
else static if(__traits(isStaticFunction, method))
pragma(mangle, method.mangleof)
private static ReturnType!method implementation(Parameters!method args) {
auto env = activeEnv;
if(env is null)
throw new Exception("JNI not active in this thread");
if(!_jmethodID) {
jclass jc;
if(!internalJavaClassHandle_) {
jc = (*env).FindClass(env, (T._javaParameterString[1 .. $-1] ~ "\0").ptr);
if(!jc)
throw new Exception("Cannot find Java class " ~ T._javaParameterString[1 .. $-1]);
internalJavaClassHandle_ = jc;
} else {
jc = internalJavaClassHandle_;
}
_jmethodID = (*env).GetStaticMethodID(env, jc,
__traits(identifier, method).ptr,
// java method string is (args)ret
("(" ~ DTypesToJniString!(typeof(args)) ~ ")" ~ DTypesToJniString!(typeof(return)) ~ "\0").ptr
);
if(!_jmethodID)
throw new Exception("Cannot find static Java method " ~ T.stringof ~ "." ~ __traits(identifier, method));
}
auto jobj = internalJavaClassHandle_;
import std.string;
mixin(ImportImplementationString.replace("STATIC", "Static"));
}
else
pragma(mangle, method.mangleof)
private static ReturnType!method implementation(Parameters!method args, T this_) {
auto env = activeEnv;
if(env is null)
throw new Exception("JNI not active in this thread");
auto jobj = this_.getJavaHandle();
if(!_jmethodID) {
auto jc = (*env).GetObjectClass(env, jobj);
_jmethodID = (*env).GetMethodID(env, jc,
__traits(identifier, method).ptr,
// java method string is (args)ret
("(" ~ DTypesToJniString!(typeof(args)) ~ ")" ~ DTypesToJniString!(typeof(return)) ~ "\0").ptr
);
if(!_jmethodID)
throw new Exception("Cannot find Java method " ~ T.stringof ~ "." ~ __traits(identifier, method));
}
import std.string;
mixin(ImportImplementationString.replace("STATIC", ""));
}
}
private template DTypesToJniString(Types...) {
static if(Types.length == 0)
string DTypesToJniString = "";
else static if(Types.length == 1) {
alias T = Types[0];
static if(is(T == void))
string DTypesToJniString = "V";
else static if(is(T == string))
string DTypesToJniString = "Ljava/lang/String;";
else static if(is(T == wstring))
string DTypesToJniString = "Ljava/lang/String;";
else static if(is(T == int))
string DTypesToJniString = "I";
else static if(is(T == bool))
string DTypesToJniString = "Z";
else static if(is(T == byte))
string DTypesToJniString = "B";
else static if(is(T == wchar))
string DTypesToJniString = "C";
else static if(is(T == short))
string DTypesToJniString = "S";
else static if(is(T == long))
string DTypesToJniString = "J";
else static if(is(T == float))
string DTypesToJniString = "F";
else static if(is(T == double))
string DTypesToJniString = "D";
else static if(is(T == size_t))
string DTypesToJniString = "I"; // possible FIXME...
else static if(is(T == IJavaObject))
string DTypesToJniString = "LObject;"; // FIXME?
else static if(is(T : IJavaObject)) // child of this but a concrete type
string DTypesToJniString = T._javaParameterString;
/+ // FIXME they are just "[" ~ element type string
else static if(is(T == IJavaObject[]))
string DTypesToJniString = jobjectArray;
else static if(is(T == bool[]))
string DTypesToJniString = jbooleanArray;
else static if(is(T == byte[]))
string DTypesToJniString = jbyteArray;
else static if(is(T == wchar[]))
string DTypesToJniString = jcharArray;
else static if(is(T == short[]))
string DTypesToJniString = jshortArray;
else static if(is(T == int[]))
string DTypesToJniString = jintArray;
else static if(is(T == long[]))
string DTypesToJniString = jlongArray;
else static if(is(T == float[]))
string DTypesToJniString = jfloatArray;
else static if(is(T == double[]))
string DTypesToJniString = jdoubleArray;
+/
else static assert(0, "Unsupported type for JNI call " ~ T.stringof);
} else {
import std.typecons;
string DTypesToJni = DTypesToJni!(Types[0]) ~ DTypesToJni(Types[1 .. $]);
}
}
private template DTypesToJni(Types...) { private template DTypesToJni(Types...) {
static if(Types.length == 0) static if(Types.length == 0)
alias DTypesToJni = Types; alias DTypesToJni = Types;
@ -177,8 +428,8 @@ private template DTypesToJni(Types...) {
alias DTypesToJni = jdouble; alias DTypesToJni = jdouble;
else static if(is(T == size_t)) else static if(is(T == size_t))
alias DTypesToJni = jsize; alias DTypesToJni = jsize;
else static if(is(T == jobject)) else static if(is(T : IJavaObject))
alias DTypesToJni = IJavaObject; alias DTypesToJni = jobject;
else static if(is(T == IJavaObject[])) else static if(is(T == IJavaObject[]))
alias DTypesToJni = jobjectArray; alias DTypesToJni = jobjectArray;
else static if(is(T == bool[])) else static if(is(T == bool[]))
@ -204,7 +455,19 @@ private template DTypesToJni(Types...) {
} }
} }
auto DDataToJni(T)(JNIEnv* env, T data) { auto DDataToJni(T...)(JNIEnv* env, T data) {
import std.meta;
struct Tmp {
AliasSeq!(DTypesToJni!(T)) args;
}
Tmp t;
foreach(idx, ref arg; t.args)
arg = DDatumToJni(env, data[idx]);
return t;
}
auto DDatumToJni(T)(JNIEnv* env, T data) {
static if(is(T == void)) static if(is(T == void))
static assert(0); static assert(0);
else static if(is(T == string)) { else static if(is(T == string)) {
@ -236,7 +499,7 @@ auto DDataToJni(T)(JNIEnv* env, T data) {
else static if(is(T == size_t)) return cast(int) data; else static if(is(T == size_t)) return cast(int) data;
else static if(is(T : IJavaObject)) return data.getJavaHandle(); else static if(is(T : IJavaObject)) return data.getJavaHandle();
else static assert(0, "Unsupported type " ~ T.stringof); else static assert(0, "Unsupported type " ~ T.stringof);
/* // FIXME: finish these /* // FIXME: finish these.
else static if(is(T == IJavaObject[])) else static if(is(T == IJavaObject[]))
alias DTypesToJni = jobjectArray; alias DTypesToJni = jobjectArray;
else static if(is(T == bool[])) else static if(is(T == bool[]))
@ -348,7 +611,11 @@ private mixin template JavaExportImpl(T, alias method) {
import std.traits; import std.traits;
import std.string; import std.string;
static if(__traits(identifier, method) == "__ctor")
static assert(0, "Cannot export D constructors");
static private string JniMangle() { static private string JniMangle() {
// this actually breaks with -betterC though so does a lot more so meh.
static if(is(T : JavaClass!(JP, P), string JP, P)) static if(is(T : JavaClass!(JP, P), string JP, P))
return "Java_" ~replace(JP, ".", "_") ~ (JP.length ? "_" : "") ~ P.stringof ~ "_" ~ __traits(identifier, method); return "Java_" ~replace(JP, ".", "_") ~ (JP.length ? "_" : "") ~ P.stringof ~ "_" ~ __traits(identifier, method);
else static assert(0); else static assert(0);
@ -358,8 +625,17 @@ private mixin template JavaExportImpl(T, alias method) {
pragma(mangle, JniMangle()) pragma(mangle, JniMangle())
// I need it in the DLL, but want it to be not accessible from outside... alas. // I need it in the DLL, but want it to be not accessible from outside... alas.
export /*private*/ static DTypesToJni!(ReturnType!method) privateJniImplementation(JNIEnv* env, jobject obj, DTypesToJni!(Parameters!method) args) { export /*private*/ static DTypesToJni!(ReturnType!method) privateJniImplementation(JNIEnv* env, jobject obj, DTypesToJni!(Parameters!method) args) {
// FIXME: efficiency and possibly pull the same object again if possible // set it up in the thread for future calls
auto dobj = new T(); ActivateJniEnv thing = ActivateJniEnv(env);
// FIXME: pull the same D object again if possible... though idk
ubyte[__traits(classInstanceSize, T)] byteBuffer;
byteBuffer[] = (cast(const(ubyte)[]) typeid(T).initializer())[];
// I specifically do NOT call the constructor here, since those may forward to Java and make things ugly!
// The init value is cool as-is.
auto dobj = cast(T) byteBuffer.ptr;
dobj.internalJavaHandle_ = obj; dobj.internalJavaHandle_ = obj;
// getMember(identifer) is weird but i want to get the method on this // getMember(identifer) is weird but i want to get the method on this
@ -373,7 +649,7 @@ private mixin template JavaExportImpl(T, alias method) {
} }
} else { } else {
try { try {
return DDataToJni(env, __traits(getMember, dobj, __traits(identifier, method))(JavaParamsToD!(Parameters!method)(env, args).args)); return DDatumToJni(env, __traits(getMember, dobj, __traits(identifier, method))(JavaParamsToD!(Parameters!method)(env, args).args));
} catch(Throwable t) { } catch(Throwable t) {
jniRethrow(env, t); jniRethrow(env, t);
return typeof(return).init; // still required to return... return typeof(return).init; // still required to return...
@ -404,6 +680,9 @@ interface IJavaObject {
associating it back with Java across calls may be impossible. associating it back with Java across calls may be impossible.
+/ +/
class JavaClass(string javaPackage, CRTP) : IJavaObject { class JavaClass(string javaPackage, CRTP) : IJavaObject {
static assert(__traits(isFinalClass, CRTP), "Java classes must be final on the D side and " ~ CRTP.stringof ~ " is not");
enum Import; /// UDA to indicate you are importing the method from Java. Do NOT put a body on these methods. enum Import; /// UDA to indicate you are importing the method from Java. Do NOT put a body on these methods.
enum Export; /// UDA to indicate you are exporting the method to Java. Put a D implementation body on these. enum Export; /// UDA to indicate you are exporting the method to Java. Put a D implementation body on these.
@ -413,10 +692,20 @@ class JavaClass(string javaPackage, CRTP) : IJavaObject {
mixin JavaImportImpl!(CRTP, __traits(getMember, CRTP, memberName)); mixin JavaImportImpl!(CRTP, __traits(getMember, CRTP, memberName));
else static if(is(attr == Export)) else static if(is(attr == Export))
mixin JavaExportImpl!(CRTP, __traits(getMember, CRTP, memberName)); mixin JavaExportImpl!(CRTP, __traits(getMember, CRTP, memberName));
else static if(memberName == "__ctor")
static assert("JavaClasses can only be constructed by Java. Try making a constructor in Java, then make an @Import this(args); here.");
} }
protected jobject internalJavaHandle_; protected jobject internalJavaHandle_;
protected jobject getJavaHandle() { return internalJavaHandle_; } protected jobject getJavaHandle() { return internalJavaHandle_; }
protected static jclass internalJavaClassHandle_;
static import std.string;
static if(javaPackage.length)
public static immutable string _javaParameterString = "L" ~ std.string.replace(javaPackage, ".", "/") ~ "/" ~ CRTP.stringof ~ ";";
else
public static immutable string _javaParameterString = "L" ~ CRTP.stringof ~ ";";
} }
@ -801,6 +1090,3 @@ union jvalue
jdouble d; jdouble d;
jobject l; jobject l;
} }
jint JNI_OnLoad(JavaVM* vm, void* reserved);
void JNI_OnUnload(JavaVM* vm, void* reserved);