I try to make a GUI application that can tolerate piped data as input, if present.
The use case is either it gets started from file explorer, and it may have a command line argument or not. Or, it is started from a batch file (or a console) in this manner:
echo somedata | myprogram.exe
or cat file | myprogram.exe
or myprogram.exe < file
...whichever method that is supposed to make a stream of data available into stdin
.
or maybe
sourceofstream | myprogram.exe -
Using dash as filename as a form of "politeness" to the program to indicate stdin is the source, but not necessary if std::cin.rdbuf()->in_avail()
can work.
My experience:
std::cin.rdbuf()->in_avail()
doesn't work, it's always false.getline(std::cin, l)
orstd::istream_iterator<char>(std::cin)
always skips with no action.
It seems that cin/stdin
is always unavailable in /subsystem:windows
executables.
The only thing I managed to make work, was to call:
AttachConsole(-1);
freopen("CONIN$", "r", stdin);
After that, the C style functions like fread(...stdin)
are able to access the TTY input (also isatty
returns non 0). But it's not what I'm looking for, it forgoes of the input pipe, I don't want keyboard input. (Secondary remark: C++ streams don't reconnect. std::cin remains broken)
I want my input pipe (that are supposed to be set ready by the likes of CreateProcess or fork() within cmd.exe I guess?).
Full source:
#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
The CMake has:
add_executable(MyApp WIN32 ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
The WIN32 cmake argument creates a build command line for the linker that looks like this:
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"