Using multiple console windows for output

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite












In my current project I found the need to concurrently track the progress of multiple concurrent components, and having all of them output their (debug) information into a single console window made things very unorganised. So I wrote this little library so I can now display each component's output in a dedicated console window.



Notes



  • As Windows only allows 1 console per process, I need to start a little helper process per additional console. The path is currently hard-coded, but that can easily be changed.

  • The process wrapper is very minimalistic - it only does what is necessary for this library to work and nothing more. (I might extend it in the future, though.)

pipe.hpp



#pragma once
#include <string>
#include <Windows.h>

namespace win32

enum class pipe_state

client,
server_connected,
server_disconnected
;

enum class pipe_mode

read,
write,
both
;

struct buffer_size

const size_t value;

explicit buffer_size(const size_t value)
: value value
;

struct read_buffer_size : public buffer_size

explicit read_buffer_size(const size_t value)
: buffer_size value
;

struct write_buffer_size : buffer_size

explicit write_buffer_size(const size_t value)
: buffer_size value
;

struct buffer

char *data;
buffer_size size;

buffer(char *data, size_t size)
: data data ,
size size
;

class pipe

HANDLE handle;
std::string pipe_name;
pipe_state state;

void connect();
void disconnect();

pipe(HANDLE handle, std::string name, pipe_state state);
public:
static pipe create(
const std::string &name,
pipe_mode mode,
read_buffer_size read_buffer,
write_buffer_size write_buffer);

static pipe open(const std::string &name, pipe_mode mode);

pipe(const pipe &other) = delete;
pipe(pipe &&other) noexcept;

pipe &operator=(const pipe &other) = delete;
pipe &operator=(pipe &&other) noexcept;

virtual ~pipe();

void close();
const std::string &name() const;
size_t read(buffer buf);
void write(const buffer buf);
;



pipe.cpp



#include "pipe.hpp"
#include <thread>

using namespace std::string_literals;

namespace win32

constexpr static auto pipe_name_prefix = R"(\.pipe)";

void pipe::connect()

if (state != pipe_state::server_disconnected) return;

if (!ConnectNamedPipe(handle, nullptr) && GetLastError() != ERROR_PIPE_CONNECTED)

CloseHandle(handle);
throw std::runtime_error("unable to connect to pipe");


state = pipe_state::server_connected;


void pipe::disconnect()

if (state == pipe_state::server_connected)

DisconnectNamedPipe(handle);
state = pipe_state::server_disconnected;



pipe::pipe(HANDLE handle, std::string name, pipe_state state)
: handle handle , pipe_name std::move(name) , state state

pipe pipe::create(
const std::string &name,
pipe_mode mode,
read_buffer_size read_buffer,
write_buffer_size write_buffer)
PIPE_WAIT,
1,
write_buffer.value,
read_buffer.value,
1,
nullptr);

if (handle == INVALID_HANDLE_VALUE) throw std::runtime_error("unable to create pipe");

return pipe handle, name, pipe_state::server_disconnected ;


pipe pipe::open(const std::string &name, pipe_mode mode)

auto full_name = pipe_name_prefix + name;

DWORD open_mode = 0;

switch (mode)
GENERIC_WRITE;
break;


for (auto tries = 3; tries > 0; --tries)

const auto handle = CreateFileA(full_name.c_str(), open_mode, 0, nullptr, OPEN_EXISTING, 0, nullptr);

if (handle != INVALID_HANDLE_VALUE)

return pipe handle, name, pipe_state::client ;


if (GetLastError() != ERROR_PIPE_BUSY) throw std::runtime_error("unable to open pipe");
if (!WaitNamedPipeA(name.c_str(), 10000)) throw std::runtime_error("unable to wait on pipe");

std::this_thread::sleep_for(std::chrono::milliseconds(1000));


throw std::runtime_error("unable to open pipe");


pipe::pipe(pipe &&other) noexcept
: handle other.handle , pipe_name other.pipe_name , state other.state

other.handle = INVALID_HANDLE_VALUE;


pipe &pipe::operator=(pipe &&other) noexcept

close();

handle = other.handle;
state = other.state;
pipe_name = std::move(other.pipe_name);
other.handle = INVALID_HANDLE_VALUE;

return *this;


pipe::~pipe()

close();


void pipe::close()

if (handle != INVALID_HANDLE_VALUE)

disconnect();
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;



const std::string &pipe::name() const

return pipe_name;


size_t pipe::read(buffer buf)

connect();

DWORD bytes_read;
if (!ReadFile(handle, buf.data, buf.size.value, &bytes_read, nullptr))

throw std::runtime_error("pipe read failed");


return bytes_read;


void pipe::write(const buffer buf)




process.hpp



#pragma once
#include <string>
#include <Windows.h>
#include <vector>

namespace win32

enum class process_creation_flags : DWORD

none = 0,
new_console = CREATE_NEW_CONSOLE,
;

class process

std::vector<char> cmdline;
PROCESS_INFORMATION process_info;

public:
process(
const std::string &executable_path,
const std::string &command_line_args,
process_creation_flags flags = process_creation_flags::none);

process(const process &) = delete;
process(process &&other) noexcept;

process &operator=(const process &) = delete;
process &operator=(process &&other) noexcept;

void terminate(int exit_code = 0);

virtual ~process() noexcept;
;



process.cpp



#include "process.hpp"

namespace win32

process::process(const std::string& executable_path, const std::string& command_line_args, process_creation_flags flags)

std::copy(std::begin(command_line_args), std::end(command_line_args), std::back_inserter(cmdline));
cmdline.push_back('');

STARTUPINFOA startup_info;
GetStartupInfoA(&startup_info);

if (!CreateProcessA(executable_path.c_str(), cmdline.data(), nullptr, nullptr, false, static_cast<DWORD>(flags), nullptr, nullptr, &startup_info, &process_info))

