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

Qt Windows下链接子系统与入口函数(终结版)(可同时存在main和WinMain函数)的更多相关文章

  1. qt windows下的配置 以及VS2010的使用

    qt在windows下的使用方式有两种: 1.将qt内置在vs下,例如,内置在vs2010下,使用vs的编译器及调试器. 2.在windows下,使用qt creator以及MingW作为编译器的使用 ...

  2. QT Windows下生成动态链接库

    目标:需要将一个QT程序生成动态链接库 Windows环境下Qt生成的共享库文件其后缀为dll,可以在程序运行过程中动态加载 新建项目,选择库 选择共享库 建立好项目后生成三个文件,两个.h一个.cp ...

  3. Qt Windows下开机自动启动自己的程序

    源地址:http://blog.csdn.net/chrisfxs/article/details/13279491 版权声明:本文为博主原创文章,未经博主允许不得转载. void Widget::R ...

  4. windows下linux子系统安装

    1.打开Windows功能中的使用于linux的Windows子系统 2.应用商店中下载需要的linux 3.下载完成后运行等待安装并输入用户名密码  4.查看系统信息 先后 sudo apt-get ...

  5. pthread在Qt+Windows下的使用

    pthread是牛逼的跨平台线程库,无需多介绍. 下载pthread-win32,解压后将x86里的pthreadVC2.dll放到system32目录里,将pthreadVC2.lib放入项目中去, ...

  6. QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数

    QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...

  7. Windows下Codeblocks调试Cocos2d-x项目体验(一次失败的体验)

    很久之前的一篇文章有介绍过在Ubuntu下安装Cocos2d-x3.11并使用Codeblock调试Cocos2d-x程序:http://www.cnblogs.com/moonlightpoet/p ...

  8. QT创建窗口程序、消息循环和WinMain函数(为主线程建立了一个QEventLoop,并执行exec函数)

    使用QT也有一段时间了,有的时候需要跟踪代码到QT的源码中去查找问题.在这里我将记录一下我跟踪QT源码学习到的一些知识. 我的开发环境是VC6.0+QT4.3.3.QT已经不为VC6.0提供addin ...

  9. c++ 网络编程(四) LINUX/windows下 socket 基于I/O复用的服务器端代码 解决多进程服务端创建进程资源浪费问题

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9613861.html 好了,继上一篇说到多进程服务端也是有缺点的,每创建一个进程就代表大量的运 ...

随机推荐

  1. MySql 优化 网上资料

    1.选取最适用的字段属性 MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快.因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽 ...

  2. windows批处理(.bat)

    转自http://www.cnblogs.com/shiney/archive/2011/07/04/2097236.html 本文在运行中有一些小小的问题,我修改了一下,将会在稳重标出 批处理文件是 ...

  3. DenyHosts 安装及配置详解

    DenyHosts是Python语言写的一个程序,它会分析sshd的日志文件(/var/log/secure),当发现重 复的攻击时就会记录IP到/etc/hosts.deny文件,从而达到自动屏IP ...

  4. jQuery学习-事件之绑定事件(四)

    今天我们来学习jQuery.Event对象.jQuery为了添加自己的处理机制,及可以传递用户自定义数据,于是Event对象就出世了.   1 jQuery.Event = function( src ...

  5. 实现拦截API的钩子(Hook)

    道理不多讲,简单说就是将系统API的跳转地址,替换为我们自己写的API的地址,所以要求我们自定义的API函数要和被拦截的API有相同的参数.在用完后,记得恢复. 因为要挂全局的钩子,所以Hook的部分 ...

  6. Delphi代码中嵌入ASM代码

    前言 Delphi作为一个快速高效的开发平台,使用的人越来越多,但熟悉在Delphi代码中嵌入ASM代码的程序员我想不多,因为这方面的资料太少了,另一方面,它还需要有基本的汇编语言知识,关於汇编语言的 ...

  7. objective-c 可变参数

    容易发现Cocoa Foundation 中提供了一些可变参数的方法,如: NSLog(NSString *format, ...) 在实际的编程实践中,我们也需要自己实现可变参数的方法.在Objc中 ...

  8. SQL Server 中@@IDENTITY的用法

    原文地址:http://www.studyofnet.com/news/145.html 本文导读:@@IDENTITY是返回上次插入的标识值,标识值一般指的是自动增长值.但是如果想只返回插入到当前作 ...

  9. SQL server指定随机数范围

    declare @randnum int=0declare @startnum int =0declare @endnum int=0 set @startnum = 150 set @endnum ...

  10. android 百度地图定位功能实现

    历经几天时间,终于把定位功能给实现了,可谓是费劲千辛万苦啊,有定位知识还有图层知识,在这里我把代码给大家贴出来,一起分享一下下啦. package com.example.foreveross.off ...