JNI(Java Native Interface
						,
						Java
						本地接口
						)
						技术大家都不陌生,它可以帮助解决
						Java
						访问底层硬件的局限和执行效率的提高。关于
						JNI
						的开发,大多数资料讨论的都是如何用
						C/C++
						语言开发
						JNI
						,甚至于
						JDK
						也提供了一个
						javah
						工具来自动生成
						C
						语言程序框架。但是,对于广大的
						Delphi
						程序员来说,难道就不能用自己喜爱的
						Delphi
						与
						Java
						互通消息了吗?
				
		
		
		
		
				
						通过对
						javah
						生成的
						C
						程序框架和
						JDK
						中的
						jni.h
						文件的分析,我们发现,
						Java
						利用
						JNI
						访问本地代码的关键在于
						jni.h
						中定义的
						JNINativeInterface_
						这个结构
						(Struct)
						,如果用
						Delhpi
						语言改写它的定义,应该也可以开发
						JNI
						的本地代码。幸运的是,在网上有现成的代码可以帮助你完成这个繁杂的工作,在
						
								http://delphi-jedi.org
						
						上提供了一个
						jni.pas
						文件,就是用
						Delphi
						语言重写的
						jni.h
						。我们只需在自己的
						Delphi
						工程中加入
						jni.pas
						就可以方便地开发出基于
						Delphi
						语言的
						JNI
						本地代码。
						
								
								
								
						
				
		
		
		
		
				
						本文将利用
						jni.pas
						,讨论用
						Delphi
						语言开发
						JNI
						本地代码的基本方法。
				
		
		
		
		
				
						先来看一个经典的
						HelloWorld
						例子。编写以下
						Java
						代码:
						
								
								
						
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												class HelloWorld
										 
										 
												{
										 
										 
												
														  public native void displayHelloWorld();
										 
										 
												
														  static
										 
										 
												
														  {
										 
										 
												
														    System.loadLibrary("HelloWorldImpl");
										 
										 
												
														  }
										 
										 
												}
										 | 
				
		
		
		
		
				
						这段代码声明了一个本地方法
						displayHelloWorld
						,它没有参数,也没有返回值,但是希望它能在屏幕上打印出“您好!中国。”字样。这个任务我们打算交给了本地的
						Delphi
						来实现。同时,在这个类的静态域中,用
						System.loadLibrary()
						方法装载
						HelloWorldImpl.dll
						。注意,这里只需要给出文件名而不需要给出扩展名
						
								dll
						
						。
				
		
		
		
		
				
						这时候,如果在我们的
						Java
						程序中使用
						HelloWorld
						类的
						displayHelloWorld
						方法,系统将抛出一个
						java.lang.UnsatisfiedLinkError
						的错误,因为我们还没有为它实现本地代码。
				
		
		
		
		
				
						下面再看一下在
						Delphi
						中的本地代码的实现。新建一个
						DLL
						工程,工程名为
						HelloWorldImpl
						,输入以下代码:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												Uses
										 
										 
												
														  JNI;
										 
										 
												procedure Java_HelloWorld_displayHelloWorld(PEnv: PJNIEnv; Obj: JObject);stdcall; 
										 
										 
												begin
										 
										 
												
														  Writeln('
												您好!中国。
												');
										 
										 
												end;
										 
										 
												exports
										 
										 
												
														  Java_HelloWorld_DisplayHelloWorld;
										 
										 
												end.
										 | 
				
		
		
		
		
				
						这段代码首先导入
						jni.pas
						单元。然后实现了一个叫
						Java_HelloWorld_displayHelloWorld
						的过程,这个过程的命名很有讲究,它以
						
								Java
						
						
								开头,用下划线将
								Java
						
						
								类的包名、类名和方法名连起来。这个命名方法不能有误,否则,
								Java
						
						
								类将无法将
								nativ
						
						
								方法与它对应起来。同时,在
								Win32
						
						
								平台上,此过程的调用方式只能声明为
								stdcall
						
						
								。
						
						虽然在
						HelloWorld
						类中声明的本地方法没有参数,但在
						Delphi
						中实现的具体过程则带有两个参数:
						PEnv : PJNIEnv
						和
						Obj : JObject
						。(这两种类型都是在
						jni.pas
						中定义的)。其中,
						PEnv
						参数代表了
						Jvm
						环境,而
						Obj
						参数则代表调用此过程的
						Java
						对象。当然,这两个参数,在我们这个简单的例子中是不会用到的。因为我们编译的是
						dll
						文件,所以在
						exports
						需要输出这个方法。
						
								
								
						
				
		
		
		
		
				
						编译
						Delphi
						工程,生成
						
								HelloWorldImp.dll
						
						
								文件,放在运行时系统能够找到的目录,一般是当前目录下,
						
						并编写调用
						HelloWorld
						类的
						Java
						类如下:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												class MainTest
										 
										 
												{
										 
										 
												
														  public static void main(String[] args)
										 
										 
												
														  {
										 
										 
												
														    new HelloWorld().displayHelloWorld();
										 
										 
												
														  }
										 
										 
												}
										 | 
				
		
		
		
		
				
						运行它,如果控制台输出了“您好!中国。”,恭喜你,你已经成功地用
						Delphi
						开发出第一个
						JNI
						应用了。
				
		
		
		
		
				
						接下来,我们稍稍提高一点,来研究一下参数的传递。还是
						HelloWorld
						,修改刚才写的
						displayHelloWorld
						方法,让显示的字符串由
						Java
						类动态确定。新的
						displayHelloWorld
						方法的
						Java
						代码如下:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												public native void displayHelloWorld(String str);
										 | 
				
		
		
		
		
				
						修改
						Delphi
						的代码,这回用到了过程的第一个固有参数
						PEnv
						,如下:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												procedure Java_HelloWorld_displayHelloWorld(PEnv: PJNIEnv; Obj: JObject; str: JString); stdcall; 
										 
										 
												var
										 
										 
												
														  JVM: TJNIEnv;
										 
										 
												begin
										 
										 
												
														  JVM := TJNIEnv.Create(PEnv);
										 
										 
												
														  Writeln(JVM.UnicodeJStringToString(str));
										 
										 
												
														  JVM.Free;
										 
										 
												end;
										 | 
				
		
		
		
		
				
						在该过程的参数表中我们增加了一个参数
						str : JString
						,这个
						str
						就负责接收来自
						HelloWorld
						传入的
						str
						实参。注意实现代码的不同,因为使用了参数,就涉及到参数的数据类型之间的转换。从
						Java
						程序传过来的
						Java
						的
						String
						对象现在成了特殊的
						JString
						类型,而
						JString
						在
						Delphi
						中是不可以直接使用的。需要借助
						TJNIEnv
						提供的
						UnicodeJStringToString()
						方法来转换成
						Delphi
						能识别的
						string
						类型。所以,需要构造出
						TJNIEnv
						的实例对象,使用它的方法(
						TJNIEnv
						提供了众多的方法,这里只使用了它最基本最常用的一个方法),最后,记得要释放它。对于基本数据类型的参数,从
						Java
						传到
						Delphi
						中并在
						Delphi
						中使用的步骤就是这么简单。
				
		
		
		
		
				
						我们再提高一点点难度,构建一个自定义类
						Book
						,并把它的实例对象作为参数传入
						Delphi
						,研究一下在本地代码中如何访问对象参数的公共字段。
				
		
		
		
		
				
						首先,定义一个简单的
						Java
						类
						Book
						,为了把问题弄得稍微复杂一点,我们在
						Book
						中增加了一个
						java.util.Date
						类型的字段,代码如下:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												public class Book
										 
										 
												{
										 
										 
												
														  public String title;  //
												标题
										 
										 
												
														  public double price; //
												价格
										 
										 
												
														  public Date pdate;  //
												购买日期
										 
										 
												}
										 | 
				
		
		
		
		
				
						同样,在
						HelloWorld
						类中增加一个本地方法
						displayBookInfo
						,代码如下:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												public native void displayBookInfo(Book b);
										 | 
				
		
		
		
		
				
						Delphi
						的代码相对于上面几个例子来说,显得复杂了一点,先看一下代码:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												procedure Java_HelloWorld_displayBookInfo(PEnv: PJNIEnv; Obj: JObject; b:JObject); stdcall;
										 
										 
												var
										 
										 
												
														 JVM: TJNIEnv;
										 
										 
												
														 c,c2: JClass;
										 
										 
												
														 fid:JFieldID;
										 
										 
												mid:JMethodID;
										 
										 
												title,datestr:string;
										 
										 
												price:double;
										 
										 
												pdate:JObject;
										 
										 
												begin
										 
										 
												
														  JVM := TJNIEnv.Create(PEnv);
										 
										 
												
														  c:=JVM.GetObjectClass(b);
										 
										 
												
														  fid:=JVM.GetFieldID(c,'title','Ljava/lang/String;');
										 
										 
												
														  title:=JVM.UnicodeJStringToString(JVM.GetObjectField(b,fid));
										 
										 
												
														  fid:=JVM.GetFieldID(c,'price','D');
										 
										 
												
														  price:=JVM.GetDoubleField(b,fid);
										 
										 
												
														  fid:=JVM.GetFieldID(c,'pdate','Ljava/util/Date;');
										 
										 
												
														  pdate:=JVM.GetObjectField(b,fid);
										 
										 
												
														  c2:=JVM.GetObjectClass(pdate);
										 
										 
												
														  mid:=JVM.GetMethodID(c2,'toString','()Ljava/lang/String;');
										 
										 
												
														  datestr:=JVM.JStringToString(JVM.CallObjectMethodA(pdate,mid,nil));
										 
										 
												
														  
												
										 
										 
												
														  WriteLn(Format('%s  %f  %s',[title,price,datestr]));
										 
										 
												
														 
												
										 
										 
												
														  JVM.Free;
										 
										 
												end;
										 | 
				
		
		
		
		
				
						参数
						b:JObject
						就是传入的
						Book
						对象。先调用
						GetObjectClass
						方法,根据
						b
						对象获得它所属的类
						c
						,然后调用
						GetFieldID
						方法从
						ç
						中获取一个叫做
						title
						的属性的字段
						ID
						,一定要传入正确的类型签名。然后通过
						GetObjectField
						方法就可以根据得到的字段
						ID
						从对象中得到字段的值。注意这里的次序:我们得到传入的对象参数
						
								(Object)
						
						
								,就要先得到它的类
								(Class)
						
						
								,这样既有了对象实例,又有了类,以后就从类中得到字段
								ID
						
						
								,根据字段
								ID
						
						
								从对象中得到字段值。对于类的静态字段,则可以直接从类中获取它的值而不需要通过对象。
						
						如果要调用对象的方法,操作步骤也基本类似,也需要从类中获取方法
						ID
						,再执行对象的相应方法。在本例中,因为我们增加了一个
						java.util.Date
						类型的字段,要访问这样的字段,也只能先把它做为
						JObject
						读入,再以同样的方法进一步去访问它的成员(属性或方法)。本例中演示了如何访问
						Date
						对象的成员方法
						toString
						。
						
				
				
				
				
						
						
				
		
		
				
						要正确地访问类对象的成员属性(字段)及成员方法,最重要的一点是一定要给出正确的签名,在
						Java
						中对于数据类型和方法的签名有如下的约定:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												
														数据类型
														/
												
												
														方法
														
																
																
														
												
										 | 
								
								
										 
												
														签名
														
																
																
														
												
										 | 
						
						
						
								
								
								
										 
												byte
										 | 
								
								
										 
												B
										 | 
						
						
						
								
								
								
										 
												char
										 | 
								
								
										 
												C
										 | 
						
						
						
								
								
								
										 
												double
										 | 
								
								
										 
												D
										 | 
						
						
						
								
								
								
										 
												float
										 | 
								
								
										 
												F
										 | 
						
						
						
								
								
								
										 
												int
										 | 
								
								
										 
												I
										 | 
						
						
						
								
								
								
										 
												long
										 | 
								
								
										 
												J (
												注意:是
												J
												不是
												L)
										 | 
						
						
						
								
								
								
										 
												short
										 | 
								
								
										 
												S
										 | 
						
						
						
								
								
								
										 
												void
										 | 
								
								
										 
												V
										 | 
						
						
						
								
								
								
										 
												boolean
										 | 
								
								
										 
												Z
												(注意:是
												Z
												不是
												B
												)
										 | 
						
						
						
								
								
								
										 
												类类型
										 | 
								
								
										 
												L
												跟完整类名,如
												Ljava/lang/String; 
												(注意:以
												L
												开头,要包括包名,以斜杠分隔,最后有一个分号作为类型表达式的结束)
										 | 
						
						
						
								
								
								
										 
												数组
												type[]
										 | 
								
								
										 
												[type
												,例如
												 float[]
												的签名就是
												[float
												,如果是二维数组,如
												float[][]
												,则签名为
												[[float
												,(注意:这里是两个
												 [ 
												符号)。
												
														
														
												
										 | 
						
						
						
								
								
								
										 
												方法
										 | 
								
								
										 
												(
												参数类型签名
												)
												返回值类型签名,例如方法:
												 float fun(int a,int b)
												,它的签名为
												(II)F
												,
												(
												注意:两个
												I
												之间没有逗号!
												)
												,而对于方法
												String toString()
												,则是
												()Ljava/lang/String;
												。
										 | 
				
		
		
		
		
				
						通过上面的例子,我们了解了访问对象参数的成员属性或方法的基本步骤和多个
						Get
						方法的使用。
						TJNIEnv
						同时提供了多个
						Set
						方法,可以修改传入的对象参数的字段值,因为
						Java
						对象参数都是以传址的方式进行传递的,所以修改的结果可以在
						Java
						程序中得到反映。
						TJNIEnv
						提供的
						Get/Set
						方法,都需要两个基本参数:对象实例(
						JObject
						类型)和字段
						ID
						(
						JField
						类型),就可以根据提供的对象和字段
						ID
						来获取或设置这个对象的这个字段的值。
				
		
		
		
		
				
						现在我们了解了在
						Delphi
						代码中使用以及修改
						Java
						对象的操作步骤。进一步,如果需要在
						Delphi
						中从无到有地创建一个新的
						Java
						对象,可以吗?再来看一个例子,在
						Delphi
						中创建
						Java
						类的实例,操作方法其实也非常简单。
				
		
		
		
		
				
						先在
						Java
						代码中增加一个本地方法,如下:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												
														 public native Book findBook(String t);
										 | 
				
		
		
		
		
				
						然后,修改
						Delphi
						代码,增加一个函数(因为有返回值,所以不再是过程而是函数了):
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												function Java_HelloWorld_findBook(PEnv: PJNIEnv; Obj: JObject; t:JString):JObject; stdcall;
										 
										 
												var
										 
										 
												
														 JVM: TJNIEnv;
										 
										 
												
														 c: JClass;
										 
										 
												
														 fid:JFieldID;
										 
										 
												
														 b:JObject;
										 
										 
												
														 mid:JMethodID;
										 
										 
												begin
										 
										 
												
														  JVM := TJNIEnv.Create(PEnv);
										 
										 
												
														 
												
										 
										 
												
														  c:=JVM.FindClass('Book');
										 
										 
												
														  mid:=JVM.GetMethodID(c,'<init>','()V');
										 
										 
												
														  b:=JVM.NewObjectV(c,mid,nil);
										 
										 
												
														  fid:=JVM.GetFieldID(c,'title','Ljava/lang/String;');
										 
										 
												
														  JVM.SetObjectField(b,fid,t);
										 
										 
												
														  fid:=JVM.GetFieldID(c,'price','D');
										 
										 
												
														  JVM.SetDoubleField(b,fid,99.8);
										 
										 
												
														  Result:=b;
										 
										 
												
														 
												
										 
										 
												
														  JVM.Free;
										 
										 
												end;
										 | 
				
		
		
		
		
				
						这里先用
						FindClass
						方法根据类名查找到类,然后获取构造函数的方法
						ID
						,构造函数名称固定为“
						<init>
						”,注意签名为“
						()V
						”说明使用了
						Book
						类的一个空的构造函数。然后就是使用方法
						NewObjectV
						根据类和构造函数的方法
						ID
						来创建类的实例。创建了类实例,再对它进行操作就与前面的例子没有什么两样了。对于非空的构造函数,则略为复杂一点。需要设置它的参数表。还是上面的例子,在
						Book
						类中增加一个非空构造函数:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												public Book(Strint t,double p){
										 
										 
												
														 this.title=t;
										 
										 
												this.price=p;
										 
										 
												}
										 | 
				
		
		
		
		
				
						在
						Delphi
						代码中,
						findBook
						函数修改获取方法
						ID
						的代码如下:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												mid:=JVM.GetMethodID(c,'<init>','(Ljava/lang/String;D)V');
										 | 
				
		
		
		
		
				
						构造函数名称仍是“
						<init>
						”,方法签名表示它有两个参数,分别是
						String
						和
						double
						。然后就是参数的传入了,在
						Delphi
						调用
						Java
						对象的方法如果需要传入参数,都需要构造出一个参数数组。在变量声明中加上:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												args : array[0..1] of JValue;
										 | 
				
		
		
		
		
				
						注意!参数都是
						JValue
						类型,不管它是基本数据类型还是对象,都作为
						JValue
						的数组来处理。在代码实现中为参数设置值,并将数组的地址作为参数传给
						NewObjectA
						方法:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												
														  args[0].l:=t; // t
												是传入的
												JString
												参数
										 
										 
												
														  args[1].d:=9.8;
										 
										 
												
														 
												
										 
										 
												
														  b:=JVM.NewObjectA(c,mid,@args);
										 | 
				
		
		
		
		
				
						为
						JValue
						类型的数据设置值的语句有点特殊,是吧?我们打开
						jni.pas
						,查看一下
						JValue
						的定义,原来它是一个
						packed record
						,已经包括了多种数据类型,
						JValue
						的定义如下:
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												
														  JValue = packed record
										 
										 
												
														  case Integer of
										 
										 
												
														    0: (z: JBoolean);
										 
										 
												
														    1: (b: JByte   );
										 
										 
												
														    2: (c: JChar   );
										 
										 
												
														    3: (s: JShort  );
										 
										 
												
														    4: (i: JInt    );
										 
										 
												
														    5: (j: JLong   );
										 
										 
												
														    6: (f: JFloat  );
										 
										 
												
														    7: (d: JDouble );
										 
										 
												
														    8: (l: JObject );
										 
										 
												
														  end;
										 | 
				
		
		
		
		
				
						下面再来看一下错误处理,在调试前面的例子中,大家也许看到了一旦在
						Delphi
						的执行过程中发生了错误,控制台就会输出一大堆错误信息,如果想要屏蔽这些信息,也就是说希望在
						Delphi
						中捕获错误并直接处理它,应该怎么做?也很简单,在
						TJNIEnv
						中提供了两个方法可以方便地处理在访问
						Java
						对象时发生的错误。
						
								
								
						
				
		
		
		
		
				
				
				
						
						
						
								
								
								
										 
												var
										 
										 
												… …
										 
										 
												ae:JThrowable;
										 
										 
												begin
										 
										 
												… …
										 
										 
												ae:=JVM.ExceptionOccurred;
										 
										 
												
														  if ( ae<>nil ) then
										 
										 
												
														   begin
										 
										 
												
														    Writeln(Format('Exception handled in Main.cpp: %d', [longword(ae)]));
										 
										 
												
														    JVM.ExceptionDescribe;
										 
										 
												
														    JVM.ExceptionClear;
										 
										 
												
														   end;
										 
										 
												… …
										 | 
				
		
		
		
		
				
						用方法
						ExceptionOccurred
						可以捕获
						Java
						抛出的错误,并存入
						JThrowable
						类型的变量中。用
						ExceptionDescribe
						可以显示出
						Java
						的错误信息,而
						ExceptionClear
						显然就是清除错误,让它不再被抛出。
				
		
		
		
		
				
						至此,我们已经把从
						Java
						代码通过
						JNI
						技术访问
						Delphi
						本地代码的步骤做了初步的探讨。在
						jni.pas
						中也提供了从
						Delphi
						中打开
						Java
						虚拟机执行
						Java
						代码的方法,有兴趣的读者不妨自己研究一下。
				
		
	posted on 2006-12-19 05:41 
坏男孩 阅读(1314) 
评论(1)  编辑  收藏  所属分类: 
java命令学习