在Java程序中调用C函数--打印"HelloWorld"

简介: 源地址:http://java.sun.com/docs/books/jni/html/start.html#26346 本文是将书中的第二章单独抽出来,红色部分为译者注.

源地址:http://java.sun.com/docs/books/jni/html/start.html#26346

本文是将书中的第二章单独抽出来,红色部分为译者注.

1.概述

这个打印的过程是用JDK或Java 2 SDK写一个简单的Java程序,程序会调用一个C函数打印"HelloWorld".这个过程将包括以下步骤:

  1. 创建一个Java类(HelloWorld.java),以及定义一个native方法.
  2. 使用javac去编译这个HelloWorld源文件,生成HelloWorld.class.
  3. 使用javah –jni 来生成C的头文件(HelloWorld.h),这个头文件包含native方法的实现原型.javah是JDK或者Java 2 SDK提供的一个工具.
  4. 编写C的代码(HelloWorld.c),实现这个native方法.
  5. 把这个C的实现编译到Java native库中,创建HelloWorld.dll或者libHello-World.so.
  6. 使用Java运行时解释器运行HelloWorld程序.无论是类文件(HelloWorld.class),还是native库(HelloWorld.dll或者libHelloWorld.so),都是在运行时加载.

剩下的部分将详细介绍这些步骤:

 

2.Native方法声明

用Java语言写下面的程序.该程序定义的类名是HelloWorld,包含一个native方法,print.

class HelloWorld {
     private native void print();
     public static void main(String[] args) {
         new HelloWorld().print();
     }
     static {
         System.loadLibrary("HelloWorld");
     }
 }

该类不在任何包中

HelloWorld的类定义用一个print的本地方法开始.其次就是main方法,它实例化了一个HelloWorld,并且调用这个实例的print的本地方法.类定义的最后部分是一个静态代码块,它加载了包含print本地方法实现的native库.

在native方法(例如print)的定义和常规的Java语言方法的定义中有这样的两个差异.一个本地方法声明必须包含native修饰符,native修饰符表明,该方法以另一种语言实现.此外,本地方法的定义是以分号结束,因为在类本身中,没有实现本地方法,我们将在一个独立的C文件中实现这个print方法.

在调用本地方法print之前,native库必须要加载print的实现.在这种情况下,我们在HelloWorld类的静态代码块中加载native库,Java虚拟机会自动运行静态代码块,并且在调用任何HelloWorld类的方法之前,以确保native库在print本地方法调用之前就已经加载了.

我们定义一个main方法能去运行HelloWorld,HelloWorld.main调用本地方法打印,就像是调用常规方法一样.

System.loadLibrary需要一个库名,然后定位到符合这个名字的native库中,并加载到应用程序的native库.我们将在本书的后面讨论具体的加载过程.现在只需要简单地记住,为了System.loadLibrary("HelloWorld")成功,我们需要创建一个native库,用来调用Win32上的HelloWorld.dll或者在Solaris上的libHelloWorld.so.

3.编译HelloWorld

在你定义HelloWorld类之后,保存源代码为文件HelloWorld.java,然后用javac 编译器编译源码:

javac HelloWorld.java

这个命令将在当前目录生成一个HelloWorld.class文件.

4.创建Native方法的头文件

下一步,我们将使用javah工具来生成一个JNI风格(JNI-style)的头文件.当在C 上面实现本地方法时很有用.你可以在HelloWorld.class上运行javah,就像下面一样:

javah –jni HelloWorld

生成的头文件的名字是在类名后面追加一个".h".上面显示的命令将生成一个文件名为HelloWorld.h的文件.我们将不在这里列出生成的头文件.这个头文件最重要的部分Java_HelloWorld_print的函数原型,这是C 函数实现的HelloWorld.print方法:

JNIEXPORT void JNICALL

Java_HelloWorld_print (JNIEnv *, jobject);

现在忽略JNIEXPORT和JNICALL宏.你可能已经注意到,本地方法C 的实现接收了两个参数,然而实际上对应的native方法声明不接收任何参数.对于每一个本地方法实现,第一个参数都是JNIEnv接口指针(JNIEnv interface pointer),第二个参数是一个指向HelloWorld对象本身(有点像C++的"this"指针)的引用.我们将在本书的后面讨论如何使用JNIEnv接口指针和jobject.但是现在这个简单的例子就忽略这两个参数.

5.写本地方法的实现

用javah生成的JNI风格的头文件能够帮助你去完成C/C++的本地方法实现.你写的函数必须遵循生成的头文件的原型.你能在C 文件HelloWorld.c中实现HelloWorld.print方法,就像下面一样:

#include <jni.h>
#include <stdio.h>
#include "HelloWorld.h"
 
JNIEXPORT void JNICALL 
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
    printf("Hello World!/n");
    return;
}

该本地方法的实现很简单,它使用了printf函数来显示字符串"HelloWorld!",然后返回.正如前面提到,两个参数,JNIEnv指针和指向对象的引用,都被忽略了.

这个C 程序包含了三个头文件:

  • jni.h--这个头文件提供了本地代码需要调用JNI函数的信息.当写本地方法的时候,你必须在你的C 或者 C++源文件中,永远包含这个文件.
  • stdio.h--以上代码段也包含了stdio.h ,因为它使用了printf函数.
  • HelloWorld.h--这是你用javah生成的头文件,它包含了Java_HelloWorld_print的函数原型.

6.编译C 源码和创建native库

请记住,当你在HelloWorld.java创建HelloWorld.class的时候,你包含了一行加载native库的代码在你的程序里面:

System.loadLibrary("HelloWorld");

现在所有必要的C 代码已经写完了,你需要去编译HelloWorld.c和创建这个native库.

