我在多线程应用程序中使用控制台。现在,它只接受输出(printf等),到目前为止我没有任何问题。但是,我希望能够支持控制台输入,这就是我生活变得复杂的地方。
为了预先警告,我对使用控制台输入和输出的更复杂的细微差别非常不熟悉。我在这个主题上的经验并没有比printf / cout,scanf / cin和使用SetConsoleTextAttribute()
更改颜色(在窗口上)更进一步。
我更希望尽可能保持程序的交叉兼容性,但我不反对必须编写特定于平台的代码,只要我能为其他平台找到可行的替代方案。
从概念上讲,我希望控制台能够运行它自己的线程,这样它就可以在使用cin等待时锁定,而不会冻结整个程序或其他线程。任何线程都可以将控制台输出发送到此线程,该线程将以干净的方式输出(可能使用线程安全队列),并且控制台读取的任何输入都会将命令发送到相应的线程。
我的第一个问题是,当我输入一些输入时,任何输出都会显示在我正在键入的内容中。我想要处理的解决方案是保留控制台的底线以进行输入,并将输出转到第二个最后一行,将输入线向下推。我怎么能这样做?
答案 0 :(得分:2)
你真的不想在写入控制台的其余部分时试图保留部分控制台输入。至少,如果你只是在写滚动文本的话。这是可能的,但充满了错误和方式比它的价值更麻烦。有关问题的一些提示,请参阅Async Console Output。
当然,使用conio.h不可能做到这一点。
您可以分配两个控制台屏幕缓冲区,其中一个用于输入,另一个用于程序输出。程序正常运行时,会选择输出屏幕缓冲区,并在屏幕上看到输出滚动。但是当你的程序正在等待用户输入时,你交换屏幕缓冲区以便输出仍然存在,但是在另一个屏幕缓冲区中。
您最终必须自己格式化输出并调用WriteConsoleOutput,并将其传递给您要写入的屏幕缓冲区的句柄。它变得非常复杂,而且很难做到正确。如果它甚至可能。我知道我过去花了太多时间,而且总是有奇怪的问题。
我不会说你想做什么是不可能的。但是,我会说,你将会遇到困难。
答案 1 :(得分:2)
Wellp,我用pdcurses解决了它。如果其他人想要做类似的事情,请按照我的方式做。首先,我这样初始化控制台:
Console::Console(bool makeConsole)
{
if (makeConsole == false)
return;
if (self)
throw ("You only need one console - do not make another!\n");
self = this;
#ifdef WIN32
AllocConsole();
#endif
initscr();
inputLine = newwin(1, COLS, LINES - 1, 0);
outputLines = newwin(LINES - 1, COLS, 0, 0);
if (has_colors())
{
start_color();
for (int i = 1; i <= COLOR_WHITE; ++i)
{
init_pair(i, i, COLOR_BLACK);
}
}
else
wprintw(outputLines, "Terminal cannot print colors.\n");
scrollok(outputLines, TRUE);
scrollok(inputLine, TRUE);
leaveok(inputLine, TRUE);
nodelay(inputLine, TRUE);
cbreak();
noecho();
keypad(inputLine, TRUE);
initCommands();
hello("Starting %s.\n", APP_NAME);
hellomore("Version %i.%i.%i.\n\n", APP_MAJORVER, APP_MINORVER, APP_REVISION);
}
接下来,这是负责处理输出的函数。它实际上非常简单,我不需要做任何特别的事情来保持线程安全。我可能根本没有遇到任何问题,但一个简单的解决方法是在它上面打一个互斥量。
void Console::sendFormattedMsg(short prefixColor, const char* prefix, short color, const char* format, ...)
{
if (!self)
return;
va_list args;
va_start(args, format);
if (has_colors())
{
if (prefix)
{
wattron(outputLines, A_BOLD | COLOR_PAIR(prefixColor));
wprintw(outputLines, prefix);
}
if (color == COLOR_WHITE)
wattroff(outputLines, A_BOLD);
wattron(outputLines, COLOR_PAIR(color));
vwprintw(outputLines, format, args);
wattroff(outputLines, A_BOLD | COLOR_PAIR(color));
}
else
{
wprintw(outputLines, prefix);
vwprintw(outputLines, format, args);
}
wrefresh(outputLines);
va_end(args);
}
最后,输入。这个需要相当多的微调。
void Console::inputLoop(void)
{
static string input;
wattron(inputLine, A_BOLD | COLOR_PAIR(COLOR_WHITE));
wprintw(inputLine, "\n> ");
wattroff(inputLine, A_BOLD | COLOR_PAIR(COLOR_WHITE));
wprintw(inputLine, input.c_str());
wrefresh(inputLine);
char c = wgetch(inputLine);
if (c == ERR)
return;
switch (c)
{
case '\n':
if (input.size() > 0)
{
sendFormattedMsg(COLOR_WHITE, "> ", COLOR_WHITE, input.c_str());
cprint("\n");
executeCommand(&input[0]);
input.clear();
}
break;
case 8:
case 127:
if (input.size() > 0) input.pop_back();
break;
default:
input += c;
break;
}
}
这是从处理窗口消息的同一个线程的每一帧运行的。我使用wgetch()
禁用了nodelay()
的阻止行为,从而无需在其自己的线程中运行控制台输入。我也禁用了回显并手动回显输入。在输入窗口上启用滚动允许我使用简单的&#34; \ n&#34;清除它的内容,如果用户输入任何内容,则用更新的内容替换它。它支持一个简单的多线程终端能够输入输入以及从多个线程接收输出的所有功能。
答案 2 :(得分:1)
要禁用回显字符,请检查以下内容: Reading a password from std::cin
可能将其与this guy's blog post on non-blocking Win32 console io结合使用。