絮絮叨叨
笔者最近做一个项目,跟一个写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
就是动态链接库的名称.
就这样结束了。