I'm doing a tutorial about JNI to get comfortable with it for my project. However, I'm stuck on this particular part of the tutorial which is running the Java program. I'm using Eclipse where I've created a Java project and put the files inside a package. When I try to follow the tutorial without using Eclipse, I get no errors, so I assume it's a path related issue.
As for my error, I get an
UnsatisfiedLinkError: no libhello in java.library.path
when I run this in my terminal:
java -Djava.library.path=. helloJNI.HelloJNI
Even when I specify my path, it doesn't work. If you want to reproduce my steps, here's what I did:
After creating HelloJNI.java, I ran the following in the terminal:
javac -h . HelloJNI.java
This command created the generated header file given below.
After this, I wrote HelloJNI.c, navigated to the package folder and then compiled it using this command in the same directory:
gcc -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so HelloJNI.c
Then I moved up one directory to ~/eclipse-workspace/HelloJNI/src and ran this command:
java -Djava.library.path=. HelloJNI
Which yields the error described above.
Here's my code:
HelloJNI.java
package helloJNI;
public class HelloJNI { // Save as HelloJNI.java
static {
System.loadLibrary("hello"); // Load native library hello.dll (Windows) or libhello.so (Unixes)
// at runtime
// This library contains a native method called sayHello()
}
// Declare an instance native method sayHello() which receives no parameter and returns void
private native void sayHello();
// Test Driver
public static void main(String[] args) {
new HelloJNI().sayHello(); // Create an instance and invoke the native method
}
}
HelloJNI.c
// Save as "HelloJNI.c"
#include <jni.h> // JNI header provided by JDK
#include <stdio.h> // C Standard IO Header
#include "helloJNI_HelloJNI.h" // Generated
// Implementation of the native method sayHello()
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
helloJNI_HelloJNI.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class helloJNI_HelloJNI */
#ifndef _Included_helloJNI_HelloJNI
#define _Included_helloJNI_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: helloJNI_HelloJNI
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_helloJNI_HelloJNI_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
I have also tried setting the path to my working directory, but I still get the same result. What am I doing wrong?
I moved up one directory to ~/eclipse-workspace/HelloJNI/src
This means that your libhello.so is in ~/eclipse-workspace/HelloJNI/src/helloJNI directory, isn't it?
Now, to let Java find this library, run
java -Djava.library.path=helloJNI helloJNI.HelloJNI
or use the absolute path
java -Djava.library.path=~/eclipse-workspace/HelloJNI/src/helloJNI helloJNI.HelloJNI
Related
Am just starting learn jni and decided to do a hello world. i created a helloworld java file whose code is
public class HelloWorld {
public native void displayHelloWorld();
static{
System.loadLibrary("hello");
}
public static void main(String[] args){
new HelloWorld().displayHelloWorld();
}
}
and i compiled it to get the following header file
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
followed by creating a c file whose code is
#include<jni.h>
#include "HelloWorld.h"
#include <stdio.h>
JNIEXPORT void JNICALL
Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj){
printf("Hello World \n");
return;
}
and compiled it to create the helloworld.o file
and now when i tried to create the .dll file using the command
gcc -shared -o hello.dll HelloWorld.o -Wl,--add-stdcall-alias
i get this error
At line:1 char:42
+ gcc -shared -o hello.dll HelloWorld.o -Wl,--add-stdcall-alias
+ ~
Missing argument in parameter list.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingArgument
what am i missing?
The problem is that in PowerShell comma has a special meaning of comma operator unlike, e.g., in Bash, where in such context there is no any special meaning to it. When your command is parsed, -Wl is considered a parameter name and , is interpreted to form an array (parameter list) as its argument. As seen in linked documentation, , is necessarily binary operator unless in expression mode, whereas for command invocation argument mode is used. But there's no left argument for this comma, thus Missing argument in parameter list error.
To solve this, you can change the command to make PowerShell stop intepreting , in a special way by making -Wl,--add-stdcall-alias a quoted string using one of the types of strings. For our simple case any string type would do, e.g. with single-quoted strings we have:
gcc -shared -o hello.dll HelloWorld.o '-Wl,--add-stdcall-alias'
Another option is to use stop parsing token --%:
gcc --% -shared -o hello.dll HelloWorld.o -Wl,--add-stdcall-alias
I want to call this minimal dummy C program (named "TEST.c"):
extern "C"
void Java_TEST_run() {}
from this Java code (named "Example.java"):
public class Example
{
public static void main(String args[])
{
System.out.println("START");
TEST test = new TEST();
test.dll_call();
System.out.println("ALL DONE!");
}
}
class TEST
{
public void dll_call()
{
run();
}
static {
try {
System.out.println("Load DLL = start ");
System.load("/home/user/Desktop/TEST.dll");
System.out.println("Load DLL = finish ");
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library failed to load.\n");
System.exit(1);
}
}
public native void run();
}
I create the C dll file by the following commands:
g++ -c TEST.c
g++ -shared -o TEST.dll TEST.o
This works all fine within the console environment, esp. I got the successful Java program execution output:
START
Load DLL = start
Load DLL = finish
ALL DONE!
Now, if I try to run the Java program from the Eclipse IDE, I got the following error:
START
Load DLL = start
Load DLL = finish
Exception in thread "main" java.lang.UnsatisfiedLinkError: 'void Example.TEST.run()'
at test1/Example.TEST.run(Native Method)
at test1/Example.TEST.dll_call(Example.java:21)
at test1/Example.Example.main(Example.java:11)
To my understanding, this means the Java program running from Eclipse does successfully find the c dll file, but when trying to enter the dll file, it fails by finding the dummy function Java_TEST_run().
As the c code "TEST.c" is already as minimal as possible, and as the execution works fine from the console, I do not understand, why it fails from Eclipse.
Can someone please advice me, how to make this minimal c code working from Java in Eclipse?
Operating system is Ubuntu 18.04 with openjdk 11.0.6 and Eclipse 4.14.0.
This is a follow up question from this question.
Any help much appreciated, thank you!
I was able to resolve the issue with Eclipse.
The C/C++ code needs to include the name of the Eclipse project, e.g. "Example", in the function name. In above code, this means:
extern "C"
void Java_TEST_run() {}
needs to be changed to:
extern "C"
void Java_Example_TEST_run() {}
I'll give you quick answer
First of all you will need JNI for c/c++ and it will generate header for your .java file.
For example, if we have class from your example we will have header output like this
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TEST */
#ifndef _Included_TEST
#define _Included_TEST
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: TEST
* Method: run
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_TEST_run
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
You can generate this type of header by typing
javac -h . TEST.java for your given class
Last thing we have to do is create our .cpp file to add function body, you can just copy decleration from JNI generated header file
#include "TEST.h"
JNIEXPORT void JNICALL Java_TEST_run(JNIEnv * env, jobject obj){
//my very special code
}
And that would be it.
In order to add JNI to your Visual Studio (if you are using one) you will have to go to project properties and add additional include and additional library from jdk folder.
If you are using g++, add include directory of JDK and library.
Here is my problem: I'm trying to use JNI in my Java project as a way of talking to the Win32 API. I get this error at run time in eclipse:
Exception in thread "main" java.lang.UnsatisfiedLinkError: com.printer.PrinterWinAPI.GetStatus(Ljava/lang/String;)J
at com.printer.PrinterWinAPI.GetStatus(Native Method)
at com.printer.PrinterWinAPI.<init>(PrinterWinAPI.java:14)
at com.printer.PrinterWinAPI.main(PrinterWinAPI.java:25)
But what is really weird about my issue is that I can successfully compile my project using the cmd:
javac PrinterWinAPI.java
Then the program runs fine by running:
java PrinterWinAPI
From what I understand, eclipse successfully locates my .dll file but can't find the GetStatus() function inside the file. I tried to compile it using visual studio both in x86 and x86_64 AND mingw gcc in both x86 and x86_64. Here are my files:
The java file which implements the JNI interface:
PrinterWinAPI.java
package com.printer;
public class PrinterWinAPI {
static {
System.load("C:\\GetPrinterStatus.dll");
}
public Long status;
public String name;
public PrinterWinAPI(String printerName) {
this.name = printerName;
this.status = this.GetStatus(this.name);
}
private native long GetStatus(String str);
public static void main(String[] args) {
PrinterWinAPI printer = new PrinterWinAPI("PRINTER NAME EXAMPLE");
System.out.println(printer.status);
}
}
PrinterWinAPI.h, generated using javah PrinterWinAPI:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h> /* Header for class PrinterWinAPI */
#ifndef _Included_PrinterWinAPI
#define _Included_PrinterWinAPI
#ifdef __cplusplus extern "C" {
#endif /* * Class: PrinterWinAPI * Method:
GetStatus * Signature: (Ljava/lang/String;)J */
JNIEXPORT jlong JNICALL Java_PrinterWinAPI_GetStatus (JNIEnv *, jobject, jstring);
#ifdef __cplusplus }
#endif
#endif
and here is the PrinterWinAPI.c file itself:
#include "PrinterWinAPI.h" // Generated
#include <windows.h>
#include <wincon.h>
#include <winspool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <io.h>
// Implementation of the native method
JNIEXPORT jlong JNICALL Java_PrinterWinAPI_GetStatus(JNIEnv *env,
jobject thisObj, jstring str) {
//some stuff...
return (int64_t)dwStatus;
}
Once again, the program is compiling and running fine using javac, javah and java from the command prompt, I'm 99% sure the issue is from eclipse but I really don't know where to start.
I spent several hours searching for a solution online but couldn't find anything. Why can't eclipse run my project but the java binaries ran manually can?
My jre/jdk is build 1.8.0_101-b13 64-bits
Eclipse: Oxygen.3a Release (4.7.3a)
OS: Windows 7 64-bits
#user2543253 Thank you it worked! It was correctly compiling because my java file wasn't in a package so the header was correct but since the java file was nested in a package in my project the correct header was Java_com_printer_PrinterWinAPI_GetStatus instead of Java_PrinterWinAPI_GetStatus.
I tried to implement JNI.
first I create Java class containing one native method, and compile it using "javac HelloWorld.java" and then create header file using "javah HelloWorld" ... here is the code
class HelloWorld {
private native void print();
public static void main(String[] args) {
new HelloWorld().print();
}
static {
System.loadLibrary("HelloWorld");
}
}
HelloWorld.h file is as shown below .....
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: print
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_print
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
after this i created HelloWorld.c file ... here is the code
#include
#include
#include "HelloWorld.h"
JNIEXPORT void JNICALL
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
printf("Hello World!\n");
return;
}
and then compile my HelloWorld.c file using below mention command in Visual studio 2008
cl -Ic:\java\jdk\include -Ic:\java\jdk\include\win32 -MD -LD HelloWorld.c -FeHelloWorld.dll
it compiles pretty well and dll and other files are created in the same bin folder where "HelloWorld.class" file is .
but while running java file using "java HelloWorld" command msvcr90.dll file missing error occurs....
I tried to reinstall my JDK but still same problem
what should I do ...
This error is related to build settings in Visual Studio. You can select static link of CRT library (use /MT option instead /MD) or copy msvcr90.dll to directory with your HelloWorld.dll or other directory in %PATH%.
I'm getting UnsatisfiedLinkError when invoking C functions from JNI though my setup seems correct. Here's what I've done:
There's a Java class:
package com.mycompany.myproduct;
public class Foo {
static {
System.loadLibrary("external");
}
public void native do_foo();
}
I've placed libexternal.so to the LD_LIBRARY_PATH, compiled the class, and executed javah over it. Resulting com_mycompany_myproduct_Foo.h file:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_mycompany_myproduct_Foo */
#ifndef _Included_com_mycompany_myproduct_Foo
#define _Included_com_mycompany_myproduct_Foo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_com_mycompany_myproduct_Foo
* Method: do_foo
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_mycompany_myproduct_Foo_do_1foo(JNIEnv *, jobject);
Implemented a C delegation in ctinative.c (not sure if extern "C" is needed there):
#include "com_mycompany_myproduct_Foo.h"
#include "External.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_com_mycompany_myproduct_Foo
* Method: do_foo
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_mycompany_myproduct_Foo_do_1foo(JNIEnv *, jobject) {
do_foo(); // this is a function that defined in External.h
}
#ifdef __cplusplus
}
#endif
Compiled that and got ctinative.o:
gcc -x c -g -m64 -DUNIX=1 -DUSE_SBUF=1 -DMAIN_VERSION=0 -DC_VER=7 -I$(EXTERNAL_SDK_ROOT)/include -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux -o ctinative.o -c ctinative.c
Here's the output of nm ctinative.o (is having U there normal?):
0000000000000000 T Java_com_mycompany_myproduct_Foo_do_1foo
U do_foo
Placed that ctinative.o to LD_LIBRARY_PATH. Now when invoking Foo.do_foo() I'm getting UnsatisfiedLinkError:
java.lang.UnsatisfiedLinkError: com.mycompany.myproduct.Foo.do_foo()V
at com.mycompany.myproduct.Foo.do_foo(Native Method)
If I remove ctinative.o from LD_LIBRARY_PATH the error does not change. If I remove libexternal.so from LD_LIBRARY_PATH then of course I'm getting:
java.lang.UnsatisfiedLinkError: no external in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1734)
at java.lang.Runtime.loadLibrary0(Runtime.java:823)
at java.lang.System.loadLibrary(System.java:1028)
at com.mycompany.myproduct.Foo.<clinit>
Any idea on what I'm doing wrong?
OK, my experience with native libraries on Linux is limited to toy tests, however I have used them pretty extensively on Windows. I expect the mechanism is similar, but proceed with caution :)
Java ends up calling the Java_com_mycompany_myproduct_Foo_do_1foo() native function when you execute the method fooInstance.do_foo(). This is the native function that needs to be defined in libexternal.so (or whatever you choose to load with loadLibrary()).
If I understand your question correctly, you have compiled the function Java_com_mycompany_myproduct_Foo_do_1foo() into ctinative.o, and the implementation does not appear in libexternal.so. You can check this with objdump --dynamic-reloc libexternal.so.
I believe you need to have your native implementation of Java_com_mycompany_myproduct_Foo_do_1foo() compiled into libexternal.so, or alternatively you could link ctinative.o to produce a dynamic link library something like libctinative.so.
EDIT: To join the dots, the complete mechanism would be:
Your java code calls loadLibrary() on a .so file that implements the function Java_com_mycompany_myproduct_Foo_do_1foo(). Let's call this libctinative.so.
libctinative.so dynamically loads libexternal.so through the O/S's dynamic linking mechanism --- you don't need to do anything special to make this happen apart from compiling and linking libctinative.so in the right way
Your program runs correctly, assuming no other issues :)
You have Java_com_mycompany_myproduct_Foo_do_1foo() but native void do_foo(). Was do_1foo() its name when you generated the .h/.c files? If you've changed it you have to regenerate.