22
33#include < jni.h>
44#include < dlfcn.h>
5+ #include < unistd.h>
6+
57#include < android/dlext.h>
8+ #include < android/looper.h>
69
710#include < string_view>
811
912#include < runtime-base/android-system.hh>
13+ #include < runtime-base/mainthread-dso-loader.hh>
14+ #include < runtime-base/system-loadlibrary-wrapper.hh>
1015#include < runtime-base/util.hh>
1116#include < shared/helpers.hh>
1217
@@ -19,13 +24,11 @@ namespace xamarin::android {
1924 {
2025 public:
2126 [[gnu::flatten]]
22- static void init (JNIEnv *env, jclass systemClass)
27+ static void init (JNIEnv *env, jclass systemClass, ALooper *main_looper, pid_t _main_thread_id) noexcept
2328 {
24- systemKlass = systemClass;
25- System_loadLibrary = env->GetStaticMethodID (systemClass, " loadLibrary" , " (Ljava/lang/String;)V" );
26- if (System_loadLibrary == nullptr ) [[unlikely]] {
27- Helpers::abort_application (" Failed to look up the Java System.loadLibrary method." );
28- }
29+ SystemLoadLibraryWrapper::init (env, systemClass);
30+ MainThreadDsoLoader::init (main_looper);
31+ main_thread_id = _main_thread_id;
2932 }
3033
3134 // Overload used to load libraries from the file system.
@@ -68,6 +71,7 @@ namespace xamarin::android {
6871
6972 private:
7073 static auto get_jnienv () noexcept -> JNIEnv*;
74+ static auto load_jni_on_main_thread (std::string_view const & full_name, std::string const & undecorated_name) noexcept -> void*;
7175
7276 [[gnu::always_inline]]
7377 static auto log_and_return (void *handle, std::string_view const & full_name) -> void*
@@ -145,25 +149,29 @@ namespace xamarin::android {
145149 return name;
146150 };
147151
148- // std::string is needed because we must pass a NUL-terminated string to Java, otherwise
149- // strange things happen (and std::string_view is not necessarily such a string)
150- const std::string undecorated_lib_name { get_undecorated_name (name, name_is_path) };
151- log_debug (LOG_ASSEMBLY, " Undecorated library name: {}" , undecorated_lib_name);
152-
153- JNIEnv *jni_env = get_jnienv ();
154- jstring lib_name = jni_env->NewStringUTF (undecorated_lib_name.c_str ());
155- if (lib_name == nullptr ) [[unlikely]] {
156- // It's an OOM, there's nothing better we can do
157- Helpers::abort_application (" Java string allocation failed while loading a shared library." );
158- }
159- jni_env->CallStaticVoidMethod (systemKlass, System_loadLibrary, lib_name);
160- if (jni_env->ExceptionCheck ()) {
161- log_debug (LOG_ASSEMBLY, " System.loadLibrary threw a Java exception. Will attempt to log it." );
162- jni_env->ExceptionDescribe ();
163- jni_env->ExceptionClear ();
164- log_debug (LOG_ASSEMBLY, " Java exception cleared" );
165- // TODO: should we abort? Return `nullptr`? `dlopen` still has a chance to succeed, even if loadLibrary
166- // failed but it won't call `JNI_OnLoad` etc, so the result might be less than perfect.
152+ // So, we have a rather nasty problem here. If we're on a thread other than the main one (or, to be more
153+ // precise - one not created by Java), we will NOT have the special class loader Android uses in JVM and
154+ // which knows about the special application-specific .so paths (like the one inside the APK itself). For
155+ // that reason, `System.loadLibrary` will not be able to find the requested .so and we can't pass it a full
156+ // path to it, since it accepts only the undecorated library name.
157+ // We have to call `System.loadLibrary` on the main thread, so that the special class loader is available to
158+ // it. At the same time, we have to do it synchronously, because we must be able to get the library handle
159+ // **here**. We could call to a Java function here, but then synchronization might be an issue. So, instead,
160+ // we use a wrapper around System.loadLibrary that uses the ALooper native Android interface. It's a bit
161+ // clunky (as it requires using a fake pipe(2) to force the looper to call us on the main thread) but it
162+ // should work across all the Android versions.
163+
164+ // TODO: implement the above
165+ if (gettid () == main_thread_id) {
166+ if (!SystemLoadLibraryWrapper::load (get_jnienv (), get_undecorated_name (name, name_is_path))) {
167+ // We could abort, but let's let the managed land react to this library missing. We cannot continue
168+ // with `dlopen` below, because without `JNI_OnLoad` etc invoked, we might have nasty crashes in the
169+ // library code if e.g. it assumes that `JNI_OnLoad` initialized all the Java class, method etc
170+ // pointers.
171+ return nullptr ;
172+ }
173+ } else {
174+ Helpers::abort_application (" Loading DSO on the main thread not implemented yet" sv);
167175 }
168176
169177 // This is unfortunate, but since `System.loadLibrary` doesn't return the class handle, we must get it this
@@ -177,5 +185,6 @@ namespace xamarin::android {
177185 private:
178186 static inline jmethodID System_loadLibrary = nullptr ;
179187 static inline jclass systemKlass = nullptr ;
188+ static inline pid_t main_thread_id = 0 ;
180189 };
181190}
0 commit comments