Gordon Beaton said:
Ok after looking at your code I can see what you are trying to do:
int chartX()
{
if (!jvm) {
start jvm
}
else {
AttachCurrentThread();
}
invoke(setChartSettings(someValues));
return 0;
}
First, I wonder if one of your method calls has failed and you have
invoked DestroyJavaVM() before returning, or if the caller has invoked
destroy_JVM()?
I don't think DestroyJavaVM() actually works, at any rate I have never
been successful in starting a second JVM within the same process.
Judging from similar questions in this and related newsgroups over the
years, I don't think anyone else has either (although I could be
mistaken). That is the first place I'd look.
Second, there may be some issues with references accumulating in the
calling scope. Normally, when calling from Java to C, any references
returned to C automatically become eligible for GC when the C method
returns. In your case, chartsX() is not called within the scope of a
JVM (the relationship is the opposite) so all of your references stay
alive even after the function returns. You need to manage all of the
references yourself with DeleteLocalRef() before returning. For that
reason, I don't think it's a good idea to make repeated calls to
FindClass("java/lang/String"), for example. The same is true of all of
the label and value Strings you create in the loop, as well as the
ObjectArray.
I'd suggest that you first try invoking a simple static method that
doesn't require you to create any objects on each call, in order to
eliminate those issues from the equation. Make sure you are able to
invoke methods on the JVM the second and subsequent times, then
replace that method call with your call to setChartSettings().
Some additional points...
- calling DestroyJavaVM() doesn't change the value of res, which you
use to test whether to start the JVM next time. In fact I'd base the
test on the value of the jvm pointer itself, not res. Make sure you
set jvm to NULL after calling DestroyJavaVM() so that the second
call to your function doesn't attempt to invoke a method on a
non-existant JVM. But again, I don't think you can rely on
restarting the JVM after DestroyJavaVM() has been called.
- much of the code prior to starting the JVM is done on every call to
chartsX(), but is only necessary when you actually invoke
JNI_CreateJavaVM() the first time. Put all of that code inside the
conditional block. Or preferably, break it into a separate function
to start the JVM. Just test jvm in chartsX() and invoke the function
if necesary. Several of the currently global variables should be
local to that function.
- are you sure that the character arrays jarfiles, classpath,
javafont, bootclasspath, pjadir etc, are long enough to contain the
data you write to them? Perhaps you are overwriting the bounds of
one of these.
- it is a lot easier to get and set values via method arguments than
through reflection. Consider adding one or more helper methods to
ChartWrapper so you don't need to make all those calls to
GetFieldID() and SetXXField(),
e.g. setChartProps(name, type, width, height).
/gordon
Good suggestions - Thanks.
Now I've made the following changes - here is the code snippet ..
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <jni.h>
#include "idscharts.h"
#define USER_CLASSPATH ".";
void destroyJVM();
int invokeJVM(char* bootclasspath, char* javafont, char* pjadir, char*
classpath);
void doChart(CChartData *chartData, CChartProps chartProps, int
numElements);
void cleanup(jstring jstr,jobjectArray segLabels,jobjectArray
segValues);
JavaVM *jvm;
JNIEnv *env;
jclass Global_cls;
/* Cache variables, method ids and field ids */
static jmethodID mid;
static jfieldID width;
static jfieldID height;
static jfieldID fname;
static jfieldID type;
static jclass stringClass;
int chartsX(CChartData *chartData,
CChartProps chartProps,
int numElements,
char *javaHome,
char *display,
char *chartWrapper,
char *pja,
char *jPowered)
{
char jarfiles[255] = "";
char classpath[1024] = "-Djava.class.path=";
char* jh;
char* position;
char javafont[255] = "-Djava.awt.fonts=";
char bootclasspath[255] = "-Xbootclasspath/a:";
char pjadir[255];
char search_char = '/';
int index, result;
puts("In top of code");
printf("The pja file: %s\n", pja);
jh = javaHome;
/* cut off filename from directory. used for user.home JVM option */
position = strrchr(pja, '/');
index = pja - position;
if (index < 0 )
index *= -1;
printf("This is the position: %d\n", index);
strncpy(pjadir, pja, index);
printf("%s\n", pja);
printf("%s\n", pjadir);
strcat(jarfiles, chartWrapper);
strcat(jarfiles, ":");
strcat(jarfiles, jPowered);
strcat(classpath, jarfiles);
/*printf("in the so file\n");*/
printf("%s\n", classpath);
/* append the directory and filename to java -xbootclasspath
directive */
strcat(bootclasspath, pja);
printf("%s\n", pjadir);
/* Make string of where java fonts reside */
strcat(jh,"/lib/fonts");
puts(jh);
strcat(javafont, jh);
printf("%s\n", javafont);
/* Test to see if the VM is alive */
result = invokeJVM(bootclasspath, javafont, pjadir, classpath);
if ( result < 0 )
{
destroyJVM();
}
else
{
doChart(chartData, chartProps, numElements);
}
return result;
}
int invokeJVM(char* bootclasspath,
char* javafont,
char* pjadir,
char* classpath)
{
static jint res;
if(!jvm) {
puts("No active VM ...");
#ifdef JNI_VERSION_1_4
JavaVMInitArgs vm_args;
JavaVMOption options[10];
options[0].optionString =
"-Djava.awt.headless=false";
options[1].optionString = bootclasspath;
options[2].optionString = "-Xms256m"; /* convert to variable if
needs to be dynamically adjusted */
options[3].optionString = "-Xmx512m";
options[4].optionString =
"-Dawt.toolkit=com.eteks.awt.PJAToolkit";
options[5].optionString =
"-Djava.awt.graphics=com.eteks.PJAGraphicEnvironment";
options[6].optionString =
"-Djava2d.font.usePlatformFont=false";
options[7].optionString = javafont;
options[8].optionString = pjadir;
options[9].optionString = classpath;
vm_args.version = 0x00010004;
vm_args.options = options;
vm_args.nOptions = 10;
vm_args.ignoreUnrecognized = JNI_TRUE;
/* Create the Java VM */
printf("%s\n", "Trying to create VM ...");
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
#else
JDK1_1InitArgs vm_args;
char classpath[1024];
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
/* Append USER_CLASSPATH to the default system class path */
sprintf(classpath, "%s%c%s",
vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
vm_args.classpath = classpath;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
#endif /* JNI_VERSION_1_2 */
printf(" In .so chart function \n");
if (res < 0) {
fprintf(stderr, "Can't create Java VM\n");
exit(1);
}
}
else
{
puts("VM Active ...");
(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
}
return res;
}
void doChart(CChartData *chartData,
CChartProps chartProps,
int numElements)
{
jstring jstr;
jobjectArray segLabels;
jobjectArray segValues;
char* label;
char* element;
char value[1024];
static jclass cls;
int i;
if (!Global_cls) {
cls = (*env)->FindClass(env, "ChartWrapper");
if (cls == NULL) {
goto destroy;
}
printf("Class exists\n");
/* Use weak global ref to allow C class to be unloaded */
Global_cls = (*env)->NewWeakGlobalRef(env, cls);
if (Global_cls== NULL) {
printf("%d\n", JNI_ERR);
}
(*env)->DeleteLocalRef(env, cls);
}
if (!mid) {
mid = (*env)->GetMethodID(env, Global_cls,
"setChartSettings", "([Ljava/lang/String;[Ljava/lang/String
V");
if (mid == NULL) {
goto destroy;
}
printf("setChartSettings() Method exist\n");
width = (*env)->GetFieldID(env, Global_cls, "WIDTH", "I");
if (width == NULL) {
printf("Width - Didn't get it\n");
}
height = (*env)->GetFieldID(env, Global_cls, "HEIGHT", "I");
if (height == NULL) {
printf("Height - Didn't get it\n");
}
type = (*env)->GetFieldID(env, Global_cls, "chartType", "I");
if (height == NULL) {
printf("Type - Didn't get it\n");
}
stringClass = (*env)->FindClass(env, "java/lang/String");
}
jstr = (*env)->NewStringUTF(env, " ");
/* Set individual fields in class. May change this implementation...
*/
(*env)->SetIntField(env, Global_cls, width, chartProps.width);
(*env)->SetIntField(env, Global_cls, height, chartProps.height);
(*env)->SetIntField(env, Global_cls, type, chartProps.chartType);
segLabels = (*env)->NewObjectArray (env, numElements ,stringClass,
jstr);
segValues = (*env)->NewObjectArray (env, numElements ,stringClass,
jstr);
for (i = 0; i < numElements; i++) {
label = chartData
.label;
jstr = (*env)->NewStringUTF(env, label);
sprintf(value, "%01.2f", chartData.value);
(*env)->SetObjectArrayElement(env, segLabels, i, jstr);
jstr = (*env)->NewStringUTF(env, value);
(*env)->SetObjectArrayElement(env, segValues, i, jstr);
}
fname = (*env)->GetFieldID(env, Global_cls, "chartFileName",
"Ljava/lang/String;");
if (fname == NULL) {
printf("filename - Didn't get it\n");
}
/*puts(chartProps.filename);*/
jstr = (*env)->NewStringUTF(env, chartProps.filename);
/*printf("Try to assign filename\n");*/
(*env)->SetObjectField(env, Global_cls, fname, jstr);
(*env)->CallVoidMethod(env, Global_cls, mid, segLabels,
segValues);
/* Try to cleanup after ourselves*/
cleanup(width, height, fname, type, jstr, segLabels, segValues);
destroy:
if ((*env)->ExceptionOccurred(env)) {
(*env)->ExceptionDescribe(env);
}
//(*jvm)->DestroyJavaVM(jvm);
}
void cleanup(jstring jstr,jobjectArray segLabels,jobjectArray
segValues)
{
(*env)->DeleteLocalRef(env, jstr);
(*env)->DeleteLocalRef(env, segLabels);
(*env)->DeleteLocalRef(env, segValues);
}
void destroyJVM() {
(*jvm)->DestroyJavaVM(jvm);
}
I'm going to add the helper methods as you suggested. Obviously it's
not in the listing above.
What's happening now is it locating the VM, but it crashes after the
second time through. Probably still not releasing all necessary
resources??
One thing that might be a problem is the for loop; I'm getting the
values of the elements from a C structure and an array of structs, so
I think I'm going to have to use some kind of loop. I then pass two
String Arrays to java that will populate a Vector. If there is a
better way of accomplishing this, please enlighten me.
Thanks in advanced