Ecplise Java JNI, java.lang.UnsatisfiedLinkError loading dll - java

I'm having a problem with loading printer dll. I have a dll file from the printer manufacturer (JniPrinterStatusLib.dll). I wrote code like printer manufacturer suggested. The code is:
package com.printer.test
public class JniPrinterStatus {
static{
System.loadLibrary("JniPrinterStatusLib");
}
public native int GetStatus(String printer);
}
package com.printer.test
public class TestSample {
public static void main(String[] args) {
int status;
String printer = "MY PRINTER";
JniPrinterStatus jps = new JniPrinterStatus();
System.out.println("PRINTER NAME = " + printer);
status = jps.GetStatus(printer);
if (status == -1) {
System.out.println("status = -1");
}
else if (status == 0) {
System.out.println("status = NORMAL");
}
else if ((status & 0x00000080) != 0) {
System.out.println("status = PRINTER_STATUS_OFFLINE");
}
else if ((status & 0x00400000) != 0) {
System.out.println("status = PRINTER_STATUS_DOOR_OPEN");
}
else if ((status & 0x00000010) != 0) {
System.out.println("status = PRINTER_STATUS_PAPER_OUT");
}
else if ((status & 0x00000800) != 0) {
System.out.println("status = PRINTER_STATUS_OUTPUT_BIN_FULL");
}
else if ((status & 0x00000040) != 0) {
System.out.println("status = PRINTER_STATUS_PAPER_PROBLEM");
}
}
}
I used Eclipse to run the code, i put the dll library in the folder project and the error is
PRINTER NAME = MY PRINTER
Exception in thread "main" java.lang.UnsatisfiedLinkError: com.printer.test.JniPrinterStatus.GetStatus(Ljava/lang/String;)I
at com.printer.test.JniPrinterStatus.GetStatus(Native Method)
at com.printer.test.TestSample.main(TestSample.java:10)
If i move the source from the package "com.printer.test" to default package the code works and show:
PRINTER NAME = MY PRINTER
status = -1
I don't know how it's possible. If i compile and run the code from command prompt without package it works.
Where is the problem?
Thank you

From the javadoc for class UnsatisfiedLinkError...
Thrown if the Java Virtual Machine cannot find an appropriate
native-language definition of a method declared native.
That means that function Java_com_printer_test_JniPrinterStatus_GetStatus is not found.
Method loadLibrary in class java.lang.System usually searches the directories listed in the [System] property "java.library.path". For Windows machines, the value of this property is generally the value of the PATH environment variable.
So I suggest printing out the value of that property in your code to see whether it includes the directory containing your DLL. If it doesn't then you need to fix that, either by relocating the DLL or changing the PATH environment variable or launching your java program with the -Djava.library.path=... option. After that you need to check the signature of the native method. Dependency Walker is a tool I use at my work to accomplish this.
EDIT
Having re-read your question, I feel I did not accurately address your question, so let me add...
The default behaviour of Eclipse is to copy resource files, like DLLs, to the output folder. So if you put your DLL in folder src\com\printer\test, it will get copie to folder bin\com\printer\test. My guess is that the current, working directory, i.e. . is in your "java.library.path" which is why it works when your java code is in the default package.

Sorry, actually I wanted to write a comment, but as I'm still low on reputation, I have to try and guess an answer.
There should be no need to recompile the dll - it's just some native code to be invoked.
The java package of the class loading the dll should not make a difference, either.
You have to take care about your system architecture: A 64-bit dll file will fail in a 32-bit JRE and vice versa. Make sure, your JRE architecture matches the dll architecture.
Another thing to take into account is your working directory. Eclipse may use a working directory different from what you used when you ran you program from console.
Last but not least, please have a look at your java.library.path variable.
This page might also help: https://www.chilkatsoft.com/java-loadLibrary-Windows.asp
I covers all the details.

The expected package of the Java classes is hard-coded in the JNI library. In your case, it's the default package.
Let me expand on that. When one implements a native method in a JNI library, one has to create a public C function with a name in the following format:
Java_com_mypackage_MyClass_MyMethod
In other words, the JNI library can't provide methods for the classes in arbitrary packages - only for classes in packages that the JNI library authors had in mind.
In your case, it's the default one. The C function goes Java_JniPrinterStatus_GetStatus. If you call your class MyPrinterStatus, or place it into package com.foobar, the JNI run-time won't be able to associate the C function with the declared Java native method. That's just how JNI was designed.

