我尝试制作一个GUI应用程序,它可以容忍管道数据作为输入(如果存在的话)。
用例是从文件资源管理器启动,它可能有命令行参数,也可能没有。或者,它以这种方式从批处理文件(或控制台)启动:echo somedata | myprogram.exe
或cat file | myprogram.exe
或myprogram.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"
1条答案
按热度按时间dvtswwa31#
自我回答公布我的调查结果。
这是可行的:
但是我不会采用这种方法,因为不可能将
std::cin
重新连接到新的有效FILE*
。在
_fdopen
调用之后,重置stdin指针,以便它可以在之后透明地使用;我试过但没用的方法
因为它是Windows,而/dev/fd不存在。我没有找到
\\.\fd
的等价物。dup2
调用失败,出现-1和errno
到9(EBADF
无效文件描述符)。这是无法解释的原因。这两个描述符都是有效的,我用_get_osfhandl
验证了它们是有效的。什么不能工作,但可以在一些MSDN答案中找到:
这是非法的,并且由于stdin不是左值而无法构建。
什么是有效的,但是非法的,可以在github和MSDN上找到答案:
这确实会正常执行并允许
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.中提到的。