throw std::runtime_error("process creation failed!");



process::process(process &&other) noexcept : cmdline std::move(other.cmdline) , process_info other.process_info

other.process_info.hThread = INVALID_HANDLE_VALUE;
other.process_info.hProcess = INVALID_HANDLE_VALUE;


process & process::operator=(process &&other) noexcept

terminate();

cmdline = std::move(other.cmdline);
process_info = other.process_info;

other.process_info.hThread = INVALID_HANDLE_VALUE;
other.process_info.hProcess = INVALID_HANDLE_VALUE;

return *this;


void process::terminate(int exit_code)

if (process_info.hThread != INVALID_HANDLE_VALUE)

TerminateThread(process_info.hThread, exit_code);
CloseHandle(process_info.hThread);
process_info.hThread = INVALID_HANDLE_VALUE;


if (process_info.hProcess != INVALID_HANDLE_VALUE)
TerminateProcess(process_info.hProcess, exit_code);
CloseHandle(process_info.hProcess);
process_info.hProcess = INVALID_HANDLE_VALUE;



process::~process() noexcept

terminate();




console.hpp



#pragma once
#include <string>
#include <vector>
#include "pipe.hpp"
#include "process.hpp"

namespace win32

class console_streambuf : public std::basic_streambuf<char>

pipe &output;
std::vector<char> buffer;

protected:
int_type overflow(int_type) override;
int_type sync() override;

virtual void publish();

public:
explicit console_streambuf(pipe &output, buffer_size size);
;

class console : public std::ostream

pipe output;
process helper;

static std::string create_unique_pipe_name(std::string base);
static std::string build_command_line(std::string title, std::string pipe_name);

public:
explicit console(std::string name);
;



console.cpp



#include "console.hpp"
#include <thread>
#include <utility>

using namespace std::string_literals;

namespace win32

std::basic_streambuf<char>::int_type console_streambuf::overflow(int_type character)

publish();

if (character != traits_type::eof())

*pptr() = character;
pbump(1);
return character;


return traits_type::eof();


std::basic_streambuf<char>::int_type console_streambuf::sync()

publish();
return 0;


void console_streambuf::publish()

const auto size = pptr() - pbase();
if (size > 0)

output.write(win32::buffer buffer.data(), static_cast<size_t>(size) );
setp(buffer.data(), buffer.data() + buffer.size());



console_streambuf::console_streambuf(pipe &output, buffer_size buffer_size)
: output output , buffer(buffer_size.value)

const auto begin = buffer.data();
const auto end = begin + buffer.size();
setp(begin, end);



constexpr static auto default_buffer_size = 4096u;
const std::string executable_name = "console_helper.exe"s;

std::string console::create_unique_pipe_name(std::string base)

return base + std::to_string(::rand());


std::string console::build_command_line(std::string title, std::string pipe_name)

return executable_name + " "" + title + "" "" + pipe_name + """;


console::console(std::string name)
: basic_ostream new console_streambuf output, buffer_size default_buffer_size ,
output
pipe::create(
create_unique_pipe_name(name),
pipe_mode::write,
read_buffer_size 0 ,
write_buffer_size default_buffer_size )
,
helper executable_name, build_command_line(name, output.name()), process_creation_flags::new_console



console_helper.cpp



#include <windows.h>
#include <iostream>
#include "pipe.hpp"
#include <array>

int main(int argc, char *argv)

if(argc != 3)

std::cerr << "not enough argumentsn" << "call syntax: console_helper [title] [pipe name]" << std::endl;
return -1;


try
SetConsoleTitleA(argv[1]);
auto pipe = win32::pipe::open(argv[2], win32::pipe_mode::read);

auto buffer = std::array<char, 1024>;

for (;;)

const auto bytes_read = pipe.read(win32::buffer buffer.data(), buffer.size() );
std::cout.write(buffer.data(), bytes_read);


catch(std::exception& ex)

std::cerr << "Caught exception: " << ex.what() << std::endl;
std::cin.get();


return 0;



Example usage



#include "console.hpp"
#include <iomanip>

int main()
// creation
win32::console debug "debug" ;

// using new console for output
debug << "debug consolen";
// std stream manipulators function as usual
debug << std::hex << std::setfill('0') << std::setw(8) << 123456 << "nflush!n" << std::flush;

// wait for user input so the display stays up
std::cin.get();
// RAII destroys all used resources and terminates the helper process







share|improve this question





















  • skimming through, seems reasonable. I would wrap the HANDLE tightly in its own RTTI construct.
    – JDługosz
    May 25 at 13:22










  • @JDługosz, please don't review code in comments. I know your comment isn't an elaborate answer, but for future reference: please add an actual answer.
    – Daniel
    May 25 at 13:28










  • @JDługosz: I though about it, but it would get complicated. there are already 3 types of HANDLEs used in the code (pipe, process and thread), each requiring different acquire and release procedures and providing different capabilities, so there was little commonality (basically the only code duplication are the calls to CloseHandle between those).
    – hoffmale
    May 25 at 13:32











  • Common destructor is enough, as that is the point! Copy constructor calling DuplicateHandle is common, too. I don’t have a kill as part of my HANDLE wrapper. Automatically doing the move semantics would be handy for you.
    – JDługosz
    May 25 at 22:14
















up vote
4
down vote

favorite












In my current project I found the need to concurrently track the progress of multiple concurrent components, and having all of them output their (debug) information into a single console window made things very unorganised. So I wrote this little library so I can now display each component's output in a dedicated console window.



Notes



  • As Windows only allows 1 console per process, I need to start a little helper process per additional console. The path is currently hard-coded, but that can easily be changed.

  • The process wrapper is very minimalistic - it only does what is necessary for this library to work and nothing more. (I might extend it in the future, though.)

pipe.hpp



#pragma once
#include <string>
#include <Windows.h>

namespace win32

enum class pipe_state

client,
server_connected,
server_disconnected
;

enum class pipe_mode

read,
write,
both
;

struct buffer_size

const size_t value;

explicit buffer_size(const size_t value)
: value value
;

struct read_buffer_size : public buffer_size

explicit read_buffer_size(const size_t value)
: buffer_size value
;

struct write_buffer_size : buffer_size

explicit write_buffer_size(const size_t value)
: buffer_size value
;

struct buffer

char *data;
buffer_size size;

buffer(char *data, size_t size)
: data data ,
size size
;

class pipe

HANDLE handle;
std::string pipe_name;
pipe_state state;

void connect();
void disconnect();

pipe(HANDLE handle, std::string name, pipe_state state);
public:
static pipe create(
const std::string &name,
pipe_mode mode,
read_buffer_size read_buffer,
write_buffer_size write_buffer);

static pipe open(const std::string &name, pipe_mode mode);

pipe(const pipe &other) = delete;
pipe(pipe &&other) noexcept;

pipe &operator=(const pipe &other) = delete;
pipe &operator=(pipe &&other) noexcept;

virtual ~pipe();

void close();
const std::string &name() const;
size_t read(buffer buf);
void write(const buffer buf);
;



pipe.cpp



#include "pipe.hpp"
#include <thread>

using namespace std::string_literals;

namespace win32

constexpr static auto pipe_name_prefix = R"(\.pipe)";

void pipe::connect()

if (state != pipe_state::server_disconnected) return;

if (!ConnectNamedPipe(handle, nullptr) && GetLastError() != ERROR_PIPE_CONNECTED)

CloseHandle(handle);
throw std::runtime_error("unable to connect to pipe");


state = pipe_state::server_connected;


void pipe::disconnect()

if (state == pipe_state::server_connected)

DisconnectNamedPipe(handle);
state = pipe_state::server_disconnected;



pipe::pipe(HANDLE handle, std::string name, pipe_state state)
: handle handle , pipe_name std::move(name) , state state

pipe pipe::create(
const std::string &name,
pipe_mode mode,
read_buffer_size read_buffer,
write_buffer_size write_buffer)
PIPE_WAIT,
1,
write_buffer.value,
read_buffer.value,
1,
nullptr);

if (handle == INVALID_HANDLE_VALUE) throw std::runtime_error("unable to create pipe");

return pipe handle, name, pipe_state::server_disconnected ;


pipe pipe::open(const std::string &name, pipe_mode mode)

auto full_name = pipe_name_prefix + name;

DWORD open_mode = 0;

switch (mode)
GENERIC_WRITE;
break;


for (auto tries = 3; tries > 0; --tries)

const auto handle = CreateFileA(full_name.c_str(), open_mode, 0, nullptr, OPEN_EXISTING, 0, nullptr);

if (handle != INVALID_HANDLE_VALUE)

return pipe handle, name, pipe_state::client ;


if (GetLastError() != ERROR_PIPE_BUSY) throw std::runtime_error("unable to open pipe");
if (!WaitNamedPipeA(name.c_str(), 10000)) throw std::runtime_error("unable to wait on pipe");

std::this_thread::sleep_for(std::chrono::milliseconds(1000));


throw std::runtime_error("unable to open pipe");


pipe::pipe(pipe &&other) noexcept
: handle other.handle , pipe_name other.pipe_name , state other.state

other.handle = INVALID_HANDLE_VALUE;


pipe &pipe::operator=(pipe &&other) noexcept

close();

handle = other.handle;
state = other.state;
pipe_name = std::move(other.pipe_name);
other.handle = INVALID_HANDLE_VALUE;

return *this;


pipe::~pipe()

close();


void pipe::close()

if (handle != INVALID_HANDLE_VALUE)

disconnect();
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;



const std::string &pipe::name() const

return pipe_name;


size_t pipe::read(buffer buf)

connect();

DWORD bytes_read;
if (!ReadFile(handle, buf.data, buf.size.value, &bytes_read, nullptr))

throw std::runtime_error("pipe read failed");


return bytes_read;


void pipe::write(const buffer buf)




process.hpp



#pragma once
#include <string>
#include <Windows.h>
#include <vector>

namespace win32

enum class process_creation_flags : DWORD

none = 0,
new_console = CREATE_NEW_CONSOLE,
;

class process

std::vector<char> cmdline;
PROCESS_INFORMATION process_info;

public:
process(
const std::string &executable_path,
const std::string &command_line_args,
process_creation_flags flags = process_creation_flags::none);

process(const process &) = delete;
process(process &&other) noexcept;

process &operator=(const process &) = delete;
process &operator=(process &&other) noexcept;

void terminate(int exit_code = 0);

virtual ~process() noexcept;
;



process.cpp



#include "process.hpp"

namespace win32

process::process(const std::string& executable_path, const std::string& command_line_args, process_creation_flags flags)

std::copy(std::begin(command_line_args), std::end(command_line_args), std::back_inserter(cmdline));
cmdline.push_back('');

STARTUPINFOA startup_info;
GetStartupInfoA(&startup_info);

if (!CreateProcessA(executable_path.c_str(), cmdline.data(), nullptr, nullptr, false, static_cast<DWORD>(flags), nullptr, nullptr, &startup_info, &process_info))

throw std::runtime_error("process creation failed!");



process::process(process &&other) noexcept : cmdline std::move(other.cmdline) , process_info other.process_info

other.process_info.hThread = INVALID_HANDLE_VALUE;
other.process_info.hProcess = INVALID_HANDLE_VALUE;


process & process::operator=(process &&other) noexcept

terminate();

cmdline = std::move(other.cmdline);
process_info = other.process_info;

other.process_info.hThread = INVALID_HANDLE_VALUE;
other.process_info.hProcess = INVALID_HANDLE_VALUE;

