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

2024-02-10 19:18

本文主要是介绍Qt Windows下链接子系统与入口函数(终结版),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

能力所限,本讨论仅局限于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_WINDOWSwin32-g++:DEFINES += QT_NEEDS_QMAINwin32-borland:DEFINES += QT_NEEDS_QMAINqt:for(entryLib, $$list($$unique(QMAKE_LIBS_QT_ENTRY))) {isEqual(entryLib, -lqtmain): {CONFIG(debug, debug|release): QMAKE_LIBS += $${entryLib}$${QT_LIBINFIX}delse: 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 = QtTestisEqual(QTLIB, testlib):CONFIG += consoleqtAddLibrary($$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://hi.baidu.com/cyclone/blog/item/96624a90fb4ca081a977a4db.html

  • http://hi.baidu.com/cyclone/blog/item/31748a82fcc1d39df703a655.html

  • http://hi.baidu.com/cyclone/blog/item/730334a8820911bcca130c23.html

  • http://hi.baidu.com/cyclone/blog/item/d299af511bed309f8c5430c3.html

  • http://lists.trolltech.com/qt-interest/2007-06/thread00740-0.html


这篇关于Qt Windows下链接子系统与入口函数(终结版)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/697874

相关文章

C/C++错误信息处理的常见方法及函数

《C/C++错误信息处理的常见方法及函数》C/C++是两种广泛使用的编程语言,特别是在系统编程、嵌入式开发以及高性能计算领域,:本文主要介绍C/C++错误信息处理的常见方法及函数,文中通过代码介绍... 目录前言1. errno 和 perror()示例:2. strerror()示例:3. perror(

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

Windows Server服务器上配置FileZilla后,FTP连接不上?

《WindowsServer服务器上配置FileZilla后,FTP连接不上?》WindowsServer服务器上配置FileZilla后,FTP连接错误和操作超时的问题,应该如何解决?首先,通过... 目录在Windohttp://www.chinasem.cnws防火墙开启的情况下,遇到的错误如下:无法与

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

kotlin的函数forEach示例详解

《kotlin的函数forEach示例详解》在Kotlin中,forEach是一个高阶函数,用于遍历集合中的每个元素并对其执行指定的操作,它的核心特点是简洁、函数式,适用于需要遍历集合且无需返回值的场... 目录一、基本用法1️⃣ 遍历集合2️⃣ 遍历数组3️⃣ 遍历 Map二、与 for 循环的区别三、高

Qt 中 isHidden 和 isVisible 的区别与使用小结

《Qt中isHidden和isVisible的区别与使用小结》Qt中的isHidden()和isVisible()方法都用于查询组件显示或隐藏状态,然而,它们有很大的区别,了解它们对于正确操... 目录1. 基础概念2. 区别清见3. 实际案例4. 注意事项5. 总结1. 基础概念Qt 中的 isHidd

Python解析器安装指南分享(Mac/Windows/Linux)

《Python解析器安装指南分享(Mac/Windows/Linux)》:本文主要介绍Python解析器安装指南(Mac/Windows/Linux),具有很好的参考价值,希望对大家有所帮助,如有... 目NMNkN录1js. 安装包下载1.1 python 下载官网2.核心安装方式3. MACOS 系统安

C语言字符函数和字符串函数示例详解

《C语言字符函数和字符串函数示例详解》本文详细介绍了C语言中字符分类函数、字符转换函数及字符串操作函数的使用方法,并通过示例代码展示了如何实现这些功能,通过这些内容,读者可以深入理解并掌握C语言中的字... 目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现3.1strlen函数3.2st