I’ve developed a console game using C, and one of the main features is that it runs in the ConHost console because it gives me much more control over the console's appearance (fonts, colors, window size, etc.). However, I’m running into a problem: if I share the game with a friend, they have to manually configure the console to use ConHost since Windows now defaults to using the Windows Terminal.
There are a few workarounds that I’ve seen:
I’ve tried several methods to make my game run directly in ConHost, such as:
Ideally, I’d like my game to open in ConHost programmatically when the user runs it, without any need for manual configuration, admin privileges, or having to rely on a separate script. Is there a way to accomplish this?
I finally found the solution to the problem a while ago! To get the conhost console instead of the Windows Terminal, I found two alternatives:
You only need this solution if you specifically want to open conhost and no other console environment or process. If that's the case, don't even bother reading solution 2, as it's unnecessarily complex for this problem (unless, of course, you want to experience the demons I was battling back then).
Forget entirely about making a console application (I stubbornly focused on creating a console app and didn’t consider this possibility at all). Instead, create a Windows Application—it won't have a graphical interface nor a console, but your process will run in the background. All you need to do is allocate a console to it, and voilà, Windows will open conhost instead of the Windows Terminal, since conhost is the default for this kind of application. This is very useful!
Here’s the code to do exactly that:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, int nCmdShow) {
AllocConsole();
freopen("CONIN$", "r", stdin);
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
That's it, it's that simple! If this ever stops working, you'll have to resort to solution 2, and trust me, it’s a lot more painful than this.
If you're developing a game, there is some useful code on my GitHub repository, along with the game itself if you'd like to play it.
So, if solution 1 doesn't work for you and you're here, I’m sorry. This is the alternative approach. You need to start by creating a console application, then through this parent console application, you’ll create a child process using the Windows API to open your app/game in a child console process. This will ensure you’re using conhost programmatically.
It’s very important that you pass a command line argument while creating the new process so that it knows it’s already the child process and doesn't end up in a recursive loop (where one process keeps creating another). This way, it will open itself in this new console.
Once that’s done, you need to kill the parent console, and then you’ll have your app running on the desired console environment.
This might be useful if you want your game/program to open in a specific console environment, even if it’s not conhost.
Here’s some code that may be helpful. The original working code is lost because, unfortunately, I didn’t commit it (I thought I did). So, this might or might not work, but it should give you the main ideas:
int main(int argc, char* argv[]) {
wchar_t executable[MAX_PATH];
GetModuleFileNameW(NULL, executable, MAX_PATH);
const wchar_t* args = L"started"; // Arguments you may pass to tell the new process that it is the child, its indeferent what you pass as long as you pass something
// Verify if the archive exists
if (PathFileExistsW(executable)) {
// If the argument is present (argc>1), do not execute the game again
if (argc == 1)
{
// Calls the function to create the process
CreateProcessWithPath2(executable, args);
CloseOriginalConsole2(); //kills parent process
exit(0);
}
}
else {
wprintf(L"Archive isnt in specified route: %s\n", executable);
}
}
Here is the dough. This is primarily where errors might occur since I can’t check if the code works or not. However, I still want to post it because I know that with a little bit of adaptation, this approach will work, and someone might find it useful:
void CreateProcessWithPath2(const wchar_t* executable, const wchar_t* args) {
// Generate pipes
HANDLE hChildStdInRead, hChildStdInWrite;
HANDLE hChildStdOutRead, hChildStdOutWrite;
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE; // Handles can be hereditary to the child
saAttr.lpSecurityDescriptor = NULL;
// Create output pipe
if (!CreatePipe(&hChildStdOutRead, &hChildStdOutWrite, &saAttr, 0)) {
printf("Error creating output pipe: %d\n", GetLastError());
return;
}
// Create input pipe
if (!CreatePipe(&hChildStdInRead, &hChildStdInWrite, &saAttr, 0)) {
printf("Error creating input pipe: %d\n", GetLastError());
return;
}
// Configure structure of STARTUPINFO
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = hChildStdInRead; // Redirect stdin
si.hStdOutput = hChildStdOutWrite; // Redirect stdout
si.hStdError = hChildStdOutWrite; // Redirect stderr if you also wish to
// Process info
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
wchar_t commandLine[512];
if (args) {
wsprintf(commandLine, L"conhost.exe cmd /C \"%s\" %s", executable, args);
}
else {
}
// Create process
if (!CreateProcessW(
NULL, // Name of the executable (NULL since we usecommandLine)
commandLine, // Comand line, useful since we want to open in conhost and pass a command line argument
NULL, // Child process
NULL, // Childe thread
TRUE, // Dont inherit handle
CREATE_NEW_CONSOLE, // Create new console
NULL, // Environment
NULL, // Work directory
&si, // Process configuration
&pi // Process information
)) {
WaitForSingleObject(pi.hProcess, INFINITE);
// Read from output pipe
DWORD bytesRead;
CHAR buffer[4096];
while (ReadFile(hChildStdOutRead, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) {
buffer[bytesRead] = '\0';
printf("%s", buffer); // Show ouptput on windows console
}
// Close hanldes
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hChildStdInRead);
CloseHandle(hChildStdInWrite);
CloseHandle(hChildStdOutRead);
CloseHandle(hChildStdOutWrite);
}
else {
// If the process fails
printf("CreateProcess failed. Error: %d\n", GetLastError());
}
}
void CloseOriginalConsole2() {
DWORD dwPID;
GetWindowThreadProcessId(GetConsoleWindow(), &dwPID); // Obtain PID of the console
// Obtain process list to find conhost.exe
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE) return;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnap, &pe)) {
do {
if (pe.th32ParentProcessID == dwPID) { // Find child process of the console
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID);
if (hProcess) {
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
}
}
} while (Process32Next(hSnap, &pe));
}
CloseHandle(hSnap);
// Now we close the process of the original console
HANDLE hConsole = OpenProcess(PROCESS_TERMINATE, FALSE, dwPID);
if (hConsole) {
TerminateProcess(hConsole, 0);
CloseHandle(hConsole);
}
}
This version of CreateProcessWithPath is simpler, you have less control, but it can def work for you:
void CreateProcessWithPath(const wchar_t* executable, const wchar_t* args) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// Construct comand line
wchar_t commandLine[512];
if (args) {
wsprintf(commandLine, L"conhost.exe cmd /C \"%s\" %s", executable, args);
}
else {
}
// Close proces
if (!CreateProcessW(
NULL, // Name of the executable (NULL since we usecommandLine)
commandLine, // Comand line, useful since we want to open in conhost and pass a command line argument
NULL, // Child process
NULL, // Childe thread
TRUE, // Dont inherit handle
CREATE_NEW_CONSOLE, // Create new console
NULL, // Environment
NULL, // Work directory
&si, // Process configuration
&pi // Process information
) {
wprintf(L"CreateProcess failed (%d).\n", GetLastError());
return;
}
// Close handles
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}