📄 delphi-jni-3.html
字号:
This <i>Hello World!</i> example, as in the first <i>Hello World!</i> example is implemented as a <b>console</b> application. This was done to keep
the code as simple as possible while introducing the Invocation API. A later example will be a complete Windows GUI application.
<p>
This is the Java code that will print the words <tt><b>Hello World from Java!</b></tt> to the screen:
<pre class="sourcecode"><code><b>public</b> <b>class</b> HWJava
{
<b>public</b> <b>static</b> <b>void</b> printHello()
{
System.out.println(<font color="#9933CC">"Hello World from Java!"</font>);
}
}
</code></pre>
This Java class resides in a file called <tt>HWJava.java</tt> and simply contains one <tt><b>public static</b></tt> function
called <tt><b>printHello()</b></tt>. As you can see, this is a very trivial class which is why it is used in this example. The Delphi code
is not going to be as trivial, though. At this point, it's important that you understand the mechanisms used by
Delphi to access Java code at runtime without getting bogged down with the details of the Java code.
<p>
Here is the Delphi console application in a file called <i>HelloWorld1.dpr</i>:
<pre class="sourcecode"><code><b>program</b> HelloWorld1;
<font color="#003399"><i>{$APPTYPE CONSOLE}</i></font>
<b>uses</b>
SysUtils, JNI;
<b>var</b>
Options: <b>array</b> [0..4] <b>of</b> JavaVMOption;
VM_args: JavaVMInitArgs;
JavaVM: TJavaVM;
JNIEnv: TJNIEnv;
Cls: JClass;
Mid: JMethodID;
Errcode: Integer;
<b>begin</b>
<b>try</b>
<font color="#003399"><i>// Create the JVM (using a wrapper class)</i></font>
JavaVM := TJavaVM.Create;
<font color="#003399"><i>// Set the options for the VM</i></font>
Options[0].optionString := <font color="#9933CC">'-Djava.class.path=.'</font>;
VM_args.version := JNI_VERSION_1_2;
VM_args.options := @Options;
VM_args.nOptions := 1;
<font color="#003399"><i>// Load the VM</i></font>
Errcode := JavaVM.LoadVM(VM_args);
<b>if</b> Errcode < 0 <b>then</b>
<b>begin</b>
WriteLn(Format(<font color="#9933CC">'Error loading JavaVM, error code = %d'</font>, [Errcode]));
Exit;
<b>end</b>;
<font color="#003399"><i>// Create a Java environment from the JVM's Env (another wrapper class)</i></font>
JNIEnv := TJNIEnv.Create(JavaVM.Env);
<font color="#003399"><i>// Find the class in the file system. This is why we added</i></font>
<font color="#003399"><i>// the current directory to the Java classpath above.</i></font>
Cls := JNIEnv.FindClass(<font color="#9933CC">'HWJava'</font>);
<b>if</b> Cls = <b>nil</b> <b>then</b>
<b>begin</b>
WriteLn(<font color="#9933CC">'Can'</font><font color="#9933CC">'t find class: HWJava'</font>);
Exit;
<b>end</b>;
<font color="#003399"><i>// Find the static method 'printHello' within the HWJava class</i></font>
Mid := JNIEnv.GetStaticMethodID(Cls, <font color="#9933CC">'printHello'</font>, <font color="#9933CC">'()V'</font>);
<b>if</b> Mid = <b>nil</b> <b>then</b>
<b>begin</b>
WriteLn(<font color="#9933CC">'Can'</font><font color="#9933CC">'t find method: printHello'</font>);
Exit;
<b>end</b>;
<font color="#003399"><i>// Call the static method</i></font>
JNIEnv.CallStaticVoidMethod(Cls, Mid, []);
<b>except</b>
<b>on</b> E : Exception <b>do</b>
WriteLn(<font color="#9933CC">'Error: '</font> + E.Message);
<b>end</b>;
<b>end</b>.
</code></pre>
You should open a command window (DOS prompt) in the directory where your Delphi and Java files are and execute the Delphi program:
<BLOCKQUOTE>
<pre>
HelloWorld1 <--- <i>Type this at the command prompt</i>
Hello world from Java! <--- <i>this is displayed on the screen</i>
</pre>
</BLOCKQUOTE>
<p>
<a href="#Top">Back to top</a>
</BLOCKQUOTE>
<div class="SectionHeader">
<A NAME="CloserLook">A Closer Look at the <i>Hello World!</i> Example</A>
</div>
<BLOCKQUOTE>
In <a href="delphi-jni-1.html">Part One</a>, I explained the <i>Hello World!</i> example in great detail. I won't go into that much
depth in this example, as I expect that you have already gone through the first example and understand the basic concepts that were
presented. For this example, I will explain the portions that are new and pertain to the Invocation API.
<p>
These are the basic steps required to execute a method of a Java class from a Delphi application using JNI.pas:
<p>
<ol>
<li>Create an instance of <b>TJavaVM</b>, the wrapper class around the Java Virtual Machine.
<li>Set the options that describe how to load the VM.
<li>Load the VM
<li>Create an instance of <b>TJNIEnv</b>, the wrapper class around the Java runtime environment.
<li>Find the Java class that implements the method you wish to invoke.
<li>Find the method within the Java class.
<li>Call the method
</ol>
Let's look at each of these steps in detail:
<p>
<b><a name="CloserLook1">Create an instance of TJavaVM</a></b>:
<BLOCKQUOTE>
<pre class="sourcecode"><code><font color="#003399"><i> // Create the JVM (using a wrapper class)</i></font>
JavaVM := TJavaVM.Create;
</code></pre>
This is pretty straight-forward. <tt>JavaVM</tt> is declared as:
<pre class="sourcecode"><code> JavaVM: TJavaVM;
</code></pre>
so the code above calls the <tt>TJavaVM</tt>'s <b>constructor</b> <tt>Create</tt>, which simple instantiates a <tt>TJavaVM</tt> object
and sets <tt>JavaVM</tt> to refer to it. Note that at this point, no JVM has been loaded. The constructor simply creates the wrapper
class. We must explicitly load the VM after setting options, as shown next.
</BLOCKQUOTE>
<p>
<b><a name="CloserLook2">Setting the JVM options</a></b>:
<BLOCKQUOTE>
<pre class="sourcecode"><code><font color="#003399"><i> // Set the options for the VM</i></font>
Options[0].optionString := <font color="#9933CC">'-Djava.class.path=.'</font>;
VM_args.version := JNI_VERSION_1_2;
VM_args.options := @Options;
VM_args.nOptions := 1;
</code></pre>
The relevant variables are declared as:
<pre class="sourcecode"><code> options: <b>array</b> [0..4] <b>of</b> JavaVMOption;
VM_args: JavaVMInitArgs;
</code></pre>
and in JNI.pas, <tt>JavaVMOption</tt> and <tt>JavaVMInitArgs</tt> are defined as:
<pre class="sourcecode"><code> JavaVMOption = <b>packed</b> <b>record</b>
optionString: PAnsiChar;
extraInfo: Pointer;
<b>end</b>;
JavaVMInitArgs = <b>packed</b> <b>record</b>
version: JInt;
nOptions: JInt;
options: PJavaVMOption;
ignoreUnrecognized: JBoolean;
<b>end</b>;
</code></pre>
Essentially, the variable <tt>VM_args</tt> of type <tt>JavaVMInitArgs</tt> contains the information necessary to
configure the Java VM. These options are passed to the method (within the TJavaVM class) that will actually
load the Java VM. The members of the record represent:
<ul>
<li>version - The version of the Java Runtime Environment (JRE) and can be either <tt>JNI_VERSION_1_1 ($00010001)</tt>
or <tt>JNI_VERSION_1_2 ($00010002)</tt>.
<li>nOptions - The number of <tt>JavaVMOptions</tt> in the <tt>options</tt> array. (See next member.)
<li>options - A pointer to an array of <tt>JavaVMOption</tt>s
<li>ignoreUnrecognized - Tells the JRE to ignore unknown options.
</ul>
A <tt>JavaVMOption</tt> is simply a string (actually a PAnsiChar) that contains an option that is passed to the JRE much like you would
use on the command line. In the example above, we created a 5-element array of JavaVMOptions, although we only used one element. We
could have been more precise in the allocation, but because we are explicitly telling the <tt>JavaVMInitArgs</tt> variable, <tt>VM_args</tt>,
the number of options (<tt>VM_args.nOptions := 1</tt>), the size of the array is unimportant. In the example, only one option is specified:
<pre>
Options[0].optionString := <font color="#9933CC">'-Djava.class.path=.'</font>;
</pre>
which simply includes the current directory (denoted by the dot . ) in the JRE's search path for Java classes. We include the current
directory because that's where the Java class file, <tt>HWJava.class</tt>, is in relation to the Delphi program, <tt>HelloWorld1.exe</tt>.
This is an important option, because without it, the JRE would not be able to find the Java class. (Shortly, we will see the Delphi code that
actually causes Java to find, via a <tt>FindClass</tt> method, the Java class.) The -D option is a system property that has the syntax:
<pre>
-D(name)=(value)
</pre>
We will only use the option shown above to include the current directory in the JRE's search. Consult a reference on Java for other
options and their meanings.
</BLOCKQUOTE>
<b><a name="CloserLook3">Load the Java VM</a></b>:
<BLOCKQUOTE>
<pre class="sourcecode"><code><font color="#003399"><i> // Load the VM</i></font>
Errcode := JavaVM.LoadVM(VM_args);
<b>if</b> errcode < 0 <b>then</b>
<b>begin</b>
WriteLn(Format(<font color="#9933CC">'Error loading JavaVM, error code = %d'</font>, [Errcode]));
Exit;
<b>end</b>;
</code></pre>
Again, this is pretty straight-forward. It's also a good idea to check the return value when calling the <tt>TJavaVM.LoadVM</tt> method.
JNI.pas defines the possible return values as such:
<pre class="sourcecode"><code> JNI_OK = 0; <font color="#003399"><i>// success</i></font>
JNI_ERR = -1; <font color="#003399"><i>// unknown error</i></font>
JNI_EDETACHED = -2; <font color="#003399"><i>// thread detached from the VM</i></font>
JNI_EVERSION = -3; <font color="#003399"><i>// JNI version error</i></font>
JNI_ENOMEM = -4; <font color="#003399"><i>// not enough memory</i></font>
JNI_EEXIST = -5; <font color="#003399"><i>// VM already created</i></font>
JNI_EINVAL = -6; <font color="#003399"><i>// invalid arguments</i></font>
</code></pre>
Upon successfully loading the VM, the <tt>Env</tt> property of the <tt>TJavaVM</tt> class is set. <tt>Env</tt> maps to the private
field <tt>FEnv</tt>, which is declared as:
<pre>
FEnv: PJNIEnv;
</pre>
which is a pointer to a JNIEnv that will be used in the next step.
</BLOCKQUOTE>
<b><a name="CloserLook4">Create an instance of TJNIEnv</a></b>:
<BLOCKQUOTE>
<pre class="sourcecode"><code><font color="#003399"><i> // Create a Java environment from the JVM's Env (another wrapper class)</i></font>
JNIEnv := TJNIEnv.Create(JavaVM.Env);
</code></pre>
The <tt>TJNIEnv</tt> class is a wrapper around the raw JNI API that we saw in <a href="delphi-jni-2.html">Part Two</a> of this series. By
using the <tt>TJNIEnv</tt> class, we have a more Delphi-like interface to the API.
</BLOCKQUOTE>
<b><a name="CloserLook5">Find the Java class</a></b>:
<BLOCKQUOTE>
<pre class="sourcecode"><code><font color="#003399"><i> // Find the class in the file system. This is why we added</i></font>
<font color="#003399"><i>// the current directory to the Java classpath above.</i></font>
Cls := JNIEnv.FindClass(<font color="#9933CC">'HWJava'</font>);
<b>if</b> Cls = <b>nil</b> <b>then</b>
<b>begin</b>
WriteLn(<font color="#9933CC">'Can'</font><font color="#9933CC">'t find class: HWJava'</font>);
Exit;
<b>end</b>;
</code></pre>
If the Java class <tt>HWJava</tt> is found, the variable <tt>Cls</tt> will contain a valid value, otherwise it will contain <b>nil</b>.
<tt>Cls</tt> is declared as:
<pre>
Cls: JClass;
</pre>
</BLOCKQUOTE>
<b><a name="CloserLook6">Find the Java method</a></b>:
<BLOCKQUOTE>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -