From 60bc85d49a046f49ca18d384d4bd0cc2d5882280 Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Mon, 17 Jun 2024 10:16:24 -0400 Subject: [PATCH] fix .com/.exe switch --- src/routes/_posts/mixing-guis-and-clis-on-windows.svx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/_posts/mixing-guis-and-clis-on-windows.svx b/src/routes/_posts/mixing-guis-and-clis-on-windows.svx index 91885f8..291be46 100644 --- a/src/routes/_posts/mixing-guis-and-clis-on-windows.svx +++ b/src/routes/_posts/mixing-guis-and-clis-on-windows.svx @@ -17,14 +17,14 @@ Aha, says the clever Windows developer, but I am a practitioner of the Deep Magi If you do a), the problem is that the app doesn't have a console at its inception, so `AllocConsole` creates an entirely _new_ console, with no connection to the console from which you invoked the app. So it pops up in a new window, which is typically the default terminal emulatorOn Windows 10 and earlier, this defaults to `conhost.exe`, which is the terminal emulator equivalent of a stone knife chipped into chape by bashing it against other stones. rather than whatever you have set up, and - even worse - _it disappears as soon as your app exits_, because of course its lifecycle is tied to that of the app. So the _extremely standard_ CLI behavior of "execute, print some output, then exit" doesn't work, because there's no time to _read_ that output before the app exits and the window disappears. -Alternatively, you can call `AttachConsole` with the PID of the parent process, or you can just pass -1 instead of a real PID to say "use the console of the parent process". But this almost as terrible, because - again - the app _doesn't have a console when it launches_, so whatever shell you used to launch it will just assume that it doesn't need to wait for any output and blithely continue on its merry way. If you then attempt to write to stdout, you _will_ see the output, but it will be interleaved with your shell prompt, keyboard input, and so on, so again, not really usable. +Alternatively, you can call `AttachConsole` with the PID of the parent process, or you can just pass `-1` instead of a real PID to say "use the console of the parent process". But this almost as terrible, because - again - the app _doesn't have a console when it launches_, so whatever shell you used to launch it will just assume that it doesn't need to wait for any output and blithely continue on its merry way. If you then attempt to write to stdout, you _will_ see the output, but it will be interleaved with your shell prompt, keyboard input, and so on, so again, not really usable. Ok, so you do b) - flag your app as a CLI app, then call `FreeConsole` as soon as it launches to detach from the console that gets automatically assigned to it. Unfortunately this doesn't work either. When you launch a CLI app in a context that expects a GUI, such as the Start menu, it gets assigned a brand-new console window, again using whatever is the default terminal emulator. In my experience, it isn't consistently possible (from within the process at least) to call `FreeConsole` quickly enough to prevent this window from at least flashing briefly on the desktop. Livable? Sure, I guess, but it would be a sad world indeed if we never aimed higher than just _livable_. -Up until now, my solution has been to simply create two copies of my executable, one GUI and one CLI, put them in different directories, and add the directory of the CLI executable to my `PATH` so that it's the one that gets invoked when I run `mycommand` in a terminal. This works ok, despite being fairly inelegant, but just today I discovered a better way via [this rant](https://www.devever.net/~hl/win32con).With whose sentiments I must agree in every particular. Apparently you can specify the `CREATE_NO_WINDOW` flag when creating the process, which prevents it from creating a new window. Unfortunately, as that page notes, this requires you to control the invoation of the process, so the only way to make proper use of it is to create a "shim" executable that calls your main executable (which will be, in this case, marked as a CLI app) with the `CREATE_NO_WINDOW` flag. That post also points out that if you have a `.exe` file and a `.com` file alongside each other, Windows will prefer the `.com` file when the app is invoked via the CLI, so your shim can be a `.com` file and your main executable a `.exe`. I haven't tried this yet myself, but it sounds like it would work, and it's a general enough solution that may app frameworks such as [Tauri](https://tauri.app/)Building a Tauri app is how I encountered this problem in the first place. might eventually handle it for you. +Up until now, my solution has been to simply create two copies of my executable, one GUI and one CLI, put them in different directories, and add the directory of the CLI executable to my `PATH` so that it's the one that gets invoked when I run `mycommand` in a terminal. This works ok, despite being fairly inelegant, but just today I discovered a better way via [this rant](https://www.devever.net/~hl/win32con).With whose sentiments I must agree in every particular. Apparently you can specify the `CREATE_NO_WINDOW` flag when creating the process, which prevents it from creating a new window. Unfortunately, as that page notes, this requires you to control the invoation of the process, so the only way to make proper use of it is to create a "shim" executable that calls your main executable (which will be, in this case, the CLI-first version) with the `CREATE_NO_WINDOW` flag, for when you want to run in GUI mode. That post also points out that if you have a `.exe` file and a `.com` file alongside each other, Windows will prefer the `.com` file when the app is invoked via the CLI, so your shim can be `app.exe` and your main executable `app.com`. I haven't tried this yet myself, but it sounds like it would work, and it's a general enough solutionOne could even imagine a generalized "shim" executable which, when executed, simply looks at its current executable path, then searches in the same directory for another executable with the same name but the `.com` extension, and executes that with the `CREATE_NO_WINDOW` flag. that app frameworks such as [Tauri](https://tauri.app/)Building a Tauri app is how I encountered this problem in the first place, so I would be _quite_ happy if Tauri were to provide a might eventually handle it for you. Another solution that was suggested to me recently (I think this is the more "old school" way of handling this problem) is to create a new virtual desktop, then ensure that the spurious console window gets created there so that it's out of sight. I haven't tried this myself, so I'm not familiar with the details, but my guess is that like the above it would require you to control the invocation of the process, so there isn't really any advantage over the other method, and it's still hacky as hell. Things like this really drive home to me how thoroughly Windows relegates the CLI to being a second-class citizen. In some ways it almost feels like some of the products of overly-optimistic 1960s-era futurism, like [designing a fighter jet without a machine gun](https://en.wikipedia.org/wiki/McDonnell_Douglas_F-4_Phantom_II?useskin=vector) because we have guided missiles now, and _obviously_ those are better, right? But no, in fact it turns out that sometimes a gun was _actually_ preferable to a guided missile, because surprise! Different tools have different strengths and weaknesses. -Of course, if Microsoft had been in charge of the F-4 it would have [taken them 26 years](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/) finally add the machine gun, and when they did it would haved fired at a 30-degree angle off from the heading of the jet, so I guess we can be thankful that we aren't engaging in air-to-air dogfights with our terminal emulators, or something. +Of course, if Microsoft had been in charge of the F-4 it would have [taken them 26 years](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/) to finally add the machine gun, and when they did it would have fired at a 30-degree angle off from the heading of the jet, so I guess we can be thankful that we don't have to use our terminal emulators for air-to-air dogfights, at least.