Rust应用调用C语言动态库的操作方法

目录
  • 外部功能接口FFI
  • UDP套接字的读超时
  • Rust调用C语言动态库中的函数
  • 避免重复造轮子,使用Rust官方C语言库

外部功能接口FFI

虽然高级(脚本)编程语言的功能丰富,表达能力强,但对底层的一些特殊操作的支持并不完善,就需要以其他编程语言来实现。调用其他编程语言的接口,被称为Foreign Function Interface,直译为外部功能接口。该接口通常是调用C语言实现的外部功能模块,因为C语言接近于全能,几乎任何功能都能够实现;正如同使用汇编语言也可以实现很多功能一样,但开发效率低下。很多脚本语言提供了FFI功能,例如Python、PHP和JIT版本的Lua解析器等。同样的,Rust也提供了FFI接口,作为标准库中一个功能模块;但本文不会讨论该模块的使用方法。本文记录了笔者编写一个简单的C语言动态库,并通过Rust调用动态库导出的函数;另外,笔者直接使用Rust官方提供的libc库,直接替代笔者编写的C语言动态库,以避免重复造轮子。

UDP套接字的读超时

Rust标准库中的UDP网络功能,提供了设置套接字读超时的函数,set_read_timeout,了解C语言网络编译的开发人员都知道,相应的底层调用为setsockopt(SO_RCVTIMEO)。假设Rust标准库中UDP模块未提供该函数,就需要编写C语言代码,将其编译成一个动态库,尝试将Rust链接到该库,并调用其中定义的函数了。笔者编写的代码如下:

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>

/* export function to set socket receive timeout */
extern int normal_setsock_timeout(int sockfd,
    unsigned long timeout);

int normal_setsock_timeout(int sockfd,
    unsigned long timeout)
{
    int ret, err_n;
    struct timeval tv;

    tv.tv_sec  = (time_t) (timeout / 1000);
    tv.tv_usec = (timeout % 1000) * 1000;
    ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO,
        &tv, sizeof(tv));
    if (ret == -1) {
        err_n = errno;
        fprintf(stderr, "Error, setsockopt(%d, RECVTIMEO, %lu) has failed: %s\n",
            sockfd, timeout, strerror(err_n));
        fflush(stderr);
        errno = err_n;
        return -1;
    }
    return 0;
}

通过以下命令生成动态库libsetsock.so

gcc -Wall -O2 -fPIC -D_GNU_SOURCE -shared -o libsetsock.so -Wl,-soname=libsetsock.so mysetsock.c

笔者使用Rust编写的简单UDP服务端代码如下:

use std::net::UdpSocket;
use chrono::{DateTime, Local};

fn get_local_time() -> String {
    let nowt: DateTime<Local> = Local::now();
    nowt.to_string()
}

fn main() -> std::io::Result<()> {
    let usock = UdpSocket::bind("127.0.0.1:2021");
    if usock.is_err() {
        let errval = usock.unwrap_err();
        println!("Error, failed to create UDP socket: {:?}", errval);
        return Err(errval);
    }
    // get the UdpSocket structure
    let usock = usock.unwrap();

    // create 2048 bytes of buffer
    let mut buffer = vec![0u8; 2048];
    println!("{} -> Waiting for UDP data...", get_local_time());

    // main loop
    loop {
        let res = usock.recv_from(&mut buffer);
        if res.is_err() {
            println!("{} -> Error, failed to receive from UDP socket: {:?}",
                get_local_time(), res.unwrap_err());
            break;
        }

        let (rlen, peer_addr) = res.unwrap();
        println!("{} -> received {} bytes from {:?}:{}",
            get_local_time(), rlen, peer_addr.ip(), peer_addr.port());
    }

    // just return ok
    Ok(())
}

短短50多行代码实现了一个简单的UDP服务端,作为系统编程语言的Rust开发效率可见一斑。不过该UDP服务器的读操作是阻塞的,它会一直等待网络数据的到来:

udp_socket$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/udp_socket`
2021-07-11 19:38:29.791363796 +08:00 -> Waiting for UDP data...
2021-07-11 19:38:39.721713256 +08:00 -> received 16 bytes from 127.0.0.1:39180
2021-07-11 19:38:48.553386975 +08:00 -> received 16 bytes from 127.0.0.1:58811

Rust调用C语言动态库中的函数

与C语言类似,Rust使用extern关键字可实现对外部函数的声明,不过在调用的代码需要以unsafe关键字包成代码块。以下是笔者对上面的Rust代码的修改:

diff --git a/src/main.rs b/src/main.rs
index 304c7dc..5921106 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,12 @@
 use std::net::UdpSocket;
 use chrono::{DateTime, Local};
+use std::os::raw::c_int;
+use std::os::unix::io::AsRawFd;
+
+#[link(name = "setsock")]
+extern {
+    pub fn normal_setsock_timeout(sock_fd: c_int, timo: usize) -> c_int;
+}

 fn get_local_time() -> String {
     let nowt: DateTime<Local> = Local::now();
@@ -20,6 +27,11 @@ fn main() -> std::io::Result<()> {
     let mut buffer = vec![0u8; 2048];
     println!("{} -> Waiting for UDP data...", get_local_time());

+    // set UDP socket receive timeout
+    unsafe {
+        normal_setsock_timeout(usock.as_raw_fd() as c_int, 5000);
+    }
+
     // main loop
     loop {
         let res = usock.recv_from(&mut buffer);

修改后的主代码增加了#[link]属性,指示Rust编译器在链接时加入-lsetsock链接选项。再次编译,会发现链接命令失败:

udp_socket$ cargo build
   Compiling udp_socket v0.1.0 (/home/yejq/program/rust-lang/udp_socket)
error: linking with `cc` failed: exit status: 1

这说明虽然编译是正常的,但在链接时找不到libsetsock.so动态库。解决方法是在工程根目录下增加一个编译控制的Rust代码,文件名为build.rs,给出动态库所在的目录:

fn main() {
    println!(r"cargo:rustc-link-search=native=/home/yejq/program/rust-lang/socket_udp");
}

再次执行cargo build编译工程,链接就能成功了;使用patchelfnm等命令行工具察看,生成的可执行文件依赖了C语言编写的动态库libsetsock.so,并引用了其导出的函数符号normal_setsock_timeout

udp_socket$ cargo build
   Compiling udp_socket v0.1.0 (/home/yejq/program/rust-lang/udp_socket)
    Finished dev [unoptimized + debuginfo] target(s) in 1.72s
udp_socket$ patchelf --print-needed ./target/debug/udp_socket
libsetsock.so
libgcc_s.so.1
libpthread.so.0
libdl.so.2
libc.so.6
ld-linux-x86-64.so.2
udp_socket$ nm --undefined-only ./target/debug/udp_socket | grep -e normal_setsock
                 U normal_setsock_timeout

此时运行简单UDP服务端程序,可以确定我们增加的套接字读超时功能能够正常工作:

udp_socket$ LD_LIBRARY_PATH=/home/yejq/program/rust-lang/socket_udp ./target/debug/udp_socket
2021-07-11 19:55:26.279653039 +08:00 -> Waiting for UDP data...
2021-07-11 19:55:29.788948366 +08:00 -> received 16 bytes from 127.0.0.1:43303
2021-07-11 19:55:31.977738660 +08:00 -> received 16 bytes from 127.0.0.1:46854
2021-07-11 19:55:37.179290653 +08:00 -> Error, failed to receive from UDP socket: Os { code: 11, kind: WouldBlock, message: "Resource temporarily unavailable" }

避免重复造轮子,使用Rust官方C语言库

以上我们用C语言编写了简单的动态库,导出了一个可设置套接字读超时的函数。这个功能过于简单,费大周折编写一个动态库显得得不偿失。另一个解决方案是直接使用Rust官方提供的C语言库,该库提供了很多变量和函数(与glibc提供的宏定义和库函数、系统调用有很多重叠),可以直接添加setsockopt等系统调用的代码。修改UDP服务器代码:

diff --git a/src/main.rs b/src/main.rs
index 5921106..3f4bc84 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,11 +2,7 @@ use std::net::UdpSocket;
 use chrono::{DateTime, Local};
 use std::os::raw::c_int;
 use std::os::unix::io::AsRawFd;
-
-#[link(name = "setsock")]
-extern {
-    pub fn normal_setsock_timeout(sock_fd: c_int, timo: usize) -> c_int;
-}
+use libc;

 fn get_local_time() -> String {
     let nowt: DateTime<Local> = Local::now();
@@ -27,9 +23,17 @@ fn main() -> std::io::Result<()> {
     let mut buffer = vec![0u8; 2048];
     println!("{} -> Waiting for UDP data...", get_local_time());

-    // set UDP socket receive timeout
     unsafe {
-        normal_setsock_timeout(usock.as_raw_fd() as c_int, 5000);
+        let time_val = libc::timeval {
+            tv_sec: 5,
+            tv_usec: 0,
+        };
+
+        // set socket receive timeout via extern create, libc
+        libc::setsockopt(usock.as_raw_fd() as c_int,
+            libc::SOL_SOCKET, libc::SO_RCVTIMEO,
+            &time_val as *const libc::timeval as *const libc::c_void,
+            std::mem::size_of_val(&time_val) as libc::socklen_t);
     }

除了以上的修改,还需要在Cargo.toml文件中加入C语言库的依赖,这里笔者使用的libc版本为0.2.98:

diff --git a/Cargo.toml b/Cargo.toml
index f802b0d..eb0b78e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

 [dependencies]
+libc = "0.2.98"
 chrono = "0.4.19"

以上修改的代码,与之前相同的是,调用C语言库提供的函数也需要用到unsafe代码块;而工程根目录下的编译相关的控制代码build.rs就不再需要了;编译生成的UDP服务器也会在5秒无数据时退出。最后,能够调用C语言编写的动态库,意味着使用Rust语言来进行嵌入式系统软件的开发,是一种具备可行性的技术方案。

到此这篇关于Rust应用调用C语言动态库的操作方法的文章就介绍到这了,更多相关Rust调用C语言动态库内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Rust 所有权机制原理深入剖析

    目录 what's ownership? Scope (作用域) ownership transfer(所有权转移) move clone copy References and Borrowing(引用和借用) Mutable References(可变引用) Dangling References(悬垂引用) what's ownership? 常见的高级语言都有自己的 Garbage Collection(GC)机制来管理程序运行的内存,例如 Java.Go 等.而 Rust 引入了一种全

  • 向Rust学习Go考虑简单字符串插值特性示例解析

    目录 fmt.Printf 或 fmt.Sprintf 写拼装字符串业务 简单字符串插值 其他语言例子 Swift Kotlin C Rust 争论矛盾点 总结 fmt.Printf 或 fmt.Sprintf 写拼装字符串业务 在日常开发 Go 工程中,我们经常会用 fmt.Printf 或 fmt.Sprintf 去写类似的拼装字符串的业务. 如下代码: fmt.Printf("Hello Gopher %s, you are %d years old and you're favorite

  • Rust如何进行模块化开发技巧分享

    目录 Rust如何进行模块化开发? Package和Create Cargo的惯例 Create的作用 定义module来控制作用域和私有性 路径Path 私有边界(private boundary) pub关键字 super关键字 pub struct pub enum Use关键字 use的习惯用法 as关键字 使用 pub use 重新导出名称 导入外部包 如何将模块放入其他文件? 类似es6的模块化,Rust通过package.create.module来实现代码的模块化管理 Rust如

  • 从迷你todo 命令行入门Rust示例详解

    目录 一个迷你 todo 应用 需要安装的依赖 文件目录组织 主文件 读取文件 状态处理工厂函数 Trait(特征) Create trait Get trait Delete trait Edit trait 导出 trait 为 struct 实现 trait Pending Done 导出 struct Process 输入处理 最后 一个迷你 todo 应用 该文章将使用 Rust 从零去做一个入门级别的 TODO 命令行应用 你将学会什么? 基本的命令行操作 文件读写和文件结构组织 我

  • Rust Atomics and Locks内存序Memory Ordering详解

    目录 Rust内存序 重排序和优化 happens-before Relexed Ordering Release 和 Acquire Ordering SeqCst Ordering Rust内存序 Memory Ordering规定了多线程环境下对共享内存进行操作时的可见性和顺序性,防止了不正确的重排序(Reordering). 重排序和优化 重排序是指编译器或CPU在不改变程序语义的前提下,改变指令的执行顺序.在单线程环境下,重排序可能会带来性能提升,但在多线程环境下,重排序可能会破坏程序

  • rust引用和借用的使用小结

    目录 引用和借用 引用的使用 可变引用与不可变引用 NLL 总结 引用和借用 如果每次都发生所有权的转移,程序的编写就会变得异常复杂.因此rust和其它编程语言类似,提供了引用的方式来操作.获取变量的引用,称为借用.类似于你借别人的东西来使用,但是这个东西的所有者不是你.引用不会发生所有权的转移. 引用的使用 在rust中,引用的语法非常简单.通过&来取引用,通过*来解引用.例如: fn main() { let s1: String = "Hello".to_string()

  • Rust应用调用C语言动态库的操作方法

    目录 外部功能接口FFI UDP套接字的读超时 Rust调用C语言动态库中的函数 避免重复造轮子,使用Rust官方C语言库 外部功能接口FFI 虽然高级(脚本)编程语言的功能丰富,表达能力强,但对底层的一些特殊操作的支持并不完善,就需要以其他编程语言来实现.调用其他编程语言的接口,被称为Foreign Function Interface,直译为外部功能接口.该接口通常是调用C语言实现的外部功能模块,因为C语言接近于全能,几乎任何功能都能够实现:正如同使用汇编语言也可以实现很多功能一样,但开发效

  • golang 调用c语言动态库方式实现

    下面我们自己在 Linux 下做一个动态库(.so 文件 - Shared Object),然在用 Go 来使用它.本文所用的操作系统为 Ubuntu18.04, 以 gcc 作为编译器. 1.实现头文件,声明文件中函数.这里创建一个add.h文件. #ifndef __ADD_H__ #define __ADD_H__ char* Add(char* src, int n); #endif 2.实现add主体函数add.c #include <string.h> #include <s

  • 浅谈Linux C语言动态库及静态库

    假设在math目录下已编辑好add.c sub.c div.c mul.c func_point.c文件,func_point.c为包含main()的源文件! 动态库的制作: 方法一: gcc -c -fPIC add.c sub.c div.c mul.c //-c表示生成.o目标文件,-f后加一些编译选项,PIC表示与位置无关 gcc -shared -o libmymath.so add.o sub.o mul.o div.o//创建共享库mymath,添加add.o,sub.o,mul.

  • Qt之调用C#的动态库的解决方法

    环境:VS2019+Qt5.12 1. CLR库安装 首先,如果你VS2019没有安装CLR库,那么操作步骤为: 打开 Visual Studio Installer 在已安装中点击修改 将使用C++的桌面开发的对V142(14.25)生成工具的C++/CLI支持 点击右下角的修改,安装完成后重启软件即可 2. 新建类库(.NET Framework) 注意:此处请确认选择用于创建C#类库(.dll)的项目 此时解决方案的视图为: 一个简单的测试直接在Class1.cs文件添加内容即可,此测试中

  • android 调用JNI SO动态库的方法

    总结一下: android 调用JNI 分为静态调用与动态调用(不论动态还是静态前提都是NDK环境已经配置好的前提下) 一.静态主要就是将c(.c)或者c++(cpp)的源文件直接加到项目中进行调用, 然后在CMakeLists.txt中进行配置. 二.动态调用 1.动态调用使用已经编译好的动态库.so文件 2.android调用ndk类 生成后的so文件 public class SerialPort { p */ public static native int GetSOVer(Strin

  • C#调用非托管动态库中的函数方法

    C#如何调用一个非托管动态库中的函数呢,比如用VC6写的动态库,总之C#调用动态库的过程是比Java调用DLL动态库方便快捷多了,下面举例说明这个过程. 1.创建一个非托管动态库 代码如下: 复制代码 代码如下: //这一句是声明动态库输出一个可供外不调用的函数原型.     extern   "C"  __declspec(dllexport)  int  add( int ,  int ); int  add( int  a, int  b)      {          //实

  • Python调用C语言开发的共享库方法实例

    在helloworld工程中,编写了一个简单的两个数值相加的程序,编译成为共享库后,如何使用python对其进行调用呢? 使用ll命令列出当前目录下的共享库,其中共享库名为libhelloworld.so.0.0.0 复制代码 代码如下: ufo@ufo:~/helloworld/.libs$ ll 总用量 32 drwxr-xr-x 2 ufo ufo 4096  1月 29 14:54 ./ drwxr-xr-x 6 ufo ufo 4096  1月 29 16:08 ../ -rw-r--

  • 老生常谈C语言动态函数库的制作和使用(推荐)

    >>>>>>老生常谈C语言接静态函数库的制作和使用>>点击进入 2 动态函数库的制作和使用 动态函数库的制作步骤可以用下图来描述,具体包括 (1) 编写函数的.c文件(例如add.c.sub.c.mul.c和div.c) (2) 编写Makefile,然后make,实现函数的编译和归档入库 函数的编译:使用gcc –c add.c -fPIC只编译不链接函数.c文件,分别生成函数的目标文件(例如add.o.sub.o.mul.o和div.o). 函数的归档入

  • Java通过调用C/C++实现的DLL动态库——JNI的方法

    由于项目的需要,最近研究了java 调用DLL的方法,将如何调用的写于此,便于日后查阅: 采用的方法是JNI: Java Native Interface,简称JNI,是Java平台的一部分,可用于让Java和其他语言编写的代码进行交互. 下面是从网上摘取的JNI工作示意图: 总体说明:先在JAVA中建立一个类,通过javac生成.class,再由javah生成.h:然后将.h复制到VC下,由VC实现具体函, 并编译通过后生成DLL,将DLL放入JAVA工程中使用,完毕. 下面说说具体步骤(含实

  • linux中使用boost.python调用c++动态库的方法

    前言 最近开始使用 robot framework 测试c++的动态库,robot framework 是跑在 windows 上面,c++动态库是跑在远程linux主机上面.测试办法是让 robot framework 通过 SSHLIbrary 库执行远程机器上面的 python 脚本,python 脚本调用 C++ 动态库.所以现在要解决的是如何让python调用c++动态库. python调用c++动态库的两种办法 在上网查资料和咨询同事之后,得到两种办法:第一种将C++动态库封装成C接

随机推荐