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());
Related
I'm trying to get access to pdf files in my app using https://github.com/barteksc/PdfiumAndroid
and adding some functions like searching and highlighting using java and c++. the problem is that I always get a crash with no exception details. In debugging I found SIGABRT (signal SIGABRT) SIG:9 fault address
debug info
My native function is
#include <fpdftext.h>
JNIEXPORT jstring JNICALL Java_[my package]_PdfiumCore_nativeGetPageText(JNIEnv *env, jobject instance, jlong pagePtr) {
unsigned short* buffer;
FPDF_TEXTPAGE text_page = FPDFText_LoadPage((FPDF_PAGE) pagePtr);
int len = FPDFText_CountChars(text_page);
FPDFText_GetText(text_page,0,len,buffer);
jstring txt = env->NewString(buffer, len);
FPDFText_ClosePage(text_page);
return txt; }
java function in PdfiumCore.java
public String getPageText(PdfDocument doc, int pageIndex)
{
String txt;
synchronized (lock) {
try {
txt = nativeGetPageText(openPage(doc,pageIndex));
return txt;
}catch (Exception e)
{
Log.e("Error ",e.getMessage());
return "";
}
}
}
public long openPage(PdfDocument doc, int pageIndex) {
long pagePtr;
synchronized (lock) {
pagePtr = nativeLoadPage(doc.mNativeDocPtr, pageIndex);
doc.mNativePagesPtr.put(pageIndex, pagePtr);
return pagePtr;
}
}
function call
Log.e("Page 0",pdfiumCore.getPageText(pdfDocument,0));
The function retrieves the data once then the app crashes. I guess the problem is when the garbage collector tries to delete the buffer pointer which is used in FPDFText_GetText function. but I don't know how to solve it.
any suggestion please.
I solved the problem by deleting the pointer after getting the data by using this code in the JNI function
JNIEXPORT jstring JNICALL Java_[my package]_PdfiumCore_nativeGetPageText(JNIEnv *env, jobject instance,
jlong pagePtr) {
jstring txt;
FPDF_TEXTPAGE text_page = FPDFText_LoadPage((FPDF_PAGE) pagePtr);
int len = FPDFText_CountChars(text_page);
unsigned short *buffer = new unsigned short[len];
FPDFText_GetText(text_page, 0, len, buffer);
txt = env->NewString(buffer,len);
delete[](buffer);
FPDFText_ClosePage(text_page);
return txt;}
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
I'm quite new to JNI and right now I'm using this simple C program to create a JVM and call the main() from my Java project:
#include <stdio.h>
#include <jni.h>
JNIEnv* create_vm() {
JavaVM* jvm;
JNIEnv* env;
JavaVMInitArgs args;
JavaVMOption options[1];
args.version = JNI_VERSION_1_6;
args.nOptions = 1;
options[0].optionString = "-Djava.class.path=CLASSPATH"; //This isn't the actual classhpath, but you get the idea
args.options = options;
args.ignoreUnrecognized = JNI_FALSE;
int ret = JNI_CreateJavaVM(&jvm, (void **)&env, &args);
if (ret<0){
printf("\nUnable to Launch JVM");
} else {
printf("\nJVM launched successfully");
}
return env;
}
void invoke_class(JNIEnv* env) {
jclass UncaughtExceptionClass;
jmethodID mainMethod;
jobjectArray applicationArgs;
jstring applicationArg0;
UncaughtExceptionClass = (*env)->FindClass(env, "exceptioncatcher/ExceptionCatcher");
mainMethod = (*env)->GetStaticMethodID(env, UncaughtExceptionClass, "main", "([Ljava/lang/String;)V");
applicationArgs = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env, "java/lang/String"), NULL);
applicationArg0 = (*env)->NewStringUTF(env, "From C");
(*env)->SetObjectArrayElement(env, applicationArgs, 0, applicationArg0);
(*env)->CallStaticVoidMethod(env, UncaughtExceptionClass, mainMethod, applicationArgs);
}
int main(int argc, char **argv) {
JNIEnv* env = create_vm();
invoke_class( env );
}
This works well when running a HelloWorld type java code, but my code actually sets a Default Uncaught Exception Handler. This handler will send the info collected for each uncaught exception to a thread that will process them and send them by email.
Right now these are pretty much all the classes created and it, obviously, won't catch a lot of uncaught exceptions right now. But I use the main() to try it out:
public static void main(String[] args){
Thread.setDefaultUncaughtExceptionHandler(new DefaultExceptionHandler());
Integer i = null;
i++;
}
It works fine when I simply run it from netbeans, but the only thing that appears when using the C app is "JVM launched successfully".
Can anyone help on this one?
Managed to make it work in the end (never closed the question before), simply had to build the project and change the classpath to the .jar generated.
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]);