I'm developing a Grails web application and I need to use a JNI native library to access some specific hardware. For a simple Java application (see below) it works fine. To do this I just have to add the JAR to the Java build path and specify the "Native library location" (I use SpringSource Tool Suite on Windows7).
Working example:
import conceptacid.nativedriver.Driver;
public class Main {
public static void main(String[] args) {
System.out.println("starting...");
System.loadLibrary("AudioCardDriver");
Driver driver = Driver.instance();
String driverDescription = driver.getDriverDescription();
System.out.println( "Native application driver: " + driverDescription);
}
}
However, when I try to add it into my Grails application it fails:
Bootstrap.groovy:
import conceptacid.nativedriver.Driver;
class BootStrap {
def init = { servletContext ->
System.loadLibrary("AudioCardDriver");
Driver driver = Driver.instance();
String driverDescription = driver.getDriverDescription();
System.out.println( "Native application driver: " + driverDescription);
}
def destroy = {
}
}
the first line System.loadLibrary("AudioCardDriver"); is executed silently without any exception but the next line where I try to use my native code Driver driver = Driver.instance(); fails:
Running script C:\grails\scripts\RunApp.groovy
Environment set to development
[groovyc] Compiling 1 source file to D:\Projects3\mbr\target\classes
[delete] Deleting directory C:\Users\VShmyrev\.grails\1.3.7\projects\mbr\tomcat
Running Grails application..
2012-02-24 15:19:49,690 [main] ERROR context.GrailsContextLoader - Error executing bootstraps: java.lang.UnsatisfiedLinkError: conceptacid.nativedriver.AudioCardDriverJNI.swig_module_init()V
org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.UnsatisfiedLinkError: conceptacid.nativedriver.AudioCardDriverJNI.swig_module_init()V
at grails.util.Environment.evaluateEnvironmentSpecificBlock(Environment.java:251)
...
Caused by: java.lang.UnsatisfiedLinkError: conceptacid.nativedriver.AudioCardDriverJNI.swig_module_init()V
at conceptacid.nativedriver.AudioCardDriverJNI.swig_module_init(Native Method)
at conceptacid.nativedriver.AudioCardDriverJNI.<clinit>(AudioCardDriverJNI.java:70)
at conceptacid.nativedriver.Driver.instance(Driver.java:35)
at conceptacid.nativedriver.Driver$instance.call(Unknown Source)
at BootStrap$_closure1.doCall(BootStrap.groovy:7)
... 26 more
Application context shutting down...
I'm sure I put the DLL into a directory which is in my system PATH but it doesn't help.
What is the right way to use a native library in Grails application both in a development environment and in production?
Your DLL needs to be on a path specified in the Java system property java.library.path. On Windows the PATH environment variable and Linux the LD_LIBRARY_PATH environment variable are added to this system property. You can try logging the java.library.path system property to see if Java is looking in the right place for your DLL.
I'm guessing that the native library is being loaded multiple times in different classloaders, so forking a new JVM when running the app might help.
Related
I have a simple Spring Boot project which loads native libraries. The problem is that I have no idea how to specify the path to the native library when running the application.
I have read tons of posts explaining how to set java.library.path but every single one leads to
java.lang.UnsatisfiedLinkError: /path/to/lib/libconnector.so: libconnector.so: cannot open shared object file: No such file or directory
The project works if I run these two commands in a sequence from command line:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib
./gradlew bootRun
The library is loaded and works. But I am unable to specify the library path in my gradle file. I tried
run {
systemProperty 'java.library.path', file('/path/to/lib')
}
bootRun {
systemProperty 'java.library.path', file('/path/to/lib')
}
and all sorts of variations of this. Also tried adding VM arguments to my IDE etc. but nothing works. Could someone explain what am I doing wrong?
This is how I load the native library (located in $projectRoot/lib):
static {
// load connector library
File lib = new File("lib/" + System.mapLibraryName("connector"));
System.load(lib.getAbsolutePath());
}
I finally solved my problem. I should be passing LD_LIBRARY_PATH as an environment variable instead of java.library.path as system property when running the application.
The following Gradle modification solved my issue:
tasks.withType(JavaExec) {
environment('LD_LIBRARY_PATH', 'lib')
}
You can write a init method to auto-set java.library.path,
here are some codes:
String path = "/path/to/lib";
String path = System.getProperty("java.library.path");
// if os is windows
path += ";" + classPath.getCanonicalPath();
// if os is linux
path += ":" + classPath.getCanonicalPath();
System.setProperty("java.library.path", path);
Note: run this method first.
try to load the lib from your class ( just to test)
NB : i'am not sure but if you need to use so file you must be on a linux OS.
public class Test {
static {
try {
System.loadLibrary("yourSohere");
// or System.load("/path/to/lib.so");
}
}
}
You can simply use:
dependencies {
compile files('libs/something_local.jar')
}
I use JNA to load a c++ library (.so) in a java project. I package my library inside the jar, and load it from the jar when instantiating the java class that uses it. I do all this like so:
mvn install compiles the c++ code and packages the outcome dynamic library inside the jar.
I call in a static context when instantiating the LibraryWrapperClass the following
System.load( temp.getAbsolutePath() );
where temp is a temporary file containing the library which was found in the jar. This code is based on the work found here adamheinrich
- I call Native.loadLibrary(LIBRARYPATH) to wrap the library into a java class.
private interface Wrapper extends Library {
Wrapper INSTANCE = Native.loadLibrary( C_LIBRARY_PATH, Wrapper.class );
Pointer Constructor();
...
}
I run tests and validate that the library was found and up and running.
I use a java web project that depends on this project. It uses tomcat and runs fine in local.
My issue is that when I deploy on the server, the LibraryWrapperClass cannot instantiate. Error on server is:
java.lang.NoClassDefFoundError: Could not initialize class pacakgeName.LibraryWrapperClass
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:375)
at org.hibernate.annotations.common.util.StandardClassLoaderDelegateImpl.classForName(StandardClassLoaderDelegateImpl.java:57)
at org.hibernate.boot.internal.MetadataBuilderImpl$MetadataBuildingOptionsImpl$4.classForName(MetadataBuilderImpl.java:758)
at org.hibernate.annotations.common.reflection.java.JavaReflectionManager.classForName(JavaReflectionManager.java:144)
at...
This error seems that the library is found, since there is not the UnsatisfiedLinkError exception thrown. But something else is failing. Do someone know what could happen? How could I debug?
I recall that everything works perfectly in local.
How could I debug?
1. with strace
strace will give you what files Tomcat is trying to open : strace -f -e trace=file -o log.txt bin/startup.sh
After this, look for packageName in log.txt, or other files not found with :
egrep ' open.*No such file' log.txt
2. with JConsole
Enable JMX, launch a JConsole, go to VM summary tab, and check/compare very carefully VM arguments/classpath/library path/boot class path
3. dependency listing with ldd
If a dependency issue is likely to be the problem, the ldd sharedLibraryFile.so command lists all the dependencies and allows to track which one might be missing.
I'm trying to set up a simple Java application to connect to a SQLite database but keep receiving:
ClassNotFoundException: org.sqlite.jdbc.
I've downloaded the sqlite jdbc driver jar and placed it in the same directory as the .java, and I'm compiling on the command line with:
javac -cp sqlite-jdbc-3.7.2.jar sqlite3test.java
At runtime I then get the above exception. Below is the code:
import java.sql.*;
public class sqlite3test
{
public static void main(String args[])
{
Connection c = null;
try
{
Class.forName("org.sqlite.JDBC");
c = DriverManager.getConnection("jdbc:sqlite:cs261.db");
}
catch ( Exception e )
{
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(0);
}
System.out.println("Opened database successfully");
}
}
How do I fix this issue?
Thanks.
Check the database version installed, in my case it was: SQLite version 3.8.2
I am using sqlite-jdbc-3.8.9.1.jar which is the greater version number than of the database installed and it is working perfectly.
You have to make sure the jar file version should be greater than or equal to the database version installed in your system.
From the latest version of java (from Java 6+) you don't need to load your class file.
Remove the following line and try it should work.
Class.forName("org.sqlite.JDBC");
Note: I suggest you to rename your class name from sqlite3test to Sqlite3Test though its not mandatory. According to the JAVA code convention class name should always start with Caps letter.
There are different ways to load the driver into your classpath:
place the jar into the $JAVA_HOME/jre/lib/ext folder
using maven dependencies
using java -cp
if you are using the IDE like eclipse then right click the project -> build path -> configure build path -> libraries -> add external jars and locate your jar there.
For your reference : http://www.sqlitetutorial.net/sqlite-java/sqlite-jdbc-driver/
I am running 64 windows. My java version says:
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
My project is GWT Spring project ran on Apache Tomcat server, above java properties apply to the server (this is said to clarify that the server isn't accidentally running different Java version).
In Java code, I use System.getProperty("sun.arch.data.model") to determine Java version and based on result, I load either 64 or 32 bit dll. In the specific case, I'm now running 64bit and therefore I need 64bit dll file. I placed the file in this location:
D:\programs\apache-tomcat-7.0.64-windows-x64\webapps\war\WEB-INF\lib\MY_LIBRARYx64.dll
In the java code, I have this function to load the library:
private void setLibraryPath_private(String libraryPath) {
// Some magic which doesn't work anyway
File path = new File(configService.getProjectPrefix() + libraryPath);
try {
// Get valid canonical path (special case of absolute path)
path = new File(path.getCanonicalPath());
// Check for readability of that location
if(!path.canRead())
throw new IOException("Cannot read from this file.");
}
catch(IOException e) {
logger.error("Invalid path to R220 library: "+path.getAbsolutePath(), e);
}
// Remember path as string
this.libraryPath = path.getAbsolutePath();
try {
// Load it
library =
(R220) Native.loadLibrary(path.getCanonicalPath(), R220.class);
}
catch(Throwable e) {
// TODO: After loading problem is solved, probably replace throw with the logging
//logger.error("Failed to load R220 library: "+path.getAbsolutePath(), e);
throw new IllegalArgumentException("Failed to load R220 library: "+path.getAbsolutePath(), e);
}
// I don't know what this is
library.initLogger(configService.getLog4cplusConfigFilePath());
}
But in my log I still get error:
[INFO ] 2015-09-21 15:52:26,038 cz.techsys.web.server.R220LibService - Running 64bit version of the native libary.
[ERROR] 2015-09-21 15:52:26,113 cz.techsys.web.server.R220LibService - Failed to load R220 library: D:\programs\apache-tomcat-7.0.64-windows-x64\webapps\war\WEB-INF\lib\MY_LIBRARYx64.dll
java.lang.UnsatisfiedLinkError: Unable to load library 'D:\programs\apache-tomcat-7.0.64-windows-x64\webapps\war\WEB-INF\lib\MY_LIBRARYx64.dll': The specified module could not be found.
at com.sun.jna.NativeLibrary.loadLibrary(NativeLibrary.java:163)
at com.sun.jna.NativeLibrary.getInstance(NativeLibrary.java:236)
at com.sun.jna.Library$Handler.<init>(Library.java:140)
at com.sun.jna.Native.loadLibrary(Native.java:379)
at com.sun.jna.Native.loadLibrary(Native.java:364)
I also checked whether my library really is 64bit. It is, according to this guy:
I also compared it to the 32bit version, which looks like this:
So to sum up:
Processor architecture matches for the library and Java
The library is in readable and reachable location
What else can be causing the trouble?
Edit:
Based on some comments here, I also tried to pass library name without extension and set library path through System:
// Remove name from extension
String name = path.getName().replaceFirst("[.][^.]+$", "");
// Get directory path
String dir = path.getParentFile().getAbsolutePath();
// Print path to console for debug
logger.warn("\n Lib path: "+dir+"\n Lib name:"+name);
// Change library path
System.setProperty("jna.library.path", dir);
// Load it
library =
(R220) Native.loadLibrary(name, R220.class);
Needless to say, this had no effect, except the one that error message is now slightly shorter:
[WARN ] 2015-09-21 16:33:56,819 cz.techsys.web.server.R220LibService -
Lib path: D:\programs\apache-tomcat-7.0.64-windows-x64\webapps\war\WEB-INF\lib
Lib name:WebSightR220libx64
[ERROR] 2015-09-21 16:33:56,882 cz.techsys.web.server.R220LibService - Failed to load R220 library: D:\programs\apache-tomcat-7.0.64-windows-x64\webapps\war\WEB-INF\lib\WebSightR220libx64.dll
java.lang.UnsatisfiedLinkError: Unable to load library 'WebSightR220libx64': The specified module could not be found.
at com.sun.jna.NativeLibrary.loadLibrary(NativeLibrary.java:163)
at com.sun.jna.NativeLibrary.getInstance(NativeLibrary.java:236)
I have an application where I need to use a Native Library: libfoo.so
My code is as follows:
Accessor.java:
public class Accessor {
static {
String path = "/usr/lib/libfoo.so";
System.load(path);
}
...
}
This works perfectly fine when I deploy my war file in a standalone tomcat server.
The problem is when I try to run the embedded tomcat server when you run:
grails run-app
I get an UnsatisfiedLinkError:
Caused by UnsatisfiedLinkError: com.foo.bar.GFS_MALJNI.new_Accessor__SWIG_0(Ljava/lang/String;I)J
->> 39 | <init> in com.foo.bar.Accessor
Interestingly enough, if I change my BuildConfig.groovy file to fork mode, it also works.
BuildConfig.groovy:
grails.project.fork = [
run: [maxMemory:1024, minMemory:64, debug:false, maxPerm:256]
]
I do not want to run it in fork mode.
I noticed that two different class loaders are being used.
In the non-forked mode, this class loader was being used: java.net.URLClassLoader
In the forked mode, this class loader was being used: groovy.lang.GroovyClassLoader
The native library works correctly in the forked mode, so I needed to come up with a hack to load the library with the GroovyClassLoader in the non-forked mode.
This is how System.load is defined in the JDK Source:
System.java:
public final class System {
...
public static void load(String filename) {
Runtime.getRuntime().load0(getCallerClass(), filename);
}
...
}
It's calling load0 with the classloader and filename. The obvious solution is to call load0 with your own classloader, but you can't call it since it is package-protected.
When you write code in groovy, you have access to packge-protected and private methods/variables.
I can specify my own classloader and load the library, as such:
class Accessor {
static {
String path = "/usr/lib/libfoo.so"
//System.load(path);
Runtime.getRuntime().load0(groovy.lang.GroovyClassLoader.class, path)
}
...
}
I just tried it, and it's working in non-forked mode.
My guess is that the Accessor class is being loaded multiple times in different classloaders within the same JVM (assuming grails runs in the same JVM as the embedded Tomcat). Test this by adding debug statements to the static block.