diff --git a/GSR.Android/AndroidCryptography.cs b/GSR.Android/AndroidCryptography.cs index 55a21e4..0f6fe9f 100644 --- a/GSR.Android/AndroidCryptography.cs +++ b/GSR.Android/AndroidCryptography.cs @@ -19,9 +19,9 @@ public static class AndroidCryptography private static JMethodID _hashDataSha256MethodId; private static JMethodID _getRandomInt32MethodId; - internal static void InitializeJNI(JNIEnvPtr env) + internal static void InitializeJNI(JNIEnvPtr env, JClass gsrActivityClassId) { - _gsrActivityClassId = env.FindClass("org/psr/gsr/GSRActivity"u8); + _gsrActivityClassId = gsrActivityClassId; _hashDataSha256MethodId = env.GetStaticMethodID(_gsrActivityClassId, "HashDataSHA256"u8, "(Ljava/nio/ByteBuffer;)[B"u8); _getRandomInt32MethodId = env.GetStaticMethodID(_gsrActivityClassId, "GetRandomInt32"u8, "(I)I"u8); } diff --git a/GSR.Android/AndroidFile.cs b/GSR.Android/AndroidFile.cs index 8a66f9c..3e4b59b 100644 --- a/GSR.Android/AndroidFile.cs +++ b/GSR.Android/AndroidFile.cs @@ -21,9 +21,9 @@ public static class AndroidFile private static JMethodID _requestDocumentMethodId; private static JMethodID _openContentMethodId; - internal static void InitializeJNI(JNIEnvPtr env) + internal static void InitializeJNI(JNIEnvPtr env, JClass gsrActivityClassId) { - _gsrActivityClassId = env.FindClass("org/psr/gsr/GSRActivity"u8); + _gsrActivityClassId = gsrActivityClassId; _requestDocumentMethodId = env.GetStaticMethodID(_gsrActivityClassId, "RequestDocument"u8, "()V"u8); _openContentMethodId = env.GetStaticMethodID(_gsrActivityClassId, "OpenContent"u8, "(Ljava/lang/String;)I"u8); } diff --git a/GSR.Android/AndroidJNI.cs b/GSR.Android/AndroidJNI.cs index 06dde55..20f9a3a 100644 --- a/GSR.Android/AndroidJNI.cs +++ b/GSR.Android/AndroidJNI.cs @@ -13,13 +13,16 @@ namespace GSR.Android; /// /// Handles Android JNI initialization /// -public static class AndroidJNI +public static unsafe class AndroidJNI { // JNI version 1.4 (what SDL uses) private const int JNI_VERSION_1_4 = 0x00010004; private const int JNI_OK = 0; - public static unsafe int Initialize(nint vm) + private static JClass _gsrActivityClassId; + private static bool _nativeFunctionsRegistered; + + public static int Initialize(nint vm) { var javaVM = (JavaVM*)vm; JNIEnv* e; @@ -33,8 +36,14 @@ public static unsafe int Initialize(nint vm) try { var env = new JNIEnvPtr(e); - AndroidFile.InitializeJNI(env); - AndroidCryptography.InitializeJNI(env); + using (var gsrActivityClassId = + new LocalRefWrapper(env, env.FindClass("org/psr/gsr/GSRActivity"u8))) + { + _gsrActivityClassId = (JClass)env.NewGlobalRef(gsrActivityClassId.LocalRef); + } + + AndroidFile.InitializeJNI(env, _gsrActivityClassId); + AndroidCryptography.InitializeJNI(env, _gsrActivityClassId); fixed (byte* dispatchAndroidKeyEventName = "DispatchAndroidKeyEvent"u8, @@ -51,18 +60,48 @@ public static unsafe int Initialize(nint vm) nativeFunctions[1].Name = setDocumentRequestResultName; nativeFunctions[1].Signature = setDocumentRequestResultSignature; nativeFunctions[1].FnPtr = (delegate* unmanaged)&AndroidFile.SetDocumentRequestResult; - - var gsrActivityClassId = env.FindClass("org/psr/gsr/GSRActivity"u8); - env.RegisterNatives(gsrActivityClassId, nativeFunctions); + env.RegisterNatives(_gsrActivityClassId, nativeFunctions); + _nativeFunctionsRegistered = true; } return JNI_VERSION_1_4; } catch (Exception ex) { + Deinitialize(vm); // can't show a message box here, as this is a Java thread, not a native thread Console.Error.WriteLine($"JNI initialization has failed, this is fatal. Exception given: {ex}"); return -1; } } + + private static void Deinitialize(nint vm) + { + if (!_gsrActivityClassId.IsNull) + { + var javaVM = (JavaVM*)vm; + JNIEnv* e; + var res = javaVM->Vtbl->GetEnv(javaVM, (void**)&e, JNI_VERSION_1_4); + if (res != JNI_OK) + { + Console.Error.WriteLine($"Failed to get JNIEnv* from JavaVM*, cannot properly deinitialize JNI. Error code given: {JNIException.GetErrorCodeString(res)}"); + return; + } + + var env = new JNIEnvPtr(e); + if (_nativeFunctionsRegistered) + { + try + { + env.UnregisterNatives(_gsrActivityClassId); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Failed to unregister native functions, exception given: {ex}"); + } + } + + env.DeleteGlobalRef(_gsrActivityClassId); + } + } } diff --git a/GSR/AndroidExports.cs b/GSR/AndroidExports.cs index 041cf82..e4d5436 100644 --- a/GSR/AndroidExports.cs +++ b/GSR/AndroidExports.cs @@ -45,6 +45,19 @@ public static int JNIOnLoad(nint vm, nint reserved) _ = reserved; return AndroidJNI.Initialize(vm); } + + /// + /// Called by Java side when the native library is garbage collected + /// JNI entrypoints should be unregistered at this point, and other JNI deinitialization should occur here too + /// + /// pointer to JavaVM + /// reserved + [UnmanagedCallersOnly(EntryPoint = "JNI_OnUnload")] + public static void JNIOnUnload(nint vm, nint reserved) + { + _ = reserved; + AndroidJNI.Deinitialize(vm); + } } #endif