Java调用本地方法:原理、实践与注意事项
立即解锁
发布时间: 2025-08-18 00:08:31 阅读量: 1 订阅数: 9 

### Java调用本地方法:原理、实践与注意事项
#### 1. Java与本地代码的选择考量
Java编程语言在很多方面都优于C或C++等语言,即使是针对特定平台的应用程序。Java的优势主要体现在以下几点:
- **代码质量**:使用Java编程语言更有可能编写出无错误的代码。
- **多线程编程**:在Java中进行多线程编程可能比在大多数其他语言中更容易。
- **网络编程**:Java的网络编程代码编写轻松。
不过,在某些情况下,使用其他语言编写的代码(通常称为本地代码)可能是更好的选择,主要有以下三个原因:
1. **已有代码复用**:你已经有大量经过测试和调试的代码,将这些代码移植到Java语言会很耗时,并且移植后的代码还需要重新测试和调试。
2. **系统特性访问**:应用程序需要访问系统特性或设备,使用Java技术可能会很麻烦,甚至无法实现。
3. **代码性能优化**:代码的执行速度至关重要,例如任务具有时间敏感性,或者代码被频繁使用,优化它能带来显著的性能提升。但实际上,通过即时编译(JIT),用Java编写的密集计算代码并不比编译后的C代码慢多少。
如果处于上述三种情况之一,从Java程序中调用本地代码是有意义的。但需要注意,使用本地代码会受到一些限制,例如只能用于应用程序而不是小程序,并且本地代码库必须存在于客户端机器上,并且要与客户端机器的架构兼容。
同时,使用本地方法也有一些缺点:
- **失去可移植性**:即使将程序作为应用程序分发,也必须为每个要支持的平台提供单独的本地方法库,还需要向用户说明如何安装这些库。
- **安全问题**:用户可能不信任使用本地方法库的代码,因为本地库可能不如Java代码安全,特别是用C或C++编写的本地库,容易因无效指针使用而破坏内存,导致Java虚拟机崩溃、安全受损或操作系统故障。
因此,建议仅在万不得已时使用本地代码。如果必须访问设备,可以考虑将本地方法作为临时解决方案,最终将代码移植到Java语言。如果担心效率问题,可以对Java平台实现进行基准测试,在大多数情况下,使用即时编译器的速度已经足够。
#### 2. 从Java调用C函数
假设你有一个喜欢的C函数,不想在Java中重新实现它,以`printf`函数为例,要在Java程序中调用它,可以按以下步骤进行:
##### 2.1 声明本地方法
在Java中,使用`native`关键字声明本地方法,例如:
```java
public class Printf {
public native String printf(String s);
}
```
虽然这个类可以编译,但在运行时会抛出`UnsatisfiedLinkError`异常,因为虚拟机不知道如何找到`printf`函数。要解决这个问题,需要在JDK下完成以下三个步骤:
1. **生成C存根**:生成一个C存根函数,用于将Java平台的调用转换为实际的C函数调用。存根函数从虚拟机栈中获取参数信息,并将其传递给编译后的C函数。
2. **创建共享库**:创建一个特殊的共享库,并从中导出存根函数。
3. **加载共享库**:使用`System.loadLibrary`方法告诉Java运行时环境加载步骤2中创建的库。
##### 2.2 以`printf`函数为例的实践
我们先从一个简单的例子开始,调用本地方法打印消息“Hello, Native World!”。
首先,在Java类中声明本地方法:
```java
class HelloNative {
public native static void greeting();
// ...
}
```
注意,这里将本地方法声明为静态方法,本地方法可以是静态的也可以是非静态的,此方法不接受任何参数。
然后,编写对应的C函数。C函数的命名必须符合Java运行时环境的要求,规则如下:
1. 使用完整的Java方法名,如`HelloNative.greeting`。如果类在包中,则前缀包名,如`com.horstmann.HelloNative.greeting`。
2. 将每个点替换为下划线,并添加前缀`Java_`,例如`Java_HelloNative_greeting`。
3. 如果类名包含非ASCII字母或数字的字符(如`_`、`$`或Unicode字符代码大于`\u007F`),则将其替换为`_0xxxx`,其中`xxxx`是字符的Unicode值的四位十六进制序列。
实际上,我们可以使用`javah`工具自动生成函数名。具体步骤如下:
1. 编译Java源文件:
```sh
javac HelloNative.java
```
2. 调用`javah`工具生成C头文件:
```sh
javah HelloNative
```
生成的`HelloNative.h`文件内容如下:
```c
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloNative */
#ifndef _Included_HelloNative
#define _Included_HelloNative
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloNative
* Method: greeting
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloNative_greeting
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
```
接下来,将函数原型从头文件复制到源文件中,并实现函数:
```c
#include "HelloNative.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_HelloNative_greeting(JNIEnv* env, jclass cl) {
printf("Hello world!\n");
}
```
在这个简单的函数中,我们忽略了`env`和`cl`参数。
##### 2.3 编译C代码为动态库
编译C代码为动态库的具体命令取决于你的编译器:
- **Linux(Gnu C编译器)**:
```sh
gcc -c -fPIC -I/usr/local/jdk/include/ -I/jdk/include/linux HelloNative.c
gcc -shared -o libHelloNative.so HelloNative.o
```
- **Solaris(Sun编译器)**:
```sh
cc -G -I/usr/local/jdk/include -I/usr/local/jdk/include/solaris HelloNative.c -o libHelloNative.so
```
- **Windows(Microsoft C++编译器)**:
```sh
cl -Ic:\jdk\include -Ic:\jdk\include\win32 -LD HelloNative.c -FeHelloNative.dll
```
也可以使用Cygwin编程环境,命令如下:
```sh
gcc -c -D__int64="long long" -Ic:/jdk/include/ -Ic:/jdk/include/win32 HelloNative.c
dllwrap --add-stdcall-alias -o HelloNative.dll HelloNative.o
```
##### 2.4 加载动态库
最后,在Java类中使用静态初始化块调用`System.loadLibrary`方法加载动态库:
```java
class HelloNative {
public static native void greeting();
static {
System.loadLibrary("HelloNative");
}
}
```
编写测试程序:
```java
class HelloNativeTest {
public static void main(String[] args) {
HelloNative.greeting();
}
}
```
编译并运行这个程序,终端窗口将显示消息“Hello, Native World!”。
#### 3. 数值参数和返回值
在C和Java之间传递数字时,需要了解它们的数据类型对应关系。由于C的`int`和`long`类型的实现是平台相关的,而Java的`int`始终是32位整数,因此Java本地接口定义了`jint`、`jlong`等类型。具体的类型对应关系如下表所示:
| Java编程语言 | C编程语言 | 字节数 |
| ---- | ---- | ---- |
| boolean | jboolean | 1 |
| byte | jbyte | 1 |
| char | jchar | 2 |
| short | jshort | 2 |
| int | jint | 4 |
| long | jlong | 8 |
| float | jfloat | 4 |
| double | jdouble | 8 |
以`printf`函数格式化数字为例,Java库没有优雅的方式来格式化打印浮点数,我们可以通过调用本地方法中的`printf`函数来实现相同的功能。
首先,在Java类中声明本地方法:
```java
class Printf1 {
public static native int print(int width, int precision,
```
0
0
复制全文
相关推荐









