你有一个用 C 语言编写的核心功能库,你想在 Java 程序中调用它,SWIG 会帮你自动生成所有必要的“胶水代码”(Glue Code)和接口文件,让你可以像调用普通 Java 方法一样调用 C 函数。

(图片来源网络,侵删)
下面我将通过一个完整的、分步的例子来详细解释这个过程。
核心概念
- C 语言库: 这是你的核心业务逻辑,用 C 语言编写,一个做数学计算的库。
- SWIG 接口文件 (
.i文件): 这是一个告诉 SWIG 如何包装你的 C 库的配置文件,它定义了哪些 C 函数需要被暴露给 Java,以及如何映射数据类型和命名空间。 - SWIG 命令: 你运行这个命令,SWIG 会读取你的
.i文件和 C 源码,然后自动生成:- C 包装代码: 一组 C 文件(
.c),它们负责处理 Java 和 C 之间的数据类型转换和调用。 - Java JNI 代码: 一组 Java 文件(
.java),这些是你在 Java 端可以直接导入和使用的类。
- C 包装代码: 一组 C 文件(
- Java Native Interface (JNI): 这是 Java 官方提供的机制,允许 Java 代码和其他语言(主要是 C/C++)写的代码进行交互,SWIG 生成的底层代码就是基于 JNI 的,但它为你隐藏了所有复杂的 JNI 细节。
分步实践示例
假设我们有一个简单的 C 库,它提供一个函数,计算两个整数的和。
第 1 步:编写 C 语言库
创建你的 C 源文件,为了方便编译,通常我们会把函数声明放在头文件里。
calc.h

(图片来源网络,侵删)
#ifndef CALC_H #define CALC_H // 声明一个函数,计算两个整数的和 int add(int a, int b); #endif // CALC_H
calc.c
#include "calc.h"
// 函数的实现
int add(int a, int b) {
return a + b;
}
第 2 步:创建 SWIG 接口文件 (.i 文件)
这是最关键的一步,这个文件告诉 SWIG 如何处理你的 C 代码。
calc.i
// 1. 声明这是一个 SWIG 接口文件
%module calc
// 2. 包含 C 头文件,SWIG 会解析其中的内容
%{
#include "calc.h"
%}
// 3. 告诉 SWIG 解析 calc.h 文件中的所有内容
// 这样,calc.h 中声明的所有函数和变量都会被包装
%include "calc.h"
// 4. (可选) 类型映射示例
// SWIG 内置了大部分基本类型的映射,但这里展示如何自定义
// 将 C 的 int 映射到 Java 的 int,SWIG 默认会做好
文件解释:

(图片来源网络,侵删)
%module calc: 定义了生成的 Java 模块的名称,最终你会得到一个名为calc的 Java 包。- 这部分代码会被原封不动地复制到生成的 C 包装文件中,这里我们包含
calc.h,以便包装函数可以访问add函数的实现。 %include "calc.h": 这会指示 SWIG 解析calc.h文件,并将其中声明的add函数加入到包装列表中。
第 3 步:运行 SWIG 生成绑定代码
打开终端或命令提示符,执行以下命令:
swig -java -outdir java calc.i
命令参数解释:
swig: SWIG 的可执行文件。-java: 指定目标语言是 Java。-outdir java: 指定生成的 Java 文件(.java)输出的目录,这个目录java需要提前创建好。calc.i: 我们的 SWIG 接口文件。
执行后,你会得到以下新生成的文件:
-
在
java目录下:calcJNI.java: 这是核心的 JNI 接口文件,包含了所有与 C 代码交互的本地方法声明(如Java_calcJNI_add)。calc.java: 这是提供给用户使用的 Java 类,它包含了add方法的 Java 版本声明,内部会调用calcJNI中的方法。calcConstants.java: (如果适用) 包含常量定义。
-
在当前目录下:
calc_wrap.c: 这是 C 语言的包装代码,它实现了calcJNI.java中声明的所有 C 函数,负责数据转换和调用原始的add函数。calc.java: (可选,取决于 SWIG 版本和配置) 一个顶层 Java 模块文件。
第 4 步:编译 C 代码为动态链接库
为了让 Java 能够调用,我们需要将原始的 C 代码 (calc.c) 和 SWIG 生成的包装代码 (calc_wrap.c) 一起编译成一个动态链接库(在 Windows 上是 .dll,在 Linux 上是 .so,在 macOS 上是 .dylib)。
你需要使用 C 编译器,GCC。
在 Linux/macOS 上:
# 创建一个存放 .so 文件的目录
mkdir -p lib
# 编译命令
gcc -fPIC -shared -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux \
calc.c calc_wrap.c -o lib/libcalc.so
在 Windows 上 (使用 MinGW/g++):
# 创建一个存放 .dll 文件的目录
mkdir -p lib
# 编译命令
gcc -IC:\path\to\jdk\include -IC:\path\to\jdk\include\win32 \
-shared calc.c calc_wrap.c -o lib\calc.dll
编译参数解释:
-fPIC: 生成位置无关的代码,这是创建共享库所必需的。-shared: 生成一个共享库(动态链接库)。-I<path>: 指定头文件搜索路径,你需要包含 JDK 的include目录,以及对应操作系统的子目录(如linux,win32)。calc.c calc_wrap.c: 要编译的源文件列表。-o lib/libcalc.so: 指定输出的库文件名和路径。
第 5 步:在 Java 中调用
一切准备就绪,我们可以在 Java 代码中使用这个 C 函数了。
JavaTest.java
// 导入 SWIG 生成的 Java 类
import calc.*;
public class JavaTest {
public static void main(String[] args) {
// 直接调用 add 方法,就像调用一个普通的 Java 方法一样
int result = calc.add(10, 25);
System.out.println("从 C 库中计算 10 + 25 的结果是: " + result);
}
}
第 6 步:编译并运行 Java 程序
-
编译 Java 代码: 确保你的
CLASSPATH包含了生成 Java 文件的目录 (java)。# 在 Linux/macOS 上 javac -cp java JavaTest.java
-
运行 Java 程序: 运行时,必须告诉 JVM 你的动态链接库在哪里,使用
-Djava.library.path参数。# 在 Linux/macOS 上 java -Djava.library.path=./lib -cp .:java JavaTest
运行结果:
从 C 库中计算 10 + 25 的结果是: 35
总结与关键点
- 角色定位: SWIG 不是代码翻译器,而是跨语言接口生成器。
- 核心输入: C/C++ 源码 + SWIG 接口文件 (
.i)。 - 核心输出: 高级语言绑定代码 (Java
.java文件) + C 包装代码 (.c文件)。 - 最终产物: 一个可被 Java 加载的动态链接库 (
.so/.dll)。 - 运行时: Java 程序通过
System.loadLibrary("库名")或-Djava.library.path来加载这个动态库,从而调用 C 代码。 - 优势: 对于大型 C/C++ 库,手动编写 JNI 代码非常繁琐且容易出错,SWIG 自动化了这一过程,大大提高了开发效率和可靠性。
- 复杂性: 对于非常复杂的 C++ 代码(如模板、复杂的类继承、异常处理等),SWIG 的配置可能会变得复杂,需要深入了解 SWIG 的高级特性。
通过以上步骤,你就成功地将一个 C 函数集成到了 Java 应用程序中,这个过程可以轻松扩展到包装整个 C 库中的数百个函数和复杂的数据结构。
