C调用Rust动态链接库实现Http文件上传

絮絮叨叨

笔者最近做一个项目,跟一个写C的小伙一起开发。笔者原本是用Java写服务端接口的,那个小伙用C写客户端。客户端某个功能需要图片到服务端的Http接口上。 而写C的小伙目前对Http这块还不熟,加上C发起Http请求比较麻烦。于是笔者就想用Rust开发一个动态库链接库给C调用。在动态链接库公开一个函数,参数是Http的请求地址和图片,如果上传成功就返回1,失败就返回0。

Rust函数大致如下:

#[no_mangle]
pub extern "C" fn post_image(c_url: *const c_char, payload_ptr: *const payload_t) -> i32 {
    // TODO
}

*const c_char用于接收来自C里面的字符串,在C里面对应的是const char * *const payload_t是用来接收来自C的图片数据,在这里定应了一个Rust Struct

#[repr(C)]
pub struct payload_t {
    data: *const u8,
    len: c_int
}

这里的data表示接收一个来自C的指针,而len表示指针的长度。

下面来看整个扩展的编写过程。

开始写Rust代码

1、用cargo创建一个rust工程

cargo new mylib
cd mylib

2、在Cargo.toml文件中配置[lib]和[dependencies]

[lib]
name = "mylib"
crate-type = ["cdylib"]
path = "src/lib.rs"


[dependencies]
reqwest = {version = "0.10.0", features = ["blocking"] }
base64 = "0.11.0"

3、下面关键的步骤来了,编写lib.rs代码

use std::os::raw::{c_char, c_int};
use std::ffi::CStr;
use std::error::Error;

#[repr(C)]
pub struct payload_t {
    data: *const u8,
    len: c_int
}

#[no_mangle]
pub extern "C" fn post_image(c_url: *const c_char, payload_ptr: *const payload_t) -> i32 {
    // 在这里开始一步步将c字符串转换成&str
    let c_str: &CStr = unsafe {
        CStr::from_ptr(c_url)
    };

    if let Err(err) = c_str.to_str() {
        eprintln!("{}", err.description());
        return 0;
    }
    let str_url = c_str.to_str().unwrap();

    // 在这里开始将c传过来的文件,转换成&[u8]
    let r_payload = unsafe {
        *Box::from_raw(payload_ptr as *mut payload_t)
    };

    let bytes = unsafe {
      std::slice::from_raw_parts(r_payload.data, r_payload.len as usize)
    };

    // 开始上传文件
    if post(str_url, bytes) {
        return 1;
    }

    return 0;
}

fn post(str_url: &str, payload: &[u8]) -> bool {
    let client = reqwest::blocking::Client::new();
    let body_base64 = base64::encode(payload);
    if let Ok(res) = client.post(str_url).body(body_base64).send() {
        let status = res.status();
        println!("{:?}", res);
        println!("{:?}", res.text());
        if status == 201 {
            return true;
        }
    };
    return false;
}

在这里post函数中,把payload: &[u8]转为base64在上传的,实际不转也可以。主要得看接收文件的Http接口是如何做的。

用cbindgen生成头文件

1、安装cbindgen

cargo install --force cbindgen

2、在工程的根目录下编写cbindgen.toml文件

language = "C"

这里很简单,指定生成头文件的语言为C就可以了。这个文件为空也是可以的。

3、生成头文件

cbindgen --config cbindgen.toml --crate mylib --output mylib.h
编译动态链接库
cargo build

笔者用的是Mac系统,在target/debug目录下生成了libmylib.dylib文件,这个就是动态链接库了。接下来在这个目录下创建一个test.c文件来测试。

开始写C代码来测试

1、编写test.c

#include "mylib.h"

#include <stdio.h>

int file_size(char * file_name) 
{
    FILE *fp = fopen(file_name, "rb");
    int size;
    if (fp == NULL)
        return -1;
    fseek(fp, 0, SEEK_END);
    size = ftell(fp);
    fclose(fp);
    return size;
}

int main() 
{
    int size = 0;
    const char *c_url = "http://localhost:8080/upload";
    size = file_size("test.png");
    FILE *fp;
    char *buffer = (char*)malloc(sizeof(char)*size);
    fp = fopen("test.png", "rb");
    if (fp == NULL)
        return 0;
    fread(buffer, 1, size, fp);
    fclose(fp);
    payload_t payload;
    payload_t *payload_ptr = malloc(sizeof(payload));
    const uint8_t *buffer_ptr = (uint8_t *)buffer;
    payload_ptr->data = buffer_ptr;
    payload_ptr->len = size;
    int32_t flag = post_image(c_url, payload_ptr);
    if(flag == 1)
    {
        printf("%s\n", "上传成功");
    } 
    else
    {
        printf("%s\n", "上传失败");
    }
    free(buffer);
    buffer = NULL;
    return 0;
}

这里是用C代码上传本地的一张test.png图片到服务端。测试时记得复制一张图片到C代码所在目录。

2、编译C,并运行

gcc test.c -L ./ -lmylib -o test
./test

mylib就是动态链接库的名称.

就这样结束了。

本博客采用 知识共享署名-禁止演绎 4.0 国际许可协议 进行许可

本文标题:C调用Rust动态链接库实现Http文件上传

本文地址:https://jizhong.plus/post/2020/01/c-reference-rust-dynamic-library.html