我正在编写一个C++(Windows)客户端控制台应用程序,该应用程序从STDIN上的匿名管道读取。我希望能够按如下方式使用我的程序:
echo input text here | my_app.exe
并在应用程序中使用管道插入的文本执行某些操作
或
my_app.exe
,然后在应用内使用一些默认文本,而不是管道中的输入。
鉴于第一种情况,我目前有成功从 STDIN 上的管道读取的代码:
#include <Windows.h>
#include <iostream>
#include <string>
#define BUFSIZE 4096
int main(int argc, const char *argv[]) {
char char_buffer[BUFSIZE];
DWORD bytes_read;
HANDLE stdin_handle;
BOOL continue_reading;
unsigned int required_size;
bool read_successful = true;
stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
if (stdin_handle == INVALID_HANDLE_VALUE) {
std::cout << "Error: invalid handle value!nn";
} else {
continue_reading = true;
while (continue_reading) {
continue_reading = ReadFile(stdin_handle, char_buffer, BUFSIZE,
&bytes_read, NULL);
if (continue_reading) {
if (bytes_read != 0) {
// Output what we have read so far
for (unsigned int i = 0; i < bytes_read; i++) {
std::cout << char_buffer[i];
}
} else {
continue_reading = false;
}
}
}
}
return 0;
}
我知道我对匿名管道的唯一选择是使用 ReadFile 进行阻塞读取。如果我理解正确,关于我如何调用它,ReadFile 将继续从 STDIN 上的缓冲区读取,直到它检测到管道另一端的写入操作结束(也许读取某种"写入结束"令牌??我想知道是否有某种"开始写入"令牌,如果正在管道传输某些内容,我可以在调用 ReadFile 之前检查 STDIN。如果是这种情况,我可以跳过调用 ReadFile 并使用一些默认文本。
如果没有办法做到这一点,我总是可以传入一个命令行参数,该参数表示我不应该检查管道而只使用默认文本(或相反),但我更愿意按照我指定的方式进行操作。
看看PeekNamedPipe()
. 尽管它的名字,它适用于命名管道和匿名管道。
int main(int argc, const char *argv[])
{
char char_buffer[BUFSIZE];
DWORD bytes_read;
DWORD bytes_avail;
DWORD dw;
HANDLE stdin_handle;
bool is_pipe;
stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
is_pipe = !GetConsoleMode(stdin_handle, &dw);
if (stdin_handle == INVALID_HANDLE_VALUE) {
std::cout << "Error: invalid handle value!nn";
} else {
while (1) {
if (is_pipe) {
if (PeekNamedPipe(stdin_handle, NULL, 0, NULL, &bytes_avail, NULL)) {
if (bytes_avail == 0) {
Sleep(100);
continue;
}
}
}
if (!ReadFile(stdin_handle, char_buffer, min(bytes_avail, BUFSIZE), &bytes_read, NULL)) {
break;
}
if (bytes_read == 0) {
break;
}
// Output what we have read so far
for (unsigned int i = 0; i < bytes_read; i++) {
std::cout << char_buffer[i];
}
}
}
return 0;
}
看起来您在这里真正要做的是确定您是否具有控制台输入(使用默认值)与管道输入(使用管道输入)。
建议直接测试,而不是尝试检查输入是否准备就绪:尝试嗅探管道中是否有数据的问题在于,如果源应用生成输出的速度很慢,则应用可能会做出不正确的假设,因为还没有可用的输入。(也有可能,由于提前键入,用户可能已经键入了该区域,以便在您的应用开始检查输入是否可用之前,该区域已准备好从控制台 STDIN 读取。
另外,请记住,允许将应用与文件重定向一起使用(而不仅仅是管道)可能很有用 - 例如:
myapp.exe < some_input_file
在 unix 上进行这种"交互模式,与与重定向输入一起使用"测试的经典方法是使用 isatty(); 幸运的是,Windows CRT 中有一个等效项 - 参见函数 _isatty(); 或者使用 GetFileType() 检查 GetStdHandle(STD_INPUT_HANDLE) 上的FILE_TYPE_CHAR - 或者像 Remy 一样使用 GetConsoleMode,这只会在真正的控制台句柄上成功。
在使用执行同步 ReadFile 调用的第二个线程时,这也可以在没有重叠 I/O 的情况下工作。然后主线程等待任意时间并像上面一样运行......
希望这有帮助...