return *this;


void process::terminate(int exit_code)

if (process_info.hThread != INVALID_HANDLE_VALUE)

TerminateThread(process_info.hThread, exit_code);
CloseHandle(process_info.hThread);
process_info.hThread = INVALID_HANDLE_VALUE;


if (process_info.hProcess != INVALID_HANDLE_VALUE)
TerminateProcess(process_info.hProcess, exit_code);
CloseHandle(process_info.hProcess);
process_info.hProcess = INVALID_HANDLE_VALUE;



process::~process() noexcept

terminate();




console.hpp



#pragma once
#include <string>
#include <vector>
#include "pipe.hpp"
#include "process.hpp"

namespace win32

class console_streambuf : public std::basic_streambuf<char>

pipe &output;
std::vector<char> buffer;

protected:
int_type overflow(int_type) override;
int_type sync() override;

virtual void publish();

public:
explicit console_streambuf(pipe &output, buffer_size size);
;

class console : public std::ostream

pipe output;
process helper;

static std::string create_unique_pipe_name(std::string base);
static std::string build_command_line(std::string title, std::string pipe_name);

public:
explicit console(std::string name);
;



console.cpp



#include "console.hpp"
#include <thread>
#include <utility>

using namespace std::string_literals;

namespace win32

std::basic_streambuf<char>::int_type console_streambuf::overflow(int_type character)

publish();

if (character != traits_type::eof())

*pptr() = character;
pbump(1);
return character;


return traits_type::eof();


std::basic_streambuf<char>::int_type console_streambuf::sync()

publish();
return 0;


void console_streambuf::publish()

const auto size = pptr() - pbase();
if (size > 0)

output.write(win32::buffer buffer.data(), static_cast<size_t>(size) );
setp(buffer.data(), buffer.data() + buffer.size());



console_streambuf::console_streambuf(pipe &output, buffer_size buffer_size)
: output output , buffer(buffer_size.value)

const auto begin = buffer.data();
const auto end = begin + buffer.size();
setp(begin, end);



constexpr static auto default_buffer_size = 4096u;
const std::string executable_name = "console_helper.exe"s;

std::string console::create_unique_pipe_name(std::string base)

return base + std::to_string(::rand());


std::string console::build_command_line(std::string title, std::string pipe_name)

return executable_name + " "" + title + "" "" + pipe_name + """;


console::console(std::string name)
: basic_ostream new console_streambuf output, buffer_size default_buffer_size ,
output
pipe::create(
create_unique_pipe_name(name),
pipe_mode::write,
read_buffer_size 0 ,
write_buffer_size default_buffer_size )
,
helper executable_name, build_command_line(name, output.name()), process_creation_flags::new_console



console_helper.cpp



#include <windows.h>
#include <iostream>
#include "pipe.hpp"
#include <array>

int main(int argc, char *argv)

if(argc != 3)

std::cerr << "not enough argumentsn" << "call syntax: console_helper [title] [pipe name]" << std::endl;
return -1;


try
SetConsoleTitleA(argv[1]);
auto pipe = win32::pipe::open(argv[2], win32::pipe_mode::read);

auto buffer = std::array<char, 1024>;

for (;;)

const auto bytes_read = pipe.read(win32::buffer buffer.data(), buffer.size() );
std::cout.write(buffer.data(), bytes_read);


catch(std::exception& ex)

std::cerr << "Caught exception: " << ex.what() << std::endl;
std::cin.get();


return 0;



Example usage



#include "console.hpp"
#include <iomanip>

int main()
// creation
win32::console debug "debug" ;

// using new console for output
debug << "debug consolen";
// std stream manipulators function as usual
debug << std::hex << std::setfill('0') << std::setw(8) << 123456 << "nflush!n" << std::flush;

// wait for user input so the display stays up
std::cin.get();
// RAII destroys all used resources and terminates the helper process







share|improve this question





















  • skimming through, seems reasonable. I would wrap the HANDLE tightly in its own RTTI construct.
    – JDługosz
    May 25 at 13:22










  • @JDługosz, please don't review code in comments. I know your comment isn't an elaborate answer, but for future reference: please add an actual answer.
    – Daniel
    May 25 at 13:28










  • @JDługosz: I though about it, but it would get complicated. there are already 3 types of HANDLEs used in the code (pipe, process and thread), each requiring different acquire and release procedures and providing different capabilities, so there was little commonality (basically the only code duplication are the calls to CloseHandle between those).
    – hoffmale
    May 25 at 13:32











  • Common destructor is enough, as that is the point! Copy constructor calling DuplicateHandle is common, too. I don’t have a kill as part of my HANDLE wrapper. Automatically doing the move semantics would be handy for you.
    – JDługosz
    May 25 at 22:14












up vote
4
down vote

favorite









up vote
4
down vote

favorite











In my current project I found the need to concurrently track the progress of multiple concurrent components, and having all of them output their (debug) information into a single console window made things very unorganised. So I wrote this little library so I can now display each component's output in a dedicated console window.



Notes



  • As Windows only allows 1 console per process, I need to start a little helper process per additional console. The path is currently hard-coded, but that can easily be changed.

  • The process wrapper is very minimalistic - it only does what is necessary for this library to work and nothing more. (I might extend it in the future, though.)

pipe.hpp



#pragma once
#include <string>
#include <Windows.h>

namespace win32

enum class pipe_state

client,
server_connected,
server_disconnected
;

enum class pipe_mode

read,
write,
both
;

struct buffer_size

const size_t value;

explicit buffer_size(const size_t value)
: value value
;

struct read_buffer_size : public buffer_size

explicit read_buffer_size(const size_t value)
: buffer_size value
;

struct write_buffer_size : buffer_size

explicit write_buffer_size(const size_t value)
: buffer_size value
;

struct buffer

