Just Let It Flow

March 2, 2009

Multiple Consoles in a Windows App

Filed under: Code,Windows — adeyblue @ 4:29 am

Every so often the topic of having multiple consoles for a single application comes up on various fora around the internet and the reaction is, that you can’t. While this is correct from a technical/windows architecture standpoint, the illusion of multiple consoles can be realised in differing ways with varying by degrees of work required.

The first opportunity for multiple consoles is to look into how Windows allocates consoles. The first port of call is AllocConsole, which will setup a console for a process if it doesn’t already have one. The value indicating whether an application has a console is held in the RTL_USER_PROCESS_PARAMS structure pointed to by the PEB, in the ConsoleHandle member. If this value is NULL/0, then a new console will be created otherwise nothing will happen. Knowing this, we can create multiple consoles by nulling out the value and calling AllocConsole for every console we want to create. Since the console handle that is in the PEB struct is the one used by the Windows function, the logical conclusion for multiple consoles would be to cache the values that AllocConsole puts there and swap them in and out as required. Unfortunately, CSRSS, the process which doles out and owns the console windows, only keeps track of the last window it gave a process. Operations on any of the previous handles it allocated return an error leaving all other console windows in a zombie state. While it is fun to have lots of uncloseable console windows around, it’s no go as far as practical usage.

An aid to a more practical method of multi-consoling a process was introduced with Windows XP. Utilising the AttachConsole function, a process can connect to the console of an arbitrary process and use it as its own. Combining this with spawning console-owning child processes and a degree of multi-consoling can be achieved, all that’s required are the process id’s of the children and 3 freopen calls to rebind the stdin, stdout and stderr handles to the new console. While this is an easily implementable and practical way of going about it, using this method means that the parent process cannot have a console of its’ own as FreeConsole is needed by the switching method. Another downside is that only one console can be interactive at a time, leaving the others alive but pretty useless until they’re switched to.

A third option for effective multi-consoling starts similarly to the previous option. When a child process is spawned, rather than attaching to it’s console directly, the handles it uses for stdin, stdout and stderr can be redirected to a pair of named pipes so that whatever is written to them can be read from the parent. The implementation is made a little more tricky as extra work has to be done in the child process both to enable keyboard input and to echo any prompts to the console. The other bone of contention when implementing this method is the wrapper around the pipe communication. Communicating simple types is easy enough with the bare Read/WriteFile functions, but if you want to use objects like std::string, or be able to use other C++ standard library functionality such as std::i/ostream_iterator and std::copy, the complexity increases dramatically.

Luckily, now there’s a convenient, easy to use library that provides all of this functionality for free. Imaginatively called Multi-Console, all it requires is the inclusion of a header and linking with the generated lib and you’re all set. The lib handles the generation and deletion of a “surrogate” console exe and required input/output redirection for child processes. Lifetime of a console is governed by a reference counted handle, and communication is achieved through the normal iostream interface.

Here’s the simple example program:

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include "Console.h"
 
int main()
{
    // get a handle to the process' default built console provided by Windows
    Con::Handle original = Con::GetOriginal();
    if(original) // this will be false for a non-console application
    {
        original << "Printing hello to original console\n";
    }
    std::cout << "Printing hello to original console again\n";
    // creates a new console
    Con::Handle second;
    if(Con::Create(second))
    {
        // bring it to the front of all windows
        second->bring_to_top();
        // write a message to it
        second << "Printing to second, please input an int\n";
        // grab an int from it
        int test = 0;
        second >> test;
        // echo the int
        second << "You entered " << test << '\n';
        // create a third
        Con::Handle third;
        if(Con::Create(third))
        {
            // bring it to the top
            third->bring_to_top();
            // print a messgae
            third << "Enter a series of ints\n";
            std::vector<int> intVec;
            // grab a bunch of ints from the third's console
            // the handle needs derefencing to grab the istream from a Handle
            std::copy(std::istream_iterator<int>(*third), std::istream_iterator<int>(), std::back_inserter(intVec));
            // bring second back to the forefront
            second->bring_to_top();
            // echo the ints
            second << "Some ints were read in on third, they were:\n";
            // make_ostream_iterator is required in order to see the results on second. 
            // The copy operation will work with normal std::ostream_iterator but
            // flush will need to be called on the console handle to make them visible
            //
            // the commented line below is equivalent to the two below it
            // std::copy(intVec.begin(), intVec.end(), make_ostream_iterator<int>(second, "\n"));
            std::copy(intVec.begin(), intVec.end(), std::ostream_iterator<int>(*second, "\n"));
            second->flush();
            // simulate losing the reference to the second console
            // this will close second
            second.reset();
            // bring third back to the front
            third->bring_to_top();
            // do the same with it
            third << "Second destroyed, killing third\n";
            third.reset();
        }
        if(original)
        {
            original->bring_to_top();
            // losing a reference to original doesn't destroy the processes console
            original << "Printing to first\n";
        }
    }
    return 0;
}

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Powered by WordPress