Piped Console


C++
Winapi

Problem

Did u ever need to log some messages to the console from another process?

Maybe You were unable to AllocConsole by yourself and had to visualize it in some other way?

Probably not…

But I did :)


Solution

This library solves this problem by connecting to the console from another process via Named Pipes.


Result

Result


Requirements

  • CMake
  • (Clang | MSVC) && cxx_23
  • Windows

Optional “Requirements”:

  • Ninja
  • Make

Usage - As library

  1. Get library in your prefered way, eg.
FetchContent_Declare(
piped_console
GIT_REPOSITORY https://github.com/shv187/piped_console.git
GIT_TAG v0.1.0)
FetchContent_MakeAvailable(piped_console)
  1. Link against it, eg.
target_link_libraries(your_target PRIVATE piped_console)
  1. Use It
Emitter
#include <iostream>
#include <stdexcept>
#include <string>
#include <windows.h>
#include <fmt/core.h>
#include "piped_console/piped_console.hpp"
int main() {
SetConsoleTitleA("Emitter");
try {
piped_console::Configuration config{ "\\\\.\\pipe\\piped_console" };
piped_console::Emitter emitter(config);
fmt::println("[+] Connected to console");
while (true) {
fmt::print("Message: ");
std::string message{};
std::getline(std::cin, message);
emitter.sendMessage(message);
}
} catch (const std::runtime_error& e) {
MessageBoxA(nullptr, e.what(), "Error", MB_OK);
return 1;
}
}
Console
#include <chrono>
#include <thread>
#include <windows.h>
#include <fmt/core.h>
#include <piped_console/piped_console.hpp>
#include <winuser.h>
int main() {
SetConsoleTitleA("Console");
try {
piped_console::Configuration config{ "\\\\.\\pipe\\piped_console" };
piped_console::Listener listener(config);
while (true) {
fmt::println("[+] Waiting for client connection...");
if (listener.connectToNamedPipe()) {
fmt::println("[+] Client connected");
while (listener.isConnected()) {
const auto message = listener.getMessage();
if (!message || message.value().empty()) {
break;
}
fmt::println("[message] {}", message.value());
}
fmt::println("[?] Client disconnected");
listener.disconnectFromNamedPipe();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else {
fmt::println("[!] Failed to connect to client");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
} catch (const std::exception& e) {
MessageBoxA(nullptr, e.what(), "Error", MB_OK);
return 1;
}
}
(Optional) Dynamic library :P
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <thread>
#include <windows.h>
#include <fmt/core.h>
#include <libloaderapi.h>
#include <piped_console/piped_console.hpp>
#include <winuser.h>
uint32_t main_thread(HMODULE module_handle) {
try {
piped_console::Configuration config{ "\\\\.\\pipe\\piped_console" };
piped_console::Emitter emitter(config);
fmt::println("[+] Connected to console");
while (!GetAsyncKeyState(VK_HOME)) {
const auto message =
std::format("Main module base address: 0x{:X}", reinterpret_cast<uintptr_t>(GetModuleHandle(nullptr)));
emitter.sendMessage(message);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
} catch (const std::runtime_error& e) {
MessageBoxA(nullptr, e.what(), "Error", MB_OK);
}
FreeLibraryAndExitThread(module_handle, EXIT_SUCCESS);
}
BOOL WINAPI DllMain(HINSTANCE module_handle, DWORD call_reason, LPVOID) {
if (call_reason != DLL_PROCESS_ATTACH) {
return TRUE;
}
if (const auto handle = CreateThread(nullptr, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(main_thread),
module_handle, 0, nullptr)) {
CloseHandle(handle);
}
return TRUE;
}

Usage - Just to check out the example - Optional requirements are NEEDED here

Terminal window
git clone https://github.com/shv187/piped_console.git
cd piped_console
mkdir build
make run

Notes

Console supports emitter’s disconnection and connection of a new one.

However emitter won’t connect to a new console if something (bad?) happens to the old one.