欢迎光临
我们一直在努力

C链接库,联动 Rust、Golang、Python

基础概念铺垫

1. 链接库是什么?

写代码时很多通用功能(加密、网络、数学计算)不用每次重写,把一堆函数、变量、类打包成独立二进制文件,这个文件就是链接库。
程序编译时分两步:

  • 编译:源代码 → 机器码 目标文件 .o / .obj
  • 链接:把 目标文件 + 依赖库 合并成最终可执行程序
  • 举栗子:
    C语言直观演示

    • 业务源码(两份业务代码)
      main.c(程序入口)

    #include "calc.h"
    int main() {
    int res = add(10, 20);
    return res;
    }

    • calc.c(业务工具函数)

    int add(int a, int b) {
    return a + b;
    }

    第一步:编译(源码 → 业务目标文件 .o)

    # 两份业务代码,分别编译成 .o
    gcc -c main.c -o main.o
    gcc -c calc.c -o calc.o

    现在 main.o + calc.o = 完整业务逻辑的机器码。

    第二步:链接(链接器合并 业务.o + 系统标准库libc)

    # 输入:main.o、calc.o(业务目标文件) + 默认链接系统C标准库shturl.
    gcc main.o calc.o -o app

    链接器做的事:

  • 读取 main.o、calc.o 里的业务机器码;
  • 去系统C标准库 libc 补全 printf、exit 这类系统函数地址;
  • 把所有代码、数据、符号表整合,生成能直接运行的 app 可执行程序。
  • 如果引入 第三方 静态库

    假设我们有自己封装的公共库 libmath.a,链接命令变成:

    gcc main.o calc.o -L. -lmath -o app

    输入依旧是:业务.o文件 + 第三方库 + 系统标准库。


    2. 两大库:静态库 vs 动态库

    类型核心原理优缺点系统后缀区分
    静态库 Static Lib 编译链接时,把库完整复制粘贴进最终程序 优点:运行不依赖外部文件,分发简单;缺点:程序体积巨大,更新库必须重新编译整个程序 Windows:.libLinux:.aMacOS:.a
    动态库 Shared Lib 编译只记录引用,运行时操作系统加载库文件,多个程序共享同一份库 优点:程序体积小,单独替换库文件就能更新逻辑;缺点:分发必须附带对应库,缺失会直接崩溃 Windows:.dllLinux:.soMacOS:.dylib

    3. 链接库 是不是专为 C/C++ 诞生?

    是的,根源就是C语言

  • 早年 汇编时 代没有 库 概念,
    C语言诞生, 后为了 代码复用、模块化,设计了 编译 + 链接 模型,
    静态库 .a 直接沿用Unix系统设计;
  • C++ 完全兼容C链接模型,扩展了类、重载,动态库引入符号导出机制;
  • 后面所有 高级语言(Rust/Go/Python/Java)的 库 交互,底层全部复用操作系统提供的C ABI二进制标准,也就是说:所有语言 跨语言 调用库,本质都是走 C兼容接口。
  • ABI (Application Bin Interface)

    关键知识点:ABI( 二进制应用接口 )
    不同语言内存布局、函数调用规则不一样,但 C语言 ABI 是 全操作系统 统一标准。
    所以 任何语言 想对外提供库、调用外部库,都必须封装一层 C兼容接口,不能直接用语言自身特色语法(Rust所有权、Go协程、Python对象都不能跨库传递)。

    两个核心场景

    每个语言分两块:

  • 【场景A】本语言打包生成静态库/动态库(给其他语言调用)
  • 【场景B】本语言调用外部C/C++静态/动态库
    附带可直接运行的完整示例代码,环境:Linux(最通用,Windows/Mac标注差异)
  • 前置:写一个基础C测试库(所有语言都会调用它)

    创建 mylib.c(通用底层库)

    #include <stdio.h>

    // C兼容导出函数,计算两数相加
    int add(int a, int b) {
    return a + b;
    }

    // 打印字符串
    void hello(const char* msg) {
    printf("C lib print: %s\\n", msg);
    }

    编译C 静态库 .a

    # 1. 编译 目标文件
    gcc -c mylib.c -o mylib.o

    # 2. 打包 静态库
    ar rcs libmylib.a mylib.o
    # 产物:libmylib.a 静态库

    ar = archive 归档工具,是 Unix/Linux 自带老牌工具,本质是一个 二进制压缩打包工具,专门用来把一堆 .o 目标文件打包成静态库 .a。
    .a 文件内部就是一堆 .o 的集合,只是套了一层归档索引,方便链接器快速查找函数符号。

    编译C 动态库 .so

    # -fPIC 生成位置无关代码(动态库必须)
    gcc -shared -fPIC mylib.c -o libmylib.so
    # 产物:libmylib.so 动态库


    第一部分:Rust 操作 静态/动态库

    Rust 完全兼容 C ABI,支持:导出C库、调用C库

    1. Rust 打包生成 动态库 / 静态库(给Python/Go/C调用)

    步骤1:新建rust项目

    cargo new rust_lib && cd rust_lib

    步骤2:修改 Cargo.toml 配置输出库

    [lib]
    # 同时编译 静态库 + 动态库
    crate-type = ["cdylib", "staticlib"]

    # cdylib:C兼容动态库(输出.so/.dll/.dylib)
    # staticlib:C兼容静态库(输出.a/.lib)

    步骤3:src/lib.rs 代码(必须 extern “C” 导出C接口)

    // extern "C" 强制使用C调用ABI,跨语言必备
    #[no_mangle] // 关闭Rust名字混淆,函数名和C一致
    pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
    a + b
    }

    #[no_mangle]
    pub extern "C" fn rust_hello(msg: *const u8) {
    // 裸指针转rust字符串(unsafe操作跨语言裸指针)
    unsafe {
    let s = std::ffi::CStr::from_ptr(msg as *const i8);
    println!("Rust lib output: {}", s.to_str().unwrap());
    }
    }

    步骤4:编译

    cargo build –release
    # target/release/产物
    # Linux/Mac:
    # librust_lib.so 动态库
    # librust_lib.a 静态库
    # Windows:rust_lib.dll + rust_lib.lib

    2. Rust 调用 外部 C 静态库/动态库(上面的 libmylib)

    前置准备(统一环境 Linux x86_64)

  • 先编译出C动态库 libmylib.so,放在项目根目录
  • # mylib.c
    #include <stdio.h>
    int add(int a, int b) { return a + b; }
    void hello(const char* msg) { printf("C lib: %s\\n", msg); }

    编译生成so:

    gcc -shared -fPIC mylib.c -o libmylib.so

  • 新建rust项目
  • cargo new rust_call_c && cd rust_call_c
    # 把 libmylib.so 复制到 rust_call_c 项目根目录

    项目目录结构(两套方案通用)

    rust_call_c/
    ├── libmylib.so # 我们自己的C动态库
    ├── Cargo.toml
    ├── src/
    │ └── main.rs # Rust调用代码
    └── build.rs # 方案1专用构建脚本


    方案1:build.rs 构建脚本(工业标准,推荐,功能最强)

    前置知识
    基本介绍
    • build.rs 不是普通业务代码,是Cargo的构建通信子进程 指定的 文件。
      build.rs 是固定专属文件名,不能修改,位置也有严格规则,必须在 项目根目录下, 和 Cargo.toml 同级。

    • Cargo 设计时约定:项目根目录 build.rs 作为默认 构建 前置 脚本。

    • Cargo 会单独编译 build.rs 为临时可执行文件,在编译你的项目源码之前运行,它和 src/main.rs 是完全隔离的两套代码,互不共享作用域。

    • 一个包(项目)只能有一个构建脚本:要么根目录默认 build.rs,要么 Cargo.toml build= 指定的单个 rs文件,不能同时存在多个构建脚本。

    • 阻塞执行: 如果 build.rs 中写一个 1 个小时的定时器, 那么 build.rs 文件的代码逻辑 执行不完, cargo run 后续的执行逻辑 需要一直等待

    文本协议通信

    Cargo 和 build.rs 之间没有复杂函数/结构体交互,约定了一套纯文本规则:

    • 通信通道:子进程标准输出 stdout
    • 指令标识:行开头 必须是 cargo:
    • 格式规范:cargo:指令名=参数

    // build.rs
    fn main() {
    // 带 cargo: 前缀 → Cargo 捕获并解析为构建指令
    println!("cargo:rustc-link-search=.");
    println!("cargo:rustc-link-lib=mylib");

    // 不带前缀 → 只是普通日志,Cargo忽略,仅打印到控制台
    println!("正在配置C库链接参数");
    }

    build.rs 不需要引入任何特殊库、调用任何特殊函数,只需要向控制台打印 约定格式的字符串,就能和Cargo通信。

    每次都执行吗?

    一句话总结:
    默认不会每次 cargo run 都执行 build.rs;只有源码、监听文件、编译环境变动,或清理缓存后,才会前置运行;无变动时直接复用缓存跳过。

    • 只要满足下面 任意一条,执行 cargo run/build 时就会完整运行 build.rs:

    • build.rs 自身源码被修改;
    • build.rs 里通过 println!("cargo:rerun-if-changed=xxx") 声明的文件/文件夹发生改动; rerun-if-changed 是为了增量构建提速:
    • 构建环境发生变化:Rust版本、编译target、feature开关、传入的RUSTFLAGS变更;
    • 上一次 build.rs 执行异常崩溃(panic、非0退出码),下次强制重跑;
    • 执行 cargo clean 清空缓存后,第一次构建必然重跑。
    • 下面的 全部条件 同时满足 才会跳过 不执行 build.rs, Cargo 直接复用上次 build.rs 输出的编译参数:

    • build.rs 代码无改动;
    • 所有 rerun-if-changed 监控的文件/目录完全没变化;
    • 编译环境(target、feature、编译参数、rust版本)和上次一致;
    • 上次 build.rs 正常成功退出。
    整体流程
  • 你执行 cargo build / cargo run
  • Cargo 自动检测项目根目录存在 build.rs
  • Cargo 单独启动一个子进程,编译、运行这个 build.rs
  • Cargo 全程实时监听这个子进程的标准输出 stdout(就是 println! 打印出来的文字)
  • 只要输出行匹配固定前缀 cargo:,Cargo 就解析这行指令,转化成编译参数传给 rustc
  • 普通无 cargo: 前缀的打印,只会作为日志输出,不会参与编译配置
  • 步骤1:在项目根目录创建 build.rs

    文件名必须固定 build.rs,Cargo会自动执行这个文件,专门用来处理编译链接前置逻辑。

    // build.rs
    fn main() {
    // 指令1:告诉rustc/链接器去哪里找库文件
    // -L . 等价 gcc -L . :当前项目根目录搜索库
    println!("cargo:rustc-link-search=.");

    // 指令2:指定要链接的库名
    // -lmylib 等价 gcc -lmylib :自动匹配 libmylib.so / libmylib.a
    println!("cargo:rustc-link-lib=mylib");

    // 指令3: 设置rpath,程序运行时自动在自身目录查找so库
    println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");

    // 可选扩展:如果库不在根目录,比如 ./lib 文件夹
    // println!("cargo:rustc-link-search=./lib");
    }

    逐行解释 build.rs 语法规则
    所有 println!("cargo:xxx=yyy") 是Cargo内置特殊指令:

  • cargo:rustc-link-search=路径
    作用:传递 -L 参数给链接器,告诉链接器「去这个文件夹找 .so/.a 库文件」
  • cargo:rustc-link-lib=库名
    作用:传递 -l 参数给链接器,链接 libxxx.so 只需要写 xxx,不用加 lib 和后缀
  • cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN
    作用: 是给底层 ld 链接器传递自定义参数
    免除了 在 命令行 export LD_LIBRARY_PATH=. 的这个命令的输入
  • 步骤2:编写 src/main.rs 调用C库代码

    // src/main.rs
    use std::ffi::{c_int, c_char};

    // 声明外部C ABI函数,和C代码签名严格对应
    extern "C" {
    // C: int add(int a, int b);
    fn add(a: c_int, b: c_int) -> c_int;
    // C: void hello(const char* msg);
    fn hello(msg: *const c_char);
    }

    fn main() {
    // 跨语言FFI调用必须包裹unsafe块(裸指针、外部函数不安全)
    unsafe {
    let a: c_int = 10;
    let b: c_int = 20;
    let sum = add(a, b);
    println!("调用C add(10,20) = {}", sum);

    // C字符串必须以\\0结尾,转成*const c_char指针
    let msg = b"Hello Rust Call C\\x00".as_ptr() as *const c_char;
    hello(msg);
    }
    }

    步骤3:运行、编译、打包完整命令
    3.1 开发调试运行

    Linux动态库特性:运行时操作系统需要找到 libmylib.so,只要 libmylib.so 在同一个文件夹,直接 就能跑

    # cargo自动执行build.rs,完成链接后运行程序
    cargo run

    输出结果:

    调用C add(10,20) = 30
    C lib: Hello Rust Call C

    3.2 打包发布二进制(release正式产物)

    # 编译优化版发布程序
    cargo build –release
    # 产物路径 target/release/rust_call_c

    发布分发注意(动态库依赖坑)

    生成的 rust_call_c 程序只是记录了依赖 libmylib.so,不会把库打包进程序。
    分发时必须同步附带 libmylib.so,运行前依旧要设置 LD_LIBRARY_PATH=.,否则报错找不到共享库。

    build.rs 额外扩展能力(生产常用)

  • 库放在子文件夹 ./libs
  • println!("cargo:rustc-link-search=./libs");

  • 强制静态链接 libmylib.a(如果同时存在.a和.so)
  • println!("cargo:rustc-link-lib=static=mylib");

  • 打印调试日志,看cargo执行过程
  • println!("cargo:warning=正在链接本地libmylib.so");


    方案2:仅 Cargo.toml 配置(极简场景,功能有限)

    步骤1:删除/移除 build.rs,只用Cargo.toml配置链接规则

    修改 Cargo.toml,新增 [links] 段配置

    [package]
    name = "rust_call_c"
    version = "0.1.0"
    edition = "2021"

    # 专门配置外部C库链接
    [links.mylib]
    # 等价 build.rs 的 cargo:rustc-link-search=.
    search-path = ["."]
    # 可选:如果需要额外链接参数
    # args = ["-ldl"]

    配置逐行解释

  • [links.mylib]
    mylib = 库名,对应 libmylib.so,和 -lmylib 完全对应
  • search-path = ["."]
    数组格式,可以填多个搜索目录,等价多个 -L 参数
    search-path = [".", "./libs"] 同时搜索根目录和libs文件夹
  • 步骤2:src/main.rs 代码完全不变,和方案1通用

    use std::ffi::{c_int, c_char};

    extern "C" {
    fn add(a: c_int, b: c_int) -> c_int;
    fn hello(msg: *const c_char);
    }

    fn main() {
    unsafe {
    let sum = add(10, 20);
    println!("结果: {}", sum);
    hello(b"Test Cargo.toml\\x00".as_ptr() as *const c_char);
    }
    }

    步骤3:运行、打包命令和方案1完全一致

    Cargo.toml 的 [links] 所有配置:只影响编译阶段,传递 -L、-l、链接参数给 rustc/ld;
    LD_LIBRARY_PATH:程序运行阶段由操作系统动态加载器读取,属于运行时环境变量,和编译配置完全无关;
    Cargo.toml 没有任何配置项可以把环境变量永久写入。
    所以要手动写一下

    export LD_LIBRARY_PATH=.
    cargo run
    # 发布打包
    cargo build –release

    两套方案核心对比

    维度方案1 build.rs方案2 Cargo.toml [links]
    适用场景 正式项目、跨平台、复杂库依赖 小型Demo、单一平台、无额外逻辑
    自定义逻辑 支持if分支、文件复制、打印警告、调用外部脚本 仅静态配置路径,无运行时逻辑
    静态/动态切换 支持 static= 强制静态链接 无法区分,链接器自动选
    可读性 完整代码流程,链接逻辑集中 配置分散,复杂依赖难维护

    LD_LIBRARY_PATH 作用(运行阶段,和编译无关)

    • 编译阶段:-L 只是告诉链接器去哪里找库的符号信息
    • 运行阶段:操作系统动态加载器需要找到真实 libmylib.so 文件
      export LD_LIBRARY_PATH=. = 临时告诉系统:当前目录优先搜索动态库

    打包后分发两种解决方案(解决找不到so问题)

    • 方案A:配套分发so文件(最简单)
      发布包结构:

    dist/
    ├── rust_call_c # release二进制
    └── libmylib.so # 依赖动态库

    运行脚本 run.sh

    #!/bin/bash
    export LD_LIBRARY_PATH=$(dirname "$0")
    ./rust_call_c

    • 方案B:编译时写入rpath(永久固化库路径,不用手动export)
      修改 build.rs,嵌入rpath,把库目录写进程序二进制内部

    fn main() {
    println!("cargo:rustc-link-search=.");
    println!("cargo:rustc-link-lib=mylib");
    // rpath=$ORIGIN:程序运行时,在自身所在目录寻找so
    println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
    }

    重新 cargo build –release,打包后直接运行,不需要手动设置LD_LIBRARY_PATH。


    第二部分:Go 操作静态/动态库

    Go 通过 cgo 实现和C库互通,也能导出C兼容动态库

    1. Go 打包生成 动态库(给Python/Rust/C调用)

    新建 go_lib.go

    package main

    import "C"
    import "fmt"

    //export go_add
    func go_add(a, b C.int) C.int {
    return a + b
    }

    //export go_hello
    func go_hello(msg *C.char) {
    fmt.Printf("Go lib print: %s\\n", C.GoString(msg))
    }

    func main() {} // 导出库必须空main函数

    编译生成 动态库 .so(Linux)

    # -buildmode=c-shared 生成C兼容动态库
    go build -o libgolib.so -buildmode=c-shared go_lib.go
    # 输出 libgolib.so 动态库

    生成 静态库(极少用,Go静态库兼容性差,一般推荐动态库)

    go build -o libgolib.a -buildmode=c-archive go_lib.go

  • 静态库会把Go整套运行机制塞进你的程序里
    Go的协程、垃圾回收、内存管理是一整套独立系统。静态链接时,这套东西会直接合并到Rust/C主程序。如果你链接2个Go静态库,程序里会出现两套独立的垃圾回收、内存管理器,互相打架,直接崩溃。
    动态库.so是独立文件,每个库单独一套运行环境,互不干扰。

  • 静态Go库会抢主程序的系统控制权
    静态打包后,Go会霸占程序的内存分配、信号报错处理逻辑,和Rust/C本身的内存、报错机制冲突,经常出现内存错乱、程序卡死;
    动态库只是运行时临时加载,不会篡改主程序底层全局逻辑。

  • 静态库对编译环境要求极度苛刻
    想用Go静态库,你的Rust/C编译工具、系统底层库、Go版本必须完全一致,换台机器、升级Go就大概率链接失败;
    动态库只需要运行时存在对应.so文件,编译阶段无强制绑定,随便分发。

  • 官方定位:c-archive静态库只是备用试验功能,官方推荐跨语言交互一律用c-shared动态库。

  • 2. Go 调用外部C静态/动态库(libmylib)

    新建 call_c.go,cgo通过 注释 写 C头文件 逻辑

    package main

    /*
    #cgo LDFLAGS: -L. -lmylib // 链接libmylib库
    #include "mylib.h" // 自建头文件声明add、hello
    */

    import "C"
    import "unsafe"

    func main() {
    res := C.add(3, 7)
    println("Go调用C库 add(3,7) =", res)

    msg := C.CString("Hello Go call C")
    defer C.free(unsafe.Pointer(msg)) // 释放C字符串内存
    C.hello(msg)
    }

    配套 mylib.h 头文件

    int add(int a, int b);
    void hello(const char* msg);

    运行:

    export LD_LIBRARY_PATH=.
    go run call_c.go

    cgo通过 注释 写 C头文件 介绍

    package main

    /*
    #cgo LDFLAGS: -L. -lmylib // 链接libmylib库
    #include "mylib.h" // 自建头文件声明add、hello
    */
    import "C"

    ……

    这不是 普通注释,cgo 专用指令注释,能真实生效

    1. 为什么长得像注释却能执行?

    Go 的 cgo 有特殊规则:
    import "C" 上方 紧邻的 多行注释块,会被 Go 编译器单独解析,交给内置的 C预处理器处理,不属于Go代码注释范畴。
    普通 //、/* */ 注释在Go里会被直接忽略,但紧贴 import "C" 的注释块是cgo专属配置区。

    2. 两行指令分别干什么(大白话)

    #cgo LDFLAGS: –L. –lmylib
    #include "mylib.h"

    ① #cgo LDFLAGS: -L. -lmylib
    • #cgo:标识这是给cgo的配置指令;
    • LDFLAGS:给底层C链接器传递参数;
    • -L.:告诉链接器,当前目录找库文件;
    • -lmylib:链接 libmylib.so / libmylib.a;
      等价Rust build.rs里 println!("cargo:rustc-link-search=."); println!("cargo:rustc-link-lib=mylib");
    ② #include "mylib.h"

    标准C头文件引入语法,作用是读取头文件,告诉cgo add、hello 两个C函数的签名,否则Go不知道这两个函数入参、返回值类型,编译报错。

    3. 关键限制:位置不能乱

  • 必须紧贴 import "C",中间不能有空行、不能有Go代码隔开;
  • 只能写在文件最顶部、package main 之后、import "C" 之前;
  • 如果挪到别的地方,就变成普通注释,完全失效。
  • 失效示例(中间空一行,指令作废)

    package main

    /*
    #cgo LDFLAGS: -L. -lmylib
    #include "mylib.h"
    */

    // 空一行隔开,直接失效

    import "C"

    真正无作用的普通注释

    如果是下面这种,就纯文本、完全没用:

    package main

    // 普通单行注释,随便写,cgo不会解析
    // #cgo LDFLAGS: -L. -lmylib

    import "C"

    单行 // 注释不被cgo解析,只有紧贴import "C"的多行/* */块内的 #cgo、#include 才会生效。


    第三部分:Python 操作静态/动态库

    Python 不能生成静态库(Python解释器机制决定),只能生成动态库;
    同时Python调用外部库只用动态库(不支持直接链接静态库),核心工具:ctypes 标准库。

    而且 Python是解释型,没有原生ABI。

    1. Python 打包生成 动态库(两种方式)

    使用 Cython(Python转C,编译成.so/.dll,最常用)

    1)安装cython

    pip install cython

    2)创建 py_lib.pyx

    cpdef int py_add(int a, int b):
    return a + b

    cpdef void py_hello(char* msg):
    print("Python(Cython) lib:", msg)

    3)创建编译脚本 setup.py

    from setuptools import setup, Extension
    from Cython.Build import cythonize

    ext = Extension(
    "pylib",
    sources=["py_lib.pyx"],
    )

    setup(ext_modules=cythonize(ext))

    4)编译生成动态库

    python setup.py build_ext –inplace
    # 生成 pylib.cpython-xxx.so 动态库

    2. Python 调用 外部 动态库

    使用内置 ctypes,无需额外安装,示例调用之前的 libmylib.so

    import ctypes

    # 加载动态库
    lib = ctypes.CDLL("./libmylib.so")

    # 指定函数参数、返回值类型(必须,否则数值错乱)
    lib.add.argtypes = (ctypes.c_int, ctypes.c_int)
    lib.add.restype = ctypes.c_int

    lib.hello.argtypes = (ctypes.c_char_p,) # char* 字符串

    # 调用add
    res = lib.add(100, 200)
    print(f"Python调用C库 add(100,200) = {res}")

    # 传入字节字符串(C字符串必须以\\0结尾)
    lib.hello(b"Hello Python ctypes\\x00")

    调用Rust编译的 librust_lib.so 示例

    import ctypes
    rust_lib = ctypes.CDLL("./librust_lib.so")
    rust_lib.rust_add.restype = ctypes.c_int
    print(rust_lib.rust_add(66, 34))
    rust_lib.rust_hello(b"Call Rust from Python\\x00")

    调用Go编译的 libgolib.so 示例

    import ctypes
    go_lib = ctypes.CDLL("./libgolib.so")
    print(go_lib.go_add(11, 22))
    go_lib.go_hello(b"Call Go from Python\\x00")

    关键限制:Python无法直接使用静态库

    Python运行时动态加载二进制,没有编译链接阶段,.a/.lib 静态库只能在编译程序时嵌入,Python做不到。


    四、三大语言打包/调用库能力总汇总表

    1. 能否生成静态库/动态库

    语言生成静态库(.a/.lib)生成动态库(.so/.dll/.dylib)
    C/C++ ✅ 原生支持 ✅ 原生支持
    Rust ✅ staticlib ✅ cdylib(C兼容)
    Go ⚠️ c-archive,兼容性差,极少用 ✅ c-shared 推荐
    Python ❌ 无法生成 ✅ 需Cython编译C扩展so

    2. 能否调用外部静态/动态库

    语言调用静态库调用动态库
    C/C++ ✅ gcc -static ✅ -l链接动态库
    Rust ✅ 编译时链接.a ✅ 运行加载so
    Go ✅ cgo链接.a ✅ cgo链接so
    Python ❌ 不支持 ✅ ctypes 运行加载

    五、避坑

  • 所有跨语言库交互,只能用C基础类型
    不能传Rust String、Go slice、Python对象、C++ std::string,只能用 int、char*、裸指针等C基础类型。

  • 动态库分发坑
    Linux运行程序找不到 .so 报错:error while loading shared libraries
    解决:临时 export LD_LIBRARY_PATH=.;永久把库目录写入 /etc/ld.so.conf 更新缓存。

  • Windows特殊规则
    Windows动态库 .dll 需要配套 .lib 导入库;导出函数必须加 __declspec(dllexport),否则外部无法调用。

  • 为什么Rust必须 #[no_mangle]
    Rust编译器会自动修改函数名(名字混淆,支持泛型/重载),不加这个标记,外部C/Python找不到函数入口。

  • Go cgo性能损耗
    Go和C库互相调用会切换运行时,高频计算场景优先纯Go实现,减少跨库调用。

  • Python ctypes内存风险
    传给动态库的字符串必须是字节串,手动管理内存,跨库分配的内存不能交叉释放(C分配内存C释放,Rust分配Rust释放)。

  • 赞(0)
    未经允许不得转载:171主机测评 » C链接库,联动 Rust、Golang、Python
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址