• Qt Windows下链接子系统与入口函数(终结版)(可同时存在main和WinMain函数)


    Qt Windows下链接子系统与入口函数(终结版)

    转载自:http://blog.csdn.net/dbzhang800/article/details/6358996

    能力所限,本讨论仅局限于MSVC的cl编译器和MinGW的gcc编译器。

    • 第一部分:不涉及Qt(理清链接子系统和入口函数)
    • 第二部分:Qt的链接子系统和入口函数(与第一部分完全对应上)
    • 第三部分:QtTest模块出现控制台的原因与方案

    • 第四部分:Graeme Gill 给出的很有意思的代码。

    再探 链接子系统

    在  浅谈Console与Windows子系统   一文中我们简单讨论了一个Windows系统下的 Console 和 Windows 两个链接子系统,但是描述可能有些乱。这儿换种方式整理一下:

    • console 子系统 ==> 拥有黑色CMD窗口

    • windows 子系统 ==> 没有黑色CMD窗口

    考虑一个简单的程序代码

    代码中定义两个入口函数:main和WinMain(不要觉得两个同时出现很奇怪),下面测试时

    •  源码3种情况 :只有main、只有WinMain、二者同时存在
    •  链接子系统3中情况 :不指定子系统、指定windows子系统、指定console子系统
    •  编译器2种 :msvc的cl、mingw的gcc
    #include <windows.h> 
    
    int main()
    {
      MessageBoxW (NULL, L"Hello World from main!", L"hello", MB_OK | MB_ICONINFORMATION); 
      return 0; 
    }
    
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, int nShowCmd) 
    { 
      MessageBoxW (NULL, L"Hello World from WinMain!", L"hello", MB_OK | MB_ICONINFORMATION); 
      return 0; 
    }

    不指定链接子系统

    如果我们分别用MSVC的编译器cl 和 MinGW的编译器gcc (在不指定链接子系统的情况下)分别编译

    cl /EHsc hello.c user32.lib
    gcc hello.c -o hello

    会有什么效果呢:

    源码入口函数

    编译器

    默认链接子系统

    默认入口函数

    只有main

    gcc

    console

     

    cl

    console

     

    只有WinMain

    gcc

    console

     

    cl

    windows

     

    WinMain、main并存

    gcc

    console

    main,(无法选择WinMain入口,除非你去掉main入口)

    cl

    console

    main,,但可以通过  
    /entry:WinMainCRTStartup 选择WinMain入口

    可以看到:

    •  只有一种情况下不是控制台程序(即不弹黑色的cmd窗口)
    •  MSVC下可以选择根据需要选择入口函数
    •  MinGW下只要main存在,永远不会使用WinMain

    在Qt中,如果是控制台程序(CONFIG+=console),程序只有一个入口,也就是你写的main函数;如果是GUI程序,则处于双入口并存的局面(第二部分对此有详细解释)。

    注意Qt的处理方式:在MinGW下,双入口时它将main改成了qMain,如果你在用MinGW,如果你有兴趣可以自己做如下实验:

    •  不要启用CONFIG+=console,不用使用QtTest 模块
    •  将你的main函数改成qMain
    •  然后和平时一样编译,运行

    指定windows子系统

    更进一步:如果我们制定链接子系统呢,比如,指定windows子系统(注意此处的选项,我们在Qt部分的CONFIG+=windows对应的文件中会再次看到它)

    cl /EHsc hello.c /link /subsystem:windows user32.lib
    gcc hello.c -o hello  -Wl,-subsystem,windows

    源码入口函数

    编译器

    需要指定入口函数

    只有main

    gcc

    不需要

    cl

    必须指定 /entry:mainCRTStartup

    只有WinMain

    gcc

    不需要

    cl

    不需要

    WinMain、main并存

    gcc

    不需要(始终是main入口)

    cl

    默认是WinMain,可以通过  
    /entry:mainCRTStartup 选择main入口

    指定console子系统

    为了完整起见,看一下指定console子系统的情况(注意此处的选项,我们在Qt部分的CONFIG+=console对应的文件中会再次看到它)

    cl /EHsc hello.c /link /subsystem:console user32.lib
    gcc hello.c -o hello  -Wl,-subsystem,console

    结果:

    源码入口函数

    编译器

    需要指定入口函数

    只有main

    gcc

    不需要

    cl

    不需要

    只有WinMain

    gcc

    不需要

    cl

    必须指定 /entry:WinMainCRTStartup

    WinMain、main并存

    gcc

    不需要(始终是main入口)

    cl

    默认是main,可以通过  
    /entry:WinMainCRTStartup 选择WinMain入口

    入口函数

    看前面的3个表,入口函数应该会让你眼花缭乱,但,其实,很简单...

    MinGW

    • 对于MinGW来说,入口函数和链接子系统无关。无论指定什么子系统,它都会寻找main这个入口,如果main找不到,才会去找WinMain入口。

    • 这意味着什么呢?
      • 意味着如果代码中同时存在main和WinMain,你无法使用WinMain入口!!(注意Qt中的处理方法)

    • 具体一点:
      • 如果 main 函数不存在,libmaingw32.a将被链接进来,该库里面提供了一个main函数(该函数将调用用户的WinMain函数)

      • 可以注意和Qt提供 qtmain 这个库进行对比哈

    MSVC

    对 MSVC 系列的编译器,指定链接子系统比如 /subsystem:console,链接器就会寻找main函数,并选择mainCRTStartup函数;对windows子系统,情况类似。

    当我们程序的入口函数是 WinMain 时,如果指定 console 子系统,链接器将报错,这时我们可以指定入口点启动函数 /entry:WinMainCRTStartup 来解决这种问题。

    Qt指定链接子系统

    Qt默认是设置了windows子系统(因为Qt是界面库,它默认设置这个很容易理解哈),因为不用手动输入CONFIG+=windows,我们应该更熟悉下面这个语句:

    CONFIG += console

    console.prf

    看过qmake的manual,我们可以知道,CONFIG 中指定的东西一般要对应于 features 文件(即 console.prf 或 windows.prf 文件)

    这两个文件在 $$QTDIR/mkspecs/features/win32 目录下,其内容会被包含进我们的*.pro文件。

    我们打开 console.prf 文件看看:

    CONFIG -= windows
    contains(TEMPLATE, ".*app") : QMAKE_LFLAGS += $$QMAKE_LFLAGS_CONSOLE

    呵呵,很容易理解对吧,就是设置一个链接选项。

    根据我们所用编译器(比如mingw-g++)的不同,去看看相应的qmake.conf文件($$QTDIR/mkspecs/win32-g++/qmake.conf):

    QMAKE_LFLAGS_CONSOLE    = -Wl,-subsystem,console

    通过第一部分的学习,这么简单的东西,现在不需要解释了吧。我们接下来重点看一下windows.prf文件

    windows.prf

    这个东西就复杂多了。我们的关注点:

     1. 指定了链接选项(和前面console一样,此处略)

     2. 定义了一个宏 QT_NEEDS_QMAIN (该宏存在是,我们的main函数其实被替换成了qMain)

     3. 链接了一个新的库 qtmain.lib (libqtmain.a)

    CONFIG -= console
    contains(TEMPLATE, ".*app"){
        QMAKE_LFLAGS += $$QMAKE_LFLAGS_WINDOWS
        win32-g++:DEFINES += QT_NEEDS_QMAIN
        win32-borland:DEFINES += QT_NEEDS_QMAIN
    
        qt:for(entryLib, $$list($$unique(QMAKE_LIBS_QT_ENTRY))) {
            isEqual(entryLib, -lqtmain): {
                CONFIG(debug, debug|release): QMAKE_LIBS += $${entryLib}$${QT_LIBINFIX}d
                else: QMAKE_LIBS += $${entryLib}$${QT_LIBINFIX}
            } else {
                QMAKE_LIBS += $${entryLib}
            }
        }
    }

    QMAKE_LIBS_QT_ENTRY

    这个文件有些复杂,里面有个QMAKE_LIBS_QT_ENTRY,它涉及另外一个问题,就是我们在Qt在Windows下的入口函数 一文中提到的:

    • 我们在Qt程序中只写main函数,从来不写WinMain函数

    • Qt 的lib目录下有 qtmain.lib 和 qtmaind.lib(或者 libqtmain.a和 libqtmaind.a) 这样库
      • 该库提供了WinMain 入口,并调用我们写的main函数

    • 为了证实我的说法,我们此处可以查看其源码:%QTDIR%/src/winmain/qtmain_win.cpp
    /*
      WinMain() - Initializes Windows and calls user's startup function main().
      NOTE: WinMain() won't be called if the application was linked as a "console"
      application.
    */
    
    extern "C"
    int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR, int cmdShow)
    {
    ...
        int result = main(argc, argv.data());
    ...
    }
    • 对应上了没,当我们指定windows子系统时,MSVC不是需要WinMain入口么,qtmain就提供了这个入口,该入口进而调用了我们自己写的main函数!

    QT_NEEDS_QMAIN

    注意,注意 ,发现问题没?我们一开始提到了,当WinMain入口和main入口同时出现时,采用MSVC编译器时,我们可以根据链接子系统选择使用哪一个入口。

    可是,我们同时说了,当采用MinGW时,两个入口同时出现时WinMain入口永远不会被使用。这可怎么办?

    • 这样一来,qtmain 这个库对与 MinGW 来说就没有任何作用了。确实如此
    • 但是,Qt官方还是让他起作用了,这就是,对于MinGW,当使用windows子系统时定义 QT_NEEDS_QMAIN 宏的原因

    还是一切用代码说话:无论打开 qwindowdefs.h 还是 qtmain_win.cpp 这个文件,我们都能看到这样的代码

    #if defined(QT_NEEDS_QMAIN)
    int qMain(int, char **);
    #define main qMain
    #endif
    • 哈哈哈,好玩吧,当该宏出现时,我们的最最常见的main函数,其实被宏替代成了 qMain 了。
    • MinGW 你不是牛么?你不是在main和WinMain同时出现时不使用WinMain么,我惹不起,我把main改成qMain

    QtTest模块

    这是涉及控制台的有一个地方。非常诡异哈,一旦启用了该模块,就会出现控制台,还很难去掉。在Qt程序弹出CMD窗口 一文中我们讨论了这个问题,并给出一个能工作但很不优雅的方案。这儿我试图告诉大家问题原因及根本解决方法。

    启用QtTest 有两种方法:

    CONFIG += qtestlib

    QT += testlib

    前者

    前者直接配置CONFIG,我们直接去看qtestlib.prf文件(你知道的,在$$QTDIR/mkspecs/features/目录下)就行了:

    CONFIG += console
    qtAddLibrary(QtTest)

    文件内容很短,只有两行:

    • 第一行:强制设置了console链接子系统
    • 第二行:设置头文件路径和库文件(这是必须的哈)

    注意:如果你不想要控制台,去掉这儿的第一行就可以啦。

    后者

    QT += testlib 是 CONFIG += QT 的细化,我们需要查看 qt.prf 文件(文件长,摘取片段):

    for(QTLIB, $$list($$lower($$unique(QT)))) {
       DEFINES *= $$upper(QT_$${QTLIB}_LIB)
       isEqual(QTLIB, testlib):qlib = QtTest
       isEqual(QTLIB, testlib):CONFIG += console
       qtAddLibrary($$qlib)

    除了多定义了一个QT_TESTLIB_LIB宏外,和完全前者一样。使用该方式是,如果不想要控制台,直接注释掉console这行即可。

    注意:此处也解释了  qmake之CONFIG与QT   一文中的问题。

    from邮件列表

    我们知道:

    • 非windows平台下,没有链接子系统的问题。一个程序,在控制台中启动时,就是一个控制台程序,程序可以直接输出数据到控制台。在窗口系统双击启动时,则不出现控制台。
    • 在windows下,则区分这两个东西,所以我们同样需要对两个debug和release分别设置。

    废话写了这么多了,看点有意思的,前些天,Qt-interest 邮件列表中,Graeme Gill 在对控制台是否出现的控制上,有个新的想法:让windows下的程序和unix下有同样的行为。

    只需要在main函数开始加入如下代码(需要头文件windows.h):

    #ifdef Q_WS_WIN
        {
            BOOL (WINAPI *AttachConsole)(DWORD dwProcessId);
            *(FARPROC *)&AttachConsole =
              GetProcAddress(LoadLibraryA("kernel32.dll"), "AttachConsole");
    
                if (AttachConsole != NULL && AttachConsole(((DWORD)-1))) {
                    if (_fileno(stdout) == -1)
                        freopen("CONOUT$","wb",stdout);
                    if (_fileno(stderr) == -1)
                        freopen("CONOUT$","wb",stderr);
                    if (_fileno(stdin) == -1)
                        freopen("CONIN$","rb",stdin);
                }
        }
    #endif // Q_WS_WIN

    很有意思哈,只要检测到在console下运行,则链接上标准输入、标准输出、标准出错。

    参考

    http://blog.csdn.net/wsh6759/article/details/7432317

  • 相关阅读:
    《图解算法》读书笔记(十)K最近邻算法
    《图解算法》读书笔记(九) 动态规划
    《图解算法》读书笔记(八) 贪婪算法
    Go 常用包之fmt、flag包(四)
    GO环境及初始化项目(二)
    nginx fpm 常见错误对比分析
    Ueditor富文本添加、编辑视频,视频不显示解决方案
    phpunit 测试
    mysql 主从并行复制(MTS)
    Explain执行计划详解
  • 原文地址:https://www.cnblogs.com/findumars/p/5011806.html
Copyright © 2020-2023  润新知