23

如何创建执行以下操作的 Windows 应用程序:

  • 在没有命令行参数的情况下调用它是一个常规的 GUI 应用程序
  • 指定可选的“--help”命令行参数会导致应用程序将使用文本写入标准输出然后终止
  • 它必须是单个可执行文件。通过使控制台应用程序 exec 成为第二个可执行文件,不会作弊。
  • 假设主要应用程序代码是用 C/C++ 编写的
  • 如果在指定“--help”时没有创建 GUI 窗口,则可以加分。(即,短暂的窗口没有闪烁)

根据我的经验,控制台应用程序的标准 Visual Studio 模板没有 GUI 功能,并且普通的 win32 模板不会将其标准输出发送到父 cmd shell。

4

3 回答 3

25

微软将控制台和 GUI 应用程序设计为互斥的。这种短视意味着没有完美的解决方案。最流行的方法是拥有两个可执行文件(例如 cscript / wscript、java / javaw、devenv.com / devenv.exe 等),但是您已经表明您认为这是“作弊”。

您有两个选择 - 制作“控制台可执行文件”或“gui 可执行文件”,然后使用代码尝试提供其他行为。

  • 图形用户界面可执行文件:

cmd.exe将假定您的程序没有控制台 I/O,因此在继续之前不会等待它终止,这在交互模式下(即不是批处理)意味着显示下一个(“ C:\>”)提示并从键盘读取。因此,即使您使用 AttachConsole,您的输出也会与cmd' 的输出混合,如果您尝试输入,情况会变得更糟。这基本上是一个非首发。

  • 控制台可执行文件:

与想象相反,没有什么可以阻止控制台可执行文件显示 GUI,但有两个问题。

第一个是,如果您从不带参数的命令行运行它(所以您需要 GUI), cmd仍然会等待它终止,然后再继续,因此特定控制台在此期间将无法使用。这可以通过启动同一可执行文件的第二个进程来克服(您认为这是作弊吗?),将 DETACHED_PROCESS 标志传递给 CreateProcess() 并立即退出。然后,新进程可以检测到它没有控制台并显示 GUI。

下面是说明这种方法的 C 代码:

#include <stdio.h>
#include <windows.h>

int main(int argc, char *argv[])
{
    if (GetStdHandle(STD_OUTPUT_HANDLE) == 0) // no console, we must be the child process
    {
        MessageBox(0, "Hello GUI world!", "", 0);
    }
    else if (argc > 1) // we have command line args
    {
        printf("Hello console world!\n");
    }
    else // no command line args but a console - launch child process
    {
        DWORD dwCreationFlags = CREATE_DEFAULT_ERROR_MODE | DETACHED_PROCESS;
        STARTUPINFO startinfo;
        PROCESS_INFORMATION procinfo;
        ZeroMemory(&startinfo, sizeof(startinfo));
        startinfo.cb = sizeof(startinfo);
        if (!CreateProcess(NULL, argv[0], NULL, NULL, FALSE, dwCreationFlags, NULL, NULL, &startinfo, &procinfo))
            MessageBox(0, "CreateProcess() failed :(", "", 0);
    }
    exit(0);
}

我用 cygwin 的 gcc - YMMV 和 MSVC 编译它。

第二个问题是,当从资源管理器运行时,您的程序会在瞬间显示一个控制台窗口。没有编程方式解决这个问题,因为控制台是在应用程序启动时由 Windows 创建的,在它开始执行之前。您唯一能做的就是,在您的安装程序中,使用 SW_HIDE(即 0)的“显示命令”创建程序的快捷方式。这只会影响控制台,除非您在程序中故意尊重 STARTUPINFO 的 wShowWindow 字段,所以不要这样做。

我已经通过破解 cygwin 的“mkshortcut.exe”对此进行了测试。如何在您选择的安装程序中完成它取决于您。

当然,用户仍然可以通过在资源管理器中找到可执行文件并双击它来运行您的程序,绕过隐藏控制台的快捷方式并看到控制台窗口的短暂黑色闪烁。你对此无能为力。

于 2008-09-22T03:26:14.397 回答
17

您可以使用AllocConsole()WinApi 函数为 GUI 应用程序分配控制台。您还可以尝试使用 附加到父进程的控制台,AttachConsole()如果它已经有一个,这是有道理的。重定向到此控制台的完整代码将如下所示stdoutstderr

if(AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()){
    freopen("CONOUT$", "w", stdout);
    freopen("CONOUT$", "w", stderr);
}

我在Pidgin源代码中找到了这种方法(参见WinMain()pidgin/win32/winpidgin.c)

于 2014-09-28T17:30:25.177 回答
4

我知道我的答案来晚了,但我认为这里情况的首选技术是“.com”和“.exe”方法。

根据您对两个可执行文件的定义,这可能被认为是“作弊”,但它只需要对程序员部分进行很少的更改,并且可以完成一个并忘记。此外,此解决方案没有 Hugh 解决方案的缺点,您可以在瞬间显示控制台窗口。

在 Windows 命令行中,如果您运行程序但未指定扩展名,则查找可执行文件的优先顺序将优先选择 .com 而非 .exe。

然后,您可以使用技巧让“.com”成为 stdin/stdout/stderr 的代理并启动同名的 .exe 文件。这给出了允许程序在从控制台调用时以命令行模式执行的行为(可能仅在检测到某些命令行参数时),同时仍然能够作为没有控制台的 GUI 应用程序启动。

有各种文章对此进行了描述,例如“如何将应用程序同时作为 GUI 和控制台应用程序?” (请参阅下面链接中的参考资料)。

我在 google 代码上托管了一个名为dualsubsystem的项目,该项目更新了该技术的旧 codeguru 解决方案,并提供了源代码和工作示例二进制文件。

我希望这会有所帮助!

于 2009-07-08T13:57:27.437 回答