在VC中编译、运行程序的小知识点

fmddlmyy 2005-04-28 10:45:37
最近我抽空研究、整理了一下VC中几个以前比较模糊的问题,写成这篇短文,希望和碰到过类似问题的朋友共享。 如果我的理解有不正确的地方,欢迎大家指正。
文章的3、4小节参照了vcforever的专栏(http://blog.csdn.net/vcforever/archive/2004/12/14/215936.aspx)。其它信息来源于MSDN和自己的摸索。

1、Run-Time Library
Run-Time Library是编译器提供的标准库,提供一些基本的库函数和系统调用。
我们一般使用的Run-Time Library是C Run-Time Libraries。当然也有Standard C++ libraries。
C Run-Time Libraries实现ANSI C的标准库。VC安装目录的CRT目录有C Run-Time库的大部分源代码。
C Run-Time Libraries有静态库版本,也有动态链接库版本;有单线程版本,也有多线程版本;还有调试和非调试版本。
可以在"project"-"settings"-"C/C++"-"Code Generation"中选择Run-Time Library的版本。

动态链接库版本:
/MD Multithreaded DLL 使用导入库MSVCRT.LIB
/MDd Debug Multithreaded DLL 使用导入库MSVCRTD.LIB

静态库版本:
/ML Single-Threaded 使用静态库LIBC.LIB
/MLd Debug Single-Threaded 使用静态库LIBCD.LIB
/MT Multithreaded 使用静态库LIBCMT.LIB
/MTd Debug Multithreaded 使用静态库LIBCMTD.LIB

C Run-Time Library的标准io部分与操作系统的关系很密切,在Windows上,CRT的io部分代码只是一个包装,底层要用到操作系统内核kernel32.dll中的函数,在编译时使用导入库kernel32.lib。这也就是为什么在嵌入式环境中,我们一般不能直接使用C标准库。
在Linux环境当然也有C标准库,例如:
ld -o output /lib/crt0.o hello.o -lc
参数"-lc"就是在引用C标准库libc.a。猜一猜"-lm"引用哪个库文件?

2、常见的编译参数
VC建立项目时总会定义"Win32"。控制台程序会定义"_CONSOLE",否则会定义"_WINDOWS"。Debug版定义"_DEBUG",Release版定义"NDEBUG"

与MFC DLL有关的编译常数包括:
_WINDLL 表示要做一个用到MFC的DLL
_USRDLL 表示做一个用户DLL(相对MFC扩展DLL而言)
_AFXDLL 表示使用MFC动态链接库
_AFXEXT 表示要做一个MFC扩展DLL
所以:
Regular, statically linked to MFC _WINDLL,_USRDLL
Regular, using the shared MFC DLL _WINDLL,_USRDLL,_AFXDLL
Extension DLL _WINDLL,_AFXDLL,_AFXEXT

CL.EXE编译所有源文件,LINK.EXE链接EXE和DLL,LIB.EXE产生静态库。

3、subsystem和可执行文件的启动
LINK的时候需要指定/subsystem,这个链接选项告诉Windows如何运行可执行文件。
控制台程序是/subsystem:"console"
其它程序一般都是/subsystem:"windows "

将 subsystem 选成"console"后,Windows在进入可执行文件的代码前(如mainCRTStartup),就会产生一个控制台窗口。
如果选择"windows",操作系统就不产生console窗口,该类型应用程序的窗口由用户自己创建。

可执行文件都有一个Entry Point,LINK时可以用/entry指定。缺省情况下,如果subsystem是“console”,Entry Point是 mainCRTStartup(ANSI)或wmainCRTStartuup(UNICODE),即:
/subsystem:"console" /entry:"mainCRTStartup" (ANSI)
/subsystem:"console" /entry:"wmainCRTStartuup" (UNICODE)
mainCRTStartup 或 wmainCRTStartuup 会调用main或wmain。
值得一提的是,在进入应用程序的Entry Point前,Windows的装载器已经做过C变量的初始化,有初值的全局变量拥有了它们的初值,没有初值的变量被设为0。

如果subsystem是“windows”,Entry Point是WinMain(ANSI)或wWinMain(UINCODE),即:
/subsystem:"windows" /entry:"WinMainCRTStartup" (ANSI)
/sbusystem:"windows" /entry:"wWinMainCRTStartup" (UINCODE)
WinMainCRTStartup 或 wWinMainCRTStartup 会调用 WinMain 或 wWinMain。

如果使用MFC框架,WinMain也会被埋藏在MFC库中(APPMODUL.CPP):
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
"_t"是一个宏,对于ANSI版本,"_tWinMain"就是"WinMain";对于UINCODE版本,"_tWinMain"就是"wWinMain"。

全局C++对象的构造函数是在什么地方调用的?答案是在进入应用程序的Entry Point后,在调用main函数前的初始化操作中。所以MFC的theApp的构造函数是在_tWinMain之前调用的。


4、不显示Console窗口的Console程序
在默认情况下/subsystem 和/entry开关是匹配的,也就是:
"console"对应"mainCRTStartup"或者"wmainCRTStartup"
"windows"对应"WinMain"或者"wWinMain"
我们可以通过手动修改的方法使他们不匹配。例如:

#include "windows.h"
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" ) // 设置入口地址
void main(void)
{
MessageBox(NULL, "hello", "Notice", MB_OK);
}

这个Console程序就不会显示Console窗口。如果选/MLd的话,这个程序只需要链接LIBCD.LIB user32.lib kernel32.lib。

5、VC中缺省库冲突的解决
VC的编译器在编译程序时有两个习惯:
a、在从头开始编译时,将源文件名按字母排序后,依次处理;
b、一边编译一边决定需要哪些缺省库。
它的这些习惯有时会造成奇怪的编译错误,例如项目中有两个文件:
charutil.c
gbnni.cpp
其中gbnni.cpp用到了MFC库。

它老兄当然是先处理charutil.c,然后觉得需要link一个C Runtime库,根据项目设置选择了LIBCMTD.lib。
然后又处理gbnni.cpp,因为要用MFC,又决定要link nafxcwd.lib。
最后link的时候,就会出现以下冲突:
nafxcwd.lib(afxmem.obj) : error LNK2005: "void __cdecl operator delete(void *)" (??3@YAXPAX@Z) already defined in LIBCMTD.lib(dbgdel.obj)
其实,如果先link了nafxcwd.lib,就没有必要再link LIBCMTD.lib,也就不会产生冲突。

解决这类问题有两个办法。
a、让项目的第一个文件包含MFC的头文件,这样编译器就不会想到找C Runtime库。这样就要把c文件改成cpp了。
b、将需要link C Runtime库的文件的名字改大一些,让它排在后面。
使用IDE当然很方便,但既然使用了别人写的工具,有时就不得不琢磨、迁就它的习性。
...全文
973 32 打赏 收藏 转发到动态 举报
写回复
用AI写文章
32 条回复
切换为时间正序
请发表友善的回复…
发表回复
ss3295 2005-07-03
  • 打赏
  • 举报
回复
谢谢楼主!
Willpro 2005-06-16
  • 打赏
  • 举报
回复
mark
Akitce 2005-05-04
  • 打赏
  • 举报
回复
学习..
szliuy 2005-05-04
  • 打赏
  • 举报
回复
mark一下,以后好找。
Stefine 2005-05-04
  • 打赏
  • 举报
回复
为楼主的这种精神鼓掌

努力学习ing
ironox 2005-05-04
  • 打赏
  • 举报
回复
佩服楼主,象楼主学习!
surstar 2005-05-04
  • 打赏
  • 举报
回复
ss3295(阳光)
你的2005 错误,在MSDN 上一, 说法和搂住的 查不多~
fmddlmyy 2005-05-03
  • 打赏
  • 举报
回复
再帖一段学习笔记:

什么是subsystem?
NT架构(Windows NT、Windows XP、Windows 2003)的初始设计是很有野心的,它希望在NT上可以不加修改地运行OS2、UNIX程序。
所以在NT中有subsystem的概念,每个subsystem针对一个平台,ntdll.dll是所有subsystem的基础。或者说ntdll.dll统一提供NT系统的API接口,subsystem为各个平台的应用程序提供包装。
在winnt.h中,对subsystem的定义如下:
#define IMAGE_SUBSYSTEM_UNKNOWN 0 // Unknown subsystem.
#define IMAGE_SUBSYSTEM_NATIVE 1 // Image doesn't require a subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_GUI 2 // Image runs in the Windows GUI subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_CUI 3 // Image runs in the Windows character subsystem.
#define IMAGE_SUBSYSTEM_OS2_CUI 5 // image runs in the OS/2 character subsystem.
#define IMAGE_SUBSYSTEM_POSIX_CUI 7 // image runs in the Posix character subsystem.
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS 8 // image is a native Win9x driver.
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9 // Image runs in the Windows CE subsystem.
CUI就是Console UI了。我们使用的subsystem主要是3和2。

NT架构另一个主要概念就是用户态和核心态了。32位计算机的地址空间中,0x0-0x10000是保留的,然后0x80000000以下属于用户态,0x80000000以上属于核心态。核心态管理所有硬件。用户态不能使用核心态的任何东西。在核心态运行的程序,例如驱动程序,可以在系统中为所欲为,当然错误的后果会很严重。
在用户态看起来很底层的东西,例如Win32 subsystem的核心:kernel32.dll、user32.dll、gdi32.dll,基本上只是ntdll.dll的一个包装,而ntdll.dll包装了从用户态到核心态的system call,也称作“Native System Service”。
用户态不能访问核心态的任何函数和变量,所以system call不同于一般的API调用。system call可以被看作:将要调用的功能ID放到eax,然后执行INT 2e。
ntdll.dll通过system call使用核心态的ntoskrnl.exe和win32k.sys提供的功能。ntoskrnl.exe被尊称为“Executive”,可以看作是NT的大脑级模块。win32k.sys提供NT图形库接口的API。

用Win32dsm查看了NT核心模块的导入表,整理了它们之间的调用关系,可见:
http://fmddlmyy.home4u.china.com/images/NT_kernel.jpg
fmddlmyy 2005-05-01
  • 打赏
  • 举报
回复
有的朋友太客气了。整理这些资料,在我只是学习的一部分。贴在这里,如果有错误,可以得到指点。说不上什么辛苦、奉献。
我当然算不上什么高手。我的工作是嵌入式开发。对Windows、Linux平台,我只是一般用户,有时需要写一些小程序。

关于“Windows装载器的资料”,后来又找了找,有3个信息来源,我觉得不错。

1、讨论得最详细的应该是《Inside Microsoft Windows 2000, Third Edition》的第6章"Processes, Threads, and Jobs"。
在MSDN上正好有这一章的样本。我下载后,为了方便阅读,作了些编辑。文章中引用的图片原来需要另外打开,我将图片下载后,直接嵌入到页面中。但只改了第一页。
有兴趣的朋友可以从http://fmddlmyy.home4u.china.com/inside win2k.rar下载。

2、我在一个日本网站找到一套微软培训"Windows Kernel Internals"的幻灯片。内容如下:
01_WindowsKernelOverview.pdf
02_ObjectManager.pdf
03_VirtualMemory.pdf
04_ThreadScheduling.pdf
05_Synchronization.pdf
06_TrapsInterruptsExceptions.pdf
07_IOArchitecture.pdf
08_NTFS.pdf
09_Registry.pdf
10_LPC.pdf
11_WindowsServices.pdf
12_Processes.pdf
13_AdvVirtualMemory.pdf
14_CacheManager.pdf
15_UserModeHeapManager.pdf
16_Win32K.pdf
17_CommonCodingErrors.pdf
虽然不详细,但可以参考。有兴趣的朋友可以从http://fmddlmyy.home4u.china.com/Windows_Kernel_Internals.zip下载。

3、另外,网站http://www.relsoft.net上有一些介绍NT kernel的文章,写得很不错。给我印象很深的是一篇介绍Native API的文章,写得浅显易懂。

我的目的是想知道WIndows程序是怎么装载的。其实网上有位朋友三、两句话就说清楚了;
“系统创建进程,初始化了半天,最后启动一个APC,把ldrinitializethunk 插入队列,等待调度,该函数调用LdrpInitialize,drpInitialize 调用LdrpInitializeProcess,在用户态把该加载的都加载了,把静态连接的dll都dll_process_attach了,然后进入到run_time库的入口地址。”
进一步查一些资料,只是求证一下,再了解一些细节,以满足好奇心。

有位朋友说"好多不懂",其实现在网上找资料很方便,懂不懂只是愿不愿意花时间而已。谈到时间,我要赶快去老丈人家抱小孩了,五一后就是爸爸月,即我要负责宝宝的全面陪护,基本没时间再在网上闲逛了。
蟾宫伐桂 2005-04-30
  • 打赏
  • 举报
回复
楼主辛苦,收藏
hushuangyan74 2005-04-30
  • 打赏
  • 举报
回复
楼主是高手吧!我好多多不懂!
Featured 2005-04-30
  • 打赏
  • 举报
回复
对楼主的这种奉献精神表示赞赏,
希望论坛上多多见到这种有点提点性的帖子,
多多见到这种乐于分享、乐于奉献的楼主,
此乃万民之福也……
Featured 2005-04-30
  • 打赏
  • 举报
回复
使用dll,采用显式声明时,可以用如下三种方式添加lib:

a.可以到“project”菜单下选择“Add To Project”,“Files”,然后把lib文件加进来。
b.也可以设置工程选项:到“project”菜单下选择“Settings”,然后选择“link”选项卡,在其中的Object/library Modules中填入“yourLib.lib”
c.还有一种方法,直接在CMyDlg.cpp文件开始处添加一句预编译指令:
#pragma comment(lib,” yourLib.lib”)
y_cc 2005-04-30
  • 打赏
  • 举报
回复
楼主辛苦了
fmddlmyy 2005-04-30
  • 打赏
  • 举报
回复
“不显示Console窗口”的例子来自vcforever的专栏(http://blog.csdn.net/vcforever/archive/2004/12/14/215936.aspx)。选择这个例子,因为它较好地说明/subsystem 和/entry开关的关系。

其实如果只是想关掉Console程序的Console窗口,我试过一个更直接的方法:那就是在EXE文件中将PE文件头的Subsystem从3改成2。在EXE文件中,PE文件头的偏移地址是0x3c,Subsystem是一个WORD,它在PE文件头中的偏移是0x5c。

再为MFC库开张清单:
MFC的库可以静态链接,也可以动态链接。静态库和动态库又有Debug和Release,ANSI和Unicode版本之分。

静态MFC库主要有:
ANSI Debug NAFXCWD.LIB
ANSI Release NAFXCW.LIB
Unicode Debug UAFXCWD.LIB
Unicode Release UAFXCW.LIB

动态链接库主要有;
ANSI Debug MFCxxD.LIB (core,MFCxxD.DLL),
MFCOxxD.LIB (OLE,MFCOxxD.DLL),
MFCDxxD.LIB (database,MFCDxxD.DLL),
MFCNxxD.LIB (network,MFCNxxD.DLL),
MFCSxxD.LIB (static)

ANSI Release MFCxx.LIB (combined,MFCxx.DLL)
MFCSxx.LIB (static)

Unicode Debug MFCxxUD.LIB (core,MFCxxUD.DLL),
MFCOxxUD.LIB (OLE,MFCOxxUD.DLL),
MFCDxxUD.LIB (database,MFCDxxUD.DLL),
MFCNxxUD.LIB (network,MFCNxxUD.DLL),
MFCSxxUD.LIB (static)

Unicode Release MFCxxU.DLL (combined,MFCxxU.DLL),
MFCSxxU.LIB (static)

xx是版本号。上面的LIB文件除了MFCSxx*.LIB以外都是导入库。
MFC动态链接库版本也需要静态链接一些文件,这些文件就放在MFCSxx*.LIB中。例如包含_tWinMain的appmodul.cpp。
hopen 2005-04-30
  • 打赏
  • 举报
回复
不错的文章
不过我觉得出现库冲突的大部分问题是因为库之间的线程设置不对
一个lib使用单线程模型,另一个使用多线程模型会出现冲突
还有我觉得最好不要在工程中设置连接的lib,在程序里面写可能更好一点
#pragm comment(lib, "a")
这样设置就和工程没有关系了,多人开发时不错的选择
uoyevoli 2005-04-30
  • 打赏
  • 举报
回复
收藏了。谢谢
wzh0591 2005-04-30
  • 打赏
  • 举报
回复
不错的文章,收藏了。谢谢!
fmddlmyy 2005-04-30
  • 打赏
  • 举报
回复
先链接libcmtd.lib,再链接msvcrtd.lib,就会产生这样的错误。估计你的主程序和library使用了不同的CRT设置。
建议你在主程序的"settings"-"link"-"input"的"Objects/library modules"中旗帜鲜明地指定库的顺序,例如:
mfc42d.lib msvcrtd.lib libcmtd.lib 其它库
要点是保证msvcrtd.lib出现在libcmtd.lib前面。
anlywei 2005-04-29
  • 打赏
  • 举报
回复
谢谢,学习
加载更多回复(12)

16,472

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