JNI, загрузка нативных библиотек. Меняем java.library.path на лету
В подмножестве экосистемы Java, относящейся в основном к JNI (без которого никуда не деться, если приходиться интегрироваться с каким-то legacy или просто редким и специфическим кодом, написанном на С или каком-то другом языке), есть такое понятие, как java.library.path. Вкратце, это в некотором роде аналог classpath, только не для Java классов и *.jar файлов, а для нативных библиотек — системное свойство, которое указывает JVM, где искать эти самые нативные библиотеки (.dll в винде или .so под юниксами).
Свойство это устанавливается один раз, перед запуском JVM, через глобальные system properties, или как ключ -Dname=value для JVM, и после этого оно становится read-only. Точнее, менять-то его можно, но никакого эффекта на работу программы это не окажет, т.к. после того как вы обновите это свойство, JVM не перечитает его и не будет использовать новое значение.
Под катом — про то, как все таки поменять это свойство в рантайме, и немного о том, как собственно работает загрузка нативных библиотек в Java.
Однако, возможность менять java.library.path на лету была бы очень кстати — тогда бы не пришлись много раз генерить, переписывать и перезаписывать скрипты для запуска JBoss-a, например, чтобы отразить в них все нужные пути ДО старта аппсервера.
И такая возможность, изменять эти пути, по которым JVM ищет нативные библиотеки, на самом деле есть. Конкретные приемы показаны тут — blog.cedarsoft.com/2010/11/setting-java-library-path-programmatically и еще вот тут — nicklothian.com/blog/2008/11/19/modify-javalibrarypath-at-runtime.
А здесь я опишу сам механизм загрузки, и почему то, что описано по ссылкам, работает. Обычно, нативные библиотеки загружаются через статический инициализатор:
* This source code was highlighted with Source Code Highlighter .
Который выглядит так:
public static void loadLibrary( String libname) <
Runtime.getRuntime().loadLibrary0(getCallerClass(), libname);
>
* This source code was highlighted with Source Code Highlighter .
И далее в классе Runtime:
synchronized void loadLibrary0(Class fromClass, String libname) <
// Проверяем, разрешено ли загружать данную конкретную библиотеку
SecurityManager security = System.getSecurityManager();
if (security != null ) <
security.checkLink(libname);
>
if (libname.indexOf(( int ) File .separatorChar) != -1) <
throw new UnsatisfiedLinkError( «Directory separator» +
«should not appear in library name: » + libname);
>
ClassLoader.loadLibrary(fromClass, libname, false );
>
* This source code was highlighted with Source Code Highlighter .
Т.е. в итоге, нативные библиотеки загружаются, так же как и обычные классы, через ClassLoader. У класса ClassLoader есть два свойства, в которых кешируются проинициализированные пути поиска.
// The paths searched for libraries
static private String usr_paths[];
static private String sys_paths[];
* This source code was highlighted with Source Code Highlighter .
Код метода ClassLoader.loadLibrary(fromClass, libname, false), довольно длинный, и загроможденный многочисленными проверками, в сокращенном виде выглядит это так.
// Invoked in the java.lang.Runtime class to implement load and loadLibrary.
static void loadLibrary(Class fromClass, String name,
boolean isAbsolute) <
ClassLoader loader = (fromClass == null ) ? null : fromClass.getClassLoader();
if (sys_paths == null ) <
// это то, что нам нужно
usr_paths = initializePath( «java.library.path» );
// а это для тех библиотек, которые загружаются из классов,
// загруженных из boot classpath.
sys_paths = initializePath( «sun.boot.library.path» );
>
// Дальше попытка загрузить библиотеку, и дальше,
// если найти ее так и не удалось, то —
// Oops, it failed
throw new UnsatisfiedLinkError( «no » + name + » in java.library.path» );
>
* This source code was highlighted with Source Code Highlighter .
Таким образом, теперь механизм загрузки нативной библиотеки стал более понятен.
Вы можете либо выставить в null свойство sys_paths у класслоадера, либо просто поменять свойства sys_paths / usr_paths, добавив к ним нужные пути к вашим нативным библиотекам.
Источник
Binding a Java Library
The Android community has many Java libraries that you may want to use in your app; this guide explains how to incorporate Java libraries into your Xamarin.Android application by creating a Bindings Library.
Overview
The third-party library ecosystem for Android is massive. Because of this, it frequently makes sense to use an existing Android library than to create a new one. Xamarin.Android offers two ways to use these libraries:
Create a Bindings Library that automatically wraps the library with C# wrappers so you can invoke Java code via C# calls.
Use the Java Native Interface (JNI) to invoke calls in Java library code directly. JNI is a programming framework that enables Java code to call and be called by native applications or libraries.
This guide explains the first option: how to create a Bindings Library that wraps one or more existing Java libraries into an assembly that you can link to in your application. For more information about using JNI, see Working with JNI.
Xamarin.Android implements bindings by using Managed Callable Wrappers (MCW). MCW is a JNI bridge that is used when managed code needs to invoke Java code. Managed callable wrappers also provide support for subclassing Java types and for overriding virtual methods on Java types. Likewise, whenever Android runtime (ART) code wishes to invoke managed code, it does so via another JNI bridge known as Android Callable Wrappers (ACW). This architecture is illustrated in the following diagram:
A Bindings Library is an assembly containing Managed Callable Wrappers for Java types. For example, here is a Java type, MyClass , that we want to wrap in a Bindings Library:
After we generate a Bindings Library for the .jar that contains MyClass , we can instantiate it and call methods on it from C#:
To create this Bindings Library, you use the Xamarin.Android Java Bindings Library template. The resulting binding project creates a .NET assembly with the MCW classes, .jar file(s), and resources for Android Library projects embedded in it. You can also create Bindings Libraries for Android Archive (.AAR) files and Eclipse Android Library projects. By referencing the resulting Bindings Library DLL assembly, you can reuse an existing Java library in your Xamarin.Android project.
When you reference types in your Binding Library, you must use the namespace of your binding library. Typically, you add a using directive at the top of your C# source files that is the .NET namespace version of the Java package name. For example, if the Java package name for your bound .jar is the following:
Then you would put the following using statement at the top of your C# source files to access types in the bound .jar file:
When binding an existing Android library, it is necessary to keep the following points in mind:
Are there any external dependencies for the library? – Any Java dependencies required by the Android library must be included in the Xamarin.Android project as a ReferenceJar or as an EmbeddedReferenceJar. Any native assemblies must be added to the binding project as an EmbeddedNativeLibrary.
What version of the Android API does the Android library target? – It is not possible to «downgrade» the Android API level; ensure that the Xamarin.Android binding project is targeting the same API level (or higher) as the Android library.
What version of the JDK was used to compile the library? – Binding errors may occur if the Android library was built with a different version of JDK than in use by Xamarin.Android. If possible, recompile the Android library using the same version of the JDK that is used by your installation of Xamarin.Android.
Build Actions
When you create a Bindings Library, you set build actions on the .jar or .AAR files that you incorporate into your Bindings Library project – each build action determines how the .jar or .AAR file will be embedded into (or referenced by) your Bindings Library. The following list summarizes these build actions:
EmbeddedJar – Embeds the .jar into the resulting Bindings Library DLL as an embedded resource. This is the simplest and most commonly-used build action. Use this option when you want the .jar automatically compiled into byte code and packaged into the Bindings Library.
InputJar – Does not embed the .jar into the resulting Bindings Library .DLL. Your Bindings Library .DLL will have a dependency on this .jar at runtime. Use this option when you do not want to include the .jar in your Bindings Library (for example, for licensing reasons). If you use this option, you must ensure that the input .jar is available on the device that runs your app.
LibraryProjectZip – Embeds an .AAR file into the resulting Bindings Library .DLL. This is similar to EmbeddedJar, except that you can access resources (as well as code) in the bound .AAR file. Use this option when you want to embed an .AAR into your Bindings Library.
ReferenceJar – Specifies a reference .jar: a reference .jar is a .jar that one of your bound .jar or .AAR files depends on. This reference .jar is used only to satisfy compile-time dependencies. When you use this build action, C# bindings are not created for the reference .jar and it is not embedded in the resulting Bindings Library .DLL. Use this option when you will make a Bindings Library for the reference .jar but have not done so yet. This build action is useful for packaging multiple .jars (and/or .AARs) into multiple interdependent Bindings Libraries.
EmbeddedReferenceJar – Embeds a reference .jar into the resulting Bindings Library .DLL. Use this build action when you want to create C# bindings for both the input .jar (or .AAR) and all of its reference .jar(s) in your Bindings Library.
EmbeddedNativeLibrary – Embeds a native .so into the binding. This build action is used for .so files that are required by the .jar file being bound. It may be necessary to manually load the .so library before executing code from the Java library. This is described below.
These build actions are explained in more detail in the following guides.
Additionally, the following build actions are used to help importing Java API documentation and convert them into C# XML documentation:
- JavaDocJar is used to point to Javadoc archive Jar for a Java library that conforms to a Maven package style (usually FOOBAR-javadoc**.jar** ).
- JavaDocIndex is used to point to index.html file within the API reference documentation HTML.
- JavaSourceJar is used to complement JavaDocJar , to first generate JavaDoc from sources and then treat the results as JavaDocIndex , for a Java library that conforms to a Maven package style (usually FOOBAR-sources**.jar** ).
The API documentation should be the default doclet from Java8, Java7 or Java6 SDK (they are all different format), or the DroidDoc style.
Including a Native Library in a Binding
It may be necessary to include a .so library in a Xamarin.Android binding project as a part of binding a Java library. When the wrapped Java code executes, Xamarin.Android will fail to make the JNI call and the error message java.lang.UnsatisfiedLinkError: Native method not found: will appear in the logcat out for the application.
The fix for this is to manually load the .so library with a call to Java.Lang.JavaSystem.LoadLibrary . For example assuming that a Xamarin.Android project has shared library libpocketsphinx_jni.so included in the binding project with a build action of EmbeddedNativeLibrary, the following snippet (executed before using the shared library) will load the .so library:
Adapting Java APIs to C⧣
The Xamarin.Android Binding Generator will change some Java idioms and patterns to correspond to .NET patterns. The following list describes how Java is mapped to C#/.NET:
Setter/Getter methods in Java are Properties in .NET.
Fields in Java are Properties in .NET.
Listeners/Listener Interfaces in Java are Events in .NET. The parameters of methods in the callback interfaces will be represented by an EventArgs subclass.
A Static Nested class in Java is a Nested class in .NET.
An Inner class in Java is a Nested class with an instance constructor in C#.
Binding Scenarios
The following binding scenario guides can help you bind a Java library (or libraries) for incorporation into your app:
Binding a .JAR is a walkthrough for creating Bindings Libraries for .jar files.
Binding an .AAR is a walkthrough for creating Bindings Libraries for .AAR files. Read this walkthrough to learn how to bind Android Studio libraries.
Binding an Eclipse Library Project is a walkthrough for creating binding libraries from Android Library Projects. Read this walkthrough to learn how to bind Eclipse Android Library Projects.
Customizing Bindings explains how to make manual modifications to the binding to resolve build errors and shape the resulting API so that it is more «C#-like».
Troubleshooting Bindings lists common binding error scenarios, explains possible causes, and offers suggestions for resolving these errors.
Источник