【问题标题】:Win32 Console application that can open windows可以打开窗口的 Win32 控制台应用程序
【发布时间】:2014-06-11 19:26:01
【问题描述】:

我的问题很简单。在 Ubuntu 中,通常可以使用或不使用 GUI 来部署程序,可能使用像 --showGUI 这样的标志。我本质上想在windows中重新创建这个功能,但似乎windows应用程序以win_main开头,而控制台应用程序以main开头。

那么产生这种行为所需的基本结构是什么?例如。在 Visual Studio 2012 中,我应该从 Windows 应用程序开始,然后隐藏窗口并写入控制台吗?或者我可以从一个空的控制台应用程序开始,然后使用 windows API 创建一个窗口吗?

谢谢

(c/c++,顺便说一句)

【问题讨论】:

  • "windows 应用程序以 win_main 开头,而控制台应用程序以 main 开头" - 不正确。具有 Windows 子系统的应用程序可以使用 main 函数。
  • 好吧,从技术上讲,您可以从一个空项目开始并显示窗口。当您开始使用 Windows 应用程序时,它会为您做很多事情并创建一个窗口。至于创建控制台应用程序并显示一个窗口,当你这样做时,你的入口点是 _tmain,而 Windows 应用程序的入口点是 WinMain。最好的办法是从一个 Windows 应用程序开始,然后想办法显示一个控制台窗口。

标签: c++ windows winapi


【解决方案1】:

一个控制台应用程序开始连接到一个控制台。然后它可以创建它认为合适的窗口——基本上与专门为 Windows 子系统编写的应用程序没有什么不同。

理论上你可以做相反的事情:为 windows 子系统创建一个应用程序,然后将一个控制台附加到它上面。不过,这增加了相当多的额外工作。标准库中的启动代码通常会将 stdin/cin、stdout/cout、stderr/cerr 附加到控制台。如果您创建一个 windows 子系统程序,然后附加一个控制台,您基本上必须重现该代码以将控制台附加到标准流。

因此,从为控制台子系统编写的程序开始并让它创建窗口通常比从为 Windows 子系统编写的程序开始并让它创建/附加控制台更容易。

mainWinMain 而言:这控制了程序默认链接到哪个子系统。也就是说,如果您有一个名为main 的函数,它将默认为控制台子系统。如果您有一个名为 WinMain 的函数,它将默认使用 windows 子系统(顺便说一下,如果您同时定义两者,我不记得它会做什么——我建议不要这样做)。

但是,如果您愿意,可以使用链接器标志强制选择子系统,因此您可以使用 main 作为 windows 子系统程序的入口点,或使用 WinMain 作为控制台的入口点- 子系统程序。不过,我通常建议不要这样做。

【讨论】:

  • 我明白了。好的,所以我相信我想要的行为将来自一个控制台应用程序,如果提供了命令行标志,它会创建一个窗口。
  • 再跟进一次,稍作修改,这里的方法在 Visual Studio 2012 中有效:gamedev.net/topic/…
【解决方案2】:

Windows 应用程序可以同时具有 GUI 和控制台窗口。只是没有人以这种方式设置它们。你必须自己处理。

以下是一些执行此操作的示例代码:

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <iostream>

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
bool ReconnectIO(bool OpenNewConsole);

int CALLBACK WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,int nCmdShow)
{
    MSG msg;
    HWND hwnd;
    WNDCLASS wc;

    if(!ReconnectIO(false))
        printf("Started from command prompt\n");

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.lpszClassName = "Window";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpszMenuName  = NULL;
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClass(&wc);
    hwnd = CreateWindow(wc.lpszClassName, "Window",
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                100, 100, 350, 250, NULL, NULL, hInstance, NULL);  

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    printf("Entering GetMessage loop\n");
    while (GetMessage(&msg, NULL, 0, 0))
    {
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

/*******************************************************************************
 * NAME:
 *    ReconnectIO
 *
 * SYNOPSIS:
 *    bool ReconnectIO(bool OpenNewConsole);
 *
 * PARAMETERS:
 *    OpenNewConsole [I] -- This controls if we open a console window or not.
 *                          True -- if the program was not started from an
 *                                  existing console open a new console window.
 *                          False -- Only connect stdio if the program was
 *                                   started from an existing console.
 *
 * FUNCTION:
 *    This function connects up the stardard IO (stdout, stdin, stderr) to
 *    the windows console.  It will open a new console window if needed
 *    (see 'OpenNewConsole').
 *
 * RETURNS:
 *    true -- A new console window was opened
 *    false -- Using an existing console window
 *
 * SEE ALSO:
 *    
 ******************************************************************************/
bool ReconnectIO(bool OpenNewConsole)
{
    int hConHandle;
    long lStdHandle;
    FILE *fp;
    bool MadeConsole;

    MadeConsole=false;
    if(!AttachConsole(ATTACH_PARENT_PROCESS))
    {
        if(!OpenNewConsole)
            return false;

        MadeConsole=true;
        if(!AllocConsole())
            return false;   // Could throw here
    }

    // STDOUT to the console
    lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
    hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
    fp = _fdopen( hConHandle, "w" );
    *stdout = *fp;
    setvbuf( stdout, NULL, _IONBF, 0 );

     // STDIN to the console
    lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);
    hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
    fp = _fdopen( hConHandle, "r" );
    *stdin = *fp;
    setvbuf( stdin, NULL, _IONBF, 0 );

    // STDERR to the console
    lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
    hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
    fp = _fdopen( hConHandle, "w" );
    *stderr = *fp;
    setvbuf( stderr, NULL, _IONBF, 0 );

    // C++ streams to console
    std::ios_base::sync_with_stdio();

    return MadeConsole;
}

程序的顶部只是一个带有 WinMain() 入口点的普通 Window 程序。魔法来自 ReconnectIO() 函数。如果需要,它将重新连接标准 io 并打开一个控制台窗口。

当你从命令行启动程序时,标准输出会转到它,当你从桌面启动时,只打开主窗口。

它确实有一个缺点,那就是当从命令行启动时,它会立即返回,而不是阻塞直到程序退出。启动代码正在这样做,我还没有研究如何停止它。

您可以查看http://dslweb.nwnexus.com/~ast/dload/guicon.htm 的更多信息,了解正在发生的事情。

【讨论】:

    【解决方案3】:

    这在 Windows 中确实很难做到。

    1. 如果您创建的程序使用 Windows 子系统,则很难知道要附加到哪个控制台。 (如果它是从“运行”菜单或桌面快捷方式启动而没有控制台怎么办?)

    2. 如果您创建一个使用控制台子系统的程序,您总是会得到一个控制台。 (您也可以创建窗口,但是,如果您从现有控制台以外的地方启动,无论您是否愿意,都会出现一个新的控制台窗口。)

    Visual Studio 有一个技巧可以解决这个问题。如果仔细观察,您会发现在同一目录中有 两个 devenv 可执行文件:devenv.exe 是 Windows 应用程序,devenv.com 是控制台应用程序。

    所有快捷方式都直接指向 .exe。但是,如果您在控制台窗口中键入devenv,并且安装目录在您的路径中,它实际上会启动控制台之一 devenv.com。这是因为在 PATHEXT 环境变量中,.com 通常列在 .exe 之前。

    如果您包含 devenv.com 可以在控制台模式下严格处理的命令行选项(如 /?),它会。否则,它只会为您调用 devenv.exe 并退出。

    【讨论】:

    • @isanae:感谢您澄清有关 PATHEXT 的一些信息!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-24
    • 1970-01-01
    • 1970-01-01
    • 2013-06-30
    • 2013-06-06
    • 2010-10-27
    • 1970-01-01
    相关资源
    最近更新 更多