每个编程语言都有其适用的范围,当人们需要结合不同的生态去完成一些功能时,就会遇到不同语言通信的问题,本篇文章我们结合一个具体示例,展示如何通过FFI语言交互接口实现在Java中调用Python。
Why
- 主要有下面两种原因:
- 使用方式:例如采用Java开发的平台希望给用户提供易用的Python接口
- 生态集成:例如将Java中的分布式能力和Python的AI生态相结合
How
- 现如今有如下的解决方案:
- IPC方案: 比如Py4J
- 问题:因为涉及到进程间通信以及序列化,所以会有性能问题
- Python运行在JVM的方案:比如Jython
- 问题:因为CPython现在是主流,所以会有兼容性问题
- FFI(Foreign Function Interface): 比如Pemja

Prepare
- 首先需要安装以下环境,如遇到安装问题可以和ChatGPT聊一下~
- 编程语言
- Java: 1.8
- Python: 3.9
- C:c99
- 编译与链接
由于笔者开发环境采用MacOS + x86平台,所以以下教程只对此平台有效,后续再补上其他环境的相应命令
Learn By Doing
目标:Java传入日期参数, 由Python返回星期几
- 编写主体Java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package cn.syntomic.ffi;
public class FFIDemo {
static { System.load(System.getenv("LIBPYTHON")); System.load(String.format("%s/FFIDemo.dylib", System.getProperty("user.dir"))); }
public static void main(String[] args) { System.out.println(new FFIDemo().dayOfWeek("1994-05-05")); }
private native int dayOfWeek(String date); }
|
- 可以看出我们需要首先导入两个共享库
- 运行Python所需的libpython库: 可以利用find-libpython得出
- 本地方法实现后的动态FFIDemo库:之后会介绍如何编译生成
- 生成Header文件
1
| javac -h src/main/c/cn/syntomic/ffi/include src/main/java/cn/syntomic/ffi/FFIDemo.java
|
1 2
| JNIEXPORT jint JNICALL Java_cn_syntomic_ffi_FFIDemo_dayOfWeek (JNIEnv *, jobject, jstring);
|
- 方法名: 由Java包名+类名+方法名组成
- 方法参数
JNIEnv
: 通过这个指针可以从运行的JVM中访问所需的类、对象、字段和方法jobject
: 方法所属于的Java对象jstring
: C JNI类型,详细对应可见JNI Types
- Python模块实现: 直接调用Python函数实现
1 2 3 4
| from datetime import datetime
def day_of_week(date): return datetime.strptime(date, "%Y-%d-%m").weekday() + 1
|
一点数学:如果要直接去计算星期几,可以利用数论中Zeller公式
w=y+[y/4]+[c/4]−2c+[26(m+1)/10]+d−1
所以1994-05-05
这个日期的就是星期四:
4=(94+[94/4]+[19/4]−2∗19+[26∗(5+1)/10]+5−1)mod7
- 将Python嵌入到C中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| JNIEXPORT jint JNICALL Java_cn_syntomic_ffi_FFIDemo_dayOfWeek (JNIEnv* env, jobject thisObject, jstring date) { int weekOfDay;
Py_Initialize();
const char* pName = "day_of_week"; PyRun_SimpleString("import sys"); PyRun_SimpleString("sys.path.append('./src/main/python/cn/syntomic/ffi')"); PyObject* pModule = PyImport_Import(PyUnicode_FromString(pName)); PyObject* pFunc = PyObject_GetAttrString(pModule, pName);
PyObject* pArgs = PyTuple_New(1); PyTuple_SetItem(pArgs, 0, PyUnicode_FromString((*env)->GetStringUTFChars(env, date, NULL)));
PyObject* pValue = PyObject_CallObject(pFunc, pArgs); weekOfDay = PyLong_AsLong(pValue);
if (Py_FinalizeEx() < 0) { exit(120); }
return weekOfDay; }
|
- 编译与运行
1 2 3 4 5 6 7 8 9 10 11
| export JAVA_HOME=${JAVA_HOME} export PYTHONHOME=${PYTHONHOME} export LIBPYTHON=${LIBPYTHON}
# 编译 gcc -c -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin -I${PYTHONHOME}/include/python3.9 -I${PYTHONHOME}/include src/main/c/cn/syntomic/ffi/cn_syntomic_ffi_FFIDemo.c -o FFIDemo.o # 生成动态链接库 gcc -dynamiclib -L${PYTHONHOME}/lib -lpython3.9 -ldl -o FFIDemo.dylib FFIDemo.o
javac -d target/classes/cn/syntomic/ffi src/main/java/cn/syntomic/ffi/FFIDemo.java java -cp target/classes cn.syntomic.ffi.FFIDemo
|
4
Summary
本篇文章我们以一个示例展示如何使用FFI,详细代码参考ffi_demo, 深入研究的话可以参考Pemja
Refer
- Guide to JNI
- Embedding Python in Another Application
- 基于 FFI 的 PyFlink 下一代 Python 运行时介绍