在 Windows 上通过 stdin 将字节从 Python 传递到 C++ 子进程失败

问题描述 投票:0回答:1

我有一个运行循环迭代的Python程序。在循环期间,有一部分代码可以并行运行,为此,我决定使用预编译的 C++ 程序并将其作为子进程调用。该程序需要一个大约 1K 字节长的输入,并将返回几个字节作为答案。

在 C++ 程序中,我有以下方法从 stdin 读取输入字节:

#include <iostream>
#define VAR_NUM 1000         // number of bytes to read
using namespace std;

void read_vars(int* vars){
    char buf;
    int chk;

    for(int i=0; i<VAR_NUM; i++){
        chk = fread(&buf, sizeof(char), 1, stdin);
        std::cout << (int)(unsigned char)buf << "(" << chk << ") ";
        vars[i] = (int)(unsigned char)buf;
        if(chk==0){
            if(feof(stdin)) std::cout << "[EOF] ";
            if(ferror(stdin)) std::cout << "[ERROR] ";
        }
    }
    std::cout << endl;
    return;
}

int main(){
    int* vars = (int*) malloc(VAR_NUM*sizeof(int));
    for(int i=0; i<VAR_NUM; i++) vars[i] = 0;
    read_vars(vars);
    return 0;
}

它从 stdin 读取字节并将它们作为整数放入预分配的 vars 数组中。目前,stdout 用于调试,因此读取的每个字节都会与其 fread 返回值以及发生错误时的 stdin 状态一起打印。

在 Python 端,我运行以下代码来调用已编译的 C++ 程序:

import os
import random
from subprocess import run, Popen, PIPE, DEVNULL, STDOUT

def run_cpp(vars):
    command = os.path.join('.', 'program')
    inp = bytes(vars)

    proc = Popen(command, stdin=PIPE, stderr=PIPE, text=False) # will have stdout=PIPE in final code
    print('python wrote:', proc.stdin.write(inp),'bytes\n')
    print('vars read by c++:')
    res, err = proc.communicate()

    print('\n'+'stderr:', err, '\n')
    print('stdout:', res, '\n')
    return 

# random bytes
vars = [random.randint(0,255) for i in range(1000)]           # must have the same number of bytes as VAR_NUM in C++
print('vars written by python:')
print(vars,'\n')

print(run_cpp(vars))

它创建一个调用 C++ 程序的进程,并通过管道将字节写入其标准输入中。我不会立即与子进程进行 communications() ,因为:我想在循环中创建其中许多子进程并向它们传递各自的输入。然后等待它们,因为它们都是并行运行的。 (proc.communicate()在此代码中仅用于测试)

当我在 Ubuntu Linux 上运行上面的代码时,它会按预期运行:我从 Python 写入的所有字节都正确打印在 stdout 上。在 Windows 上,我得到如下输出:

python wrote: 1000 bytes 17(1) 27(1) 23(1) 0(1) 27(1) 23(1) 15(1) 19(1) 16(1) 27(1) 11(1) 23(1) 28(1) 13(1) 28(1) 6(1) 27(1) 28(1) 6(1) 23(1) 4(1) 13(1) 3(1) 11(1) 22(1) 14(1) 11(1) 11(1) 4(1) 4(1) 13(1) 16(1) 19(1) 21(1) 21(0) [EOF] 21(0) [EOF] 21(0) [EOF] 21(0) [EOF] 21(0) [EOF] 21(0) [EOF] 21(0) [EOF] 21(0) [EOF] 21(0) [EOF] ...

proc.stdin.write(inp) 报告写入的正确字节数。前几个字节被 read_vars() 正确读取,但一段时间后,fread 似乎过早地达到了 EOF(并且 buf 中最后保存的字节被重复保存在 vars 数组中)。我为 Popen 尝试了一些不同的配置选项(例如 shell=False),我在编写后尝试刷新 proc.stdin,但无论我尝试什么,这种行为都不会改变。

如果我尝试写入大量字节,就会出现这种情况,对于 10 到 200 字节,它总是可以正常工作,对于 300 到 500 个字节,它有时会失败,而对于接近 1000 个字节(这是我的目标),它总是会失败。此外,正确传递的字节数总是会发生变化,有时它会正确读取数百个字节而不会失败,有时在前几个字节后会失败。要尝试此操作,请分别更改 C++ 和 Python 中的

#define VAR_NUM 1000vars = [random.randint(0,255) for i in range(1000)] 语句

我猜我缺少一些必需的 python/pipe/g++ 配置,以使其在 Windows 上工作。

编辑:使示例可重现。

python c++ windows subprocess pipe
1个回答
0
投票
该错误是由于 Windows 上的 stdin 未处于“二进制翻译模式”引起的,正如 Mark 和 Kenny 在评论中指出的那样。

使用

freopen(NULL, "rb", stdin);

来自这里对我不起作用。但在阅读之前使用 _setmode(_fileno(stdin), _O_BINARY)
 效果非常好(以及包含 
<fcntl.h>
<io.h>
来自此处)。

C++ 的最终工作代码:

#include <iostream> #include <io.h> #include <fcntl.h> #include <stdexcept> #define VAR_NUM 1000 // number of bytes to read using namespace std; void read_vars(int* vars){ char buf; int chk; for(int i=0; i<VAR_NUM; i++){ chk = fread(&buf, sizeof(char), 1, stdin); //std::cout << (int)(unsigned char)buf << "(" << chk << ") "; vars[i] = (int)(unsigned char)buf; if(chk==0){ if(feof(stdin)){ fwrite("[EOF error]", sizeof(char), 12, stderr); return; } if(ferror(stdin)){ fwrite("[stdin ERROR]", sizeof(char), 14, stderr); return; } } } return; } int main(){ if (_setmode(_fileno(stdin), _O_BINARY) == -1) fwrite("[stdin conversion ERROR]", sizeof(char), 25, stderr); return 1; int* vars = (int*) malloc(VAR_NUM*sizeof(int)); for(int i=0; i<VAR_NUM; i++) vars[i] = 0; read_vars(vars); return 0; }
    
© www.soinside.com 2019 - 2024. All rights reserved.