Related

Java check if program is installed on windows

Is there a way to check if a specific program is installed on Windows using Java?
I'm trying to develop a Java program that automatically creates zip archives by using the code line command from 7-Zip.
So, I would like to check in Java if on my windows OS '7-Zip' is already installed. No check for running apps or if OS is Windows or Linux. I want to get a bool (true/false) if '7-Zip' is installed on Windows.
The library Apache Commons has a class called SystemUtils - full documentation is available at https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/SystemUtils.html.
In this library you have the following static boolean properties at your disposal:
SystemUtils.IS_OS_LINUX
SystemUtils.IS_OS_WINDOWS
The unix-like solution would be to simply try to run the program with --version flag (on windows probably the /? or - like in the 7zip case - without any at all) and check whether it fails, or what the return code will be.
Something like:
public boolean is7zipInstalled() {
try {
Process process = Runtime.getRuntime().exec("7zip.exe");
int code = process.waitFor();
return code == 0;
} catch (Exception e) {
return false;
}
}
I assume that you're talking about Windows. As Java is intended to be a platform-independent language and the way how to determine it differs per platform, there's no standard Java API to check that. You can however do it with help of JNI calls on a DLL which crawls the Windows registry. You can then just check if the registry key associated with the software is present in the registry. There's a 3rd party Java API with which you can crawl the Windows registry: jRegistryKey.
Here's an SSCCE with help of jRegistryKey:
package com.stackoverflow.q2439984;
import java.io.File;
import java.util.Iterator;
import ca.beq.util.win32.registry.RegistryKey;
import ca.beq.util.win32.registry.RootKey;
public class Test {
public static void main(String... args) throws Exception {
RegistryKey.initialize(Test.class.getResource("jRegistryKey.dll").getFile());
RegistryKey key = new RegistryKey(RootKey.HKLM, "Software\\Mozilla");
for (Iterator<RegistryKey> subkeys = key.subkeys(); subkeys.hasNext();) {
RegistryKey subkey = subkeys.next();
System.out.println(subkey.getName()); // You need to check here if there's anything which matches "Mozilla FireFox".
}
}
}
If you however intend to have a platformindependent application, then you'll also have to take into account the Linux/UNIX/Mac/Solaris/etc. (in other words: anywhere where Java is able to run) ways to detect whether FF is installed. Else you'll have to distribute it as a Windows-only application and do a System#exit() along with a warning whenever System.getProperty("os.name") is not Windows.
Sorry, I don't know how to detect in other platforms whether FF is installed or not, so don't expect an answer from me for that ;)

JNI Use native method

I'm working on a project where there's the need to call some methods from dll files.
These two dlls are
EasySign.dll
EasySignJNI
EasySignJNI depends on EasySign.
I wrote the class to load EasySignJNI as follows:
package easysign;
class EasySign {
EasySign(){}
public native String EasyHashFile(String filename);
public native int EasySign(String pkcs11_driver,String pin, int type, String file_data, int out_format, String signed_file, String cert_out, int cert_format);
public native int EasyVerify(String cert_user, String file_data, String signed_file, String crl_file, String ca_file, String out_document);
static {
System.loadLibrary("EasySignJNI");
}
}
Now I would call these method from my main method like this:
public class Test {
public static void main(String[] args) throws IOException{
EasySign es = new EasySign();
System.out.println("EasyHashFile : " + es.EasyHashFile("test.txt"));
}
}
What I have to specify in the -Djava.library.path ? Only the path where my EasySignJNI.dll is located? It is possible to call native method in this way?
I'm using NetBeans for completeness.
EDIT:
I have noticed that the third party dll provided to me (the JNI dll in particular) defines method names without any package, so I'm forced to put the class that loads the dll in the default package. Is there any way to change only the dll method names including my own package name?
EDIT 2:
What I mean is that both EasySign.dll and EasySignJNI.dll are provided me as they are and I can't modify them (I have not the source code). The EasySignJNI is the JNI portion but inspecting it I have noticed thath the method sign is in the form: _java_EasySign_MethodName. When I load the dll in Java from my Easysign class (this class must reside in the "mypackage" package), I receive the jni unsatisfiedlinkerror because, if I understood right, I'm calling the "_java_mypackage_EasySign_MethodName" method, i.e the sign is different from the dll's one. So the only way to make it work is to rewrite the JNI part and build it to have the correct sign of the JNI method?
What I have to specify in the -Djava.library.path ? Only the path where my EasySignJNI.dll is located?
Correct, the operating system will locate the dependent EasySign.dll for you as long as it is available where the OS expects it to be.
It is possible to call native method in this way? I'm using NetBeans for completeness.
I read through your edit and you have successfully lost me. What default package are you referring to? (Remember that none of us know what EasySign.dll is) So, I am going to provide some info about how I do what you originally described and hopefully it will help.
First start by compiling EasySign if you have the src. Do not build a DLL or shared object, instead build a static library. If you do not have the src code for EasySign, or a prebuilt static library, you will be stuck with the dll and can continue to the next step.
Now you are ready to compile the jni portion. All of your JNI C code should basically translate your Java input/output to their JVM/Native types and call the appropriate functions in the DLL library. You want to keep this layer and thin and simple as possible because it is incredibly difficult to debug. Your C++ package names shouldn't really matter here and you can use what ever package name you want for your Java classes. You should be able to compile the JNI code and preferably static link to the EasySign.dll file so you don't need to worry about distributing it. If you must dynamically link, make sure EasySign.dll gets installed to a location that is on the DLL PATH / LDPATH because the OS will need to locate and load that file right after the JVM loads the JNI DLL.
At this point you should just be able to point -Djava.library.path at your JNI DLL's path and all should work.

