友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
深入浅出MFC第2版(PDF格式)-第12部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
中的哪些文件比 中的文件更新,然后将其 字段设为 。同时也
desFiles' ' srcFiles' ' bMatch FALSE
对存在于 中而不存在于 中的文件,令其 字段为 。
srcFiles' ' bIsNew TRUE DstDir
最后,检查 中的所有文件,将 字段为 者,拷贝到 去。
destFiles' ' bMatch FALSE
并检查 中的所有文件,将 字段为 者统统杀掉。
JBACKUP 的源代码与可执行文件放在书附盘片的Jbackup。01 子目录中。
34
…………………………………………………………Page 97……………………………………………………………
MFCCON :MFC Console 程序设计
当你的进度还在第1章的Win32 基本程序观念,我却开始讲如何设计一个MFC console
程序,是否有点时地不宜?
是有一点!所以我挑一个最单纯而无与别人攀缠纠葛的MFC 类别,写一个40 行的小
程序。目标纯粹是为了做一个导入,并与Win32 console 程序做一比较。
我所挑选的两个单纯的MFC 类别是CStdioFile 和CString :
CObject
CFile CString
CStdioFile
CMemFile
CSocketFile
COleStreamFile
在MFC 之中,CFile 用来处理正常的文件I/O 动作。CStdioFile 衍生自CFile,一个
CStdioFile C runtime f open stream Stream
对象代表以 函数 所开启的一个 文件。 文件
有缓冲区,可以文字模式(预设情况)或二进制模式开启。
CString 对象代表一个字符串,是一个完全独立的类别。
我的例子用来计算小于100 的所有费伯纳契数列(Fabonacci sequence )。费伯纳契数列
的计算方式是:
1
1。 头两个数为 。
2。 接下来的每一个数是前两个数的和。
35
…………………………………………………………Page 98……………………………………………………………
以下便是 MFCCON。CPP 內容:
#0001 // File : MFCCON。CPP
#0002 // Author : J。J。Hou / Top Studio
#0003 // Date : 1997。04。06
#0004 // Goal : Fibonacci sequencee; less than 100
#0005 // Build : cl /MT mfccon。cpp (/MT means Multithreading)
#0006
#0007 #include
#0008 #include
#0009
#0010 int main()
#0011 {
#0012 int lo; hi;
#0013 CString str;
#0014 CStdioFile fFibo;
#0015
#0016 fFibo。Open (〃FIBO。DAT〃; CFile::modeWrite |
#0017 CFile::modeCreate | CFile::typeText);
#0018
#0019 str。Format (〃%sn〃; 〃Fibonacci sequencee; less than 100 :〃);
#0020 printf(〃%s〃; (LPCTSTR) str);
#0021 fFibo。WriteString (str);
#0022
#0023 lo = hi = 1;
#0024
#0025 str。Format (〃%dn〃; lo);
#0026 printf(〃%s〃; (LPCTSTR) str);
#0027 fFibo。WriteString (str);
#0028
#0029 while (hi 《 100)
#0030 {
#0031 str。Format (〃%dn〃; hi);
#0032 printf(〃%s〃; (LPCTSTR) str);
#0033 fFibo。WriteString (str);
#0034 hi = lo + hi;
#0035 lo = hi lo;
#0036 }
#0037
#0038 fFibo。Close ();
#0039 return 0;
#0040 }
以下是执行结果(在 console 窗口和 FIBO。DAT 档案中,结果都一样):
36
…………………………………………………………Page 99……………………………………………………………
Fibonacci sequencee; less than 100 :
1 1 2 3 5 8
13
21
34
55
89
这么简单的例子中,我们看到MFC Console 程序的几个重点:
1。 程序进入点仍为main
2。 需包含所使用之类别的头文件(本例为AFX。H )
3。 可直接使用与GUI 无关的MFC 类别(本例为CStdioFile 和CString )
4。 编辑时需指定/MT,表示使用多执行线程版本的C runtime 函数库。
第4点需要多做说明。在MFC console 程序中一定要指定多线程版的C runtime 函数库,
所以必须使用/MT 选项。如果不做这项设定,会出现这样的联结错误:
Microsoft (R) 32…Bit Incremental Linker Version 5。00。7022
Copyright (C) Microsoft Corp 1992…1997。 All rights reserved。
/out:mfccon。exe
mfccon。obj
nafxcw。lib (thrdcore。obj) : error LNK2001: unresolved external symbol __endthreadex
nafxcw。lib (thrdcore。obj) : error LNK2001: unresolved external symbol __beginthreadex
mfccon。exe : fatal error LNK1120: 2 unresolved externals
表示它找不到__beginthreadex 和__endthreadex 。怪了,我们的程序有调用它们吗?没
有,但是MFC 有!这两个函数将在稍后与执行线程有关的小节中讨论。
MFCCON 的源代码与可执行文件放在书附盘片的mfccon。01 子目录中。
37
…………………………………………………………Page 100……………………………………………………………
什么是C Runtime 函数库的多线程版本
当C runtime 函数库于1970s 年代产生出来时,PC 的内存容量还很小,多任务是
个新奇观念,更别提什么多执行线程了。因此以当时产品为基础所演化的C runtime 函
数库在多线程(multithreaded)的表现上有严重问题,无法被多线程程序使用。
利用各种同步机制( )如 、 、 、
synchronous mechanism critical section mutex semaphore
,可以重新开发一套支持多执行线程的 函数库。问题是,加上这样的能
event runtime
力,可能导至程序代码大小和执行效率都遭受不良波及 即使你只激活了一个执行
…
线程。
Visual C++ 的折衷方案是提供两种版本的C runtime 函数库。一种版本给单线程程序
使用,一种版本给多线程程序使用。多线程版本的重大改变是,第一,变量如errno 者
现在变成每个执行线程各拥有一个。第二,多线程版中的数据结构以同步机制加以保护。
Visual C++ 一共有六个C runtime 函数库产品供你选择:
◆ ( )
Single…Threaded static libc。lib 898;826
( )
Multithreaded static libcmt。lib 951;142
◆
Multithreaded DLL msvcrt。lib 5;510;000
◆
( )
Debug Single…Threaded static libcd。lib 2;374;542
◆
( )
Debug Multithreaded static libcmtd。lib 2;949;190
◆
◆ Debug Multithreaded DLL msvcrtd。lib 803;418
Visual C++ 编译器提供下列选项,让我们决定使用哪一个C runtime 函数库:
◆ ( )
/ML Single…Threaded static
◆ ( )
/MT Multithreaded static
( )
/MD Multithreaded DLL dynamic import library
◆
( )
/MLd Debug Single…Threaded static
◆
( )
/MTd Debug Multithreaded static
◆
◆ ( )
/MDd Debug Multithreaded DLL dynamic import library
38
…………………………………………………………Page 101……………………………………………………………
进程与执行线程 ( )
Process and Thread
OS/2 Windows NT Windows 95 PC
、 以及 都支持多执行线程,这带给 程序员一股令人兴
奋的气氛。然而它带来的不全然是利多,如果不谨慎小心地处理执行线程的同步问题,程
序的错误以及除错所花的时间可能使你发誓再也不碰「执行线程」这种东西。
我们习惯以进程(process )表示一个执行中的程序,并且以为它是CPU 排程单位。事
实上执行线程才是排程单位。
核心对象
首先让我解释什么叫作「核心对象」(kernel object )。「GDI 对象」是大家比较熟悉的
GDI Pen Brush GDI
东西,我们利用 函数所产生的一支笔( )或一支刷( )都是所谓的「
对象」。但什么又是「核心对象」呢?
你可以说核心对象是系统的一种资源(噢,这说法对GDI 对象也适用),系统对象一
旦产生,任何应用程序都可以开启并使用该对象。系统给予核心对象一个计数值(usage
count )做为管理之用。核心对象包括下列数种:
核心对象 产生方法
event CreateEvent
mutex CreateMutex
semaphore CreateSemaphore
file CreateFile
file…mapping CreateFileMapping
process CreateProcess
thread CreateThread
前三者用于执行线程的同步化:file…mapping 对象用于内存映射文件(memory mapping
file process thread
), 和 对象则是本节的主角。这些核心对象的产生方式(也就是我们
39
…………………………………………………………Page 102……………………………………………………………
API handle
所使用的 )不同,但都会获得一个 做为识别;每被使用一次,其对应的计
1 CloseHandle
数值就加 。核心对象的结束方式相当一致,调用 即可。
process
「 对象」究竟做什么用呢?它并不如你想象中用来「执进程序代码」;不,程序代码
的执行是执行线程的工作,「process 对象」只是一个数据结构,系统用它来管理进程。
一个进程的诞生与死亡
执行一个程序,必然就产生一个进程(process )。最直接的程序执行方式就是在shell (如
Win95 的文件总管或Windows 3。x 的文件管理员)中以鼠标双击某一个可执行文件图标
App。exe App shell CreateProcess
(假设其为 ),执行起来的 进程其实是 调用 激活的。
让我们看看整个流程:
1。 shell 调用CreateProcess 激活App。exe 。
1
2。 系统产生一个「进程核心对象」,计数值为 。
3。 系统为此进程建立一个4GB 地址空间。
4。 加载器将必要的码加载到上述地址空间中,包括App。exe 的程序、资料,以及
DLLs DLLs
所需的动态联结函数库( )。加载器如何知道要加载哪些 呢?它
们被记录在可执行文件(PE 文件格式)的。idata section 中。
5。 系统为此进程建立一个执行线程,称为主执行线程(primary thread )。执行线程才是
CPU 时间的分配对象。
6。 系统调用C runtime 函数库的Startup code 。
7。 Startup code 调用App 程序的WinMain 函数。
8。 App 程序开始运作。
9。 使用者关闭App 主窗口,使WinMain 中的消息循环结束掉,于是WinMain 结束。
10。 回到Startup code 。
ExitProcess
11。 回到系统,系统调用 结束进程。
40
…………………………………………………………Page 103……………………………………………………………
可以说,透过这种方式执行起来的所有Windows 程序,都是shell 的子进程。本来,母
进程与子进程之间可以有某些关系存在,但shell 在调用CreateProcess 时已经把母子之间
的脐带关系剪断了,因此它们事实上是独立实例。稍后我会提到如何剪断子进程的脐带。
产生子进程
你可以写一个程序, 专门用来激活其它的程序。关键就在于你会不会使用
CreateProcess API
。这个 函数有众多参数:
CreateProcess(
LPCSTR lpApplicationName;
LPSTR lpmandLine;
LPSECURITY_ATTRIBUTES lpProcessAttributes;
LPSECURITY_ATTRIBUTES lpThreadAttributes;
BOOL bInheritHandles;
DWORD dwCreationFlags;
LPVOID lpEnvironment;
LPCSTR lpCurrentDirectory;
LPSTARTUPINFO lpStartupInfo;
LPPROCESS_INFORMATION lpProcessInformation
);
第一个参数lpAppl icationName 指定可执行档档名。第二个参数lp mandLine 指定欲
传给新进程的命令列(mand line )参数。如果你指定了lpAppl icationName,但没有
扩展名,系统并不会主动为你加上。EXE 扩展名;如果没有指定完整路径,系统就只在
lpAppl icationName NULL
目前工作目录中寻找。但如果你指定 为 的话,系统会以
lp mandLine 的第一个「段落」(我的意思其实是术语中所谓的token )做为可执行档
档名;如果这个档名没有指定扩展名,就采用预设的〃。EXE〃 扩展名;如果没有指定路
径,Windows 就依照五个搜寻路径来寻找可执行文件,分别是:
1。 调用者的可执行文件所在目录
2。 调用者的目前工作目录
3。 Windows 目录
4。 Windows System 目录
41
…………………………………………………………Page 104……………………………………………………………
5。 环境变量中的path 所设定的各目录
让我们看看实例:
CreateProcess(〃E:CWIN95NOTEPAD。EXE〃; 〃README。TXT〃;。。。);
系统将执行E:CWIN95NOTEPAD。EXE ,命令列参数是〃README。TXT〃。如果我们这
样子调用:
CreateProcess(NULL; 〃NOTEPAD README。TXT〃;。。。);
系统将依照搜寻次序,将第一个被找到的NOTEPAD。EXE 执行起来,并转送命令列参
数〃README。TXT〃 给它。
建立新进程之前,系统必须做出两个核心对象,也就是「进程对象」和「执行线程对象」。
CreateProcess 的第三个参数和第四个参数分别指定这两个核心对象的安全属性。至于第
五个参数(TR UE 或FALSE )则用来设定这些安全属性是否要被继承。关于安全属性及
其可被继承的性质,碍于本章的定位,我不打算在此介绍。
第六个参数dwCreationFlags 可以是许多常数的组合,会影响到进程的建立过程。这些
CREA TE SUSPENDED
_
常数中比较常用的是 ,它会使得子进程产生之后,其主执行线程立
刻被暂停
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!