不同的操作系统支持不同的方法来建立native库,在Solaris上面,下面的命令生成一个共享库调用libHelloWorld.so:

cc -G -I/java/include -I/java/include/solaris
HelloWorld.c -o libHelloWorld.so

-G选项指示这个C 编译器去生成一个共享库,而不是一个普通的Solaris的可执行文件.因为本书页面宽度的限制,我把一行分成了两行,你需要在一行输入命令,或者把命令放在脚本中.在Win32的系统上,下面的命令将使用Microsoft Visual C++ 编译器建立一个动态链接库(DLL)HelloWorld.dll:

cl -Ic:/java/include -Ic:/java/include/win32
-MD -LD HelloWorld.c -FeHelloWorld.dll

-MD选项将确保HelloWorld.dll已经连接到Win32的多线程C 库当中.-LD选项指示C 编译器去生成一个DLL,而不是一个普通的Win32可执行文件.当然,在Solaris和Win32,你都需要放到include路径中去,以反射你自己的安装配置.

-I<dir>把指定路径加到include的搜索路径中.
如果遇到"Cannot open include file:’jni.h’"的错误,则说明路径指定地有问题.有两种解决方案:
1.在你的JDK安装目录下有个include目录,把里面的jni.h和win32目录下的jawt_md.h和jni.md.h复制到你的C 编译器的include目录中(可能是vc/include).
2.指定正确的搜索路径.比如我的JDK安装在F:/Program Files/Java/jdk1.6.0_16/,那么上面的参数就改为:
-I"F:/Program Files/Java/jdk1.6.0_16/include" -I"F:/Program Files/Java/jdk1.6.0_16/include/win32"
如果目录中有空格要引起来.

7 运行程序

此时,你要准备好两个组件去运行这个程序.类文件(HelloWorld.class)调用本地方法,native库(HelloWorld.dll)实现这个本地方法.

因为HelloWorld类包含了它自己的main方法,你可以在Solaris 或者是Win32的操作系统上运行这个程序:

java HelloWorld

你应该会看到下列的输出:

Hello World!

设置好正确地native库路径对程序的运行是很重要的.native库路径是一个目录列表,是Java虚拟机装载native库时的搜索列表.如果你没有设置一个正确的native库路径,你就会看到类似于下列的错误:

java.lang.UnsatisfiedLinkError: no HelloWorld in library path
         at java.lang.Runtime.loadLibrary(Runtime.java)
         at java.lang.System.loadLibrary(System.java)
         at HelloWorld.main(HelloWorld.java)

要确保native库存在于native库路径的目录中.如果你是在Solaris上运行,LD_LIBRARY_PATH的环境变量是用来定义native库路径.请确保它包含的目录名称中包含了libHelloWorld.so 文件.如果libHelloWorld.so 文件在当前目录里面,你可以在标准shell(sh)或者KornShell(ksh)上发出两条命令去设置正确的LD_LIBRARY_PATH环境变量属性:

LD_LIBRARY_PATH=.
export LD_LIBRARY_PATH

这与在C shell(csh或者tcsh)中的下列命令等效:

setenv LD_LIBRARY_PATH .

如果你是在Windows95或者Windows NT 计算机上运行,设法确保HelloWorld.dll是在当前目录下,或者在PATH环境变量列出的目录中.

在Java 2 SDK 1.2版本中,你也可以在java命令行中像系统属性一样指定native库路径,如下所述:

java -Djava.library.path=. HelloWorld

-D 命令行选项是设置Java平台的系统属性,设置java.library.path属性成".",表示JVM要搜索当前目录下的native库.

 

 


 

 

如果本文有任何问题,请及时指出,以免对后来者产生不必要的困扰,不胜感激!

 

目录
相关文章
|
1月前
|
Java 流计算
利用java8 的 CompletableFuture 优化 Flink 程序
本文探讨了Flink使用avatorscript脚本语言时遇到的性能瓶颈,并通过CompletableFuture优化代码,显著提升了Flink的QPS。文中详细介绍了avatorscript的使用方法,包括自定义函数、从Map中取值、使用Java工具类及AviatorScript函数等,帮助读者更好地理解和应用avatorscript。
利用java8 的 CompletableFuture 优化 Flink 程序
|
2月前
|
XML 存储 JSON
Java程序部署
Java程序部署
|
22天前
|
Java Maven 数据安全/隐私保护
如何实现Java打包程序的加密代码混淆,避免被反编译?
【10月更文挑战第15天】如何实现Java打包程序的加密代码混淆,避免被反编译?
34 2
|
24天前
|
安全 Java Linux
java程序设置开机自启
java程序设置开机自启
|
28天前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
21 1
|
28天前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
33 1
|
1月前
|
Java 编译器 C语言
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
19 3
|
1月前
|
存储 Java 数据处理
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
29 1
|
2月前
|
消息中间件 分布式计算 Java
Linux环境下 java程序提交spark任务到Yarn报错
Linux环境下 java程序提交spark任务到Yarn报错
40 5
|
2月前
|
Java 编译器 数据库连接
探索Java中的异常处理:提升程序的鲁棒性
【9月更文挑战第25天】在Java的世界里,异常是那些不请自来、令人头疼的“客人”。它们悄无声息地潜入我们的代码,一旦出现,便可能导致程序崩溃或行为异常。但是,如果能够妥善管理这些异常,我们就能将潜在的灾难转变为增强程序鲁棒性和用户体验的机会。本文将通过深入浅出的方式,带领读者理解Java异常处理的重要性,并提供实用的策略来优雅地处理这些意外情况。让我们一起学习如何在Java中捕捉、处理和预防异常,确保我们的程序即使在面对不可预见的错误时也能保持稳健运行。
下一篇
无影云桌面