Call Java code from C - java-native-interface

I use the JNI interface to invoke Java code from C code.
While compiling I use the following command:
gcc -g -I/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include/ -I/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include/linux/ -L/usr/bin/java -L/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/jre/lib/amd64/server/ -ljvm calljava.c
And I use the following code to create the JVM:
JNIEnv* create_vm()
{
JavaVM* jvm;
JNIEnv* env;
JavaVMInitArgs args;
JavaVMOption options[1];
args.version = JNI_VERSION_1_2;
args.nOptions = 1;
options[0].optionString = "-Djava.class.path=<classpath>";
args.options = options;
args.ignoreUnrecognized = JNI_FALSE;
JNI_CreateJavaVM(&jvm, (void **)&env, &args);
return env;
}
My question is:
Is the path to the JVM hardcoded in the binary?
Can we specify the path to the java executable at runtime?
If there is a way to do that can anyone help me with the compile time flags that can be used for that?
Thanks in advance!

The "java executable" is not used at all. When you compile and link your code, you link against a shared library, the location of which is determined by the system at runtime when you launch your executable.
Unless you dynamically load the jvm shared library yourself from a known location (and subsequently look up and call the functions therein), the system is going to determine the "path to the JVM".
Usually if you want to run against a specific version, you would include that version in your application's distribution, and configure the launch of your application to ensure that the proper shared library is used (either via scripts which set the environment appropriately, dynamically loading it, or other system-specific methods).

Related

Check if C++ program is running as an app bundle or command-line on Mac

Is there a way for a C++ or Objective-C program to tell whether is it being run as a command-line application (e.g. with ./myprog in a shell) or as an app bundle (e.g. by double-clicking on a .app in Finder or running open myprog.app/ in Terminal)?
Currently I'm using the following.
CFBundleRef bundle = CFBundleGetMainBundle();
CFURLRef bundleUrl = CFBundleCopyBundleURL(bundle);
char bundleBuf[PATH_MAX];
CFURLGetFileSystemRepresentation(bundleUrl, TRUE, (UInt8*) bundleBuf, sizeof(bundleBuf));
At this point, bundleBuf now holds the path to the .app bundle or the directory containing the command-line executable. I can check whether the string ends with ".app", but this is hacky. Is there a better way to do this?
You can query the Uniform Type Identifier (UTI) of the URL (using CFURLCopyResourcePropertyForKey() with kCFURLTypeIdentifierKey) and see if it conforms to kUTTypeApplicationBundle (using UTTypeConformsTo()).
CFStringRef uti;
if (CFURLCopyResourcePropertyForKey(bundleUrl, kCFURLTypeIdentifierKey, &uti, NULL) &&
uti &&
UTTypeConformsTo(uti, kUTTypeApplicationBundle))
{
// Is bundled application
}
Using Objective-C, you can use the NSWorkspace methods -typeOfFile:error: and -type:conformsToType: for the same purpose.

JNI_CreateJavaVM() terminates with exit code 1