char *data;
buffer_size size;

buffer(char *data, size_t size)
: data data ,
size size
;

class pipe

HANDLE handle;
std::string pipe_name;
pipe_state state;

void connect();
void disconnect();

pipe(HANDLE handle, std::string name, pipe_state state);
public:
static pipe create(
const std::string &name,
pipe_mode mode,
read_buffer_size read_buffer,
write_buffer_size write_buffer);

static pipe open(const std::string &name, pipe_mode mode);

pipe(const pipe &other) = delete;
pipe(pipe &&other) noexcept;

pipe &operator=(const pipe &other) = delete;
pipe &operator=(pipe &&other) noexcept;

virtual ~pipe();

void close();
const std::string &name() const;
size_t read(buffer buf);
void write(const buffer buf);
;



pipe.cpp



#include "pipe.hpp"
#include <thread>

using namespace std::string_literals;

namespace win32

constexpr static auto pipe_name_prefix = R"(\.pipe)";

void pipe::connect()

if (state != pipe_state::server_disconnected) return;

if (!ConnectNamedPipe(handle, nullptr) && GetLastError() != ERROR_PIPE_CONNECTED)

CloseHandle(handle);
throw std::runtime_error("unable to connect to pipe");


state = pipe_state::server_connected;


void pipe::disconnect()

if (state == pipe_state::server_connected)

DisconnectNamedPipe(handle);
state = pipe_state::server_disconnected;



pipe::pipe(HANDLE handle, std::string name, pipe_state state)
: handle handle , pipe_name std::move(name) , state state

pipe pipe::create(
const std::string &name,
pipe_mode mode,
read_buffer_size read_buffer,
write_buffer_size write_buffer)
PIPE_WAIT,
1,
write_buffer.value,
read_buffer.value,
1,
nullptr);

if (handle == INVALID_HANDLE_VALUE) throw std::runtime_error("unable to create pipe");

return pipe handle, name, pipe_state::server_disconnected ;


pipe pipe::open(const std::string &name, pipe_mode mode)

auto full_name = pipe_name_prefix + name;

DWORD open_mode = 0;

switch (mode)
GENERIC_WRITE;
break;


for (auto tries = 3; tries > 0; --tries)

const auto handle = CreateFileA(full_name.c_str(), open_mode, 0, nullptr, OPEN_EXISTING, 0, nullptr);

if (handle != INVALID_HANDLE_VALUE)

return pipe handle, name, pipe_state::client ;


if (GetLastError() != ERROR_PIPE_BUSY) throw std::runtime_error("unable to open pipe");
if (!WaitNamedPipeA(name.c_str(), 10000)) throw std::runtime_error("unable to wait on pipe");

std::this_thread::sleep_for(std::chrono::milliseconds(1000));


throw std::runtime_error("unable to open pipe");


pipe::pipe(pipe &&other) noexcept
: handle other.handle , pipe_name other.pipe_name , state other.state

other.handle = INVALID_HANDLE_VALUE;


pipe &pipe::operator=(pipe &&other) noexcept

close();

handle = other.handle;
state = other.state;
pipe_name = std::move(other.pipe_name);
other.handle = INVALID_HANDLE_VALUE;

return *this;


pipe::~pipe()

close();


void pipe::close()

if (handle != INVALID_HANDLE_VALUE)

disconnect();
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;



const std::string &pipe::name() const

return pipe_name;


size_t pipe::read(buffer buf)

connect();

DWORD bytes_read;
if (!ReadFile(handle, buf.data, buf.size.value, &bytes_read, nullptr))

throw std::runtime_error("pipe read failed");


return bytes_read;


void pipe::write(const buffer buf)




process.hpp



#pragma once
#include <string>
#include <Windows.h>
#include <vector>

namespace win32

enum class process_creation_flags : DWORD

none = 0,
new_console = CREATE_NEW_CONSOLE,
;

class process

std::vector<char> cmdline;
PROCESS_INFORMATION process_info;

public:
process(
const std::string &executable_path,
const std::string &command_line_args,
process_creation_flags flags = process_creation_flags::none);

process(const process &) = delete;
process(process &&other) noexcept;

process &operator=(const process &) = delete;
process &operator=(process &&other) noexcept;

void terminate(int exit_code = 0);

virtual ~process() noexcept;
;



process.cpp



#include "process.hpp"

namespace win32

process::process(const std::string& executable_path, const std::string& command_line_args, process_creation_flags flags)

std::copy(std::begin(command_line_args), std::end(command_line_args), std::back_inserter(cmdline));
cmdline.push_back('');

STARTUPINFOA startup_info;
GetStartupInfoA(&startup_info);

if (!CreateProcessA(executable_path.c_str(), cmdline.data(), nullptr, nullptr, false, static_cast<DWORD>(flags), nullptr, nullptr, &startup_info, &process_info))

throw std::runtime_error("process creation failed!");



process::process(process &&other) noexcept : cmdline std::move(other.cmdline) , process_info other.process_info

other.process_info.hThread = INVALID_HANDLE_VALUE;
other.process_info.hProcess = INVALID_HANDLE_VALUE;


process & process::operator=(process &&other) noexcept

terminate();

cmdline = std::move(other.cmdline);
process_info = other.process_info;

other.process_info.hThread = INVALID_HANDLE_VALUE;
other.process_info.hProcess = INVALID_HANDLE_VALUE;

return *this;


void process::terminate(int exit_code)

if (process_info.hThread != INVALID_HANDLE_VALUE)

TerminateThread(process_info.hThread, exit_code);
CloseHandle(process_info.hThread);
process_info.hThread = INVALID_HANDLE_VALUE;


if (process_info.hProcess != INVALID_HANDLE_VALUE)
TerminateProcess(process_info.hProcess, exit_code);
CloseHandle(process_info.hProcess);
process_info.hProcess = INVALID_HANDLE_VALUE;



