Add support for shared libraries on Android

Native Extensions don’t currently support linking to shared libraries. On most platforms, though, this can be circumvented by bundling the libraries as resources and opening them with dlopen()/dlsym().

On Android I wasn’t able to do that. System.loadLibrary() just doesn’t find it if I add it as a simple resource (And I had some weird results when trying to use System.load() directly).

This is blocking for my FMOD library. (Or at least I don’t know enough Android to know how to proceed).

I understand that shared library support might be a big feature and might not be on the roadmap soon, but I’m asking if you can, at least, consider supporting it for Android.

2 Likes

Hi!
I’m not entirely sure what’s needed to load a shared library on Android, but I was able to try it on iOS recently. Note that I had to codesign all binaries. However, on iOS, it’s still not allowed to release with more than one binary (the main executable)

"…as a simple resource"
What type is this? Did you put it in as a bundle resource or as an Android resource? It shouldn’t matter really, they should both end up within the apk (verify the location by unzipping the apk)

As for the original task, supporting shared libraries, I think bundle resources and native extensions helps with this. I’m uncertain what extra features would be needed to support this? This case is a good example.

One thing I recall with last time I used java to load libraries (in bob.jar), it was really tricky because the top library file was dependent on another library file. Eventually, what I did was this.

  • Find the library file
  • Add the parent folder of the file, to the java path’s
    • jna.library.path, java.library.path
  • Load the dependency
  • Lastly, register the library

Here is some code to show the flow (Desktop platforms):

public class MyLoader {

    static void addToPath(String variable, String path) {
        String newPath = null;

        // Check if the property was set externally.
        if (System.getProperty(variable) != null) {
            newPath = System.getProperty(variable);
        }

        if (newPath == null) {
            // Set path where the library is found.
            newPath = path;
        } else {
            // Append path where the library is found.
            newPath += File.pathSeparator + path;
        }

        // Set the concatenated jna.library path
        System.setProperty(variable, newPath);
    }

    static {
        try {
            File lib = new File("the path to the library on disc");

            String libPath = lib.getParent();
            addToPath("jna.library.path", libPath);
            addToPath("java.library.path", libPath);

            // Make sure all libraries are in the same folder (with correct rpath etc)

            String path = libPath + "/libExtraDependency.so";
            System.load(path);

            // Note, the actual name is "libmysharedlibrary.so" on linux
            Native.register("mysharedlibrary");
        } catch (Exception e) {
            System.out.println("FATAL: " + e.getMessage());
        }
    }
}
4 Likes

I added it to res/armv7-android/lib/armeabi-v7a/ and yeah, it ended up in the APK in lib/ alongslide the main engine’s .so.

Well, on Windows you can link against the .lib file and get away by placing the .dll in bundled_resources.

On macOS you can link against the .dylib, but it won’t load it on runtime unless you add the executable’s directory to RPATH from linker flags (which I think you can do now. I have to check).

On Linux and Android you can’t link against the .so because of a -L,-static flag in the linker command line. Also, the same RPATH problem listed above applies.

On all these platforms even if you manage to link against the libraries and deliver them as bundled resources, those bundled resources aren’t copied when run in the editor and the game won’t start in the editor.

On desktop platforms (and probably on Android as well, still not sure) you can get around the beforementioned problems (and only partially around the run-in-editor one) by linking at runtime with dlopen()/dlsym(), but this makes your life horrible when you actually want to write code that uses the library.

Thanks for the tip. This is probably what was missing from my approach. I’ll give it a try. I hope it works on Android as well.

3 Likes

Did you test this approach, if so; did it work?

Nope. Didn’t have time to. I will take a look soon, but I can’t make any promises for the next week or so.

1 Like

I finally got the time to take a look on this. Nope. It doesn’t work. I get this on Android:

E System  : Ignoring attempt to set property "java.library.path" to value "/vendor/lib:/system/lib:/data/data/com.example.todo/lib".

I did manage to get the library to load by calling System.loadLibrary("fmod") from a separate jar. I don’t know why calling it directly with JNI didn’t work, but if I put the call in a separate jar and call it indirectly it works.

2 Likes