Adding new paths for native libraries at runtime in Java
If you want to add some native libraries at runtime, it was tricky with Java 8+:
This was a valid solution:
fldUsrPaths.setAccessible(true);
//get array of paths
final String[] saPath = (String[])fldUsrPaths.get(null);
//check if the path to add is already present
for (String path : saPath)
{
if (path.equals(pPath))
{
return;
}
}
//add the new path
final String[] saNewPaths = Arrays.copyOf(saPath, saPath.length + 1);
saNewPaths[saNewPaths.length - 1] = pPath;
fldUsrPaths.set(null, saNewPaths);
Since Java 10+ it's not possible anymore, because of: JDK-8210522
Some details from Stackoverflow
If someone still needs a solution for Java 10+, here it is:
VarHandle usr_paths = cl.findStaticVarHandle(ClassLoader.class, "usr_paths", String[].class);
String[] path = (String[])usr_paths.get();
...
usr_paths.set(new String[] {"A", "B"});
This code won't work if you are using Java 8. If you want support for e.g. Java < 10, simply use reflection
Sounds simple, but it is not simple.....
Here's a working solution: toPDF project (search for addLibraryPath(String pPath)).
The code in detail:
Method mStaticLookup = clsMHandles.getMethod("lookup");
Object oStaticLookup = mStaticLookup.invoke(null);
Method mLookup = clsMHandles.getMethod("privateLookupIn", Class.class, Class.forName("java.lang.invoke.MethodHandles$Lookup"));
Object oLookup = mLookup.invoke(null, ClassLoader.class, oStaticLookup);
Method mFindStatic = oLookup.getClass().getMethod("findStaticVarHandle", Class.class, String.class, Class.class);
Object oVarHandle = mFindStatic.invoke(oLookup, ClassLoader.class, "usr_paths", String[].class);
//MethodHandle mh = MethodHandles.lookup().findVirtual(VarHandle.class, "get", MethodType.methodType(Object.class));
//mh.invoke(oVarHandle);
Method mFindVirtual = oStaticLookup.getClass().getMethod("findVirtual", Class.class, String.class, Class.forName("java.lang.invoke.MethodType"));
Class<?> clsMethodType = Class.forName("java.lang.invoke.MethodType");
Method mMethodType = clsMethodType.getMethod("methodType", Class.class);
Object oMethodHandleGet = mFindVirtual.invoke(oStaticLookup, Class.forName("java.lang.invoke.VarHandle"), "get", mMethodType.invoke(null, Object.class));
Method mMethodHandleGet = oMethodHandleGet.getClass().getMethod("invokeWithArguments", Object[].class);
String[] saPath = (String[])mMethodHandleGet.invoke(oMethodHandleGet, new Object[] {new Object[] {oVarHandle}});
//check if the path to add is already present
for (String path : saPath)
{
if (path.equals(pPath))
{
return;
}
}
//add the new path
final String[] saNewPaths = Arrays.copyOf(saPath, saPath.length + 1);
saNewPaths[saNewPaths.length - 1] = pPath;
//MethodHandle mh = MethodHandles.lookup().findVirtual(VarHandle.class, "set", MethodType.methodType(Void.class, Object[].class));
//mh.invoke(oVarHandle, new String[] {"GEHT"});
mMethodType = clsMethodType.getMethod("methodType", Class.class, Class.class);
Object oMethodHandleSet = mFindVirtual.invoke(oStaticLookup, Class.forName("java.lang.invoke.VarHandle"), "set", mMethodType.invoke(null, Void.class, Object[].class));
Method mMethodHandleSet = oMethodHandleSet.getClass().getMethod("invokeWithArguments", Object[].class);
mMethodHandleSet.invoke(oMethodHandleSet, new Object[] {new Object[] {oVarHandle, saNewPaths}});
Not simple!!!
Summarized: It's not easy but it's still possible. So.... why are things getting more and more complex?
Hi,
This is just brillant, thanks a tonn! - however there is a little curveball to this. It won't wotk with java 13 unfortunately. Check my stacktrace below.
If you could make it java 13 compliant, that would be highly appreciated. Thanks again.
Stacktrace from java 13 (openjdk):
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access using Lookup on com.foo.bar.NativeLoader (file:/classes/) to class java.lang.ClassLoader
WARNING: Please consider reporting this to the maintainers of com.foo.bar.NativeLoader
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException
The NPE is the saPath from your example being null. I guess it would be oodd for usr_paths to be null, so I guess reflection filetered it away.
With JDK 15.01 it becomes even worse. Now usr_paths are reflected as not even being part of the ClassLoader class:
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at com.foo.bar.(NativeLoader.java:48)
Caused by: java.lang.NoSuchFieldException: no such field: java.lang.ClassLoader.usr_paths/[Ljava.lang.String;/getStatic
at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:980)
at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1117)
at java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:3424)
at java.base/java.lang.invoke.MethodHandles$Lookup.findStaticVarHandle(MethodHandles.java:3024)
... 5 more
Caused by: java.lang.NoSuchFieldError: usr_paths
at java.base/java.lang.invoke.MethodHandleNatives.resolve(Native Method)
at java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:1087)
at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1114)
... 7 more
Bad news, but maybe only because the class structure has changed.... I'll check both problems tomorrow.... Hopefully there's a possibility to enable this again.
any luck so far...?
Not really! It works up to Java 14 in our tests.
The problem is that they changed the internal handling of sys path... and it was their intention to prevent this "hack".
We'll try to find a solution but right now, it won't work with Java > 15.
Check: https://blog.sibvisions.com/2024/09/09/adding-new-paths-for-native-libraries-at-runtime-in-java-part-2/