process::~process() noexcept

terminate();




console.hpp



#pragma once
#include <string>
#include <vector>
#include "pipe.hpp"
#include "process.hpp"

namespace win32

class console_streambuf : public std::basic_streambuf<char>

pipe &output;
std::vector<char> buffer;

protected:
int_type overflow(int_type) override;
int_type sync() override;

virtual void publish();

public:
explicit console_streambuf(pipe &output, buffer_size size);
;

class console : public std::ostream

pipe output;
process helper;

static std::string create_unique_pipe_name(std::string base);
static std::string build_command_line(std::string title, std::string pipe_name);

public:
explicit console(std::string name);
;



console.cpp



#include "console.hpp"
#include <thread>
#include <utility>

using namespace std::string_literals;

namespace win32

std::basic_streambuf<char>::int_type console_streambuf::overflow(int_type character)

publish();

if (character != traits_type::eof())

*pptr() = character;
pbump(1);
return character;


return traits_type::eof();


std::basic_streambuf<char>::int_type console_streambuf::sync()

publish();
return 0;


void console_streambuf::publish()

const auto size = pptr() - pbase();
if (size > 0)

output.write(win32::buffer buffer.data(), static_cast<size_t>(size) );
setp(buffer.data(), buffer.data() + buffer.size());



console_streambuf::console_streambuf(pipe &output, buffer_size buffer_size)
: output output , buffer(buffer_size.value)

const auto begin = buffer.data();
const auto end = begin + buffer.size();
setp(begin, end);



constexpr static auto default_buffer_size = 4096u;
const std::string executable_name = "console_helper.exe"s;

std::string console::create_unique_pipe_name(std::string base)

return base + std::to_string(::rand());


std::string console::build_command_line(std::string title, std::string pipe_name)

return executable_name + " "" + title + "" "" + pipe_name + """;


console::console(std::string name)
: basic_ostream new console_streambuf output, buffer_size default_buffer_size ,
output
pipe::create(
create_unique_pipe_name(name),
pipe_mode::write,
read_buffer_size 0 ,
write_buffer_size default_buffer_size )
,
helper executable_name, build_command_line(name, output.name()), process_creation_flags::new_console



console_helper.cpp



#include <windows.h>
#include <iostream>
#include "pipe.hpp"
#include <array>

int main(int argc, char *argv)

if(argc != 3)

std::cerr << "not enough argumentsn" << "call syntax: console_helper [title] [pipe name]" << std::endl;
return -1;


try
SetConsoleTitleA(argv[1]);
auto pipe = win32::pipe::open(argv[2], win32::pipe_mode::read);

auto buffer = std::array<char, 1024>;

for (;;)

const auto bytes_read = pipe.read(win32::buffer buffer.data(), buffer.size() );
std::cout.write(buffer.data(), bytes_read);


catch(std::exception& ex)

std::cerr << "Caught exception: " << ex.what() << std::endl;
std::cin.get();


return 0;



Example usage



#include "console.hpp"
#include <iomanip>

int main()
// creation
win32::console debug "debug" ;

// using new console for output
debug << "debug consolen";
// std stream manipulators function as usual
debug << std::hex << std::setfill('0') << std::setw(8) << 123456 << "nflush!n" << std::flush;

// wait for user input so the display stays up
std::cin.get();
// RAII destroys all used resources and terminates the helper process







share|improve this question













In my current project I found the need to concurrently track the progress of multiple concurrent components, and having all of them output their (debug) information into a single console window made things very unorganised. So I wrote this little library so I can now display each component's output in a dedicated console window.



Notes



  • As Windows only allows 1 console per process, I need to start a little helper process per additional console. The path is currently hard-coded, but that can easily be changed.

  • The process wrapper is very minimalistic - it only does what is necessary for this library to work and nothing more. (I might extend it in the future, though.)

pipe.hpp



#pragma once
#include <string>
#include <Windows.h>

namespace win32

enum class pipe_state

client,
server_connected,
server_disconnected
;

enum class pipe_mode

read,
write,
both
;

struct buffer_size

const size_t value;

explicit buffer_size(const size_t value)
: value value
;

struct read_buffer_size : public buffer_size

explicit read_buffer_size(const size_t value)
: buffer_size value
;

struct write_buffer_size : buffer_size

explicit write_buffer_size(const size_t value)
: buffer_size value
;

struct buffer

char *data;
buffer_size size;

buffer(char *data, size_t size)
: data data ,
size size
;

class pipe

HANDLE handle;
std::string pipe_name;
pipe_state state;

void connect();
void disconnect();

pipe(HANDLE handle, std::string name, pipe_state state);
public:
static pipe create(
const std::string &name,
pipe_mode mode,
read_buffer_size read_buffer,
write_buffer_size write_buffer);

static pipe open(const std::string &name, pipe_mode mode);

pipe(const pipe &other) = delete;
pipe(pipe &&other) noexcept;

pipe &operator=(const pipe &other) = delete;
pipe &operator=(pipe &&other) noexcept;

virtual ~pipe();

void close();
const std::string &name() const;
size_t read(buffer buf);
void write(const buffer buf);
;



pipe.cpp



#include "pipe.hpp"
#include <thread>

using namespace std::string_literals;

namespace win32

constexpr static auto pipe_name_prefix = R"(\.pipe)";

void pipe::connect()

if (state != pipe_state::server_disconnected) return;

if (!ConnectNamedPipe(handle, nullptr) && GetLastError() != ERROR_PIPE_CONNECTED)

CloseHandle(handle);
throw std::runtime_error("unable to connect to pipe");


state = pipe_state::server_connected;


void pipe::disconnect()

if (state == pipe_state::server_connected)

DisconnectNamedPipe(handle);
state = pipe_state::server_disconnected;



