Win32控制台应用程序如何打开窗口?

5
我的问题很简单。在Ubuntu中,程序通常可以使用--showGUI这样的标志部署有或没有GUI。我想在Windows中重新创建这个功能,但似乎Windows应用程序从win_main开始,而控制台应用程序从main开始。
因此,需要什么基本结构来产生此行为?例如,在Visual Studio 2012中,我应该从Windows应用程序开始,然后隐藏窗口并写入控制台吗?还是我可以从空控制台应用程序开始,并使用Windows API创建窗口?
谢谢
(顺便说一句,是c/c++)

窗口应用程序以win_main开头,而控制台应用程序以main开头 - 不是真的。具有Windows子系统的应用程序可以使用main函数。 - chris
从技术上讲,您可以从一个空项目开始并显示窗口。当您使用Windows应用程序开始时,它会为您执行许多操作,并创建一个窗口。至于创建控制台应用程序并显示窗口,当您这样做时,入口点是_tmain,而Windows应用程序的入口点是WinMain。最好的选择是从Windows应用程序开始,然后找出一种方法来显示控制台窗口。 - santahopar
3个回答

6

控制台应用程序开始时附加到控制台。然后,它可以随意创建窗口——与专门为Windows子系统编写的应用程序没有什么区别。

理论上,你也可以做相反的事情:创建一个Windows子系统应用程序,然后将控制台附加到它上面。不过这会增加很多额外的工作量。标准库中的启动代码通常会对stdin/cin、stdout/cout以及stderr/cerr进行一些操作,将它们附加到控制台上。如果你创建了一个Windows子系统程序,然后附加了一个控制台,那么你基本上必须重写该代码,将控制台附加到标准流上。

因此,通常最简单的方法是从为控制台子系统编写的程序开始,然后让它创建窗口,而不是从为Windows子系统编写的程序开始,然后创建/附加控制台。

关于mainWinMain:这决定了程序默认链接的子系统。也就是说,如果有一个名为main的函数,它将默认链接到控制台子系统。如果有一个名为WinMain的函数,它将默认链接到Windows子系统(我不记得如果两个都定义了会发生什么——我建议不要这样做)。

但是,如果需要的话,你可以通过链接器标志强制选择子系统,因此你可以将main作为Windows子系统程序的入口点,或者将WinMain作为控制台子系统程序的入口点。不过,我通常也不建议这样做。


我明白了。好的,所以我相信我想要的行为将来自一个控制台应用程序,如果提供了命令行标志,则会创建一个窗口。 - Jomnipotent17
只是再跟进一次,稍作修改后,这个方法在Visual Studio 2012中可以使用:http://www.gamedev.net/topic/608057-how-do-you-create-a-win32-window-from-console-application/ - Jomnipotent17

4
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;
}

程序的顶部只是一个正常的Windows程序,具有WinMain()入口点。魔法来自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的问题! - Adrian McCarthy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接