Rust可以捕获C / C ++库的stdout / stderr吗?

时间:2020-08-26 14:37:48

标签: rust

我将C / C ++库包装在Rust板条箱中,并使用FFI对其进行调用(我使用子进程 not )。

此库记录到stdout / stderr(使用printf()std::cout),但我想“捕获”此输出并使用Rust的log板条箱来控制输出

是否可以将FFI调用的stdout / stderr重定向到log

1 个答案:

答案 0 :(得分:2)

请在下面找到说明不同之处的示例 重定向/还原stderr(文件描述符2)的步骤。

此处使用的(类似C的)样式旨在保留该样式 最小的例子;当然,您可能可以使用libc 将所有这些东西正确包装并封装在struct中。

请注意,在平凡的情况下,您可以重复 您可以多次重定向/调用/获取/恢复序列, 前提是您保持pipe_fdsaved_fdlog_file打开。

但是,在非平凡的情况下,意味着某种并发症:

  • 如果C代码产生相当长消息,我们如何检测 我们已经读完了吗?
    • 我们可以在STDERR_FILENO之后插入结束标记 消息在调用步骤中产生,然后读取log_file 直到在获取步骤中检测到此标记。 (这增加了 某种文本处理)
    • 我们可以在每次重定向之前重新创建管道和log_file 步骤,在 invoke 步骤之前关闭PIPE_WRITE端, log_file直到达到EOF,然后在获取步骤中将其关闭。 (这会增加更多系统调用的开销)
  • 如果C代码生成一条非常长消息,它不会超过 管道的内部缓冲区容量(然后是块写入)?
    • 我们可以在单独的线程中执行 invoke 步骤, 在获取步骤完成后join()(结束标记或 到达EOF),因此调用仍然看起来是串行的 从应用程序的角度来看。 (这会增加产生/加入线程的开销)
    • 一种替代方法是将应用程序的所有日志记录部分放入 在单独的线程中(全部产生一次),并保留所有 invocation 依次执行。 (如果不必将应用程序的日志记录部分 感觉是串行的,这是可以的,但是这只会报告 同样的问题,一个线程进一步)
    • 我们可以fork()执行重定向调用 子进程中的步骤(如果应用程序数据中没有 要更改,只需阅读),摆脱 restore 步骤, wait() 获取步骤完成后的过程 (到达结束标记或EOF),因此调用仍然 从应用程序的角度来看,看起来序列。 (这会增加产生/等待进程的开销,并且 排除了更改应用程序数据的能力 调用的代码)
// necessary for the redirection
extern "C" {
    fn pipe(fd: *mut i32) -> i32;
    fn close(fd: i32) -> i32;
    fn dup(fd: i32) -> i32;
    fn dup2(
        old_fd: i32,
        new_fd: i32,
    ) -> i32;
}
const PIPE_READ: usize = 0;
const PIPE_WRITE: usize = 1;
const STDERR_FILENO: i32 = 2;

fn main() {
    //
    // duplicate original stderr in order to restore it
    //
    let saved_stderr = unsafe { dup(STDERR_FILENO) };
    if saved_stderr == -1 {
        eprintln!("cannot duplicate stderr");
        return;
    }
    //
    // create resources (pipe + file reading from it)
    //
    let mut pipe_fd = [-1; 2];
    if unsafe { pipe(&mut pipe_fd[0]) } == -1 {
        eprintln!("cannot create pipe");
        return;
    }
    use std::os::unix::io::FromRawFd;
    let mut log_file =
        unsafe { std::fs::File::from_raw_fd(pipe_fd[PIPE_READ]) };
    //
    // redirect stderr to pipe/log_file
    //
    if unsafe { dup2(pipe_fd[PIPE_WRITE], STDERR_FILENO) } == -1 {
        eprintln!("cannot redirect stderr to pipe");
        return;
    }
    //
    // invoke some C code that should write to stderr
    //
    extern "C" {
        fn perror(txt: *const u8);
    }
    unsafe {
        dup(-1); // invalid syscall in order to set errno (used by perror)
        perror(&"something bad happened\0".as_bytes()[0]);
    };
    //
    // obtain the previous message
    //
    use std::io::Read;
    let mut buffer = [0_u8; 100];
    if let Ok(sz) = log_file.read(&mut buffer) {
        println!(
            "message ({} bytes): {:?}",
            sz,
            std::str::from_utf8(&buffer[0..sz]).unwrap(),
        );
    }
    //
    // restore initial stderr
    //
    unsafe { dup2(saved_stderr, STDERR_FILENO) };
    //
    // close resources
    //
    unsafe {
        close(saved_stderr);
        // pipe_fd[PIPE_READ] will be closed by log_file
        close(pipe_fd[PIPE_WRITE]);
    };
}
相关问题