【问题标题】:Can a Win32 console application detect if it has been run from the explorer or not?Win32 控制台应用程序是否可以检测它是否已从资源管理器运行?
【发布时间】:2010-10-05 09:05:13
【问题描述】:

我必须创建一个需要某些参数的控制台应用程序。如果它们丢失或错误,我会打印出错误消息。

现在的问题:如果有人通过双击从资源管理器启动程序,控制台窗口会立即消失。 (但该应用程序在资源管理器中并非完全没用,您可以将文件拖到它上面就可以了)

我可以总是等待按键,但如果用户确实从命令行启动它,我不希望这样。

有什么方法可以区分这些情况吗?

【问题讨论】:

  • +1!刚要创建一个类似的问题。

标签: winapi console


【解决方案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));
}

【讨论】:

  • 这很原始 :) 虽然这是一个 hack,但它完全符合我的需要,而且比获取 PPID 要简单得多。
  • 你应该只在没有参数传递给应用程序的情况下使用这个技巧,这样你仍然可以从 cmd.exe 执行“cls&app filename”
  • 刚刚实现了这个解决方案,它非常适合 MinGW/MSYS 编译的命令行应用程序。而且,@Anders,cls &amp;&amp; appname.exe 工作得很好。我将编辑此答案以包含我正在使用的代码,以供其他人参考。
【解决方案2】:

GetConsoleTitle()

我见过执行的代码

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 方法失败时。

【讨论】:

  • 一些反馈:我希望在使用 MinGW/MSYS 编译的命令行应用程序中执行此操作。这两种方法都不适合我——我无法区分将文件拖到 Windows 资源管理器中的应用程序和在 bash shell 内的命令行上执行它。
【解决方案3】:

我找到了一个更好的解决方案,它使用 GetConsoleProcessList 将附加的进程计数获取到当前控制台。 如果此进程是唯一附加的进程,它将在该进程存在时关闭。

我在帖子https://devblogs.microsoft.com/oldnewthing/20160125-00/?p=92922 中找到了它 但它有一个错误(至少在 Windows 10 中),因为文档禁止使用 null 调用此函数。

我的解决方案是:

DWORD procId;
DWORD count = GetConsoleProcessList(&procId, 1);
if (count < 2) ...

【讨论】:

    【解决方案4】:

    我相信cmd.exe 在启动时会设置 CMDCMDLINE 和 CMDEXTVERSION 环境变量。因此,如果设置了这些,您的程序很可能是从 shell 启动的。

    这不是万无一失的,但它确实是。

    也可以通过一些复杂且可能不可靠的方式确定您的父 PID,至少我是这么认为的。你可能想调查一下。

    【讨论】:

    • 我尝试了环境变量,无论是从cmd.exe运行还是在它之外运行,它们都不存在。但是,键入 echo %cmdcmdline% 确实会产生一些结果,因此该变量显然仅在 cmd.exe 本身中有效,在其子进程中无效。
    【解决方案5】:

    这是来自@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);
    }
    

    这种方法可以让您区分四种情况:

    • 从 IDE 调试 (F5) [进程计数 = 1]
    • 从 IDE 运行,但不调试 (Ctrl + F5) [进程计数 = 2]
    • 在资源管理器中双击 [进程计数 = 1]
    • 从命令提示符窗口运行 [进程计数 = 2]

    IsFinalProcess 为真时,您可以使用Console.ReadKey(false); 来防止应用程序退出后控制台窗口消失。

    【讨论】:

      【解决方案6】:

      编辑:我已经成功使用这个 .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] 之类的东西工作.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-04-01
        • 2020-09-05
        • 2014-08-08
        • 2012-02-19
        • 2016-01-18
        • 2011-05-20
        • 2011-02-25
        • 1970-01-01
        相关资源
        最近更新 更多