您也许已经见过这样的报告,即一些新的 Java 语言变化包含易于开发性主题。这些变化包括泛型、元数据、autoboxing、增强的 for 循环、枚举类型、静态导入、C 风格的格式化 I/O、可变参数、并发实用程序以及更简单的 RMI 接口生成。
JSR 201 包括如下四个语言变化:增强的 for 循环、枚举类型、静态导入和 autoboxing;JSR 175 指定了新的元数据功能,而 JSR 14 则详细说明了泛型。
javac 编译器执行的默认语言规范是版本 1.4。这意味着要利用以下语言变化的任何好处,需要向 javac 命令传递参数 -source 1.5。
元数据
J2SE 1.5 中的元数据特性提供这样的能力,即向 Java 类、接口、方法和字段关联附加的数据。这些附加的数据或者注释,可以被 javac 编译器或其他工具读取,并且根据配置不同,可以被保存在类文件中,也可以在运行时使用 Java 反射 API 被发现。
向 Java 平台增加元数据的一个主要原因是,使得开发工具和运行工具有一个通用的基础结构,以减少开发和部署所需的成本。工具可以使用元数据信息生成附加的源代码,或者在调试时提供附加信息。
下面的例子用元数据工具创建了一个调试元数据注释,这些元数据注释然后又简单地在运行时显示出来。可以想像,大部分的元数据标签形成一个标准,即一个良好规范的集合。
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @interface debug {
boolean devbuild() default false;
int counter();
}
public class MetaTest {
final boolean production=true;
@debug(devbuild=production,counter=1) public void testMethod() {
}
public static void main(String[] args) {
MetaTest mt = new MetaTest();
try {
Annotation[] a = mt.getClass().getMethod("testMethod").getAnnotations();
for (int i=0; i<a.length ; i++) {
System.out.println("a["+i+"]="+a+" ");
}
} catch(NoSuchMethodException e) {
System.out.println(e);
}
}
}
利用一个元数据处理工具,许多重复的代码编写步骤可以减少成一个简练的元数据标签。例如,访问某个 JAX-RPC 服务实现时所需的远程接口可以实现为:
原来(J2SE 1.5 以前版本):
public interface PingIF extends Remote {
public void ping() throws RemoteException;
}
public class Ping implements PingIF {
public void ping() {
}
}
现在:
public class Ping {
public @remote void ping() {
}
}
范型
范型一直是 Java 社团所广泛期待的,现在已经是 J2SE 1.5 的一部分了。最先见到使用泛型的地方是在 Collections API 中。Collections API 提供可以被多个 Java 类型使用的公共功能性,比如 LinkedLists、ArrayLists 和 HashMaps。下一个例子使用 1.4.2 库和默认的 javac 编译模式。
ArrayList list = new ArrayList();
list.add(0, new Integer(42));
int total = ((Integer)list.get(0)).intValue();
最后一行中的 Integer 转换是泛型所要防止的强制类型转换问题的一个例子。这个问题在于,1.4.2 Collections API 使用 Object 类来存储 Collection 对象,这就意味着在编译的时候不能找出类型匹配。问题的第一个标志信息是在运行时抛出的 ClassCastException。
带有范型化 Collections 库的同一个例子可编写为:
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(0, new Integer(42));
int total = list.get(0).intValue();
范型化 API 的用户必须使用 <> 符号简单地声明在编译类型中使用的类型。不需要任何类型转换,在本例中试图向一个 Integer 类型的集合中添加 String 对象将会在编译时被捕获。
因此,范型允许 API 设计者提供这样的公共功能性:可以与多种数据类型一起使用,也可以在编译时出于类型安全对它进行检查。
设计自己的 Generic API 比起只是使用它们来说要稍微复杂一些。请从查看 java.util.Collection 源代码和 API 指南开始。
原语类型的 Autoboxing 和 Auto-unboxing
像 int、boolean 以及它们的基于对象的对应物(比如 Integer 和 Boolean)这样的原语类型之间的转换需要大量不必要的额外编码, 尤其是当只是像 Collections API 这样的方法调用需要转换时更甚。
Java 原语类型的 autoboxing 和 auto-unboxing 产生更加精练并更加易于理解的代码。1.5 版本让所需要的转换转变成 Integer 并转换回编译器。
原来
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(0, new Integer(42));
int total = (list.get(0)).intValue();
现在
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(0, 42);
int total = list.get(0);
增强的 for 循环
Collections API 经常使用 Iterator 类。Iterator 类提供在 Collection 中顺序导航的机制。当像下面一样只是在 Collection 中遍历时,新的增强的 for 循环可取代 iterator。编译器生成必要的循环代码,因为利用范型,所以不需要额外的类型转换。
原来
ArrayList<Integer> list = new ArrayList<Integer>();
for (Iterator i = list.iterator(); i.hasNext();) {
Integer value=(Integer)i.next();
}
现在
ArrayList<Integer> list = new ArrayList<Integer>();
for (Integer i : list) { ... }
枚举类型
当使用 static final 型常量时,该类型提供枚举的类型。如果您以前在自己的应用程序中使用过标识符 enum,那么在利用 javac -source 1.5 编译时需要调整源代码。
public enum StopLight { red, amber, green };
静态导入
静态导入特性实现为“import static”,允许您从一个类引用静态常量,而不需要继承这个类。每次我们添加一个组件时,不必使用 BorderLayout.CENTER,只要引用 CENTER 就可以了。
import static java.awt.BorderLayout.*;
getContentPane().add(new JPanel(), CENTER);
格式化的输出
现在开发人员可以选择使用 printf type 功能性来生成格式化的输出。这将有助于迁移传统的 C 应用程序,作很少的更改或者不作更改就能保留相同的文本布局。
大多数公共 C printf 格式化程序是可用的,另外,一些 Java 类(比如 Date 和BigInteger)也具有格式化规则。更多信息请参见 java.util.Formatter 类。
System.out.printf("name count\n");
System.out.printf("%s %5d\n", user,total);
格式化的输入
Scanner API 为从系统控制台或任何数据流读取数据提供基本的输入功能性。下面的例子从标准输入读取一个 String 并期待下一个 int 值。
如果没有数据可用,像 next 和 nextInt 这样的 Scanner 方法将会阻塞。如果您需要处理更加复杂的输入,那么从 java.util.Formatter 类还可以得到模式匹配算法。
Scanner s=Scanner.create(System.in);
String param= s.next();
int value=s.nextInt();
s.close();
Varargs
varargs(可变参数)功能性允许多个参数传递作为方法的参数。它需要简单的 ... 符号,该符号用于接收参数列表的方法,并且它还被用于实现 printf 所需参数的灵活数量。
void argtest(Object ... args) {
for (int i=0;i <args.length; i++) {
}
}
argtest("test", "data");
并发实用程序
并发实用程序库由 Doug Lea 定义在 JSR-166 中,是 J2SE 1.5 平台中流行的并发软件包的一个特殊版本。它提供强大的、高级别的线程构造,包括 executors(这是一个线程任务框架)、线程安全队列、Timers、锁(包括原子锁)和其他同步原语。
著名的旗语(semaphore)是这样一个锁。旗语与现在使用的 wait 的使用方式相同,用于限制对一块代码的访问。旗语更加灵活,并且也允许许多并发的线程访问,同时允许您在获得一个锁之前对它进行测试。下面的例子使用刚好一个旗语,也叫做二进制旗语。更多信息请参见 java.util.concurrent 软件包。
final private Semaphore s= new Semaphore(1, true);
s.acquireUninterruptibly(); //for non-blocking version use s.acquire()
balance=balance+10; //protected value
s.release(); //return semaphore token
rmic —— rmi 编译器
您不再需要使用 rmic —— rmi 编译器工具——来生成最远程的接口存根。动态代理的引入意味着通常由存根提供的信息可以在运行时被发现。更多信息请参见 RMI 版本说明。
可扩展性和性能
1.5 版本承诺在可扩展性和性能方面的改进,新的重点在于启动时间和内存占用,使它更加易于以最大的速度部署应用程序。
最重大的一个更新是引入了 Hotspot JVM 中的类数据共享。该技术不仅在多个正在运行的 JVM 之间共享只读数据,而且改进了启动时间,因为核心的 JVM 类都是预先打包的。
性能工效是 J2SE 1.5 中的一个新特性,这意味着如果您一直使用的是以前版本中专门的 JVM 运行时选项, 那么可能值得不用选项或者用很少的选项重新验证您的性能。
监控和可管理性
监控和可管理性是 Java 平台中的 RAS (Reliability, Availability, Serviceability,即可*性、可用性、可服务性) 的一个关键组件。
JVM Monitoring & Management API (JSR-174) 指定一组全面的可以从正在运行的 JVM 进行监控的 JVM internals。 该信息可通过 JMX (JSR-003) MBeans 访问到,也可以使用 JMX 远程接口 (JSR-160) 和行业标准 SNMP 工具而远程访问得到。
最有用的一个特性是一个低内存检测程序。当超过阀值时,JMX MBeans 可以通知已注册的侦听程序。更多信息请参见 javax.management 和 java.lang.management。
为了了解新的 API 是多么容易使用,下面报告了 Hotspot JVM 中内存堆的详细使用情况。
import java.lang.management.*;
import java.util.*;
import javax.management.*;
public class MemTest {
public static void main(String args[]) {
List pools =ManagementFactory.getMemoryPoolMBeans();
for(ListIterator i = pools.listIterator(); i.hasNext();) {
MemoryPoolMBean p = (MemoryPoolMBean) i.next();
System.out.println("Memory type="+p.getType()+" Memory usage="+p.getUsage());
}
}
}
新的 JVM profiling API (JSR-163)
该版本还包含一个更强大的本机 profiling API,叫做 JVMTI。该 API 已经在 JSR 163 中指定了,并由对改善的 profiling 接口的需求所推动。但是,JVMTI 除了具有 profiling 功能之外,还想要涵盖全范围的本机内部过程工具访问,包括监控工具、调试工具以及潜在的各种各样的其他代码分析工具。
该实现包含一个用于字节码装置(instrumentation)——Java 编程语言装置服务(Java Programming Language Instrumentation Services,JPLIS)的机制。这使得分析工具只在需要的地方添加额外的配置信息(profiling)。该技术的优点是,它允许更加集中的分析,并且限制了正在运行的 JVM 上的 profiling 工具的引用。该装置甚至可以在运行时和类加载时动态地生成,并且可以作为类文件预先处理。
下面这个例子创建了一个装置钩(instrumentation hook),它可以从磁盘加载类文件的一个已修改的版本。要运行该测试,可利用 java -javaagent:myBCI BCITest 启动 JRE。
//File myBCI.java
import java.lang.instrument.Instrumentation;
public class myBCI {
private static Instrumentation instCopy;
public static void premain(String options, Instrumentation inst) {
instCopy = inst;
}
public static Instrumentation getInstrumentation() {
return instCopy;
}
}
//File BCITest.java
import java.nio.*;
import java.io.*;
import java.nio.channels.*;
import java.lang.instrument.*;
public class BCITest {
public static void main (String[] args) {
try {
OriginalClass mc = new OriginalClass();
mc.message();
FileChannel fc=new FileInputStream(new File("modified"+File.separator+"OriginalClass.class")).getChannel();
ByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, (int)fc.size());
byte[] classBuffer = new byte[buf.capacity()];
buf.get(classBuffer, 0, classBuffer.length);
myBCI.getInstrumentation().redefineClasses(new ClassDefinition[] {new ClassDefinition(mc.getClass(), classBuffer)});
mc.message();
}catch (Exception e){}
}
}
//OriginalClass.java
//Compile in current directory
//Copy source to modified directory,change message and recompile
public class OriginalClass {
public void message() {
System.out.println("OriginalClass");
}
}
改进的诊断能力
如果没有控制台窗口可用,生成的 Stack 跟踪就很笨拙。两个新的 API —— getStackTrace 和 Thread.getAllStackTraces —— 以程序的方式提供该信息。
StackTraceElement e[]=Thread.currentThread().getStackTrace();
for (int i=0; i <e.length; i++) {
System.out.println(e);
}
System.out.println("\n"+Thread.getAllStackTraces());
Hotspot JVM 包含一个致命的错误处理程序(error hander),如果 JVM 异常中断,它可以运行用户提供的脚本。使用 Hotspot JVM 可服务性代理连接器,调试工具也可以连接到一个挂起的 JVM 或者核心文件。
-XX:OnError="command"
-XX:OnError="pmap %p"
-XX:OnError="gdb %p"
optional %p used as process id
桌面客户端
Java 桌面客户端保留有 Java 平台的一个关键组件,并且这一点成了 J2SE 1.5 中许多改进的焦点。
这个 Beta 版本包含启动时间和内存占用方面的一些早期改进。该版本不仅更快,并且 Swing 工具集采用了一个暂新的叫做 Ocean 的主题。
通过建立 J2SE 1.4.2 中的更新,GTK 和 Windows XP 外观方面有了更进一步的改进。
Windows XP
Click to Enlarge
Linux/Redhat
Click to Enlarge
具有最新 OpenGL 驱动程序并且选择了图形卡的 Linux 和 Solaris 用户,可以使用下面的运行时属性从 Java2D 获得本机硬件加速:
java -Dsun.java2d.opengl=true -jar Java2D.
Linux 版本也具有快速的 X11 Toolkit,叫做 XAWT,默认情况下是启用的。如果您需要与 motif 版本进行比较,可以使用下面的系统属性:
java -Dawt.toolkit=sun.awt.motif.MToolkit -jar Notepad.jar
(X11 Toolkit 叫做 sun.awt.X11.XToolkit)
X11 Toolkit 也使用 XDnD 协议,所以您可以在 Java 和其他应用(比如 StarOffice 或 Mozilla)之间拖放简单的组件。
其他特性
核心 XML 支持
J2SE 1.5 引入了核心 XML 平台的几个修订,包括 XML 1.1 和 Namespace、XML Schema、SAX 2.0.1、XSLT 和快速 XLSTC 编译器,以及最后的 DOM 第 3 层支持。
除了支持核心 XML 之外,未来版本的 Java Web Services Developer Pack 将交付最新的 Web 服务标准:JAX-RPC & SAAJ (WSDL/SOAP)、JAXB、XML Encryption and Digital Signature,以及用于注册的 JAXR。
辅助字符支持
32 位的辅助字符支持作为传输到 Unicode 4.0 支持的一部分,已经慎重地添加到该平台。辅助字符被编码为一对特殊的 UTF16 值,以生成一个不同的字符或者码点(codepoint)。一个代理对(surrogate pair)是一个高 UTF16 值和后面的一个低 UTF16 值的组合。这些高值和低值来自一个特殊范围的 UTF16 值。
一般来说,当使用 String 或者字符序列时,核心 API 库将透明地为您处理新的辅助字符。但是因为 Java "char" 仍然保留为 16 位,所以非常少的一些使用 char 作为参数的方法,现在有了足够的可以接受 int 值的方法,其中 int 值可以代表新的更大的值。特别是 Character 类,具有附加的方法来检索当前的字符和接下来的字符,以便检索辅助的码点值,如下所示:
String u="\uD840\uDC08";
System.out.println(u+"+ "+u.length());
System.out.println(Character.isHighSurrogate(u.charAt(0)));
System.out.println((int)u.charAt(1));
System.out.println((int)u.codePointAt(0));
更多信息请参见 Character 中的 Unicode 部分。
JDBC RowSets
JDBC 行集支持有两个主要的更新。CachedRowSet 包含从数据库检索的行的内存中的集合。但是它们也是不连接的,这意味着以后更新可以与数据库重新同步。
另一个组件是 WebRowSet,它使用数据库行通过 XML 来传输数据。
参考资料:
New Language Features for Ease of Development in the Java 2 Platform, Standard Edition 1.5: http://java.sun.com/features/2003/05/bloch_qa.html
Tiger Component JSRs
003 Java Management Extensions (JMX) Specification http://jcp.org/en/jsr/detail?id=3
013 Decimal Arithmetic Enhancement http://jcp.org/en/jsr/detail?id=13
014 Add Generic Types To The Java Programming Language http://jcp.org/en/jsr/detail?id=14
028 Java SASL Specification http://jcp.org/en/jsr/detail?id=28
114 JDBC Rowset Implementations http://jcp.org/en/jsr/detail?id=114
133 Java Memory Model and Thread Specification Revision http://jcp.org/en/jsr/detail?id=133
160 Java Management Extensions (JMX) Remote API 1.0 http://jcp.org/en/jsr/detail?id=160
163 Java Platform Profiling Architecture http://jcp.org/en/jsr/detail?id=163
166 Concurrency Utilities http://jcp.org/en/jsr/detail?id=166
174 Monitoring and Management Specification for the Java Virtual Machine http://jcp.org/en/jsr/detail?id=174
175 A Metadata Facility for the Java Programming Language http://jcp.org/en/jsr/detail?id=175
200 Network Transfer Format for Java Archives http://jcp.org/en/jsr/detail?id=200
201 Extending the Java Programming Language with Enumerations, Autoboxing, Enhanced for Loops and Static Import http://jcp.org/en/jsr/detail?id=201
204 Unicode Supplementary Character Support http://jcp.org/en/jsr/detail?id=204
206 Java API for XML Processing (JAXP) 1.3 http://jcp.org/en/jsr/detail?id=206
最近在公司里做了一个手机的项目,需要JAVA程序在发送短信的时候和第三方的短信服务器连接。短信接口是用C++写的。琢磨了三天,大致搞懂了JNI的主体部分。先将心得整理,希望各位朋友少走弯路。
首先引用一篇文章,介绍一个简单的JNI的调用的过程。
JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。
JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
简单介绍及应用如下:
一、JAVA中所需要做的工作
在JAVA程序中,首先需要在类中声明所调用的库名称,如下:
- static {
- System.loadLibrary(“goodluck”);
- }
在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。
还需要对将要调用的方法做本地声明,关键字为native。并且只需要声明,而不需要具 体实现。如下:
public native static void set(int i);
public native static int get();
然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/C++的头文件。
例如程序testdll.java,内容为:
- public class testdll
- {
- static
- {
- System.loadLibrary("goodluck");
- }
- public native static int get();
- public native static void set(int i);
- public static void main(String[] args)
- {
- testdll test = new testdll();
- test.set(10);
- System.out.println(test.get());
- }
- }
用javac testdll.java编译它,会生成testdll.class。
再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。
二、C/C++中所需要做的工作
对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。
接上例子。我们先看一下testdll.h文件的内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class testdll */
#ifndef _Included_testdll
#define _Included_testdll
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: testdll
* Method: get
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);
/*
* Class: testdll
* Method: set
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
在具体实现的时候,我们只关心两个函数原型
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本地的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。
好,下面我们用testdll.cpp文件具体实现这两个函数:
#include "testdll.h"
int i = 0;
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass)
{
return i;
}
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j)
{
i = j;
}
编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll 。把goodluck.dll拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。
我的项目比较复杂,需要调用动态链接库,这样在JNI传送参数到C程序时,需要对参数进行处理转换。才可以被C程序识别。
大体程序如下:
- public class SendSMS {
- static
- {
- System.out.println(System.getProperty("java.library.path"));
- System.loadLibrary("sms");
- }
- public native static int SmsInit();
- public native static int SmsSend(byte[] mobileNo, byte[] smContent);
- }
在这里要注意的是,path里一定要包含类库的路径,否则在程序运行时会抛出异常:
java.lang.UnsatisfiedLinkError: no sms in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)
at java.lang.Runtime.loadLibrary0(Runtime.java:788)
at java.lang.System.loadLibrary(System.java:834)
at com.mobilesoft.sms.mobilesoftinfo.SendSMS.<clinit>(SendSMS.java:14)
at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
Exception in thread "main"
指引的路径应该到.dll文件的上一级,如果指到.dll,则会报:
java.lang.UnsatisfiedLinkError: C:\sms.dll: Can't find dependent libraries
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)
at java.lang.Runtime.loadLibrary0(Runtime.java:788)
at java.lang.System.loadLibrary(System.java:834)
at com.mobilesoft.sms.mobilesoftinfo.SendSMS.<clinit>(SendSMS.java:14)
at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
Exception in thread "main"
通过编译,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h头文件。(建议使用Jbuilder进行编译,操作比较简单!)这个头文件就是Java和C之间的纽带。要特别注意的是方法中传递的参数jbyteArray,这在接下来的过程中会重点介绍。
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class com_mobilesoft_sms_mobilesoftinfo_SendSMS */
- #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
- #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS
- * Method: SmsInit
- * Signature: ()I
- */
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit
- (JNIEnv *, jclass);
- /*
- * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS
- * Method: SmsSend
- * Signature: ([B[B)I
- */
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend
- (JNIEnv *, jclass, jbyteArray, jbyteArray);
- #ifdef __cplusplus
- }
- #endif
- #endif
对于我要调用的C程序的动态链接库,C程序也要提供一个头文件,sms.h。这个文件将要调用的方法罗列了出来。
- /*
- * SMS API
- * Author: yippit
- * Date: 2004.6.8
- */
- #ifndef MCS_SMS_H
- #define MCS_SMS_H
- #define DLLEXPORT __declspec(dllexport)
- /*sms storage*/
- #define SMS_SIM 0
- #define SMS_MT 1
- /*sms states*/
- #define SMS_UNREAD 0
- #define SMS_READ 1
- /*sms type*/
- #define SMS_NOPARSE -1
- #define SMS_NORMAL 0
- #define SMS_FLASH 1
- #define SMS_MMSNOTI 2
- typedef struct tagSmsEntry {
- int index; /*index, start from 1*/
- int status; /*read, unread*/
- int type; /*-1-can't parser 0-normal, 1-flash, 2-mms*/
- int storage; /*SMS_SIM, SMS_MT*/
- char date[24];
- char number[32];
- char text[144];
- } SmsEntry;
- DLLEXPORT int SmsInit(void);
- DLLEXPORT int SmsSend(char *phonenum, char *content);
- DLLEXPORT int SmsSetSCA(char *sca);
- DLLEXPORT int SmsGetSCA(char *sca);
- DLLEXPORT int SmsSetInd(int ind);
- DLLEXPORT int SmsGetInd(void);
- DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);
- DLLEXPORT int SmsSaveFlash(int flag);
- DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index);
- DLLEXPORT int SmsDelete(int storage, int index);
- DLLEXPORT int SmsModifyStatus(int storage, int index); /*unread -> read*/
- #endif
在有了这两个头文件之后,就可以进行C程序的编写了。也就是实现对JNI调用的两个方法。在网上的资料中,由于调用的方法实现的都比较简单,(大多是打印字符串等)所以避开了JNI中最麻烦的部分,也是最关键的部分,参数的传递。由于Java和C的编码是不同的,所以传递的参数是要进行再处理,否则C程序是会对参数在编译过程中提出警告,例如;warning C4024: 'SmsSend' : different types for formal and actual parameter 2等。
Sms.c的程序如下:
- #include "sms.h"
- #include "com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)
- {
- return SmsInit();
- }
-
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)
- {
- char * pSmscontent ;
- //jsize theArrayLengthJ = (*env)->GetArrayLength(env,mobileno);
- jbyte * arrayBody = (*env)->GetByteArrayElements(env,mobileno,0);
- char * pMobileNo = (char *)arrayBody;
- printf("[%s]\n ", pMobileNo);
- //jsize size = (*env)->GetArrayLength(env,smscontent);
- arrayBody = (*env)->GetByteArrayElements(env,smscontent,0);
- pSmscontent = (char *)arrayBody;
- printf("<%s>\n", pSmscontent);
- return SmsSend(pMobileNo,pSmscontent);
- }
对于C或C++,在程序上是会有稍微的不同,这可以由读者对其进行适当的修改。这里要注意的是GetArrayLength,GetByteArrayElements等这些JNI中已经包含的方法,这些方法是专门对转换参数类型而提供的。具体的方法有很多,在下一篇中会做专门的介绍。
在完成了上述的文件后,可以对sms.c进行编译,生成.dll文件(建议在release中编译,这样动态链接库的容积会比较小!)
完成.dll文件的编译后,就可以在Java中调用C程序中的方法了。例如文件test.java
- public class test {
- public test() {
- }
- public static void main(String[] args) {
- byte[] mobileno = {
- 0x31, 0x33, 0x36, 0x36, 0x31, 0x36, 0x33, 0x30, 0x36, 0x36, 0x37, 0x00};
- String smscontentemp = "早上好";
- byte[] temp = {0};
- try {
- byte[] smscontentdb = smscontentemp.getBytes("gbk");
- byte[] smscontent = new byte[smscontentdb.length + temp.length];
- System.arraycopy(smscontentdb, 0, smscontent, 0, smscontentdb.length);
- System.arraycopy(temp, 0, smscontent, smscontentdb.length, temp.length);
- SendSMS sendSMS = new SendSMS();
- sendSMS.SmsInit();
- if (sendSMS.SmsSend(mobileno, smscontent) >= 0) {
- System.out.println("chenggong !");
- }
- else {
- System.out.println("shibai !");
- }
- }catch (Exception ex) {}
- }
- }
在这个文件中要注意的有一点,就是在传递字节数组到C程序中时,最后的结尾一定要以0结束。这是一个偷懒的做法,不过是个有效的做法。因为大多数情况下,接口是由第三方提供的。所以我们一般是不知道在C的方法里,具体是怎么处理参数的。而C又是要求数组是有长度。所以,在Java中,如果你不想写程序传数组的长度,那么在数组中以0结尾就是最方便的方法了。当然,如果有更好的方法也希望大家提出。
到这里,一个完整的Java通过JNI调用动态链接库的程序就完成了。实际上也不是很复杂。只要多注意一下细节,是很容易得出来的。