最近在一个项目中遇到了一个很奇怪的问题,在基于MFC的DLL工程中,一个cpp文件除了在该工程中使用外,在另外一个工程中也会用到,所以及使用了条件编译#ifdef,具体代码如下:
|
#ifndef PYTHON_ALONE
#include "stdafx.h"
#include <ProUtil.h>
#include <ProMessage.h>
#else
#include <Windows.h>
#include <atlstr.h>
#include <tchar.h>
#endif |
但是在编译的时候总是报错:fatal
error
C1019:
unexpected
#else
后来在网上查了好多,最终终于找到问题的症结:都是stafx.h惹的祸!
只需要在这个cpp文件上右键->属性,在预编译头文件选项上选择“不使用预编译头文件”即可。(注意:是这个CPP文件的属性,而不是工程的属性)
其实之前也遇到过stafx.h导致的错误,譬如:
fatal
error
C1010:
unexpected
end
of
file
while
looking
for
precompiled
header.
Did
you
forget
to
add
'#include
"stdafx.h"'
to
your
source?
但是那时也就是根据提示将#include "stafx.h"加上而已,没有深究为什么会出现这样的问题。
下面就详细讲一下stafx.h的意义:
要讲预编译头文件的作用,要先从头文件的编译原理说起。其实头文件并不神秘,它的全部作用,就是把自己的所有内容直接“粘贴”到相应的#include 语句处。如果不相信的话,不妨做个实验,将一个 cpp 中的所有 #include语句删掉,并将它包含的文件粘贴到相应的位置,你会发现,文件的编译和运行都完全没有受到影响。其实,编译器在编译你的程序的时候,所做的第一件事,也就是展开所有的#include 语句和 #define 语句。
头文件的出现,固然给书写程序带来了很大方便。可是到了 Windows 时代后,慢慢就呈现出一些问题了。几乎所有的 Windows程序都必须包含 windows.h,而那个文件却硕大无比,将它展开后往所有文件中一粘贴,编译的时候立刻慢得像只蜗牛。
到了 MFC时代后,情况更为恶劣了。毕竟 C 风格的 Windows 头文件里面包含的还仅仅是函数定义和宏,编译难度不算太大,而 MFC库里面的头文件可都是类声明啊!更何况,一个最简单的工程,都会生成大量的类,需要用到大量的函数。如果工程稍微复杂一些,编译难度可想而知!
但是,人们惊奇地发现,虽然用到的头文件又多又杂,但是在一个工程中,总有那么一堆头文件,是几乎所有 cpp都必须包含的。那么,可不可以把这些头文件提取出来,只编译一编,然后所有其它 cpp就都能使用呢?没错,这就是预编译头的思想都由来!
实践证明,使用了预编译头技术后,编译速度大大提高了。可以到你的工程目录下的Debug 或 Release目录中看一看,里面有一个体积极为硕大的 .pch 文件,那就是传说中的“编译之后的预编译头”。
使用了预编译头技术后,虽然带来了极大地方便,但也造成了一个问题:由于它假定预编译头中包含过的头文件会在所有 cpp中使用,因此它在编译你的 cpp 的时候,就会将预编译头中已经编译完的部分加载到内存中。如果它突然发现你的 cpp居然没有包含预编译头,它就会很郁闷,因为它不知道该如何将已编译完的部分从内存中请出去,整个编译过程就会失败。
因此,如果你使用了预编译头技术,就必须在所有的 cpp 中包含预编译头。MFC 工程中为你建立了一个默认的预编译头stdafx.h,如果你愿意,也可以在自己的工程中使用其它文件名作为你的预编译头,如果你觉得有必要。
当然,你也可以不使用预编译头,就像我上面遇到的问题一样,将该文件设置成为不使用预编译头即可。
下面是我的一个实际工程的BuildLog.htm中的部分信息:
正在创建临时文件“d:\Lab\svn\Core_PDM\Source\ProePDM2005\ProePDM2005\Release\RSP00002939846852.rsp”,其内容为
[
/O2 /GL /I "..\Python25\include" /I "..\ComLIB" /I"..\PDMPython" /D "WIN32" /D "_WINDOWS" /D "NDEBUG" /D "_USRDLL" /D"PRO_USE_VAR_ARGS" /D "_WINDLL" /D "_AFXDLL" /D "_UNICODE" /D"UNICODE" /FD /EHsc /MD /Yu"stdafx.h"/Fp"Release\ProePDM2005.pch" /Fo"Release\\" /Fd"Release\vc80.pdb"/W3 /c /Wp64 /Zi /TP .\ProePDM2005.cpp
]
正在创建命令行“cl.exe@d:\Lab\svn\Core_PDM\Source\ProePDM2005\ProePDM2005\Release\RSP00002939846852.rsp/nologo /errorReport:prompt”
正在创建临时文件“d:\Lab\svn\Core_PDM\Source\ProePDM2005\ProePDM2005\Release\RSP00002A39846852.rsp”,其内容为
[
/O2 /GL /I "..\Python25\include" /I "..\ComLIB" /I"..\PDMPython" /D "WIN32" /D "_WINDOWS" /D "NDEBUG" /D "_USRDLL" /D"PRO_USE_VAR_ARGS" /D "_WINDLL" /D "_AFXDLL" /D "_UNICODE" /D"UNICODE" /FD /EHsc /MD /Yc"stdafx.h"/Fp"Release\ProePDM2005.pch" /Fo"Release\\" /Fd"Release\vc80.pdb"/W3 /c /Wp64 /Zi /TP .\stdafx.cpp
]
正在创建命令行“cl.exe@d:\Lab\svn\Core_PDM\Source\ProePDM2005\ProePDM2005\Release\RSP00002A39846852.rsp/nologo /errorReport:prompt”
正在创建临时文件“d:\Lab\svn\Core_PDM\Source\ProePDM2005\ProePDM2005\Release\RSP00002B39846852.rsp”,其内容为
[
/O2 /GL /I "..\Python25\include" /I "..\ComLIB" /I"..\PDMPython" /D "WIN32" /D "_WINDOWS" /D "NDEBUG" /D "_USRDLL" /D"PRO_USE_VAR_ARGS" /D "_WINDLL" /D "_AFXDLL" /D "_UNICODE" /D"UNICODE" /FD /EHsc /MD /Fo"Release\\"/Fd"Release\vc80.pdb" /W3 /c /Wp64 /Zi /TP .\PySw.cpp
]
|
|
在上面的工程中,PySw.cpp文件包含了本文开头的那段代码,但是没有预定义PYTHON_ALONE,将该文件设置为“不使用预编译头”才得以编译通过。所以我们在编译的过程中可以看到,在编译stafx.cpp文件时,使用了/Yc选项,即创建预编译头;在编译ProePDM2005.cpp文件时使用了/Yu选项,即使用预编译头;而在编译PySw.cpp文件时,则没有使用预编译头,但是由于该文件没有PYTHON_ALONE定义,所以在条件编译前几行代码时,会直接将stdafx.h的内容展开,一起编译到PySw.cpp文件中,这点可以从编译之后的obj文件的大小中得到验证,下面是编译之后生成的几个Obj文件大小的截图:
将代码改为以下形式,并将PySw.cpp文件设置为“使用预编译头”,编译之后的obj文件大小如下图所示
|
#include "stdafx.h"
#include <ProUtil.h>
#include <ProMessage.h>
//#ifndef PYTHON_ALONE
//#else
//#include <Windows.h>
//#include <atlstr.h>
//#include <tchar.h>
//#endif |
对比两个Obj文件大小的截图可以发现,PySw.cpp使用预编译头时,PySw.obj文件的大小为48KB,而PySw.cpp不使用预编译头时,PySw.obj文件大小为1715KB,差不多相当于将stafx.obj(1976KB)和原来的叠加在一起的结果。
可以看一下MSDN网站上关于预编译头的使用的部分:
# Makefile : Illustrates the effective use ofprecompiled
OBJS = myapp.obj applib.obj
# List all stable header files in the STABLEHDRS macro.
STABLEHDRS = stable.h another.h
# List the final header file to be precompiled here:
BOUNDRY = stable.h
====================================
# Compile myapp
myapp.obj
: myapp.cpp$(UNSTABLEHDRS)
stable.pch
$(CPP)$(CLFLAGS)/Yu$(BOUNDRY)
myapp.cpp
# Compile applib
applib.obj : applib.cpp $(UNSTABLEHDRS) stable.pch
$(CPP)$(CLFLAGS)/Yu$(BOUNDRY)
applib.cpp
# Compile headers
stable.pch : $(STABLEHDRS)
$(CPP)$(CLFLAGS)/Yc$(BOUNDRY)
applib.cpp myapp.cpp
|
从关系图顶部开始,STABLEHDRS 和BOUNDRY 都是 NMAKE宏,您在这些宏中列出不大可能需要重新编译的文件。只有在预编译头文件 (STABLE.pch)不存在或对两个宏中列出的文件进行更改时,才使用命令字符串
|
CL /c /W3 /Yc$(BOUNDRY) applib.cpp myapp.cpp
|
编译这些文件。无论在哪一种情况下,预编译头文件都将只包含来自 STABLEHDRS 宏中所列文件的代码。在 BOUNDRY宏中列出最后一个想预编译的文件。
在这些宏中列出的文件可以是头文件,也可以是 C或 C++ 源文件。
在上面的这个例子中,工程中的预编译头只有头文件,没有与之对应的cpp文件,在我们的工程中stafx.cpp中只有#include "stafx.h"一句,那么我们可不可以也将stafx.cpp删掉呢?其实是可行的。
但是我们把stafx.cpp删掉之后,重新编译,会报类似下面的错:
error C2859:d:\lab\svn\core_pdm\source\proepdm2005\proepdm2005\release\vc80.pdbis not the pdb file that was used when this precompiled header wascreated, recreate the precompiled header.
按照提示,我们需要重新生成预编译头,所以我们先把Debug或Release(此处是Release)文件夹下的pch文件删掉,再重新编译,这时又会报下面的错:
fatal error C1083: Cannot openprecompiled header file: 'Release\ProePDM2005.pch': No such file ordirectory
一般情况下,遇到了pchmissing错误,只需要重新编译即可,但是我们此时却无法通过重新编译来解决这个问题。因为我们把产生pch文件的stafx.cpp删掉了,所以这时候我们需要使用另外的文件产生pch文件。我们可以在另外的cpp文件,如此处的ProePDM2005.cpp文件上右键->属性,在预编译头的选项中设置为"创建预编译头(/Yc)",然后再重新编译就可以了。但是这会导致的结果是pch文件巨大,并且只要ProePDM2005.cpp文件发生了改变,pch文件就会重新生成,没有起到使用预编译头降低编译时间的作用,所以还是把不需要经常修改的文件作为预编译头比较好。下面是这次重新编译之后的结果(PySw.cpp未使用预编译头):
通过以上的总结,我们基本上已经将预编译头的作用和使用时需要注意的事项搞清楚了,这样今后遇到跟stafx.h有关的错误时就不会束手无措了。
(最后吐槽一下Wiz的编辑器,同样的字体同样的字号显示效果却不一样,让人无语啊,得跟老魏提提意见啊!)
参考: