【发布时间】:2010-10-05 09:05:13
【问题描述】:
我必须创建一个需要某些参数的控制台应用程序。如果它们丢失或错误,我会打印出错误消息。
现在的问题:如果有人通过双击从资源管理器启动程序,控制台窗口会立即消失。 (但该应用程序在资源管理器中并非完全没用,您可以将文件拖到它上面就可以了)
我可以总是等待按键,但如果用户确实从命令行启动它,我不希望这样。
有什么方法可以区分这些情况吗?
【问题讨论】:
-
+1!刚要创建一个类似的问题。
我必须创建一个需要某些参数的控制台应用程序。如果它们丢失或错误,我会打印出错误消息。
现在的问题:如果有人通过双击从资源管理器启动程序,控制台窗口会立即消失。 (但该应用程序在资源管理器中并非完全没用,您可以将文件拖到它上面就可以了)
我可以总是等待按键,但如果用户确实从命令行启动它,我不希望这样。
有什么方法可以区分这些情况吗?
【问题讨论】:
参见http://support.microsoft.com/kb/99115,“信息:防止控制台窗口消失”。
想法是使用GetConsoleScreenBufferInfo 来确定光标没有从最初的0,0 位置移动。
来自@tomlogic 的代码示例,基于参考的知识库文章:
// call in main() before printing to stdout
// returns TRUE if program is in its own console (cursor at 0,0) or
// FALSE if it was launched from an existing console.
// See http://support.microsoft.com/kb/99115
#include <stdio.h>
#include <windows.h>
int separate_console( void)
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (!GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE), &csbi))
{
printf( "GetConsoleScreenBufferInfo failed: %lu\n", GetLastError());
return FALSE;
}
// if cursor position is (0,0) then we were launched in a separate console
return ((!csbi.dwCursorPosition.X) && (!csbi.dwCursorPosition.Y));
}
【讨论】:
cls && appname.exe 工作得很好。我将编辑此答案以包含我正在使用的代码,以供其他人参考。
我见过执行的代码
if (!GetConsoleTitle(NULL, 0) && GetLastError() == ERROR_SUCCESS) {
// Console
} else {
// GUI
}
但是...我发现AttachConsole() 更有帮助
在 C++ 中(我想不到,而且我不是 C++ 程序员)
if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
// GUI
} else {
// Console, and you have a handle to the console that already exists.
}
更有效。此外,如果您发现自己处于 GUI 环境中,并且希望尽可能长时间地呆在那里,但后来发现发生了一些灾难性的事情,这可能会真正使用转储到控制台窗口(您不能因为编写编辑框而感到羞耻)窗口将其批量或附加到 NT 系统日志并抛出 MessageBox()),然后您可以在稍后的过程中 AllocConsole(),当 GUI 方法失败时。
【讨论】:
我找到了一个更好的解决方案,它使用 GetConsoleProcessList 将附加的进程计数获取到当前控制台。 如果此进程是唯一附加的进程,它将在该进程存在时关闭。
我在帖子https://devblogs.microsoft.com/oldnewthing/20160125-00/?p=92922 中找到了它 但它有一个错误(至少在 Windows 10 中),因为文档禁止使用 null 调用此函数。
我的解决方案是:
DWORD procId;
DWORD count = GetConsoleProcessList(&procId, 1);
if (count < 2) ...
【讨论】:
我相信cmd.exe 在启动时会设置 CMDCMDLINE 和 CMDEXTVERSION 环境变量。因此,如果设置了这些,您的程序很可能是从 shell 启动的。
这不是万无一失的,但它确实是。
也可以通过一些复杂且可能不可靠的方式确定您的父 PID,至少我是这么认为的。你可能想调查一下。
【讨论】:
echo %cmdcmdline% 确实会产生一些结果,因此该变量显然仅在 cmd.exe 本身中有效,在其子进程中无效。
这是来自@DanielBenSassoon 的适用于 C# 的出色答案。在 Visual Studio 2019 和 Windows 10 中测试。
// Gets a list of the process IDs attached to this console
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetConsoleProcessList(uint[] processList, uint processCount);
public static bool IsFinalProcess()
{
// See: https://devblogs.microsoft.com/oldnewthing/20160125-00/?p=92922
uint[] procIDs = new uint[64];
uint processCount = GetConsoleProcessList(procIDs, 64);
return (processCount < 2);
}
这种方法可以让您区分四种情况:
当IsFinalProcess 为真时,您可以使用Console.ReadKey(false); 来防止应用程序退出后控制台窗口消失。
【讨论】:
编辑:我已经成功使用这个 .bat/.cmd 包装器几年了:
@ECHO OFF
REM Determine if script was executed from an interactive shell
(echo %CMDCMDLINE% | find /i "%~0" >NUL 2>&1) && (set IS_INTERACTIVE=false) || (set IS_INTERACTIVE=true)
<call console application here>
REM Wait for keystroke
if "%IS_INTERACTIVE%" == "false" (
echo.
echo Hit any key to close.
pause >NUL 2>&1
)
这里的优点是这将适用于所有控制台应用程序,无论是您自己的还是其他人的(您可能没有可以修改的源)。缺点是,你有一个单独的包装器。
我在 2014 年最初的回答是这样的:
这就像一个魅力:
@echo off
for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" goto nonconsole
:console
<do something>
goto exit
:nonconsole
<do something>
pause
:exit
复制自this thread。我也尝试自己评估 %cmdcmdline%,但是存在关于引号字符 (") 的问题,这会阻止 if "%cmdcmdline%" == "%ComSpec%" goto [target] 之类的东西工作.
【讨论】: