Java Native Interface: "Translate" java call to JNI

C

ckirchho

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
 
G

Gordon Beaton

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
 
C

Chris Uppal

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
 
C

ckirchho

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
 
G

Gordon Beaton

'-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
 
C

ckirchho

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

Gordon said:
'-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
 
C

ckirchho

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
 
G

Gordon Beaton

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().

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
 
C

ckirchho

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
 
G

Gordon Beaton

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
 
C

ckirchho

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
 
C

ckirchho

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.
 
C

Chris Uppal

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
 
C

ckirchho

Hallo Chris,

sorry for the late answer, I was on vacation for a couple of weeks.
Here are some answers to your questions/comments:

Chris said:
Can you clarify exactly which of the three forms of CallStaticVoidMethod() you
are using ? There are three of them:

CallStaticVoidMethod()
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
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).

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top