pipe::pipe(HANDLE handle, std::string name, pipe_state state)
: handle handle , pipe_name std::move(name) , state state

pipe pipe::create(
const std::string &name,
pipe_mode mode,
read_buffer_size read_buffer,
write_buffer_size write_buffer)
PIPE_WAIT,
1,
write_buffer.value,
read_buffer.value,
1,
nullptr);

if (handle == INVALID_HANDLE_VALUE) throw std::runtime_error("unable to create pipe");

return pipe handle, name, pipe_state::server_disconnected ;


pipe pipe::open(const std::string &name, pipe_mode mode)

auto full_name = pipe_name_prefix + name;

DWORD open_mode = 0;

switch (mode)
GENERIC_WRITE;
break;


for (auto tries = 3; tries > 0; --tries)

const auto handle = CreateFileA(full_name.c_str(), open_mode, 0, nullptr, OPEN_EXISTING, 0, nullptr);

if (handle != INVALID_HANDLE_VALUE)

return pipe handle, name, pipe_state::client ;


if (GetLastError() != ERROR_PIPE_BUSY) throw std::runtime_error("unable to open pipe");
if (!WaitNamedPipeA(name.c_str(), 10000)) throw std::runtime_error("unable to wait on pipe");

std::this_thread::sleep_for(std::chrono::milliseconds(1000));


throw std::runtime_error("unable to open pipe");


pipe::pipe(pipe &&other) noexcept
: handle other.handle , pipe_name other.pipe_name , state other.state

other.handle = INVALID_HANDLE_VALUE;


pipe &pipe::operator=(pipe &&other) noexcept

close();

handle = other.handle;
state = other.state;
pipe_name = std::move(other.pipe_name);
other.handle = INVALID_HANDLE_VALUE;

return *this;


pipe::~pipe()

close();


void pipe::close()

if (handle != INVALID_HANDLE_VALUE)

disconnect();
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;



const std::string &pipe::name() const

return pipe_name;


size_t pipe::read(buffer buf)

connect();

DWORD bytes_read;
if (!ReadFile(handle, buf.data, buf.size.value, &bytes_read, nullptr))

throw std::runtime_error("pipe read failed");


return bytes_read;


void pipe::write(const buffer buf)




process.hpp



#pragma once
#include <string>
#include <Windows.h>
#include <vector>

namespace win32

enum class process_creation_flags : DWORD

none = 0,
new_console = CREATE_NEW_CONSOLE,
;

class process

std::vector<char> cmdline;
PROCESS_INFORMATION process_info;

public:
process(
const std::string &executable_path,
const std::string &command_line_args,
process_creation_flags flags = process_creation_flags::none);

process(const process &) = delete;
process(process &&other) noexcept;

process &operator=(const process &) = delete;
process &operator=(process &&other) noexcept;

void terminate(int exit_code = 0);

virtual ~process() noexcept;
;



process.cpp



#include "process.hpp"

namespace win32

process::process(const std::string& executable_path, const std::string& command_line_args, process_creation_flags flags)

std::copy(std::begin(command_line_args), std::end(command_line_args), std::back_inserter(cmdline));
cmdline.push_back('');

STARTUPINFOA startup_info;
GetStartupInfoA(&startup_info);

if (!CreateProcessA(executable_path.c_str(), cmdline.data(), nullptr, nullptr, false, static_cast<DWORD>(flags), nullptr, nullptr, &startup_info, &process_info))

throw std::runtime_error("process creation failed!");



process::process(process &&other) noexcept : cmdline std::move(other.cmdline) , process_info other.process_info

other.process_info.hThread = INVALID_HANDLE_VALUE;
other.process_info.hProcess = INVALID_HANDLE_VALUE;


process & process::operator=(process &&other) noexcept

terminate();

cmdline = std::move(other.cmdline);
process_info = other.process_info;

other.process_info.hThread = INVALID_HANDLE_VALUE;
other.process_info.hProcess = INVALID_HANDLE_VALUE;

return *this;


void process::terminate(int exit_code)

if (process_info.hThread != INVALID_HANDLE_VALUE)

TerminateThread(process_info.hThread, exit_code);
CloseHandle(process_info.hThread);
process_info.hThread = INVALID_HANDLE_VALUE;


if (process_info.hProcess != INVALID_HANDLE_VALUE)
TerminateProcess(process_info.hProcess, exit_code);
CloseHandle(process_info.hProcess);
process_info.hProcess = INVALID_HANDLE_VALUE;



process::~process() noexcept

terminate();




console.hpp



#pragma once
#include <string>
#include <vector>
#include "pipe.hpp"
#include "process.hpp"

namespace win32

class console_streambuf : public std::basic_streambuf<char>

pipe &output;
std::vector<char> buffer;

protected:
int_type overflow(int_type) override;
int_type sync() override;

virtual void publish();

public:
explicit console_streambuf(pipe &output, buffer_size size);
;

class console : public std::ostream

pipe output;
process helper;

static std::string create_unique_pipe_name(std::string base);
static std::string build_command_line(std::string title, std::string pipe_name);

public:
explicit console(std::string name);
;



console.cpp



#include "console.hpp"
#include <thread>
#include <utility>

using namespace std::string_literals;

namespace win32

std::basic_streambuf<char>::int_type console_streambuf::overflow(int_type character)

publish();

if (character != traits_type::eof())

*pptr() = character;
pbump(1);
return character;


return traits_type::eof();


std::basic_streambuf<char>::int_type console_streambuf::sync()

publish();
return 0;


void console_streambuf::publish()

const auto size = pptr() - pbase();
if (size > 0)

output.write(win32::buffer buffer.data(), static_cast<size_t>(size) );
setp(buffer.data(), buffer.data() + buffer.size());



console_streambuf::console_streambuf(pipe &output, buffer_size buffer_size)
: output output , buffer(buffer_size.value)

