Multithreaded console I/O Multithreaded console I/O multithreading multithreading

Multithreaded console I/O


You really don't want to go down the road of trying to reserve part of the console for input while writing to the rest of the console. At least, not if you're just writing scrolling text. It's possible, but fraught with error and way more trouble than it's worth. See Async Console Output for a few hints of the problems.

Certainly, it's not possible to do this using just conio.h.

You could allocate two console screen buffers, with one being for input and one for program output. When your program is running normally, the output screen buffer is selected and you see the output scrolling on the screen. But when your program is waiting for user input, you swap screen buffers so that the output is still going, but in the other screen buffer.

You end up having to format the output yourself and call WriteConsoleOutput, passing it the handle of the screen buffer you want to write to. It gets complicated in a real hurry, and it's very difficult to get right. If it's even possible. I know I've spent way too much time on it in the past, and there were always odd problems.

I won't say that what you want to do isn't possible. I will say, however, that you're going to have a tough time with it.


Wellp, I solved it using pdcurses. In case someone else wants to do something similar, here's how I did it. First, I initialize the console thusly:

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);}

Next, This is the function responsible for handling output. It's actually very simple, I don't need to do anything special to keep it thread-safe. I might simply not have encountered any issues with it, but an easy fix would be to slap a mutex on it.

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);}

And finally, input. This one required quite a bit of fine-tuning.

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;    }}

This is run every frame from the same thread that handles window messages. I disabled wgetch()'s blocking behavior using nodelay(), eliminating the need to have console input running in it's own thread. I also disable echoing and echo the input manually. Enabling scrolling on the input window allows me to clear it's contents using a simple "\n", replacing it with updated contents if the user has typed anything. It supports everything one would expect from a simple, multi-threaded terminal capable to typing input as well as receiving output from multiple threads.


To disable echoing characters check this out:Reading a password from std::cin

Maybe combine that with this guy's blog post on non-blocking Win32 console io.

You might also find this stuff useful:conio.h,pdcurses