Python跨语言调用其他语言方法

  • 通过FFI(外部程序接口)调用,Java中称JNI,将Rust/Go/C/C++按C类型编译称动态连接库,Python中使用ctypes加载并调用
  • 通过进程间通信IPC调用(如使用socket)
  • 通过网络接口调用,如RPC,HTTP接口等
  • 通过本地虚拟机/解释器/代理调用,如Python调用Java, 执行JavaScript等
  • 通过WASM接口调用

Rust编写C动态连接库

Rust按C类型构建为动态连接库,Python中使用ctypes,加载动态连接库并调用

  1. 使用cargo新建rust库
cargo new calc --lib
  1. 打开calc项目,修改Cargo.toml,增加lib选项
    Cargo.toml
...

[lib]  
crate-type = ["dylib"]
  1. 在src/lib.rs中编写被调用函数
    src/lib.rs
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

#[no_mangle]  
pub extern "C" fn add_int(a: i32, b: i32) -> i32 {  
    a + b  
}


#[no_mangle]  
pub extern "C" fn add_float(a: f32, b: f32) -> f32 {  
    a + b  
}


#[no_mangle]  
pub extern "C" fn add_str<'a>(a: *const c_char, b: *const c_char) -> *mut c_char {  
    let a_cstr = unsafe {CStr::from_ptr(a)};
    let a_str = a_cstr.to_str().unwrap();
    let b_cstr = unsafe {CStr::from_ptr(b)};
    let b_str = b_cstr.to_str().unwrap();
    let c_string = String::from(a_str) + &String::from(b_str);
    let c = CString::new(c_string).unwrap().into_raw();
    c
}

其中

  • #[no_mangle] 表示禁止Rust编译器修改原函数名称
  • pūb extern "C" 指将函数导出为C类型外部可调用函数
  • 由于Rust中的char和C中的不太一样,变换方法为 *c_char -> CStr -> &str -> String -> CString -> *c_char
  1. 编译为动态链接库
    在项目根目录执行
cargo build --release

会在项目target/release目录,根据不同的操作系统生成 libcalc.dylib 或 libcalc.dylib 或 libcalc.dll动态链接库

  1. 使用Python调用动态链接库方法
    call_rust.py
import ctypes


lib = ctypes.CDLL("./target/release/libcalc.dylib")
print(lib.add_int(3, 5))


lib.add_float.argtypes = [ctypes.c_float, ctypes.c_float]
lib.add_float.restype = ctypes.c_float
print(lib.add_float(3.2, 5.3))

lib.add_str.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
lib.add_str.restype = ctypes.c_char_p
print(lib.add_str(b'Hello', b'World'))

运行输出如下:

8
8.5
b'HelloWorld'

参考