const auto begin = buffer.data();
const auto end = begin + buffer.size();
setp(begin, end);



constexpr static auto default_buffer_size = 4096u;
const std::string executable_name = "console_helper.exe"s;

std::string console::create_unique_pipe_name(std::string base)

return base + std::to_string(::rand());


std::string console::build_command_line(std::string title, std::string pipe_name)

return executable_name + " "" + title + "" "" + pipe_name + """;


console::console(std::string name)
: basic_ostream new console_streambuf output, buffer_size default_buffer_size ,
output
pipe::create(
create_unique_pipe_name(name),
pipe_mode::write,
read_buffer_size 0 ,
write_buffer_size default_buffer_size )
,
helper executable_name, build_command_line(name, output.name()), process_creation_flags::new_console



console_helper.cpp



#include <windows.h>
#include <iostream>
#include "pipe.hpp"
#include <array>

int main(int argc, char *argv)

if(argc != 3)

std::cerr << "not enough argumentsn" << "call syntax: console_helper [title] [pipe name]" << std::endl;
return -1;


try
SetConsoleTitleA(argv[1]);
auto pipe = win32::pipe::open(argv[2], win32::pipe_mode::read);

auto buffer = std::array<char, 1024>;

for (;;)

const auto bytes_read = pipe.read(win32::buffer buffer.data(), buffer.size() );
std::cout.write(buffer.data(), bytes_read);


catch(std::exception& ex)

std::cerr << "Caught exception: " << ex.what() << std::endl;
std::cin.get();


return 0;



Example usage



#include "console.hpp"
#include <iomanip>

int main()
// creation
win32::console debug "debug" ;

// using new console for output
debug << "debug consolen";
// std stream manipulators function as usual
debug << std::hex << std::setfill('0') << std::setw(8) << 123456 << "nflush!n" << std::flush;

// wait for user input so the display stays up
std::cin.get();
// RAII destroys all used resources and terminates the helper process









share|improve this question












share|improve this question




share|improve this question








edited May 26 at 18:22
























asked May 25 at 11:47









hoffmale

4,275630




4,275630











  • skimming through, seems reasonable. I would wrap the HANDLE tightly in its own RTTI construct.
    – JDługosz
    May 25 at 13:22










  • @JDługosz, please don't review code in comments. I know your comment isn't an elaborate answer, but for future reference: please add an actual answer.
    – Daniel
    May 25 at 13:28










  • @JDługosz: I though about it, but it would get complicated. there are already 3 types of HANDLEs used in the code (pipe, process and thread), each requiring different acquire and release procedures and providing different capabilities, so there was little commonality (basically the only code duplication are the calls to CloseHandle between those).
    – hoffmale
    May 25 at 13:32











  • Common destructor is enough, as that is the point! Copy constructor calling DuplicateHandle is common, too. I don’t have a kill as part of my HANDLE wrapper. Automatically doing the move semantics would be handy for you.
    – JDługosz
    May 25 at 22:14
















  • skimming through, seems reasonable. I would wrap the HANDLE tightly in its own RTTI construct.
    – JDługosz
    May 25 at 13:22










  • @JDługosz, please don't review code in comments. I know your comment isn't an elaborate answer, but for future reference: please add an actual answer.
    – Daniel
    May 25 at 13:28










  • @JDługosz: I though about it, but it would get complicated. there are already 3 types of HANDLEs used in the code (pipe, process and thread), each requiring different acquire and release procedures and providing different capabilities, so there was little commonality (basically the only code duplication are the calls to CloseHandle between those).
    – hoffmale
    May 25 at 13:32











  • Common destructor is enough, as that is the point! Copy constructor calling DuplicateHandle is common, too. I don’t have a kill as part of my HANDLE wrapper. Automatically doing the move semantics would be handy for you.
    – JDługosz
    May 25 at 22:14















skimming through, seems reasonable. I would wrap the HANDLE tightly in its own RTTI construct.
– JDługosz
May 25 at 13:22




skimming through, seems reasonable. I would wrap the HANDLE tightly in its own RTTI construct.
– JDługosz
May 25 at 13:22












@JDługosz, please don't review code in comments. I know your comment isn't an elaborate answer, but for future reference: please add an actual answer.
– Daniel
May 25 at 13:28




@JDługosz, please don't review code in comments. I know your comment isn't an elaborate answer, but for future reference: please add an actual answer.
– Daniel
May 25 at 13:28












@JDługosz: I though about it, but it would get complicated. there are already 3 types of HANDLEs used in the code (pipe, process and thread), each requiring different acquire and release procedures and providing different capabilities, so there was little commonality (basically the only code duplication are the calls to CloseHandle between those).
– hoffmale
May 25 at 13:32





@JDługosz: I though about it, but it would get complicated. there are already 3 types of HANDLEs used in the code (pipe, process and thread), each requiring different acquire and release procedures and providing different capabilities, so there was little commonality (basically the only code duplication are the calls to CloseHandle between those).
– hoffmale
May 25 at 13:32













Common destructor is enough, as that is the point! Copy constructor calling DuplicateHandle is common, too. I don’t have a kill as part of my HANDLE wrapper. Automatically doing the move semantics would be handy for you.
– JDługosz
May 25 at 22:14




Common destructor is enough, as that is the point! Copy constructor calling DuplicateHandle is common, too. I don’t have a kill as part of my HANDLE wrapper. Automatically doing the move semantics would be handy for you.
– JDługosz
May 25 at 22:14















active

oldest

votes











Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195157%2fusing-multiple-console-windows-for-output%23new-answer', 'question_page');

);

Post as a guest



































active

oldest

votes













active

oldest

votes









active

oldest

votes






active

oldest

votes










 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195157%2fusing-multiple-console-windows-for-output%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Python Lists

Aion

JavaScript Array Iteration Methods