In C++ (JNI), I get an already running JVM using the following JNI function:
JNI_GetCreatedJavaVMs(&jvm,1,&vmnb);
Then I attach the current thread using the following code:
jvm->AttachCurrentThread((void**)&env, NULL);
Question: How can I set classpath in that case? Thanks.
Note: Creating a new JVM and passing the classpath in vm_args to the new JVM is not an option for me.
As it is possible to append the classpath inside Java, so I found an alternative way to set classpath inside C++ through Java. As I'm already running JVM, I use the append classpath method (void addPath(String path), that is posted in this answer), inside the Java program that is already running in the JVM. I access the addPath java method from C++ using JNI calls to append the classpath. The classpath passed to addPath method from C++ should not include "-Djava.class.path" and it should be just complete path to the .jar file, i.e. "C:\\folder\\abc.jar". So sequence is: 1) get already running JVM, attach current thread to the JVM, get JNI environment pointer and then call addPath java function (of another running thread) from C++ using JNI. I can now access the classes of new classpath (.jar file) successfully from C++.
I ran into this issue, and I did not have the option to call back into the JVM, so I implemented the whole add_path busniess on the JNI side.
void add_path(JNIEnv* env, const std::string& path)
{
const std::string urlPath = "file:/" + path;
jclass classLoaderCls = env->FindClass("java/lang/ClassLoader");
jmethodID getSystemClassLoaderMethod = env->GetStaticMethodID(classLoaderCls, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
jobject classLoaderInstance = env->CallStaticObjectMethod(classLoaderCls, getSystemClassLoaderMethod);
jclass urlClassLoaderCls = env->FindClass("java/net/URLClassLoader");
jmethodID addUrlMethod = env->GetMethodID(urlClassLoaderCls, "addURL", "(Ljava/net/URL;)V");
jclass urlCls = env->FindClass("java/net/URL");
jmethodID urlConstructor = env->GetMethodID(urlCls, "<init>", "(Ljava/lang/String;)V");
jobject urlInstance = env->NewObject(urlCls, urlConstructor, env->NewStringUTF(urlPath.c_str()));
env->CallVoidMethod(classLoaderInstance, addUrlMethod, urlInstance);
std::cout << "Added " << urlPath << " to the classpath." << std::endl;
}
Related
And here is a demo (I omit utils, They just check if exception and print message):
First try, It should work:
C++ part:
jclass jClass_java_lang_System = env->FindClass("java/lang/System");
jmethodID jMethodID_java_lang_System_load = env->GetStaticMethodID(jClass_java_lang_System, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_java_lang_System, jMethodID_java_lang_System_load, library_path);
No exception but after that, Call a native method cause UnsatisfiedLinkError.
Second try, Write a wrapper method:
public static void load(String path) {
System.load(path);
}
And call it from C++
jmethodID jMethodID_Driver_load = env->GetStaticMethodID(jClass_Driver, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_Driver, jMethodID_Driver_load, library_path);
checkException(env);
It just a wrapper for System.load and nothing else, It working fine. The native call working properly.
Then for more test but not make any sense - Use both of them:
jclass jClass_java_lang_System = env->FindClass("java/lang/System");
jmethodID jMethodID_java_lang_System_load = env->GetStaticMethodID(jClass_java_lang_System, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_java_lang_System, jMethodID_java_lang_System_load, library_path);
if (!checkException(env)) std::cout << "Load by rt.jar no Exception" << std::endl;
jclass jClass_Driver = env->FindClass("Driver");
jmethodID jMethodID_Driver_load = env->GetStaticMethodID(jClass_Driver, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_Driver, jMethodID_Driver_load, library_path);
checkException(env); // The first UnsatisfiedLinkError print by this util
// Second UnsatisfiedLinkError print by native method call, I omit it.
Got this result:
Load by rt.jar no Exception
java.lang.UnsatisfiedLinkError: Native Library XXXXX already loaded in another classloader
java.lang.UnsatisfiedLinkError: XXXXXXX
That make it more confuse, The first try show load by java.lang.System-load() not working but in fact the library is loaded. Then throw a duplicate load exception.
And reverse the order:
jclass jClass_Driver = env->FindClass("Driver");
jmethodID jMethodID_Driver_load = env->GetStaticMethodID(jClass_Driver, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_Driver, jMethodID_Driver_load, library_path);
checkException(env);
jclass jClass_java_lang_System = env->FindClass("java/lang/System");
jmethodID jMethodID_java_lang_System_load = env->GetStaticMethodID(jClass_java_lang_System, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_java_lang_System, jMethodID_java_lang_System_load, library_path);
if (!checkException(env)) std::cout << "Load by rt.jar no Exception" << std::endl;
And got this result:
Load by wrapper no Exception
java.lang.UnsatisfiedLinkError: Native Library XXXX already loaded in another classloader
Result is - 2468
Result is - 2468
Even throw a duplicate load exception, The native call working propproperly.
Question: What happen? How to solve?
When you load a native library with System.load() the VM will try to bind any JNI functions it finds to their Java counterparts, i.e. the class that declares the native methods. It can only do that when that class is already loaded. If you load the class afterwards you will have unbound native methods, and when you call them you get an UnsatisfiedLinkError.
To be able to call your wrapper method, you do load the class and therefore the VM can bind the native methods. To make this work with only a call to System.load(), make sure the VM already has the class. That said, it would probably be better to use the usual way of loading the native library from a static initializer in the class itself. loadLibrary will also find statically linked libraries. So if you separate the JNI functions from the rest of your code, and put them into their own library, you can statically link that and use loadLibrary with a simple name.
JNI_CreateJavaVM function method does not work and cannot be debugged.
The development environment is win10 x64, jdk version is 1.8
Visual studio 2017 Community Edition Writing a C++ project
I am learning about JNI. I am trying to run The Invocation API. The following URL is an example of the official website documentation.
Click here!
I built the project and added a project dependency that contains jvm.lib. And I put jvm.dll in the project directory. I successfully run this program.
Main.test() is a method of print hello world. But the program exits when executing JNI_CreateJavaVM, the console shows that the return value is 1.
I can't get into debugging, I don't know what happened.
#include <jni.h>
int main() {
printf("begin..........\n");
JavaVM *jvm; /* denotes a Java VM */
JNIEnv *env; /* pointer to native method interface */
JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
JavaVMOption* options = new JavaVMOption[1];
char optionString[] = "-Djava.class.path =D:/Program Files/Java/jdk1.8.0_191/lib/";
options[0].optionString = optionString;
vm_args.version = JNI_VERSION_1_8;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
/* load and initialize a Java VM, return a JNI interface
* pointer in env */
int res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
printf("result=%d", res);
delete options;
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);
/* We are done. */
jvm->DestroyJavaVM();
return 0;
}
I expect this jvm can be called, but it is forced to exit when the program is executed to `int res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
Where am I wrong? Why is it not working?
Exit screenshot
enter image description here
jvm.dll location
enter image description here
jvm.lib linker additional library directories
enter image description here
jvm.lib linker additional Dependencies
enter image description here
Classpath
There is a space between key and equal sign. The space must be removed.
char optionString[] = "-Djava.class.path =D:/Program Files/Java/jdk1.8.0_191/lib/";
Classpath value
The java.class.path value must point to the base directory where your compiled .class files are located.
It looks like you're not using a package name, then it's the directory where Main.class is located, so probably it should look something like this:
char optionString[] = "-Djava.class.path=c:/Users/name/MyJavaPrograms/classes";
Access violation
SEGV (or exception 0xC0000005) is also generated intentionally on JVM startup to verify certain CPU/OS features.
see this fine answer: https://stackoverflow.com/a/36258856
In Visual Studio, when the exception dialog is shown, simply turn off that it breaks there. This will prevent you from seeing it again the next time you start the program again.
Java
Just for the sake of completeness: the Java method should look like this:
public class Main {
public static void test(int num) {
System.out.println("Java: test called with '" + num + "'");
}
...
Visual Studio Configuration
The jvm.dll needs to be found. In Visual Studio under Configuration Properties/Debugging/Environment add PATH=%PATH%;<path-to-jdk>\bin\server
For later deployment you could think about putting the whole JRE into a subfolder of your application and reference it with a relative path.
Demo
Finally a brief demo (added a \n here printf("result=%d\n", res); to have a separate line):
i did a DLL that export methods from another DLL in JNI.
JNIEXPORT bool JNICALL getIsWordInPhonemListFR(const char* word)
{
isWordInPhonemListFR method = NULL;
BOOL fRunTimeLinkSuccess = FALSE;
HINSTANCE hGetProcIDDLL = LoadLibrary(L"PhoneticEngineFR2.dll");
if (!hGetProcIDDLL) {
std::cout << "kcould not load the dynamic library" << std::endl;
}else{
method = (isWordInPhonemListFR)GetProcAddress(hGetProcIDDLL, "isWordInPhonemListFR");
}
return method(word);
}
System.load in java doesn't give any errors but when i want to use my native method, i get a
java.lang.UnsatisfiedLinkError:
Both DLLs are in the same folder in my project. It was working fine when working in visual studio but there is something wrong with the export in JNI. The JNI onLoad also doesn't trigger in java.
Thanks
i am dumb, i had a Namespace over all my JNI export methods.
Therefore, they were not executed. Now it works.
In android OS I want to call an user defined java class API from a standalone code.
i.e. If there is a class "HelloWorldActivity" which has "getint" API . I would like to call this from a native app "nativecaller"
I found post related to this however I was not clear how the implementation was done.
https://groups.google.com/forum/#!topic/android-ndk/_JidHzVWHM8
So here is the code snippet:
#include <jni.h>
#include <cutils/log.h>
#include <stdlib.h>
int main(){
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString = "-Djava.class.path=/data/";
vm_args.version = JNI_VERSION_1_6;
vm_args.options = options;
vm_args.nOptions = 1;
vm_args.ignoreUnrecognized = JNI_FALSE;
/* Create the Java VM */
int res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
if(!res){
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("com/abc/mypackage/HelloWorld"); //it is not able to find the class
if(!cls)LOGE("\n\n\nclass not found!!!");
else{
jmethodID mid = env->GetMethodID(cls, "getint", "(V)I");
env->CallStaticVoidMethod(cls, mid,10);
}
/* We are done. */
jvm->DestroyJavaVM();
}
else
LOGE("\n\n\n\n CreateJAVAVM failed!!");
}
FindClass is returning null.
1.Is it possible to access class inside an activity (an apk)
2.What should -Djava.class.path point to ?
Any input is appreciated!
Dalvik provides a command called dalvikvm, which isn't too far removed from what you're trying to do. It's just a command-line wrapper for libdvm.so (try adb shell dalvikvm -help). You can see the source code here.
Try a quick test: instead of looking up your application class, look up something that you know will be there (say, java/lang/String). That will tell you if the VM is able to do anything at all.
On a device, BOOTCLASSPATH will already be configured in your environment (adb shell printenv BOOTCLASSPATH), but CLASSPATH will not. Set the CLASSPATH environment variable to a colon-separated list of .jar or .apk files, not a list of directories.
You will need to run as root, so that your command-line application has permission to create an entry in /data/dalvik-cache for your APK. (If such an entry already exists, you may not need to be root.)
If something doesn't work, check the logcat output for details.
I'm embedding Java into a C++ application. As part of this I need to expose native functions to java, as well as calling java functions from C++.
Do I need to put the functions I want to call from java into a shared library? Or can they be compiled into the host application somehow?
Here's what I've tried so far, but it gives a java.lang.UnsatisfiedLinkError
Compilation
I'm building on OS X 10.5 using
g++ -Wall -I/System/Library/Frameworks/JavaVM.framework/Headers/ -framework JavaVM -g test.cpp
Java Test File : TestObject.java
// To build this you need to do a `javac TestObject.java`
// To get the signatures do a `javap -d TestObject`
// To generate the .h file do a `javah TestObject`
public class TestObject
{
public native TestObject get_property( String k );
}
C++ Test File : test.cpp
#include <jni.h>
#include <assert.h>
JNIEXPORT jobject JNICALL Java_TestObject_get_1property(JNIEnv * jni_env, jobject obj, jstring key)
{
//Just a stub implementation for now.
jclass klass = jni_env->GetObjectClass( obj );
jmethodID constructor = jni_env->GetMethodID( klass, "<init>", "()V");
jobject retval = jni_env->NewObject(klass, constructor );
return retval;
}
int main()
{
JavaVM* jvm;
JavaVMInitArgs vm_args;
JavaVMOption options[1];
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 1;
options[0].optionString = "-Djava.class.path=.";
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_FALSE;
JNIEnv * env;
JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
jclass klass = (env)->FindClass("TestObject");
assert( klass );
jmethodID constructor = env->GetMethodID( klass, "<init>", "()V");
assert( constructor );
jobject obj = env->NewObject(klass, constructor );
jmethodID test_method = (env)->GetMethodID( klass, "get_property", "(Ljava/lang/String;)LTestObject;" );
assert( test_method );
jvalue args[1];
args[0].l = env->NewStringUTF("k");
jobject rv = env->CallObjectMethodA(obj, test_method, args );
jthrowable exc = env->ExceptionOccurred();
if(exc)
{
env->ExceptionDescribe();
env->ExceptionClear();
}
//TODO: do something with rv
}
Normally the JVM expects to find native method definitions in a shared library that has been loaded via System#load or System#loadLibrary, and in most cases that is the most convenient approach. However, there does exist an alternative for situations like yours, where you would prefer to include the implementations directly in your executable.
If you call JNIEnv::RegisterNatives, you can instead pass the JVM a list of function pointers corresponding to the native methods in a particular class. When some Java code calls one of those methods, the JVM will know to invoke the function pointer you passed to RegisterNatives instead of searching through dynamically-loaded libraries.
JNINativeMethod methods[] = {
{
"frobFabulously",
"(Ljava/lang/Object;)V",
reinterpret_cast<void*>(NativeFrobFabulouslyImpl)
},
};
env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(JNINativeMethod));
It's been a while since I've messed with JNI, so I'm a little rusty on the topic. I think your problem is that you're declaring the get_property method as native. This means that the JVM expects to find a shared library exposing the get_property method. Here's the documentation on java.lang.UnsatisfiedLinkError.
UnsatisfiedLinkError is thrown when (1) attempting to call a native
method that has not been loaded or (2) when loadLibrary or load method
in Runtime or System is called for a file that cannot be found.
You declare a Java method native only if you're going to implement that method in C or C++ and then call it from Java. Since you're trying to do the opposite, i.e call Java methods from native code, you need to actually implement the get_property method in Java. In native code you'll then create a class instance of TestObject and call the get_property method on this instance.
I found a Sun tutorial on how to embed the JVM in native code. The book itself begins with examples of how to call native code from Java.
Try this one:
When you execute the Java application, add the missing link file with "LD_LIBRARY_PATH"
Something like
LD_LIBRARY_PATH=[the link file path need be included] java xxx.class
The path can use absolute path. Hope this might be helpful.
I think you should try writing the JNI function in another file. When you javah TestObject.java, a file TestObject.h will be generated. Create a file TestObject.c with the implemented function. Then build a shared library using the native code.
( Something like g++ -G -I/pkgs/jdk1.4/include TestObject.C -o libTestObject.so)
Also in TestObject.java, load the library statically like static{ System.loadLibrary("TestIbject");
The libTestObject.so should be added to LD_LIBRARY_PATH ( On a Linux environment)