通过在JNI框架内实战Java程序调用C程序,我们可以推导出Java程序基于JNI规范调用异质语言编写的程序的必要条件:
被调用的函数和方法被封装在标准的动态链接库中,由动态链接库将其输出。这是整个JNI规范存在的基础。
在两种语言之间存在着一个充当“翻译”角色的中介。在Java调用C语言的场合中,这个中介就是jni.h:在开发被调用的动态链接库的过程中,首先引入jni.h,然后实现利用javah生成的头文件中定义的函数即可。
以上是实践JNI规范的必要条件。从中可以推导出以下的结论——任何语言只要能满足这两个条件,那么这种语言就具备被Java程序调用的可能性。
在Windows平台上,主流开发语言都能编译产生动态链接库;但是能找到一种类似jni.h、在两种异质语言之间映射数据结构的中介却不是非常容易。本章介绍Java语言如何在JNI框架内调用Delphi编译产生的动态链接库,在此过程中发挥类似jni.h作用的是jni.pas——同样由Sun公司开发,针对Delphi的JNI框架的实现。
Delphi是诞生于Win32时代的一款优秀的IDE,曾经拥有庞大的开发群体。随着Borland(相信本书的读者对Borland公司不会陌生,不仅因为它传奇般的故事,也因为它的另一款主打产品JBuilder)公司主营业务的转型,也随着Windows平台从Win32时代向.NET时代的迁移,Delphi也正在经历着深刻的蜕变。尽管如此,Delphi仍然保持着一年一个大版本的升级速度。Delphi拥有众多的成功案例,因此本书在介绍完Java和C语言的互操作之后,安排一节内容介绍Java如何调用Delphi程序。
本节涉及的Delphi程序在Delphi 7.0环境下编译通过。jni.pas在配书光盘中本节的源码目录中可以找到。
15.3.1 简单例程
在本章的顺序编排方面,先以一个最简单的例程“开场”,在这个例程中不涉及任何的参数传递,只要Java主调程序能够触发Delphi方法的执行便达到目的。
我们先来看看做为主调方的Java程序。程序很简单:在HelloWorld寻找并载入Project1.dll,并定义本地方法:displayHelloWorld()。
代码清单15-15 调用Delphi函数的例程——HelloWorld
1. public class HelloWorld
2. {
3. public native void displayHelloWorld();
4. static
5. {
6. System.loadLibrary("Project1");
7. }
8. }
Main则负责调用HelloWorld。
代码清单15-16 调用Delphi函数的例程——Main
1. public class Main
2. {
3. public static void main(String[] args)
4. {
5. HelloWorld hw = new HelloWorld();
6. hw.displayHelloWorld();
7. }
8. }
接下来的工作是编写Delphi程序。关于Delphi语法、Delphi工程的结构,以及如何在Delphi中开发动态链接库,不准备介绍,本书假设读者们已经掌握相关知识。以下是Delphi工程文件(.dpr文件)——在Delphi中如果不涉及任何窗体的话,是无须创建单元文件的。
代码清单15-17 调用Delphi函数的例程——Project1.dpr
1. library HelloWorldImpl;
2.
3. uses
4. JNI;
5.
6. procedure Java_HelloWorld_displayHelloWorld(PEnv: PJNIEnv; Obj: JObject); {$IFDEF WIN32} stdcall; {$ENDIF} {$IFDEF LINUX} cdecl; {$ENDIF}
7. begin
8. WriteLn('Hello world!');
9. end;
10.
11. exports
12. Java_HelloWorld_displayHelloWorld;
13.
14. end.
可以看到,在JNI框架内的Delphi程序和C程序很类似,首先引入了jni.pas,而且无论方法的命名,还是固有的两个输入参数:PJNIEnv和Jobject。编译以上工程文件后将产生动态链接库文件Project1.dll。
15.3.2 关闭窗口实用程序
我们知道,在原生的Windows程序中查找一个窗口的句柄和关闭一个窗口是很容易做到的,只需调用Win32 API即可;可是Java程序却没有和操作系统直接交互的能力,要实现这个需求就比较困难了。跨越虚拟机、实现和操作系统的直接交互,正是JNI的一大存在价值。
本节将演示Java程序如何“借助”Delphi程序的本领,实现自身能力的提升。相比较上一节的例程,本例程具有以下需要注意的亮点:
Delphi程序提供两个方法,一个是函数,另一个是过程。
Java程序向Delphi方法传入参数。
Delphi函数向Java程序返回函数结果。
在Delphi程序中使用JNI上下文,即PJNIEnv变量。
以下是声明本地方法的Java类FindWindow:
代码清单15-18 调用Delphi函数的例程——FindWindow
1. class FindWindow
2. {
3. public native boolean ifExistsWindow(String itemName);
4. public native void closeWindow(String itemName);
5. static
6. {
7. System.loadLibrary("Project1");
8. }
9. }
Delphi程序将向Java程序输出两个方法:boolean ifExistsWindow(String)和void closeWindow(String),两个方法完成的功能分别是查找窗口和关闭窗口。按照JNI命名约定,在Delphi程序中,这两个方法将被命名为:Java_FindWindow_ifExistsWindow()和Java_FindWindow_closeWindow()。
以下是Java主程序Main。
代码清单15-19 调用Delphi函数的例程——Main
1. import java.io.*;
2.
3. class Main
4. {
5. public static void main(String[] args)
6. {
7. FindWindow fw = new FindWindow();
8. boolean result=fw.ifExistsWindow( "TITLE1" );
9. if(result)
10. fw.closeWindow( "TITLE1" );
11. }
12. }
Main.java的执行逻辑是,根据窗口标题查找窗口,如果该窗口存在的话,则予以关闭。在Main.java的第8行调用了一个返回布尔值的Delphi方法ifExistsWindow(),可见Delphi的布尔类型返回值可以在Java程序中直接使用。在程序的第10行调用了一个Delphi过程closeWindow()。
两次调用传入的字符串参数是对应着被查找和关闭的窗口标题的配置项名。本例假设需要查找和关闭记事本程序,记事本的窗口标题是“TranVerCtl.ini”,则配置文件TranVerCtl.ini的内容如下:
[PARAM]
TITLE1=无标题 - 记事本
下面是Delphi工程文件Project1.dll的源代码。
代码清单15-20 调用Delphi函数的例程——Project1.dpr
1. library FindWindowTool;
2.
3. uses
4. JNI,Windows,SysUtils,Messages,inifiles;
5.
6. function Java_FindWindow_ifExistsWindow(PEnv: PJNIEnv; Obj: JObject; itemName:JString):JBoolean; {$IFDEF WIN32} stdcall; {$ENDIF} {$IFDEF LINUX} cdecl; {$ENDIF}
7. var
8. JVM: TJNIEnv;
9. hWndClose:HWnd;
10. ini:tinifile;
11. item:String;
12. caption:String;
13. begin
14. JVM := TJNIEnv.Create(PEnv);
15. item:=JVM.JStringToString(itemName);
16. ini:=tinifile.create('.\TranVerCtl.ini');
17. caption:=ini.readstring('PARAM',item,'');
18. ini.Free;
19. writeln('窗口名:'+caption);
20. hWndClose := findwindow( nil , pchar(caption) );
21. if hWndClose<>0 then
22. begin
23. result:=true;
24. writeln('找到该窗口!');
25. end
26. else
27. begin
28. result:=false;
29. writeln('没有找到该窗口!');
30. end;
31. end;
32.
33. procedure Java_FindWindow_closeWindow(PEnv: PJNIEnv; Obj: JObject; itemName:JString); {$IFDEF WIN32} stdcall; {$ENDIF} {$IFDEF LINUX} cdecl; {$ENDIF}
34. var
35. JVM: TJNIEnv;
36. hWndClose:HWnd;
37. ini:tinifile;
38. item:String;
39. caption:String;
40. begin
41. JVM := TJNIEnv.Create(PEnv);
42. item:=JVM.JStringToString(itemName);
43. ini:=tinifile.create('.\TranVerCtl.ini');
44. caption:=ini.readstring('PARAM',item,'');
45. ini.Free;
46. hWndClose := findwindow( nil , pchar(caption) );
47. if hWndClose<>0 then
48. begin
49. SendMessage(hWndClose,WM_CLOSE,0,0);
50. writeln('关闭成功!');
51. end
52. else
53. writeln('关闭失败!');
54. end;
55.
56. exports
57. Java_FindWindow_ifExistsWindow,Java_FindWindow_closeWindow;
58.
59. end.
Delphi程序如何读取配置文件、如何查找和关闭窗口不是我们最关注的,请注意以下方面。
JString和JBoolean等数据类型:这些由jni.pas定义的特有的数据类型,对应着Java语言中的数据类型。表15-1是Java和Delphi的基础数据类型的对照关系,以及jin.pas对这些Java数据类型的重定义。
表15-1 Java和Delphi的基础数据类型的对照关系
|
Java Type |
Delphi Type |
Native Type |
|
boolean |
Boolean |
jboolean |
|
byte |
Shortint |
jbyte |
|
char |
WideChar |
jchar |
|
double |
Double |
jdouble |
|
float |
Single |
jfloat |
|
int |
Longint |
jint |
|
long |
Int64 |
jlong |
|
short |
Smallint |
jshort |
|
void |
N/A |
void |
JNI上下文的获取方式:PJNIEnv和JObject两种类型的输入参数,是遵循JNI规范的方法所固有的。
jni.pas中定义的数据类型转换方法:和jni.h定义了一系列类似GetStringUTFChars()的类型转换函数一样,jni.pas为Object Pascal和Java两种语言之间数据类型的转换提供了方法,如例程中的JStringToString()。
正如读者们所看到的,在JNI框架内,Java程序调用C和Delphi程序,其步骤和方式是大同小异的,区别主要在语法方面。如果有兴趣,或者在实际工作中需要大规模使用Java程序调用Delphi程序的话,相信拥有了本节介绍的基础知识,结合上一节各种数据类型的输入输出的实战经验,读者们能够快速地全面掌握这一项技能。







