lots of cool stuff

This commit is contained in:
Adam D. Ruppe 2019-12-18 14:43:49 -05:00
parent 643527c897
commit 35d58a4c0d
1 changed files with 321 additions and 90 deletions

411
jni.d
View File

@ -120,15 +120,85 @@
+/
module arsd.jni;
// FIXME: java.lang.CharSequence is the interface for String. We should support that just as well.
// FIXME: if user defines an interface with the appropriate RAII return values,
// it should let them do that for more efficiency
// e.g. @Import Manual!(int[]) getJavaArray();
/+
final class CharSequence : JavaClass!("java.lang", CharSequence) {
@Import string toString(); // this triggers a dmd segfault! whoa. FIXME dmd
}
+/
/++
This is an interface in Java that the built in String class
implements. In D, our string is not an object at all, and
overloading an auto-generated method with a user-defined one
is a bit awkward and not automatic, so I'm instead cheating
here and pretending it is a class we can construct in a D
overload.
This is not a great solution. I'm also considering a UDA
on the param to indicate Java actually expects the interface,
or even a runtime lookup to disambiguate, but meh for now
I'm just sticking with this slightly wasteful overload/construct.
+/
final class CharSequence : JavaClass!("java.lang", CharSequence) {
this(string data) {
auto env = activeEnv;
assert(env !is null);
wchar[1024] buffer;
const(wchar)[] translated;
if(data.length < 1024) {
size_t len;
foreach(wchar ch; data)
buffer[len++] = ch;
translated = buffer[0 .. len];
} else {
import std.conv;
translated = to!wstring(data);
}
// Java copies the buffer so it is perfectly fine to return here now
internalJavaHandle_ = (*env).NewString(env, translated.ptr, cast(jsize) translated.length);
}
this(wstring data) {
auto env = activeEnv;
assert(env !is null);
internalJavaHandle_ = (*env).NewString(env, data.ptr, cast(jsize) data.length);
}
}
/++
Can be used as a UDA for methods or classes where the D name
and the Java name don't match (for example, if it happens to be
a D keyword).
---
@JavaName("version")
@Import int version_();
---
+/
struct JavaName {
string name;
}
private string getJavaName(alias a)() {
string name = __traits(identifier, a);
static foreach(attr; __traits(getAttributes, a))
static if(is(typeof(attr) == JavaName))
name = attr.name;
return name;
}
// semi-FIXME: java.lang.CharSequence is the interface for String. We should support that just as well.
// possibly other boxed types too, like Integer.
// FIXME: in general, handle substituting subclasses for interfaces nicely
// FIXME: solve the globalref/pin issue with the type system
//
// FIXME: in general i didn't handle overloads at all. Using RegisterNatives should make that doable... and I might be able to remove some of the pragma(mangle) that way too. And get more error checking done up-front.
// FIXME: what about the parent class of the java object? Best we can probably do is an interface but perhaps it could be auto-generated by the JavaClass magic. It could take the list and just copy the @Import items.
/+
@ -153,30 +223,8 @@ module arsd.jni;
//pragma(crt_constructor) // fyi
//pragma(crt_destructor)
// FIXME: put this in a mixin instead of assuming it is needed/wanted?
extern(C)
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
/+
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
// Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
jclass c = env->FindClass("com/example/app/package/MyClass");
if (c == nullptr) return JNI_ERR;
// 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;
+/
extern(System)
export jint JNI_OnLoad(JavaVM* vm, void* reserved) {
try {
import core.runtime;
// note this is OK if it is already initialized
@ -187,10 +235,20 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
}
activeJvm = vm;
JNIEnv* env;
if ((*vm).GetEnv(vm, cast(void**) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
foreach(init; classInitializers_)
if(init(env) != 0)
return JNI_ERR;
return JNI_VERSION_1_6;
}
extern(C)
void JNI_OnUnload(JavaVM* vm, void* reserved) {
extern(System)
export void JNI_OnUnload(JavaVM* vm, void* reserved) {
activeJvm = null;
import core.runtime;
try {
@ -224,17 +282,44 @@ struct ActivateJniEnv {
/++
Creates a JVM for use when `main` is in D. Keep the returned
struct around until you are done with it.
struct around until you are done with it. While this struct
is live, you can use imported Java classes and methods almost
as if they were written in D.
If `main` is in Java, this is not necessary and should not be
used.
If you use this, you will need to link in a jni shared lib.
This function will try to load the jvm with dynamic runtime
linking. For this to succeed:
On Windows, make sure the path to `jvm.dll` is in your `PATH`
environment variable, or installed system wide. For example:
$(CONSOLE
set PATH=%PATH%;c:\users\me\program\jdk-13.0.1\bin\server
)
On Linux (and I think Mac), set `LD_LIBRARY_PATH` environment
variable to include the path to `libjvm.so`.
$(CONSOLE
export LD_LIBRARY_PATH=/home/me/jdk-13.0.1/bin/server
)
Failure to do this will throw an exception along the lines of
"no jvm dll" in the message.
+/
auto createJvm()() {
struct JVM {
version(Windows)
import core.sys.windows.windows;
else
import core.sys.posix.dlfcn;
static struct JVM {
ActivateJniEnv e;
JavaVM* pvm;
void* jvmdll;
@disable this(this);
@ -242,22 +327,77 @@ auto createJvm()() {
if(pvm)
(*pvm).DestroyJavaVM(pvm);
activeJvm = null;
version(Windows) {
if(jvmdll) FreeLibrary(jvmdll);
} else {
if(jvmdll) dlclose(jvmdll);
}
}
}
JavaVM* pvm;
JNIEnv* env;
auto res = JNI_CreateJavaVM(&pvm, cast(void**) &env, null);
JavaVMInitArgs vm_args;
JavaVMOption[0] options;
//options[0].optionString = "-Djava.compiler=NONE"; /* disable JIT */
//options[1].optionString = "-verbose:jni"; /* print JNI-related messages */
//options[1].optionString = `-Djava.class.path=c:\Users\me\program\jni\`; /* user classes */
//options[2].optionString = `-Djava.library.path=c:\Users\me\program\jdk-13.0.1\lib\`; /* set native library path */
vm_args.version_ = JNI_VERSION_1_6;
vm_args.options = options.ptr;
vm_args.nOptions = cast(int) options.length;
vm_args.ignoreUnrecognized = true;
//import std.process;
//environment["PATH"] = environment["PATH"] ~ `;c:\users\me\program\jdk-13.0.1\bin\server`;
version(Windows)
auto jvmdll = LoadLibraryW("jvm.dll"w.ptr);
else
auto jvmdll = dlopen("libjvm.so");
if(jvmdll is null)
throw new Exception("no jvm dll");
version(Windows)
auto fn = cast(typeof(&JNI_CreateJavaVM)) GetProcAddress(jvmdll, "JNI_CreateJavaVM");
else
auto fn = cast(typeof(&JNI_CreateJavaVM)) dlsym(jvmdll, "JNI_CreateJavaVM");
if(fn is null)
throw new Exception("no fun");
auto res = fn(&pvm, cast(void**) &env, &vm_args);//, args);
if(res != JNI_OK)
throw new Exception("create jvm failed"); // FIXME: throw res);
activeJvm = pvm;
return JVM(ActivateJniEnv(env), pvm);
return JVM(ActivateJniEnv(env), pvm, jvmdll);
}
version(Windows)
private extern(Windows) bool SetDllDirectoryW(wstring);
@JavaName("Throwable")
final class JavaThrowable : JavaClass!("java.lang", JavaThrowable) {
@Import string getMessage();
@Import StackTraceElement[] getStackTrace();
}
final class StackTraceElement : JavaClass!("java.lang", StackTraceElement) {
@Import this(string declaringClass, string methodName, string fileName, int lineNumber);
@Import string getClassName();
@Import string getFileName();
@Import int getLineNumber();
@Import string getMethodName();
@Import bool isNativeMethod();
}
private void exceptionCheck(JNIEnv* env) {
if((*env).ExceptionCheck(env)) {
@ -266,6 +406,7 @@ private void exceptionCheck(JNIEnv* env) {
// do I need to free thrown?
(*env).ExceptionClear(env);
// FIXME
throw new Exception("Java threw");
}
}
@ -274,6 +415,31 @@ 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) == string) || is(typeof(return) == wstring)) {
// I can't just use JavaParamsToD here btw because of lifetime worries.
// maybe i should fix it down there though because there is a lot of duplication
auto jret = (*env).CallSTATICObjectMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
typeof(return) ret;
auto len = (*env).GetStringLength(env, jret);
auto ptr = (*env).GetStringChars(env, jret, null);
static if(is(T == wstring)) {
if(ptr !is null) {
ret = ptr[0 .. len].idup;
(*env).ReleaseStringChars(env, jret, ptr);
}
} else {
import std.conv;
if(ptr !is null) {
ret = to!string(ptr[0 .. len]);
(*env).ReleaseStringChars(env, jret, ptr);
}
}
return ret;
} else static if(is(typeof(return) == int)) {
auto ret = (*env).CallSTATICIntMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
@ -302,13 +468,57 @@ private enum ImportImplementationString = q{
auto ret = (*env).CallSTATICCharMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
return ret;
} else static if(is(typeof(return) == E[], E)) {
// Java arrays are represented as objects
auto jarr = (*env).CallSTATICObjectMethod(env, jobj, _jmethodID, DDataToJni(env, args).args);
exceptionCheck(env);
auto len = (*env).GetArrayLength(env, jarr);
static if(is(E == int)) {
auto eles = (*env).GetIntArrayElements(env, jarr, null);
auto res = eles[0 .. len];
(*env).ReleaseIntArrayElements(env, jarr, eles, 0);
} else static if(is(E == bool)) {
auto eles = (*env).GetBooleanArrayElements(env, jarr, null);
auto res = eles[0 .. len];
(*env).ReleaseBooleanArrayElements(env, jarr, eles, 0);
} else static if(is(E == long)) {
auto eles = (*env).GetLongArrayElements(env, jarr, null);
auto res = eles[0 .. len];
(*env).ReleaseLongArrayElements(env, jarr, eles, 0);
} else static if(is(E == short)) {
auto eles = (*env).GetShortArrayElements(env, jarr, null);
auto res = eles[0 .. len];
(*env).ReleaseShortArrayElements(env, jarr, eles, 0);
} else static if(is(E == wchar)) {
auto eles = (*env).GetCharArrayElements(env, jarr, null);
auto res = eles[0 .. len];
(*env).ReleaseCharArrayElements(env, jarr, eles, 0);
} else static if(is(E == float)) {
auto eles = (*env).GetFloatArrayElements(env, jarr, null);
auto res = eles[0 .. len];
(*env).ReleaseFloatArrayElements(env, jarr, eles, 0);
} else static if(is(E == double)) {
auto eles = (*env).GetDoubleArrayElements(env, jarr, null);
auto res = eles[0 .. len];
(*env).ReleaseDoubleArrayElements(env, jarr, eles, 0);
} else static if(is(E == byte)) {
auto eles = (*env).GetByteArrayElements(env, jarr, null);
auto res = eles[0 .. len];
(*env).ReleaseByteArrayElements(env, jarr, eles, 0);
} else static if(is(E : IJavaObject)) {
// FIXME: implement this
typeof(return) res = null;
} else static assert(0, E.stringof ~ " not supported array element type yet"); // FIXME handle object arrays too. which would also prolly include arrays of arrays.
return res;
} 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) {
private mixin template JavaImportImpl(T, alias method, size_t overloadIndex) {
import std.traits;
private static jmethodID _jmethodID;
@ -362,7 +572,7 @@ private mixin template JavaImportImpl(T, alias method) {
jc = internalJavaClassHandle_;
}
_jmethodID = (*env).GetStaticMethodID(env, jc,
__traits(identifier, method).ptr,
getJavaName!method.ptr,
// java method string is (args)ret
("(" ~ DTypesToJniString!(typeof(args)) ~ ")" ~ DTypesToJniString!(typeof(return)) ~ "\0").ptr
);
@ -387,7 +597,7 @@ private mixin template JavaImportImpl(T, alias method) {
if(!_jmethodID) {
auto jc = (*env).GetObjectClass(env, jobj);
_jmethodID = (*env).GetMethodID(env, jc,
__traits(identifier, method).ptr,
getJavaName!method.ptr,
// java method string is (args)ret
("(" ~ DTypesToJniString!(typeof(args)) ~ ")" ~ DTypesToJniString!(typeof(return)) ~ "\0").ptr
);
@ -403,62 +613,49 @@ private mixin template JavaImportImpl(T, alias method) {
private template DTypesToJniString(Types...) {
static if(Types.length == 0)
string DTypesToJniString = "";
enum 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;";
enum string DTypesToJniString = "V";
else static if(is(T == string)) {
enum string DTypesToJniString = "Ljava/lang/String;";
} else static if(is(T == wstring))
enum string DTypesToJniString = "Ljava/lang/String;";
else static if(is(T == int))
string DTypesToJniString = "I";
enum string DTypesToJniString = "I";
else static if(is(T == bool))
string DTypesToJniString = "Z";
enum string DTypesToJniString = "Z";
else static if(is(T == byte))
string DTypesToJniString = "B";
enum string DTypesToJniString = "B";
else static if(is(T == wchar))
string DTypesToJniString = "C";
enum string DTypesToJniString = "C";
else static if(is(T == short))
string DTypesToJniString = "S";
enum string DTypesToJniString = "S";
else static if(is(T == long))
string DTypesToJniString = "J";
enum string DTypesToJniString = "J";
else static if(is(T == float))
string DTypesToJniString = "F";
enum string DTypesToJniString = "F";
else static if(is(T == double))
string DTypesToJniString = "D";
enum string DTypesToJniString = "D";
else static if(is(T == size_t))
string DTypesToJniString = "I"; // possible FIXME...
enum string DTypesToJniString = "I"; // possible FIXME...
else static if(is(T == IJavaObject))
string DTypesToJniString = "LObject;"; // FIXME?
enum 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;
+/
enum string DTypesToJniString = T._javaParameterString;
else static if(is(T == E[], E))
enum string DTypesToJniString = "[" ~ DTypesToJniString!E;
else static assert(0, "Unsupported type for JNI call " ~ T.stringof);
} else {
import std.typecons;
string DTypesToJni = DTypesToJni!(Types[0]) ~ DTypesToJni(Types[1 .. $]);
private string helper() {
string s;
foreach(Type; Types)
s ~= DTypesToJniString!Type;
return s;
}
enum string DTypesToJniString = helper;
}
}
@ -515,8 +712,10 @@ private template DTypesToJni(Types...) {
alias DTypesToJni = jdoubleArray;
else static assert(0, "Unsupported type for JNI " ~ T.stringof);
} else {
import std.typecons;
alias DTypesToJni = AliasSeq!(DTypesToJni!(Types[0]), DTypesToJni(Types[1 .. $]));
import std.meta;
// FIXME: write about this later if you forget the ! on the final DTypesToJni, dmd
// says "error: recursive template expansion". woof.
alias DTypesToJni = AliasSeq!(DTypesToJni!(Types[0]), DTypesToJni!(Types[1 .. $]));
}
}
@ -562,7 +761,7 @@ auto DDatumToJni(T)(JNIEnv* env, T data) {
else static if(is(T == float)) return data;
else static if(is(T == double)) return 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 is null ? null : data.getJavaHandle();
else static assert(0, "Unsupported type " ~ T.stringof);
/* // FIXME: finish these.
else static if(is(T == IJavaObject[]))
@ -675,22 +874,24 @@ void jniRethrow(JNIEnv* env, Throwable t) {
);
}
private mixin template JavaExportImpl(T, alias method) {
private mixin template JavaExportImpl(T, alias method, size_t overloadIndex) {
import std.traits;
import std.string;
static if(__traits(identifier, method) == "__ctor")
static assert(0, "Cannot export D constructors");
/+
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))
return "Java_" ~replace(JP, ".", "_") ~ (JP.length ? "_" : "") ~ P.stringof ~ "_" ~ __traits(identifier, method);
else static assert(0);
}
+/
extern(C)
pragma(mangle, JniMangle())
extern(System)
//pragma(mangle, JniMangle())
// 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) {
// set it up in the thread for future calls
@ -716,19 +917,28 @@ private mixin template JavaExportImpl(T, alias method) {
static if(is(typeof(return) == void)) {
try {
__traits(getMember, dobj, __traits(identifier, method))(JavaParamsToD!(Parameters!method)(env, args).args);
__traits(getOverloads, dobj, __traits(identifier, method))[overloadIndex](JavaParamsToD!(Parameters!method)(env, args).args);
} catch(Throwable t) {
jniRethrow(env, t);
}
} else {
try {
return DDatumToJni(env, __traits(getMember, dobj, __traits(identifier, method))(JavaParamsToD!(Parameters!method)(env, args).args));
return DDatumToJni(env, __traits(getOverloads, dobj, __traits(identifier, method))[overloadIndex](JavaParamsToD!(Parameters!method)(env, args).args));
} catch(Throwable t) {
jniRethrow(env, t);
return typeof(return).init; // still required to return...
}
}
}
shared static this() {
nativeMethodsData_ ~= JNINativeMethod(
getJavaName!method.ptr,
("(" ~ DTypesToJniString!(Parameters!method) ~ ")" ~ DTypesToJniString!(ReturnType!method) ~ "\0").ptr,
&privateJniImplementation
);
}
}
/++
@ -776,27 +986,48 @@ class JavaClass(string javaPackage, CRTP) : IJavaObject {
static assert("JavaClasses can only be constructed by Java. Try making a constructor in Java, then make an @Import this(args); here.");
// implementations
static foreach(attr; __traits(getAttributes, __traits(getMember, CRTP, memberName))) {
static foreach(oi, overload; __traits(getOverloads, CRTP, memberName))
static foreach(attr; __traits(getAttributes, overload)) {
static if(is(attr == Import))
mixin JavaImportImpl!(CRTP, __traits(getMember, CRTP, memberName));
mixin JavaImportImpl!(CRTP, overload, oi);
else static if(is(attr == Export))
mixin JavaExportImpl!(CRTP, __traits(getMember, CRTP, memberName));
mixin JavaExportImpl!(CRTP, overload, oi);
}
}
protected jobject internalJavaHandle_;
protected jobject getJavaHandle() { return internalJavaHandle_; }
__gshared static protected /*immutable*/ JNINativeMethod[] nativeMethodsData_;
protected static jclass internalJavaClassHandle_;
protected static int initializeInJvm_(JNIEnv* env) {
import core.stdc.stdio;
auto internalJavaClassHandle_ = (*env).FindClass(env, (_javaParameterString[1 .. $-1] ~ "\0").ptr);
if(!internalJavaClassHandle_) {
fprintf(stderr, ("Cannot find Java class for " ~ CRTP.stringof));
return 1;
}
if((*env).RegisterNatives(env, internalJavaClassHandle_, nativeMethodsData_.ptr, cast(int) nativeMethodsData_.length)) {
fprintf(stderr, ("RegisterNatives failed for " ~ CRTP.stringof));
return 1;
}
return 0;
}
shared static this() {
classInitializers_ ~= &initializeInJvm_;
}
static import std.string;
static if(javaPackage.length)
public static immutable string _javaParameterString = "L" ~ std.string.replace(javaPackage, ".", "/") ~ "/" ~ CRTP.stringof ~ ";";
public static immutable string _javaParameterString = "L" ~ std.string.replace(javaPackage, ".", "/") ~ "/" ~ getJavaName!CRTP ~ ";";
else
public static immutable string _javaParameterString = "L" ~ CRTP.stringof ~ ";";
public static immutable string _javaParameterString = "L" ~ getJavaName!CRTP ~ ";";
}
__gshared /* immutable */ int function(JNIEnv* env)[] classInitializers_;
@ -820,7 +1051,7 @@ class JavaClass(string javaPackage, CRTP) : IJavaObject {
import core.stdc.stdarg;
//version (Android):
extern (C):
extern (System):
@system:
nothrow:
@nogc: