I have requirement to integrate spring with some native code written C and I am new to spring and don't have any idea about it if anybody have experience to integrate spring with JNI then please share with me.
Thanks in advance.
The question is quite vague but I can offer a couple of bits of advice from work I've done in the past.
Forget about integrating with "Spring". Concentrate on integrating with Java and let Spring help you out later.
Be very, very careful with the types and memory allocation in C
Here's a book on JNI that I found immensely helpful when I was working on it. It's a little dated but still valid
I did most of the work in C and then patched in a simple single method hook to Java. That way I did most of the integration with the existing code in its native language.
Give your C code a main method that lets you test the C code independently of the Java. Then you can compile your C file as both a library (dll/so) and an executable. The executable can be called on it's own with arguments to test the calls to existing calls.
The general process is.
Java End
public class statusBlock {
/* A Java representation of a Status Block */
private long errcode = 0;
private long errref = 0;
private String errmsg = "";
private long[] TmidArray;
private long evt_id = 0;
private short IgViolation_severity = 0;
}
public class MyFunkyJNIClass {
private Object response;
/**
* To generate the C-header for a native call use: javah -jni
* example.package.MyFunkyJNIClass from target/classes folder.
*/
private native int nativeExecuteFunction(int callType, Object payload, Object response);
public MyFunkyJNIClass() {
System.loadLibrary("theCLibrary");
}
}
In the example above I load the C side of the code using System.loadLibrary and define some fields that I can populate from my C code. To execute just call the native function nativeExecuteFunction(1, "my payload", respObject); On the C side I can use the first argument to choose what to do. It simplified my problems at the time
C Side
JNIEXPORT jint JNICALL Java_example_package_MyFunkyJNIClass_nativeExecuteFunction(JNIEnv *env, jobject this, jint CallType, jobject Payload)
{
// **** JNI Specific declarations for mapping ****
jclass cls, cls2, cls3;
jmethodID mid, mid2;
jfieldID fid;
jint rc = 0;
jsize js = 0;
jbyte jb;
jobject jobj, jobj2, jro;
jobjectArray jobjArray, jobjArray2;
_svc_results results;
switch ((int)CallType)
{
case CALLTYPE1: // 1
DEBUG_PRINT("JNI Call Type 1 in progress...\n");
// JNI mapping happens here
stat = DoSomethingInC(args, &results);
// Map from C structure (_statblk) to Java object
if (stat == SUCCESS) {
DEBUG_PRINT("\tMapping from C structure (_statblk) to Java object\n");
cls = (*env)->FindClass(env, "Lexample/package/statusBlock;");
mid = (*env)->GetMethodID(env, cls, "<init>", "()V"); if (mid == NULL) return -1;
jro = (*env)->NewObject(env, cls, mid); if (jro == NULL) return -1;
fid = (*env)->GetFieldID(env, cls, "errcode","I"); if (fid == NULL) return -1;
(*env)->SetIntField(env, jro, fid, (jint)results.statblk.errcode);
DEBUG_PRINT("\t\tMapped errcode: %d\n",results.statblk.errcode);
fid = (*env)->GetFieldID(env, cls, "errref","I"); if (fid == NULL) return -1;
(*env)->SetIntField(env, jro, fid, (jint)results.statblk.errref);
DEBUG_PRINT("\t\tMapped errref: %d\n",results.statblk.errref);
fid = (*env)->GetFieldID(env, cls, "errmsg","[B"); if (fid == NULL) return -1;
jobj = (*env)->NewByteArray(env, MAX_ERR);
(*env)->SetByteArrayRegion(env, (jbyteArray)jobj, 0, MAX_ERR, (jbyte*)results.statblk.errmsg);
(*env)->SetObjectField(env, jro, fid, jobj);
(*env)->DeleteLocalRef(env, jobj);
DEBUG_PRINT("\t\tMapped errmsg: %s\n",results.statblk.errmsg);
fid = (*env)->GetFieldID(env, cls, "TmidArray","[I"); if (fid == NULL) return -1;
jobj = (*env)->NewIntArray(env, (jsize)results.statblk.TmidArray.TmidArray_len);
(*env)->SetIntArrayRegion(env, (jintArray)jobj, 0,
(jsize)results.statblk.TmidArray.TmidArray_len,
(jint*)results.statblk.TmidArray.TmidArray_val);
(*env)->SetObjectField(env, jro, fid, jobj);
(*env)->DeleteLocalRef(env, jobj);
DEBUG_PRINT("\t\tMapped TmidArray\n");
fid = (*env)->GetFieldID(env, cls, "evt_id","I"); if (fid == NULL) return -1;
(*env)->SetIntField(env, jro, fid, (jint)results.statblk.evt_id);
DEBUG_PRINT("\t\tMapped evt_id: %d\n",results.statblk.evt_id);
cls = (*env)->GetObjectClass(env, this);
fid = (*env)->GetFieldID(env, cls, "response","Ljava/lang/Object;"); if (fid == NULL) return -1;
(*env)->SetObjectField(env, this, fid, jro);
DEBUG_PRINT("\tMapping from C structure (_statblk) to Java object - DONE\n");
} else {
DEBUG_PRINT("JNI Call Type 1 in progress... Returning Error: %d\n", stat);
return (jint)stat;
}
/* Free our native memory */
cls = (*env)->GetObjectClass(env, Payload);
fid = (*env)->GetFieldID(env, cls, "message","Ljava/lang/String;"); if (fid == NULL) return -1;
jobj = (*env)->GetObjectField(env, Payload, fid);
GPS_Free(results.statblk.TmidArray.TmidArray_val);
GPS_Free(results.statblk.ErrorArray.ErrorArray_val);
DEBUG_PRINT("JNI RTP Posting in progress... DONE\n");
break;
case PING: // 2
DEBUG_PRINT("No Java to C mapping required\n");
DEBUG_PRINT("JNI Ping in progress...\n");
stat = doPing();
DEBUG_PRINT("No C to Java mapping required\n");
// Stop null pointer exception if client tries to access the response object.
cls = (*env)->FindClass(env, "Lexample/package/EmptySerializableClass;");
mid = (*env)->GetMethodID(env, cls, "<init>", "()V"); if (mid == NULL) return -1;
jro = (*env)->NewObject(env, cls, mid); if (jro == NULL) return -1;
cls = (*env)->GetObjectClass(env, this);
fid = (*env)->GetFieldID(env, cls, "response","Ljava/lang/Object;"); if (fid == NULL) return -1;
(*env)->SetObjectField(env, this, fid, jro);
DEBUG_PRINT("JNI Ping in progress... DONE\n");
return (jint)rpc_stat;
break;
default:
fprintf(stderr,"Unknown call type\n");
rc = -1;
break;
}
return rc;
}
I could go on and on, but it just takes a bit of careful reading of that book.
There doesn't really need to be any further integration with Spring. You could stick a #Component or #Service annotation on the MyFunkyJNIClass.
I hope this is of some help to you.
Echoing previous answers that Spring Integration is not really the important thing here. More importantly, is having the JVM interface to your native C/C++ node. Since the question was asked there are a number of online resources available that should help new users to this space.
JNI Integration walktrough
The Java™ Native Interface: Programmer’s Guide and Specification book is also quite useful
If you really want to go deep then the official specification is very helpful
Related
I've just looked at anyone else questions but it seems that nobody has the same problem of mine. I have a Java class HelloWorldHandler.java (in org.eclipse.gemoc.testapplilauncher.handlers package in org.eclipse.gemoc.testapplilauncher project) that launches another java application. Then I have a c file launcherC.c (in the same project but different directory) that is supposed to call the execute method in the java file, through JNI. Now, all my previous JNI applications worked, this one doesn't. The only difference is that HelloWorldHandler.java is not in the default package (so the .class is not directy in /bin) and there are multiple package imports (maybe something changes with the FindClass call (?)).
I launch the c file with
gcc -fPIC -I"/usr/lib/jvm/java-8-oracle/include" -I"/usr/lib/jvm/java-8-oracle/include/linux" -L/usr/lib/jvm/java-8-oracle/lib/amd64/jli/ -L/usr/lib/va-8-oracle/jre/lib/amd64/server/ -o launcherC launcherC.c -ljli -ljvm
and
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib/jvm/java-8-oracle/jre/lib/amd64/server/
and there is no problem. But when I execute it, it doesn't do anything, without any Expections and errors. I checked and the FindClass returns NULL.
HelloWorldHandler.java
package org.eclipse.gemoc.testapplilauncher.handlers;
public class HelloWorldHandler {
#Execute
public static void execute() {
ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IFile ff = root.getFile(new Path("d/Test.launch"));
ILaunchConfiguration res = manager.getLaunchConfiguration(ff);
DebugUITools.launch(res, ILaunchManager.DEBUG_MODE);
}
}
launcherC.c
#include <jni.h>
#include <string.h>
int main()
{
JavaVMOption options[1];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;
long status;
jclass cls;
jmethodID mid;
options[0].optionString = "-Djava.class.path=/home/ezambon/Desktop/modeling/org/eclipse/gemoc/testAppliLauncher/bin/org/eclipse/gemoc/testapplilauncher/handlers";
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_2;
vm_args.nOptions = 1;
vm_args.options = options;
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if (status != JNI_ERR)
{
cls = (*env)->FindClass(env, "HelloWorldHandler");
if(cls != 0) {
//printf("asdfgh\n");
mid = (*env)->GetStaticMethodID(env, cls, "execute", "()V");
if(mid !=0) {
(*env)->CallStaticVoidMethod(env, cls, mid);
}
}
//printf("qwerty");
(*jvm)->DestroyJavaVM(jvm);
return 0;
}
else
return -1;
}
All the suggestions are really welcome, I'm getting crazy. Thank you :)
First problem you setup classpath wrong
Instead of
options[0].optionString = "-Djava.class.path=/home/ezambon/Desktop/modeling/org/eclipse/gemoc/testAppliLauncher/bin/org/eclipse/gemoc/testapplilauncher/handlers";
you should have
options[0].optionString = "-Djava.class.path=/home/ezambon/Desktop/modeling/org/eclipse/gemoc/testAppliLauncher";
and you should use full class name
cls = (*env)->FindClass(env, "org.eclipse.gemoc.testapplilauncher.handlers.HelloWorldHandler");
But I suppose this is not all your problems.
When i run the following :
#include<stdio.h>
#include"Package_HelloWorld.h"
#include"Package_Prompt.h"
jstring Java_Package_Prompt_getLine
(JNIEnv *env, jobject obj,jstring prompt) {
char buf[128];
const jbyte *str;
str = (*env)->GetStringUTFChars(env,prompt,NULL);
if(str == NULL) {
return NULL;
}
printf("%s",str);
(*env)->ReleaseStringUTFChars(env,prompt,str);
scanf("%s",buf);
return (*env)->NewStringUTF(env,buf);
}
to generate the dll file i get the following errors.
My IDE is Code:Blocks. What is the reason i get these errors ?
Can anybody explain to me why can I get a callback when jvm allocates some java objects, but not others? Here is what I am doing:
static jvmtiCapabilities capa;
static jvmtiEnv* jvmti = NULL;
static const char* fileName = "C:\\temp\\ObjectInitCallbackDump.txt";
static ofstream outFileStream;
void JNICALL callbackObjectAllocation ( jvmtiEnv* jvmti_env,
JNIEnv* jni_env,
jthread thread,
jobject object,
jclass object_klass,
jlong size )
{
char* generic_ptr_class;
char* class_name;
jvmtiError error;
error = jvmti_env->GetClassSignature(object_klass, &class_name, &generic_ptr_class);
if (check_jvmti_error(jvmti_env, error, "Failed to get class signature")) {
return;
}
outFileStream << class_name << std::endl;
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
jint result;
jvmtiError error;
jvmtiEventCallbacks callbacks;
outFileStream.open(fileName,ios::trunc);
result = jvm->GetEnv((void**) &jvmti, JVMTI_VERSION_1_1);
if (result != JNI_OK || jvmti == NULL) {
printf("error\n");
return JNI_ERR;
} else {
printf("loaded agent\n");
}
(void) memset(&capa, 0, sizeof(jvmtiCapabilities));
capa.can_generate_vm_object_alloc_events = 1;
error = jvmti->AddCapabilities(&capa);
if (check_jvmti_error(jvmti, error, "Unable to set capabilities") != JNI_OK) {
return JNI_ERR;
}
(void) memset(&callbacks, 0, sizeof(callbacks));
callbacks.VMObjectAlloc = &callbackObjectAllocation;
error = jvmti->SetEventCallbacks(&callbacks, (jint) sizeof(callbacks));
if (check_jvmti_error(jvmti, error, "Unable to set callbacks") != JNI_OK) {
return JNI_ERR;
}
error = jvmti->SetEventNotificationMode( JVMTI_ENABLE,
JVMTI_EVENT_VM_OBJECT_ALLOC,
(jthread) NULL);
if (check_jvmti_error(jvmti, error,
"Unable to set method entry notifications") != JNI_OK) {
return JNI_ERR;
}
return JNI_OK;
}
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {
outFileStream.close();
}
When I examine the file that I create, I do not see the classes that I am interested in, although I know they are there and NetBeans tells me there is exactly one instance of that class in the jvm. Any thoughts???
Nikita
For performance reasons, the JVMTI only supports allocation events for objects that cannot be detected through bytecode instrumentation (BCI), as explained in the JVMTI VMObjectAlloc event documentation. This means that the event will not be triggered for most object allocations. I assume that the allocations you are missing fall within that category.
Fortunately, it's not really too difficult to intercept all object allocations using BCI. The HeapTracker demo illustrates precisely how to intercept all object allocations in a JVMTI agent using java_crw_demo in addition to the VMObjectAlloc events.
Maybe you are just not hitting your check?
If this is the code you actually run then you have a bug; you missed the ; at the end of the object
Your compare should be like this:
if (strcmp(class_name,"Ljavax/swing/JFrame;") == 0) {
printf("Got the sucker!!!");
}
My goal is passing a byte array of variable length from native code to Java side. The Java class instance method takes bytearray as its argument:
private void writeBuffer(final byte[] buffer)
{
}
I am able to find a method ID in the native code:
jclass cls = (*env)->FindClass(env,"class_path");
jmethodID writeBufferMethodID = (*env)->GetMethodID(env, cls, "writeBuffer", "([B)V");
But still can't figure out how to pass a byte array properly. I have tried:
jbyteArray retArray = (*env)->NewByteArray(env, data_size);
void *temp = (*env)->GetPrimitiveArrayCritical(env, (jarray)retArray, 0);
memcpy(temp, decoded_frame->data[0], data_size);
(*env)->CallVoidMethod(env, obj, writeBufferMethodID, retArray);
(*env)->ReleasePrimitiveArrayCritical(env, retArray, temp, 0);
and also:
retArray = (*env)->NewByteArray(env, data_size);
(*env)->SetByteArrayRegion(env, retArray, 0, data_size, (jbyte *)decoded_frame->data[0]);
(*env)->CallVoidMethod(env, obj, writeBufferMethodID, retArray);
The Java method gets called, but after a while the application crashes. Moreover, all values in Java buffers I receive equal to zero so it seems the content is not copied at all.
I verified the buffers content (decoded_frame->data[0]) on the native side by writing them into the binary file and there is no problem, the file contains exactly what I expect.
I call that method periodically; the size of array can vary in each call.
What is the correct and most effective way? Allocating a new array during each call is obviously a silly idea, but I do not know, how to avoid that if the array size varies.
EDIT:
I have rewritten my code this way and it seems to be OK now.
called in a while loop:
...do some decoding...
if(!retArray)
retArray = (*env)->NewByteArray(env, data_size);
if((*env)->GetArrayLength(env, retArray) != data_size)
{
(*env)->DeleteLocalRef(env, retArray);
retArray = (*env)->NewByteArray(env, data_size);
}
void *temp = (*env)->GetPrimitiveArrayCritical(env, (jarray)retArray, 0);
memcpy(temp, decoded_frame->data[0], data_size);
(*env)->CallVoidMethod(env, obj, writeBufferMethodID, retArray);
(*env)->ReleasePrimitiveArrayCritical(env, retArray, temp, 0);
called at loop exit:
(*env)->DeleteLocalRef(env, retArray);
Try calling ReleasePrimitiveArrayCritical before you call CallVoidMethod.
Below example works for passing char[] from C code to Java byte[].
void JNICALL Java_com_example_testapplication_MainActivity_getJNIByteArrayArg(JNIEnv *jenv, jobject jobj)
{
jclass clazz = (*jenv)->FindClass(jenv, "com/example/testapplication/MainActivity"); // class path
jmethodID mid = (*jenv)->GetMethodID(jenv, clazz, "addData", "([B)V");// function name
jbyteArray retArray;
char data[] = {'a','b',3,4,5};
int data_size = 5;
if(!retArray)
retArray = (*jenv)->NewByteArray(jenv, data_size);
if((*jenv)->GetArrayLength(jenv, retArray) != data_size)
{
(*jenv)->DeleteLocalRef(jenv, retArray);
retArray = (*jenv)->NewByteArray(jenv, data_size);
}
void *temp = (*jenv)->GetPrimitiveArrayCritical(jenv, (jarray)retArray, 0);
memcpy(temp, data, data_size);
(*jenv)->ReleasePrimitiveArrayCritical(jenv, retArray, temp, 0);
(*jenv)->CallVoidMethod(jenv, jobj, mid, retArray);
}
public void addData(byte[] data) {
System.out.println("Buyya: From C: " + new String(data));
}
You can also use SetByteArrayRegion for setting jbyteArray content:
(*env)->SetByteArrayRegion(env, retArray, 0, data_size, decoded_frame->data[0]);
Perhaps I'm being too ambitious here, but I'm trying to pass a shared_ptr back to Java through an Exception like so.
I am able to catch the Exception in java, but when I try to access any methods in the ManagementProcessor object itself I get a SIGSEGV. If I use new ManagementProcessorPtr() to send in an empty one I get the correct behavior (I throw a different exception).
Any insights?
Thanks!
-Chip
typedef boost::shared_ptr<ManagementProcessor> ManagementProcessorPtr;
%include "boost_shared_ptr.i"
%shared_ptr(ManagementProcessor);
%typemap(javabase) Exception "java.lang.RuntimeException";
%typemap(javabase) AuthenticationExceptionManagementProcessor "NS/Exception";
%exception {
try {
$action
}
catch (AuthenticationException<ManagementProcessor> & e ) {
jclass eclass = jenv->FindClass("NS/AuthenticationExceptionManagementProcessor");
if ( eclass ) {
jobject excep = 0;
jmethodID jid;
jstring message = jenv->NewStringUTF(e.getMessage().c_str());
jstring file = jenv->NewStringUTF(e.getFileName().c_str());
ManagementProcessorPtr* realm = new ManagementProcessorPtr(e.getRealm());
jlong jrealm;
*(ManagementProcessorPtr **)&jrealm = realm;
jid = jenv->GetMethodID(eclass, "<init>",
"("
"LNS/ManagementProcessor;"
"J"
"Ljava/lang/String;"
"Ljava/lang/String;"
"J)V");
if (jid) {
excep = jenv->NewObject(eclass, jid,
jrealm,
e.getApiErrNum(),
message,
file,
e.getLineNum());
if (excep) {
jenv->Throw((__jthrowable*) excep);
}
}
}
Client code:
} catch (AuthenticationExceptionManagementProcessor e) {
java.lang.System.err.println(e);
ManagementProcessor mp = e.getRealm();
java.lang.System.err.println("got mp");
java.lang.System.out.println(mp.getUUID());
}
a boost::shared_ptr is a c++ class. what makes you think that it is the same as a ManagementProcessorPtr*?
And of course the right answer is that first I have to create the Java ManagementProcessor object with the ctor that takes a CPtr:
jclass mpclass = jenv->FindClass("NS/ManagementProcessor");
jobject jmp = 0;
jmethodID mpid;
ManagementProcessorPtr *realm = new ManagementProcessorPtr(e.getRealm());
jlong jrealm;
*(ManagementProcessorPtr **)&jrealm = realm;
mpid = jenv->GetMethodID(mpclass, "<init>", "(JZ)V");
jmp = jenv->NewObject(mpclass, mpid, jrealm, true);
...
excep = jenv->NewObject(eclass, jid,
jmp,
e.getApiErrNum(),
message,
file,
e.getLineNum());