I'm trying to call a Java method from C++ using JNI. To do that I've installed jdk1.7.0_51, linking against jdk1.7.0_51\lib\jvm.lib, including jdk1.7.0_51\include and jdk1.7.0_51\include\win32. using the following code in Visual Studio 2012 I tried to create a Java vm object - but the function always terminates my application with exit code 1 (the function doesn't return 1: my program terminates completly and sends the exit code 1).
#include <iostream>
#include "jni.h"
int main(int argc, char*argv[]){
JNIEnv* env = nullptr;
JavaVM* jvm = nullptr;
JavaVMInitArgs vm_args;
JavaVMOption options[2];
options[0].optionString = "-Djava.class.path=.";
options[1].optionString = "-DXcheck:jni:pedantic";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 2;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE; // remove unrecognized options
int ret = JNI_CreateJavaVM(&jvm, (void**) &env, &vm_args);
std::cout << "This code is never reached" << std::endl;
return 0;
}
OS: Windows 7 (x64)
Compiler: Visual Studio 2012 (x86/Win32 Project)
Java VM: jdk1.7.0_51, i586 (should be ok in my opinion, because I'm compiling for x86 - otherwise linkage with jvm.lib wouldn't work)
I've already tried to using both: jdk1.7.0_51\jre\bin\client\jvm.dll as well as jdk1.7.0_51\jre\bin\Server\jvm.dll - with the same result (I'm not entirely sure what the difference is though).
Any ideas & suggestions would be highly appreciated.
Using static linking
remove the jvm.dll from your project directories. The dll must be loaded from it's original location, as it seems that other DLLs are involved, found by references.
Set the PATH environement variable to start with the folder of a JRE jvm.dll. And don't use the "c:\folder with space in name" notation (that is surrounding the path with double quotes). Just use set path=c:\folder with space in name;%PATH%. That mistake made my previous attempts worthless.
Using dynamic linking.
remove the jvm.dll from your project directories. The dll must be loaded from it's original location, as it seems that other DLLs are involved, found by references.
Drop jvm.lib from your project configuration
Use LoadLibrary, with the full path for jvm.dll (escape '\' or use '/')
Use GetProcAddress for "JNI_CreateJavaVM"
Make sure to use a proper typedef for the function pointer (use JNICALL as calling convention)
Patching your code with above steps makes my VS2012/Seven64/x86Debug/JDK1.6 project to output "This code is never reached" (with ret == JNI_OK)

How to make OCaml bytecode that works on Windows and Linux

I want to compile some OCaml bytecode and have it run on Windows and Unix-type systems. My source code works fine if I recompile it on each platform, but the bytecode isn't portable.
Sample code:
open Unix
let on_windows = Filename.dir_sep <> "/";;
Printf.printf "On windows: %b\n" on_windows;;
let child = create_process "gpg" (Array.of_list ["gpg"; "--version"]) stdin stdout stderr;;
Printf.printf "Child %d\n" child;;
Build command:
ocamlbuild -use-ocamlfind -pkg unix test.byte
If I compile on Windows and run on Linux, I get
Fatal error: unknown C primitive `win_waitpid'
If I compile on Linux and run on Windows I get:
Fatal error: unknown C primitive `unix_waitpid'
How can I make the bytecode work everywhere?
Obviously, the reason is that ocaml unix library has different names for C stubs depending on the platform, and this hinders bytecode portability. I do not see the way out except for patching stub names..
Merely renaming the C symbols doesn't work because there are two completely different versions of the unix.ml module (though with the same interface). If you link your code statically, you'll get the one for the build platform and it won't work on the other platform.
The solution is to create and distribute a .cma archive (with the Unix module not linked in) rather than an executable. Then use ocaml to link dynamically on the target platform:
http://roscidus.com/blog/blog/2013/07/07/ocaml-binary-compatibility/#windows--linux-compatibility
Update: This isn't safe. When you do ocaml /path/to/script.ml, it adds the current directory (not the directory containing the script) to the start of the search path. Thus:
$ cd /tmp
$ /usr/bin/myprog
will first try to load myprog's libraries (e.g. unix.cma) from /tmp.
Should be fixed in 4.03: http://caml.inria.fr/mantis/view.php?id=6081

JNI, Java to C++ in Eclipse: undefined reference to '_imp__JNI_CreateJavaVM#12'

I'm a beginner to c++ and JNI and I want to call a Java methode from my C++ program.
When compiling (with Eclipse) I get the following error:
undefined reference to '_imp__JNI_CreateJavaVM#12'
I searched for this problem and came across this post
There the answer was, if I get that right, including the jvm library to the compiling command. Since I'm not compiling by hand I'm not sure how I can make Eclipse do this. Could somebody explain that step by step for a complete beginner?
Here is the code, in case the compiling command won't change anything and the code has some errors.
In this part the error is displayed when calling JNI_CreateJavaVM:
JNIEnv* create_vm(JavaVM ** jvm) {
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options;
/*
* The following is a little messy, but options.optionString wants a char* not a string
* So I convert the String-path to a char-array
*/
string stringPath = "-Djava.class.path=C:\\Users\\Chris\\workspacejava\\PP\\src\\Tests"; //Path to the java source code
int sLen = stringPath.length();
char javaPath [sLen + 1];
int i = 0;
for(; i < sLen; i++)
{
javaPath[i] = stringPath[i];
}
javaPath[i] = '\0';
options.optionString = javaPath;
vm_args.version = JNI_VERSION_1_6; //JDK version. This indicates version 1.6
vm_args.nOptions = 1;
vm_args.options = &options;
vm_args.ignoreUnrecognized = 0;
int ret = JNI_CreateJavaVM(jvm, (void**)&env, &vm_args); //This call results in the error: undefined reference to '_imp__JNI_CreateJavaVM#12'
if(ret < 0)
printf("\nUnable to Launch JVM\n");
return env;}
And here I call this function:
int main(int argc, char* argv[])
{
JNIEnv *env;
JavaVM * jvm;
env = create_vm(&jvm);
if (env == NULL)
return 1;
...
int n = jvm->DestroyJavaVM();
return 0;
}
Further Informations: (I don't know if they help)
I use Windows 7. Both Eclipse and JDK are 64Bit. I'm using MinGW GCC to compile my code.
I'm glad for every piece of advice
That's right, you need to add the jvm library to your build.
Open the project properties dialog.
Select C++ Builder > Settings on the left.
Select the Tool Setting tab.
Select MinGW C++ Linker > Libraries.
Add jvm in the Libraries section.
Add the path to your JDK lib folder in the Libraries Search Path section.
I have a JAVA_HOME environment variable pointing to a JRE subfolder of a JDK so I enter the search path like this: "${env_var:JAVA_HOME}/../lib", with quotes because the environment variable expands to a path with spaces in it.
Now, you might have 32-bit and/or 64-bit JDKs installed. Be sure to use files from the same bit-ness as your compiler output. That includes running your exe with the right jvm.dll first on the DLL search path. On 64-bit Windows, the 32-bit JDK is usually installed under C:\Program Files (x86)\Java....
The #12 says that your compiler is producing 32-bit code (the arguments to JNI_CreateJavaVM are 3 pointers of 4-bytes each) so the linker needs the 32-bit version of jvm.lib rather than the 64-bit version.
BTW—Your java.class.path looks a suspicious for two reasons:
Tests looks like the name of a class. The class path should be a ;-separated list of paths to the root folder of packages. If Tests is a class in the default package, just lop off Tests from your class path.
Eclipse usually sets up Java projects with separate source and binary folders. The paths in the class path are where java should search for .class files. If your project is configured to compile .java files from the src folder to .class files in the bin folder, then replace src with bin in your class path.

Creating a java7 JVM with the JNI invocation API

We have a class in java 7 and need to load it from native code. I have already used java 6 with JNI but java 6 can't load that class. So I installed the new JDK, changed include directories and link references in my VC project etc. All was well until I wanted to start the jre7 from JNI:
JNI_CreateJavaVM takes the the java version in vm_args.version parameter but there's no definition for a newer version than 1.6.
JavaVMInitArgs vm_args;
...
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 2;
vm_args.options = options;
vm_args.ignoreUnrecognized = 0;
int ret = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
Calling FindClass for the java7 class obviously produces an UnsupportedClassVersionError.
The problem: How do I create a java7 JVM with JNI_CreateJavaVM?
For Mac users, I highly recommend to read this article:
How do I switch between Java 7 and Java 6 on OS X 10.8.2?
https://superuser.com/questions/490425/how-do-i-switch-between-java-7-and-java-6-on-os-x-10-8-2
I successfully compiled and ran the JNI example in which I call some static methods from a Java class I created. I solved the problem by invoking "java_home" like this:
/usr/libexec/java_home -v 1.6.0_45 --exec javac Sample.java
The problem are on path environment variable, that's refers to jre6 before jre7.
On visual C++->Property Pages->Debugging->Environment, I changed to:
path=C:\Java\jre7\bin;C:\Java\jre7\bin\client;%path%
Or directly on: My Computer->Properties->Advanced->Environment Variables