how to get ExePath/MainClassPath/MainJarPath of java App at runtime?

The "Path of the running EXE" is well defined for Windows Applications written in C#, C++, VB, etc.
-
Java Applications on Windows are not "EXE-Applications" but a class-file or a jar-file is started rather than an EXE-file.
So for java Applications the term "ExePath" should be translated to "MainClassPath" or to "JarPath" resp.
-
In some cases a programmer needs to know the physical Path of the Application's jar or MainClass.
(e.g. when you develop a large project both in java and in c# with identical classes and identical methods)
-
thanks to other stackoverflow users this statement does the job:
String exePath = URLDecoder.decode(this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8")
and now comes my question:
If I put the same code into any helper/utils jar-library then it will return the path of the helperlib.jar, it will NOT return the path of my MainClass/AppJar !
-
So the final getExePath() helper method should look something like:
return(URLDecoder.decode(Thread.currentThread().getStartingThread().getMainClass().getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8"));
(if there only were methods like getStartingThread() and getMainClass() in java ...)
-
please, point me to the final solution, how can I implement these steps:
get the starting Thread
get the Main Class of the starting Thread
get the Path of the Main Class
if I get you right, just put the method with a parameter of the desired class into your helperlib-class ...
e.g.:
public static String getExePath(Object main) {
String path = "";
try {
path = URLDecoder.decode(main.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return path;
}
then you can call it e.g. from inside your main-jar with the parameter 'this' ...
System.out.println("AppPath:\n" + helperlib.getExePath(this));
... and you got the path of the class specified in you parameter
hope it helps and sorry for my bad english ... ;)

JNA to call a DLL without source code from java

I have to call a dll method and I don't have the source code from dll, I was reading about JNI and understood that you should have the source to input the JNI library in the code (.h).
My second shoot is JNA, but I am getting the same error, although you don't have to change anything in DLL.
I created two classes to test:
interface:
package icom;
import com.sun.jna.Library;
public interface IConectorT extends Library {
int StartConector(byte[] conectorStatus, String icomPath);
}
DLL method call:
package icom;
import com.sun.jna.Native;
public class ConectorTJna {
public static void main(String args[]) {
IConectorT lib = (IConectorT) Native.loadLibrary("ConectorT", IConectorT.class);
int teste = lib.StartConector(null, "C:\\ICOM");
System.out.println("RESULT: " + teste);
}
}
When I call the lib.StartConector method I get this:
Exception in thread "main" java.lang.UnsatisfiedLinkError: Error
looking up function 'StartConector': The specified procedure could not
be found. at com.sun.jna.Function.(Function.java:179) at
com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:350) at
com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:330) at
com.sun.jna.Library$Handler.invoke(Library.java:203) at
$Proxy0.StartConector(Unknown Source) at
icom.ConectorTJna.main(ConectorTJna.java:10)
Did you specify path to the library, e.g. using system property?
Here are the details from "Getting Started with JNA" guide:
Make your target library available to your Java program. There are two
ways to do this:
The preferred method is to set the jna.library.path system property to
the path to your target library. This property is similar to
java.library.path, but only applies to libraries loaded by JNA.
Change the appropriate library access environment variable before launching
the VM. This is PATH on Windows, LD_LIBRARY_PATH on Linux, and
DYLD_LIBRARY_PATH on OSX.
Taken from: https://github.com/twall/jna/blob/master/www/GettingStarted.md

Is it possible to "add" to classpath dynamically in java?

java -classpath ../classes;../jar;. parserTester
How can i get the functionality in the above command programmatically? Like, is it possible to run as:
java parserTester
and get the same result? I tried using URLClassLoader but it modifies the classpath and does not add to it.
Thanx!
Thanks for the response Milhous. But that is what i am trying to do.. How is it possible to get the jar into the classpath first? I tried using a custom classloader too :(
That works.. But sorry that i need to run it only as:
java parserTester
I would like to know if such a thing is possible???
It needs to be so bcoz i have parserTester.java and .class in a separate folder. I need to retain the file structure. The parserTester makes use of a jar in a separate jar folder.
You can use a java.net.URLClassLoader to load classes with any program defined list of URL's you wish:
public class URLClassLoader
extends SecureClassLoader
This class loader is used to load
classes and resources from a search
path of URLs referring to both JAR
files and directories. Any URL that
ends with a '/' is assumed to refer to
a directory. Otherwise, the URL is
assumed to refer to a JAR file which
will be opened as needed.
The AccessControlContext of the thread
that created the instance of
URLClassLoader will be used when
subsequently loading classes and
resources.
The classes that are loaded are by
default granted permission only to
access the URLs specified when the
URLClassLoader was created.
Since:
1.2
And a little fancy footwork can extend it to support using wildcarded pathnames to pick up entire directories of JARs (this code has some references to utility methods, but their implementation should be obvious in the context):
/**
* Add classPath to this loader's classpath.
* <p>
* The classpath may contain elements that include a generic file base name. A generic basename
* is a filename without the extension that may begin and/or end with an asterisk. Use of the
* asterisk denotes a partial match. Any files with an extension of ".jar" whose base name match
* the specified basename will be added to this class loaders classpath. The case of the filename is ignored.
* For example "/somedir/*abc" means all files in somedir that end with "abc.jar", "/somedir/abc*"
* means all files that start with "abc" and end with ".jar", and "/somedir/*abc*" means all files
* that contain "abc" and end with ".jar".
*
*/
public void addClassPath(String cp) {
String seps=File.pathSeparator; // separators
if(!File.pathSeparator.equals(";")) { seps+=";"; } // want to accept both system separator and ';'
for(StringTokenizer st=new StringTokenizer(cp,seps,false); st.hasMoreTokens(); ) {
String pe=st.nextToken();
File fe;
String bn=null;
if(pe.length()==0) { continue; }
fe=new File(pe);
if(fe.getName().indexOf('*')!=-1) {
bn=fe.getName();
fe=fe.getParentFile();
}
if(!fe.isAbsolute() && pe.charAt(0)!='/' && pe.charAt(0)!='\\') { fe=new File(rootPath,fe.getPath()); }
try { fe=fe.getCanonicalFile(); }
catch(IOException thr) {
log.diagln("Skipping non-existent classpath element '"+fe+"' ("+thr+").");
continue;
}
if(!GenUtil.isBlank(bn)) {
fe=new File(fe,bn);
}
if(classPathElements.contains(fe.getPath())) {
log.diagln("Skipping duplicate classpath element '"+fe+"'.");
continue;
}
else {
classPathElements.add(fe.getPath());
}
if(!GenUtil.isBlank(bn)) {
addJars(fe.getParentFile(),bn);
}
else if(!fe.exists()) { // s/never be due getCanonicalFile() above
log.diagln("Could not find classpath element '"+fe+"'");
}
else if(fe.isDirectory()) {
addURL(createUrl(fe));
}
else if(fe.getName().toLowerCase().endsWith(".zip") || fe.getName().toLowerCase().endsWith(".jar")) {
addURL(createUrl(fe));
}
else {
log.diagln("ClassPath element '"+fe+"' is not an existing directory and is not a file ending with '.zip' or '.jar'");
}
}
log.diagln("Class loader is using classpath: \""+classPath+"\".");
}
/**
* Adds a set of JAR files using a generic base name to this loader's classpath. See #link:addClassPath(String) for
* details of the generic base name.
*/
public void addJars(File dir, String nam) {
String[] jars; // matching jar files
if(nam.endsWith(".jar")) { nam=nam.substring(0,(nam.length()-4)); }
if(!dir.exists()) {
log.diagln("Could not find directory for Class Path element '"+dir+File.separator+nam+".jar'");
return;
}
if(!dir.canRead()) {
log.error("Could not read directory for Class Path element '"+dir+File.separator+nam+".jar'");
return;
}
FileSelector fs=new FileSelector(true).add("BaseName","EG",nam,true).add("Name","EW",".jar",true);
if((jars=dir.list(fs))==null) {
log.error("Error accessing directory for Class Path element '"+dir+File.separator+nam+".jar'");
}
else if(jars.length==0) {
log.diagln("No JAR files match specification '"+new File(dir,nam)+".jar'");
}
else {
log.diagln("Adding files matching specification '"+dir+File.separator+nam+".jar'");
Arrays.sort(jars,String.CASE_INSENSITIVE_ORDER);
for(int xa=0; xa<jars.length; xa++) { addURL(createUrl(new File(dir,jars[xa]))); }
}
}
private URL createUrl(File fe) {
try {
URL url=fe.toURI().toURL();
log.diagln("Added URL: '"+url.toString()+"'");
if(classPath.length()>0) { classPath+=File.pathSeparator; }
this.classPath+=fe.getPath();
return url;
}
catch(MalformedURLException thr) {
log.diagln("Classpath element '"+fe+"' could not be used to create a valid file system URL");
return null;
}
}
I have to agree with the other two posters, it sounds like you're overcomplicating a test class.
It's not that unusual to have the .java and .class files in separate folders, while depending on jar files in yet a third, without programmatically changing the classpath.
If you're doing it because you don't want to have to type the classpath on the command line everytime, I would suggest a shell script or batch file. Better yet, an IDE.
The question I really have is why are you doing trying to manage the classpath in code?
You could implement your own class loader, but that class/jar has to be in the classpath for it to be executed.
try
java -cp *.jar:. myClass
or
export CLASSPATH=./lib/tool.jar:.
java myClass
or
java -jar file.jar
You can write a batch file or shell script file to export the classpath and run the java program.
In Windows,
set classpath=%classpath%;../classes;../jars/*
java ParserTester
In Unix,
export classpath=%classpath%:../classes:../jars/*
java ParserTester
If you name the file name as parser.bat or parser.sh, you can just run that by calling parser in respective OS.
From java 1.6, you can include all the jars in a directory into the classpath just by saying /*
If you are trying to generate a java file dynamically, compile and add into the classpath, set the directory into which the class file gets generated in the classpath beforehand. It should load the class.
If you are modifying the already generated java class, basically recompiling after modification and if you want to load the new class, you need to use your custom class loader to avoid the caching of the class.
I think what you want is an "Execution Wrapper" or a platform specific "Launcher"... typically this component is used to detect your OS and architecture and dependencies and then makes adjustments before launching your application. It is an old school design pattern (talking 80's and earlier) but is still used a lot today. The idea is that you program can be system and environment agnostic and the launcher will make preparations and tell the software everything it needs to know. Many modern open source programs do this with Shell scripts and Batch Files, etc... Apache Tomcat for example. You could just as easily make the wrapper in java an have it launch the software with a command line exec (be sure to add " &" to the end of you exec command in *NIX so your wrapper can exit leaving only your software running... also lets you close the shell window without killing the process)
Did I understand right?! The only reason you have it that you want to launch your class without specifying the classpath and load it at runtime? ...
java parserTester
instead of
java -classpath ../classes;../jar;. parserTester
Probably I didn't get your reason. But if "that's" what you want you can do the following ( although it does not make much sense to me )
Launch the class
From the main method lauch another class an programatically set the classpath there.
End of history.
Something like the following "java -pseudo code "
public static void main( String [] args ) {
String classpath = "classes;../jar";
Runtime.getRuntime().execute("java + classpath + " parserTester ");
}
Please tell me if I get it right. If you want to do something else I would gladly help.
Excellent good post, in my case I did this to work well (note: Windows specific):
set classpath=%classpath%;../lib/*
java -cp %classpath% com.test.MyClass

Categories

Resources