3

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:

  • Running the program as an administrator, which forces ConHost to open, but I feel uncomfortable asking my friends to run an unknown program with admin rights—it doesn’t seem safe.
  • Using a .bat file to launch the game, which can force the program to run in ConHost, but this feels like more of a hack than a solution.

I’ve tried several methods to make my game run directly in ConHost, such as:

  • Running the game as an administrator, which automatically launches ConHost, but I don’t feel comfortable asking users to grant admin privileges to an unknown program.
  • Using a .bat file to launch the game in ConHost, which works, but it’s not an ideal solution since it adds an extra step for the user.
  • I’ve also tried using AllocConsole() to create a new console, but this doesn’t seem to force ConHost to be used, and instead defaults to Windows Terminal. What I expect is a way to launch my console game directly in ConHost when the user runs the executable without needing to modify any console settings, use admin privileges, or rely on additional scripts like a .bat file.

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?

11
  • 1
    Why does running a batch file add an extra step for the user? Commented Feb 8 at 9:32
  • 1
    Here's an idea – adjust your program so that if it is run without arguments (i.e. by player), it creates a temporary batch file, which would run it again with a certain argument. Then use one of the exec functions to run the batch file and terminate. Commented Feb 8 at 17:22
  • 2
    A good solution would be changing the type of your C coded application from a console application on which the Windows kernel library function CreateProcess creates a console window or a terminal window depending on user configuration on being called by explorer.exe to a GUI application. But instead of opening a graphical window, your C coded application opens a console window and attaches it to your application. Commented Feb 8 at 19:49
  • 2
    See pages like Can one executable be both a console and GUI application? and the linked pages or How to create a Windows program that works both as a GUI and console application? That is often called a hybrid GUI/console application. Commented Feb 8 at 19:54
  • 2
    The knowledge of the information provided by the Microsoft documentation page PE Format could be useful for a better understanding on how to code an application as hybrid which is according to its header information a GUI application but opens a console window and uses the console window on being started from within a graphical user interface execution environment like Windows File Explorer window. The application can use the already existing console on being started from a classic console environment like a command prompt. Commented Feb 8 at 20:07

2 Answers 2

2

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:

Solution 1: Use a Windows Application to Open Conhost

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.

Solution 2: Creating a Child Process to Open Conhost

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);
 
}
Sign up to request clarification or add additional context in comments.

Comments

1

This is how I got mine to work:

bool isRunningInWindowsTerminal() 
{
    HWND hwnd = GetConsoleWindow();
    if (hwnd == NULL) return false;
            
    LRESULT result = SendMessage(hwnd, WM_GETICON, 0, 0);
    return result == 0;
}
int handleWindowsTerminal()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    char szExecutablePath[MAX_PATH];
    GetModuleFileName(NULL, szExecutablePath, MAX_PATH);

    char szCommandLine[MAX_PATH + 8] = "conhost ";
    strcat(szCommandLine, szExecutablePath);

    if (!CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) 
    {
        printf("CreateProcess failed\n");
        return 1;
    }
    printf("You can close this terminal now, bye.\n");

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return 0;
}

This just checks that you are running the program in Windows Terminal and if you are return the result.
If you are in a Windows Terminal then the handleWindowsTerminal launches your current program from conhost and that should bring the legacy terminal, this is the way to use it in a simple program:

int main (int argc, char* argv[])
{
    if (isRunningInWindowsTerminal()) 
    {
        printf("Running in Windows Terminal, not supported: Now launching legacy console...\n");
        handleWindowsTerminal();
    } 
    else // assume running in cmd.exe, todo: add checks if feeling motivated
    {
        // if (argc >= 2 && strcmp(argv[1], "cmdProtocol") == 0) # example of a quick simple check
        // {
            // Your code goes here, should now be running legacy cmd terminal
        // }
    }
    return 0;
}

I found this simple solution works for me, I'm sure it can be enhanced but just trying to give back to the community that has helped many.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.