Using multiple console windows for output

Clash 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
processwrapper 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
c++ c++17 winapi
add a comment |Â
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
processwrapper 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
c++ c++17 winapi
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 ofHANDLEs 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 toCloseHandlebetween 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 themovesemantics would be handy for you.
â JDà Âugosz
May 25 at 22:14
add a comment |Â
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
processwrapper 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
c++ c++17 winapi
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
processwrapper 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
c++ c++17 winapi
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 ofHANDLEs 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 toCloseHandlebetween 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 themovesemantics would be handy for you.
â JDà Âugosz
May 25 at 22:14
add a comment |Â
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 ofHANDLEs 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 toCloseHandlebetween 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 themovesemantics 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
add a comment |Â
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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 toCloseHandlebetween 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
movesemantics would be handy for you.â JDà Âugosz
May 25 at 22:14