Java Forum / General / October 2006
Java Native Interface: "Translate" java call to JNI
ckirchho@directmedia.de - 12 Sep 2006 15:32 GMT Hallo,
I am developing with Delphi 6 and would liek to use the Java Native Interface units in order to run FOP.
I have a batch file with the following call:
java -Xmx256m -cp "lib\fop\fop.jar;lib\fop\avalon-framework-4.1.3.jar;lib\fop\JimiProClasses.zip" org.apache.fop.apps.Fop -fo "Data.xml" -c "lib\fop\Config.xml" -pdf "Data.pdf"
I was able to easily add the "-Xmx256m" parameter in the options array. The "-cp" parameter caused an error, so I used -Djava.class.path="..." instead, but I am not sure wether it worked.
I am not too familiar with Java, what does the construct "org.apache.fop.apps.Fop" exactly express. Is it the class name and the name of a function that is called?
JNIEnv.FindClass('org.apache.fop.apps.Fop'); doesn't find anything, so that must be wrong.
Does anybody know how I would have to call the functions FindClass and GetStaticMethodID in order to achieve what I want?
Best regards,
Christian Kirchhoff
Gordon Beaton - 13 Sep 2006 07:40 GMT > I am not too familiar with Java, what does the construct > "org.apache.fop.apps.Fop" exactly express. Is it the class name and > the name of a function that is called? It's the name of a class. The method that gets called is "static void main(String[])".
> JNIEnv.FindClass('org.apache.fop.apps.Fop'); doesn't find anything, > so that must be wrong. FindClass() (when called from C at least) wants the classname spelled like this: "org/apache/fop/apps/Fop".
> Does anybody know how I would have to call the functions FindClass > and GetStaticMethodID in order to achieve what I want? Do it like this (in C, since I don't know Delphi):
fop = (*env)->FindClass(env,"org/apache/fop/apps/Fop"); /* find the method id in fop */ mid = (*env)->GetStaticMethodID(env, fop, "main", "([Ljava/lang/String;)V");
/* build the argument list */ str = (*env)->FindClass(env, "java/lang/String"); jargs = (*env)->NewObjectArray(env, num_args, str, NULL); /* prefer to do this in a loop if args already in an array of char* */ (*env)->SetObjectArrayElement(env, jargs, 0, (*env)->NewStringUTF(env, "-fo")) (*env)->SetObjectArrayElement(env, jargs, 1, (*env)->NewStringUTF(env, "Data.xml")) (*env)->SetObjectArrayElement(env, jargs, 2, (*env)->NewStringUTF(env, "-c")) /* etcetera */
/* invoke the method on fop with arguments */ (*env)->CallStaticVoidMethod(env, fop, mid, jargs);
(completely untested)
/gordon
 Signature [ don't email me support questions or followups ] g o r d o n + n e w s @ b a l d e r 1 3 . s e
Chris Uppal - 13 Sep 2006 09:30 GMT > I was able to easily add the "-Xmx256m" parameter in the options array. > The "-cp" parameter caused an error, so I used -Djava.class.path="..." > instead, but I am not sure wether it worked. That is the correct thing to do. The java.exe launcher program supplies -cp ... and -classpath ... as simple abbreviations for -Djava.class.path=....
Gordon has answered the rest of your questions. I just wanted to emphasise the extreme importance of checking for error returns and thrown exceptions at every step. (It's very uncharacteristic for Gordon to have missed that bit out ;-)
-- chris
ckirchho@directmedia.de - 14 Sep 2006 15:12 GMT Hallo,
thanks to both of you for the fast replies. I tested the recommendation and used "org/apache/fop/apps/Fop" as the class name, but still the class wasn't found.
After that I created a simple "Hello World" java script, compiled it a class, and tried to find that. Still no success.
Do I have to follow sertain conventiosn when I set the classpath? I thought I always have to use full path names. Can I use relatives path names (relative to which folder?). How do I define paths on (un)mounted network drives.
Right now I use the windows syntax for path names: JavaVM := TJavaVM.Create(JNI_VERSION_1_2); Options[0].optionString := '-Djava.class.path="C:\subfolder1\subfolder2\HWJava.class"'; Options[1].optionString := '-Xmx256m'; VM_args.version := JNI_VERSION_1_2; VM_args.options := @Options; VM_args.nOptions := 2; Errcode := JavaVM.LoadVM(VM_args); if Errcode < 0 then begin MessageDlg(Format('Error loading JavaVM, error code = %d', [Errcode]), mtError, [mbOK], 0); Exit; end; JNIEnv := TJNIEnv.Create(JavaVM.Env); Cls := JNIEnv.FindClass('HWJava'); if Cls = nil then begin MessageDlg('Can''t find class: HWJava', mtError, [mbOK], 0); Exit; end; JNIEnv.Free; JavaVM.Free;
------------------- Cls stays nil, the class couldn't be found. Do you have any idea why that is?
Best regards,
Christian Kirchhoff
Gordon Beaton - 14 Sep 2006 15:43 GMT > '-Djava.class.path="C:\subfolder1\subfolder2\HWJava.class"'; [...]
> Cls stays nil, the class couldn't be found. Do you have any idea why > that is? Do the backslashes not escape the subsequent character in Delphi strings? If so you need to double them, or replace them with forward slashes ("unix style" works on windows too).
For classes without a package (that is, classes whose fully qualified name consists of one word), the classpath should mention the directory containing the class file:
-Djava.class.path=/some/folder
For classes that belong to a package (such as org.apache.fop.apps.Fop does), there should be a directory structure that corresponds to the components in the package name, and the classfile should be at the bottom of that, like this:
/folder1/folder2/org/apache/fop/apps/Fop.class
In this case, folder2 should be mentioned in the classpath:
-Djava.class.path=/folder1/folder2
If your class is in a jarfile, the internal structure of the jarfile will reflect the package structure, and the jarfile itself (not its directory!) should be in the classpath:
-Djava.class.path=/somefolder/another/apache.jar
More on this here:
http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/classpath.html
Scroll down to "understanding the class path and package names".
/gordon
 Signature [ don't email me support questions or followups ] g o r d o n + n e w s @ b a l d e r 1 3 . s e
ckirchho@directmedia.de - 14 Sep 2006 17:19 GMT Thanks again for that reply. In the end in tunred out that the quotes around the path names were not allowed. Options[0].optionString := '-Djava.class.path="C:\subfolder1\subfolder2\HWJava.class"'; has in fact to be Options[0].optionString := '-Djava.class.path=C:\subfolder1\subfolder2\HWJava.class';
As simple as that. The backslash is o.k. by the way.
Regards,
Christian Kirchhoff
> > '-Djava.class.path="C:\subfolder1\subfolder2\HWJava.class"'; > [quoted text clipped - 41 lines] > [ don't email me support questions or followups ] > g o r d o n + n e w s @ b a l d e r 1 3 . s e ckirchho@directmedia.de - 15 Sep 2006 12:09 GMT Hallo,
I am at least one step further, now I need to pass the properties to the call and are now sure how:
Remember: The call I want to implement in Delphi is java -Xmx256m -cp "lib\fop\fop.jar;lib\fop\avalon-framework-4.1.3.jar;lib\fop\JimiProClasses.zip" org.apache.fop.apps.Fop -fo "Data.xml" -c "lib\fop\Config.xml" -pdf "Data.pdf"
So the parameters that have to be passed are: -fo "Data.xml" -c "lib\fop\Config.xml" -pdf "Data.pdf"
If I do JNIEnv.CallStaticVoidMethod(Cls, Mid, ['-fo "Data.xml" -c "Config.xml"-pdf "Cover.pdf"']); (all parameters in one string) or JNIEnv.CallStaticVoidMethod(Cls, Mid, ['-fo "Data.xml"', '-c "Config.xml"', '-pdf "Cover.pdf"']); (each parameter in a separate string)
...then JNIEnv.ExceptionOccurred is not NULL after the call, thus some exception occured.
The parameter Args in the call CallStaticVoidMethod() is an array of const. In CallStaticVoidMethod() the array elements are converted to JValues with ArgsToJValues(). The exemplary C++ code in the doc PDF works with an ObjectArray created with NewObjectArray(). I tried that, but CallStaticVoidMethod() won't accept an ObjectArray, nor would the similar procedures CallStaticVoidMethodA() or CallStaticVoidMethodV().
By the way: My Delphi app runs in a window, its not a console application. Can I get the description of the exception with any other function, or how would I have to use ExceptionDescribe() in this context?
Regards,
Christian Kirchhoff
Gordon Beaton - 15 Sep 2006 12:42 GMT > The parameter Args in the call CallStaticVoidMethod() is an array of > const. In CallStaticVoidMethod() the array elements are converted to [quoted text clipped - 3 lines] > accept an ObjectArray, nor would the similar procedures > CallStaticVoidMethodA() or CallStaticVoidMethodV(). Keep in mind that even though you pass several "program arguments" to the JVM, the method you are invoking takes only one argument, an array of String. When you run this from a command shell, the launcher collects the arguments into a single array of String.
So with CallStaticVoidMethod() you need to create an array of String objects, i.e. one String per program argument, and pass that to the method. Note that this is *not* the same as a Delphi array of pointers to the Delphi string type, you need to create actual Java String objects and a Java String array. I showed you how to do that in my earlier code example.
I don't know what the conversion ArgsToJValues does, so I can't help you there.
If you want to use CallStaticVoidMethodA() instead, then you would still need to create the Strings and the String array, but you would additionally need an array of jvalue, whose element 0 is a reference to the String array. There is little point in using that function in this case.
Finally I doubt that CallStaticVoidV() is what you want (and I doubt that you can even create the necessary va_list in Delphi).
Since this is a common thing to do, I'm sure you should be able to find Delphi examples somewhere that show you how to do this.
> By the way: My Delphi app runs in a window, its not a console > application. Can I get the description of the exception with any > other function, or how would I have to use ExceptionDescribe() in > this context? AFAIK there is only ExceptionDescribe().
/gordon
 Signature [ don't email me support questions or followups ] g o r d o n + n e w s @ b a l d e r 1 3 . s e
ckirchho@directmedia.de - 15 Sep 2006 15:29 GMT Hallo,
> So with CallStaticVoidMethod() you need to create an array of String > objects, i.e. one String per program argument, and pass that to the > method. Note that this is *not* the same as a Delphi array of pointers > to the Delphi string type, you need to create actual Java String > objects and a Java String array. I showed you how to do that in my > earlier code example. Unfortunately not. Here is the definition of the procedure in Delphi: procedure CallStaticVoidMethod(AClass: JClass; MethodID: JMethodID; const Args: array of const);
Args is an "array of const". I tried creating the Object array with NewObjectArray, adding additional ObjectArrayElements, and pass that as Args to CallStaticVoidMethod, but the compiler errored "Incompatible Types 'Array' and 'JObject'"
I have to use an array of const, but in the way I described earlier it doesn't seem to work.
Regards,
Christian
Gordon Beaton - 15 Sep 2006 15:50 GMT > Args is an "array of const". I haven't got a clue what "array of const" is.
> I tried creating the Object array with NewObjectArray, adding > additional ObjectArrayElements, and pass that as Args to > CallStaticVoidMethod, but the compiler errored "Incompatible Types > 'Array' and 'JObject'" But Array should be assignable to JObject. The corresponding types in Java and JNI are compatible (Arrays are Objects), so perhaps you need to use an explicit cast, or you need to assign the result of the array creation to a JObject instead of an Array and pass that. Other than that, I'm out of ideas.
/gordon
 Signature [ don't email me support questions or followups ] g o r d o n + n e w s @ b a l d e r 1 3 . s e
ckirchho@directmedia.de - 15 Sep 2006 16:48 GMT I managed to change the code and use CallStaticVoidMethodA in a way that the compiler doesn't throw an error. Args := JNIEnv.NewObjectArray(3, StrClass, JStr1); JNIEnv.SetObjectArrayElement(Args, 2, JStr2); JNIEnv.SetObjectArrayElement(Args, 3, JStr3); JNIEnv.CallStaticVoidMethodA(Cls, Mid, @Args);
@Args means that the pointer to Args is passed as the paranmeter. But when I run this code, the programmcloses itself without an error or exception, which is even worse than before.
So I went back to CallStaticVoidMethod(). I found a way to show the error, at least as a number: AException := JNIEnv.ExceptionOccurred; if AException <> nil then ShowMessage(Format('CheckJava: %d', [DWORD(AException)]));
I tried to make the call in two ways: Pass all parameters in one string: JNIEnv.CallStaticVoidMethod(Cls, Mid, ['-fo "Data.xml" -c "Config.xml" -pdf Cover.pdf"']); ErrorCode 180098752
Pass the parameters in separate strings: JNIEnv.CallStaticVoidMethod(Cls, Mid, ['-fo "Data.xml"', '-c "Config.xml"', '-pdf Cover.pdf"']); ErrorCode 180098744
Just a test: Pass an empty string JNIEnv.CallStaticVoidMethod(Cls, Mid, ['']); ErrorCode 180098756
I believe that it's just the way the parameter string is set up that is wrong. The syntax seems to be different than starting java from a batch file.
Like with the problem I had before where just the quotes were wrong and had to be deleted.
Does anybody by any chance now what those error codes mean?
By the way: Is there any way to get the messages that a java method prints (e.g. in a DOS Box when the script is started from within a DOS Box) when the method is called from within Delphi?
I will be on vacation til Oct 3rd, and hopefully I will find a solution afterwards.
Best Regards,
Christian.
> > Args is an "array of const". > [quoted text clipped - 16 lines] > [ don't email me support questions or followups ] > g o r d o n + n e w s @ b a l d e r 1 3 . s e Chris Uppal - 15 Sep 2006 17:22 GMT > Args is an "array of const". I tried creating the Object array with > NewObjectArray, adding additional ObjectArrayElements, and pass that as > Args to CallStaticVoidMethod, but the compiler errored "Incompatible > Types 'Array' and 'JObject'" Can you clarify exactly which of the three forms of CallStaticVoidMethod() you are using ? There are three of them:
CallStaticVoidMethod() CallStaticVoidMethodA() CallStaticVoidMethodV()
Also, can we be clear that you are attempting to provide /one/ Java object as the single parameter to the Java method you want to call ? That object is an Java array (created with NewObjectArray()) but it is a single object.
If you were working in C, then once you have created and populated that single Java object, there would then be three different ways you could invoke the method.
Using CallStaticVoidMethod() In this case you pass the jobject as one parameter to the call (in addition to the class and method ID). You should be wary of using this form from Delphi; unless Delphi understands 'C' variadic functions (functions which take variable numbers of arguments like C's printf()). I doubt whether it does, at least on the basis that 'C'-style variadic functions are incompatible with both the semantics and typical implementations of Pascal. It is possible that Borland have extended that so that Delphi can work with 'C's variadic functions, but you should check. Note that the ability to work with variadic functions at all doesn't necessarily imply the ability to work with the specific stack layout used by a MSVC variadic __stdcall function (I vaguely seem to remember that Borland's C++Builder didn't use the same layouts but that's a very long time ago...)
Using CallStaticVoidMethodA() In this case you pass a /C/ array as the parameter to the call. That array holds each of the parameters which should be passed to the Java method. So in this case that array should contain one element which itself is a Java array containing your parameter strings.
Using CallStaticVoidMethodV() If you are working in C and know what you are doing then this can be useful. In other circumstances -- such as seem to apply here -- you should just ignore it.
BTW: going back to an earlier post of yours. You said that you were setting a classpath like: -Djava.class.path=C:\subfolder1\subfolder2\HWJava.class If that's still true, and if 'HWJava.class' is not the (very strange) name of a directory, then your application is only working by the most bizarre of accidents -- there should never be any .class files explicitly mentioned on the classpath. Just directories and JAR files (re-read Gordon's earlier post if that's not clear).
-- chris
ckirchho@directmedia.de - 09 Oct 2006 17:52 GMT Hallo Chris,
sorry for the late answer, I was on vacation for a couple of weeks. Here are some answers to your questions/comments:
> > Args is an "array of const". I tried creating the Object array with > > NewObjectArray, adding additional ObjectArrayElements, and pass that as [quoted text clipped - 7 lines] > CallStaticVoidMethodA() > CallStaticVoidMethodV() Right now I concentrate on CallStaticVoidMethod()
> Also, can we be clear that you are attempting to provide /one/ Java object as > the single parameter to the Java method you want to call ? That object is an > Java array (created with NewObjectArray()) but it is a single object. Well, as I wrote before CallStaticVoidMethod is defined with an array of const as the parameter. It is a Delphi type. So I have to deal with that CallStaticVoidMethodA() works with a PJValue, which is defined in unit JNI. CallStaticVoidMethodV() works with a type called va_list, which is Delphi again. Yes, in any case it is one single object, that is passed
> If you were working in C, then once you have created and populated that single > Java object, there would then be three different ways you could invoke the [quoted text clipped - 34 lines] > classpath. Just directories and JAR files (re-read Gordon's earlier post if > that's not clear). That was for testing reasons and I corrected that later. My actual project sets the classpath to folder, JAR files and ZIP files.
I'll try to find out if I get CallStaticVoidMethod(), CallStaticVoidMethodA() or CallStaticVoidMethodV() to work anyhow...
Rgeards,
Christian
ckirchho@directmedia.de - 15 Sep 2006 15:55 GMT P.S.: Here is the code for the preocedure CallStaticVoidMethod in Delphi:
procedure TJNIEnv.CallStaticVoidMethod(AClass: JClass; MethodID: JMethodID; const Args: array of const); begin Env^.CallStaticVoidMethodA(Env, AClass, MethodID, ArgsToJValues(Args)); end;
ArgsToJValues returns a PJValue, by the way.
My problem seems to be which syntax I have to use for the parameters. JNIEnv.CallStaticVoidMethod(Cls, Mid, ['-fo "Data.xml"', '-c "Config.xml"', '-pdf Cover.pdf"']); seems to be wrong
JNIEnv.CallStaticVoidMethod(Cls, Mid, ['fo="Data.xml"', 'c="Config.xml"', 'pdf="Cover.pdf"']); is wrong, too.
JNIEnv.CallStaticVoidMethod(Cls, Mid, ['fo=Data.xml', 'c=Config.xml', 'pdf=Cover.pdf']); is wrong, too.
But then again, in my actual code I use full path names, and maybe here I have to use another syntax. But I tried it with slashes instead of backslashes, too, and it doesn't work...
Christian
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|