stdin未插入/subsystem:windows链接应用程序的管道数据

lf5gs5x2  于 2023-05-19  发布在  Windows
关注(0)|答案(1)|浏览(126)

我尝试制作一个GUI应用程序,它可以容忍管道数据作为输入(如果存在的话)。
用例是从文件资源管理器启动,它可能有命令行参数,也可能没有。或者,它以这种方式从批处理文件(或控制台)启动:
echo somedata | myprogram.execat file | myprogram.exemyprogram.exe < file
...无论是哪种方法,都可以使数据流进入stdin
或者
sourceofstream | myprogram.exe -
使用破折号作为文件名是对程序的一种“礼貌”形式,以表明stdin是源代码,但如果std::cin.rdbuf()->in_avail()可以工作,则不需要。
我的经验:

  • std::cin.rdbuf()->in_avail()不起作用,它总是假的。
  • getline(std::cin, l)std::istream_iterator<char>(std::cin)始终跳过而不执行任何操作。
    cin/stdin似乎在/subsystem:windows可执行文件中始终不可用。

我唯一能做的,就是打电话:

AttachConsole(-1);
freopen("CONIN$", "r", stdin);

在此之后,像fread(...stdin)这样的C风格函数能够访问TTY输入(isatty也返回非0)。但这不是我想要的,它放弃了输入管道,我不想要键盘输入。(次要备注:C++流不会重新连接。std::cin remains broken)
我想我的输入管道(应该是设置好的CreateProcess或fork()在cmd.exe我猜?).
完整来源:

#include <iostream>
#include <fstream>
#include <iterator>
#include <vector>
#include <filesystem>

int main(int argc, char* argv[])
{
    namespace fs = std::filesystem;
    using std::vector;

    fs::path inpath;
    std::ifstream infile;
    std::istream* in = nullptr;

    // temporary true hack to force cin is picked as source while no solution found
    if (true) // (std::cin.rdbuf()->in_avail() || (argc == 2 && argv[1][0] == '-'))  // input is piped on stdin?
        in = &std::cin;
    else if (argc <= 1)  // no command line -> open file dialog
        ;//filebox picker; // etc...
    else if (argc >= 2)
        inpath = argv[1];

    if (!inpath.empty())
        infile.open(inpath, std::ifstream::in | std::ifstream::binary);

    if (infile.good())
        in = &infile;

    if (!in) return 0;

    // repeat input data to stdout:
    *in >> std::noskipws;
    std::istream_iterator<char> stream_it(*in), end;
    std::vector<char> raw(stream_it, end);

    for (auto i : raw) std::cout << i;

    // gui stuff and treatment

    return 0;
}

#ifdef _WIN32
#include <io.h>
extern "C" { __declspec(dllimport) int __stdcall AttachConsole(unsigned long dwProcessId); }
struct HINSTANCE { int _; };
int WinMain(HINSTANCE, HINSTANCE, char*, int)
{
    //AttachConsole(-1);
    //freopen("CONIN$", "r", stdin);
    //char buffer[16] = {0};
    //fread(buffer, sizeof(char), 16, stdin);
    //std::cout << buffer;  // this makes input from console keyboard work, but not from pipe
    //std::cout << "is a TTY: " << std::boolalpha << !!isatty(_fileno(stdin)) << "\n";   // true if attach+reopen hack actived
    return main(__argc, __argv);
}
#endif

CMake具有:

add_executable(MyApp WIN32 ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)

WIN32 cmake参数为链接器创建如下所示的构建命令行:
C:\PROGRA~2\MICROS~3\2019\PROFES~1\VC\Tools\MSVC\1429~1.301\bin\Hostx64\x64\link.exe /nologo CMakeFiles\MyApp.dir\main.cpp.obj /out:MyApp.exe /implib:MyApp.lib /pdb:MyApp.pdb /version:0.0 /machine:x64 /debug /INCREMENTAL /subsystem:windows kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTFILE:CMakeFiles\MyApp.dir/intermediate.manifest CMakeFiles\MyApp.dir/manifest.res"

dvtswwa3

dvtswwa31#

自我回答公布我的调查结果。
这是可行的:

FILE* fixed_stdin_file = stdin;

int main(int argc, char* argv[])
{
    char data[128];
    fread(data, 1, 128, fixed_stdin_file);  // give up stdin
    // and give up about C++ streams
}

// /subsystem:windows adaptation:
#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
#include <io.h>
int WinMain(HINSTANCE, HINSTANCE, char*, int)
{
    // DO NOT call AttachConsole here, stdin_hdl will become
    // attached to TTY input and lose connection to the input pipe.

    HANDLE stdin_hdl = GetStdHandle(STD_INPUT_HANDLE);
    if (stdin_hdl != INVALID_HANDLE_VALUE)
    {
        int inpipe_handleconv_fd = _open_osfhandle((intptr_t)stdin_hdl , _O_RDONLY | _O_BINARY);
        fixed_stdin_file = _fdopen(inpipe_handleconv_fd, "rb");
    }
    return main(__argc, __argv);
}
#endif

但是我不会采用这种方法,因为不可能将std::cin重新连接到新的有效FILE*
_fdopen调用之后,重置stdin指针,以便它可以在之后透明地使用;
我试过但没用的方法

freopen(("/dev/fd/" + tostring(inpipe_handleconv_fd).c_str(), "r", stdin);

因为它是Windows,而/dev/fd不存在。我没有找到\\.\fd的等价物。

_dup2(inpipe_handleconv_fd, fileno(stdin));

dup2调用失败,出现-1和errno到9(EBADF无效文件描述符)。这是无法解释的原因。这两个描述符都是有效的,我用_get_osfhandl验证了它们是有效的。

  • github有很多使用GetStdHandle/open_osfhandle/_dup2方法的人的记录,但它是在为自己创建一个新的控制台之后,也许这就是区别。我不知道 *

什么不能工作,但可以在一些MSDN答案中找到:

stdin = fixed_stdin_file;

这是非法的,并且由于stdin不是左值而无法构建。
什么是有效的,但是非法的,可以在github和MSDN上找到答案:

*stdin = *fixed_stdin_file

这确实会正常执行并允许fread(..., stdin);,但这是偶然的。FILE是不透明的,不应该直接赋值。
最后,即使由于_dup2中的奇迹,标准输入可以成功地重新利用,即使这样,std::cin也变得毫无意义,因为它不能被反弹到其底层的FILE*。而且不,ios_base::sync_with_stdio不会帮助你。
正如这个答案所说的https://stackoverflow.com/a/14990896/893406,使用C++流与重定向的FILE* 的唯一方法是从不存在的FILE* 创建一个新的streambuf。* (由GroovX库http://ilab.usc.edu/rjpeters/groovx/stdiobuf_8cc-source.html提供的实现分配)*
总而言之,我给予了。微软在使用subsystem:windows创建ELF时,已经不可能使用干净、优雅的标准管道概念。我只会容忍无用的控制台窗口弹出并行我的GUI,就像搅拌机一样。最后的boss修正是使用一个双重可执行文件,一个.com和一个.exe,就像visual studio一样,在this MSDN blog.中提到的。

相关问题