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

时间:2023-12-30 20:43:20

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