Windows NT 面面观

>>>  名人論史——近當代作家的史學觀點  >>> 簡體     傳統

发表日期 : 1994.03 

还没揭开面纱的新娘总让人充满好奇,
可是 Windows NT 揭开面纱後卖的不算好。
NT 是一个大家伙,如果你在家里供养一部这玩意儿,
以目前的硬体配备来看,会不会觉得自己像在 10 坪客厅装一部 20 吨的箱型冷气 ?
这一次我介绍三本 Windows NT 书籍。

NT 被预期是作业系统的先进指标,但卖的不算好,起码比起它未推出之前的气势来看。在那个 NT 将推出而未推出,山雨欲来风满楼的时代,一位朋友对我说:『现在什麽东西沾到 NT 都是宝,都可以卖钱』,兴奋之情溢於言表,彷佛拥抱了金山银矿。

的确,还没揭开面纱的新娘总让人充满好奇。

NT 是一个大家伙;它是一个具备网路能力,拥有强制性多线多工,保密防谍能力 (security),可供多人使用的大家伙。如果你在家里供养一部这玩意儿,会不会觉得自己像是在 10 坪的客厅装一部 20 吨的箱型冷气机 ? 如果 NT 售价不贵倒也无妨 (反正它什麽都能跑: DOS 程式、Windows 3.x 程式、OS/2 程式、POSIX 程式 (UNIX 程式)),不过也别忘了它有凶悍的能力吃掉你花大把银子买来的硬体资源。

这一次我介绍三本 Windows NT 书籍。如果以在学校上作业系统这门课的角度和心情,Inside Windows NT 颇能符合;如果程式员烦恼如何把 16 位元Windows 程式移植到 32 位元 NT 环境,Moving Into Windows NT Programming 在这方面着墨多些;如果想要理论和程式两头并进 Windows NT (这最给人扎实感),Advanced Windows NT 不错。请注意,NT 的境界已远远地把 DOS 抛到後头,对於不是科班出身的软体人员而言,多工、多线、排程、虚拟空间等需要深厚理论基础的主题要越雷池一步并不容易。非科班的人我想在软体界是满多的,侯捷就是一个。软体业能够吸引各种背景的人加入并且提供任何人筑梦的机会,正是这一行业伟大的地方。

进入书籍评介之前我想就几个容易混淆不清的观念先介绍一下:

Win32 - 这是 32 位元 API。所谓 API 就是函式名称、叁数个数、叁数型态、函式回返值等等「程式介面规格」。Microsoft 希望 Win32 能够成为 32 位元API 的标准,做出来的 32 位元程式就是 Win32 程式;以档案格式来说,它们是「PE (Portable Executable) 档」。

Windows NT - 这是一个可以跑 Win32 程式的作业系统,这只是 Win32 API 载台 (作业系统) 之一,但也是目前唯一的一个。

Win32s - 最容易令人迷惑的名词。它是一个「作业系统扩充部份」(如果使用英文我应该说 "Extension of Windows",但中文说「扩充」比说「延伸」更好些;事实上 Extension 也有扩充之意)。早期的 Multimedia Extension for Windows 扩充了 Windows 3.0 的多媒体能力 (後来被整个含入 Windows 3.1),现在这个 Win32s 扩充了Windows 3.1 执行 Win32 程式的能力。多媒体扩充部份由一些 DLLs 构成,Win32s 扩充部份也是由一些 DLLs,外加一些 VxDs、EXEs 构成,它们负责把 32 位元呼叫转换为 16 位元,毕竟 Windows 3.1 中真正有的只是 16 位元函式。不是每一个 Win32 API 都可以被这些转换层转换为 Win16 API,所以从另一个角度来看,我们又把「能够在 Win32s 这个环境下执行的 32 位元程式」所能够呼叫的 Win32 API (那只占全部 Win32 的一部份) 特别归为一类,称为Win32s API。

 

carton-api-jungle.jpg (28937 bytes)
等等,等等,这里是 Win16 还是 Win32?是 Win32s 还是 Win32c?

 

背景资料 :
书名 Moving into Windows NT Programming
作者 Jason Loveman
出版 SAMS
页数 9 章,515 页
售价 US$ 39.95
磁片 Yes

1. An Overview of Windows NT
2. NT Internals
3. Porting Programs to NT
4. A Port to WINIO
5. NT Multitasking
6. The NT is for New Technology
7. Win32s
8. Portability
9. Tools

本书写给任何已经熟悉 Windows 3.x 而欲了解Windows NT 者,作者假设你已经有 SDK 经验。这本书花相当的篇幅介绍移植性,书名早就明白告诉你了。

第一章是 Windows NT 概观,讲的却并不是技术上的概观,而是关於本书的概观,包括全书范围、作者经历、Win32s 介绍 (一点点)、移植程式需多少时间等等。我们这位爱人同志有一个最高指导九原则 (很像报纸副刊上常玩的性向游戏): 如果你如何如何,就加三天;如果你如何如何,再加五天...。

第二章介绍 Windows NT 内部,开始谈些作业系统的东西,只不过讨论十分粗浅。本书着重在移植性,因此第三章第四章是重头戏。作者在第三章一一列出几个程式移植时值得注意的地方,例如利用 #ifdef(WIN32) 设计那些 16/32 位元间十分敏感的函式或叁数。事实上移植性最大的问题并不在 API 的不相容,那很容易揪出来;有些 API 的困扰不是因为名称或叁数或动作有所改变,而是因为它们处理的资料可能在 Windows 3.1 和Windows NT 中有所不同,这称为 "Unlisted API Changes"。最明显的就是视窗类别和视窗两个资料结构中的 words 和 longs 的改变,下面是个例子 :

#if defined (WIN32)
hInst=(HANDLE)GetWindowsLong(hwnd,GWL_HINSTANCE);
#else
hInst=GetWindowsWord(hwnd,GWW_HINSTANCE);
#endif

你记得这做什麽用吧,这是直接从视窗资料结构中取资料。甚至你可以定一个巨集如下:

#if defined (WIN32)
#define GetWindowsInstance(hwnd) \
((HMODULE)GetWindowsLong(hwnd,GWL_HINSTANCE))
#else
#define GetWindowsInstance(hwnd) \
((HMODULE)GetWindowsWord(hwnd,GWW_HINSTANCE))
#endif

然後我们就可以高枕无忧地这麽做 :

hInst= GetWindowsInstance(hwnd);

 

很幸运 Windowsx.h 定义了一些好用的巨集 (包括上述巨集),这个档在移植性方面扮演重要角色,如果忽略它,你自己可得多干好些活儿。我建议把它印出来好好瞧瞧。Windows.h 我认为也该印出来好好瞧瞧。我常把这类 .H 档以及 MSDN 光碟片中的文章印出来装订成册,学校附近的影印店都有装订服务。(MSDN 光碟片上期介绍过)。

在 Windows 3.1 中讨生活的人都知道这个 16 位元作业环境在记忆体管理方面还有些限制。如果你曾经尝试以 GlobalAlloc() 和 LocalInit() 组织一个SubSegment Allocation Scheme,这种架构并不适用於 NT,第一个原因是把数值放到 DS 这个动作在 Windows NT 中并不合法,第二个原因是 LocalInit() 已不复存在於 Windows NT 中。那怎麽办呢 ? 啊哈,Windows NT 根本就不需要,NT 多了新的虚拟记忆体 API 以及新的 Heap API,足以解决这个问题。

第四章介绍如何把 WINIO 移植到 Windows NT 上。想在 Windows 上继续使用方便的 printf(),你可以选择 MSC 7.0 的 QuickWin 或 Borland C++ 的 EasyWin,但这个好点子在更早之前就有人想过了,那就是 WINIO 函式库。这个 WINIO 享有一些名声,除了因为它的作者之一是知名的 Andrew Schulman,而且它三番两次出现,最近的一次是 Schulman 以它做为 Undocumented Windows 书中范例程式的萤幕输出工具。本章的移植工作大部份不在 API 的改变,而在讯息与叁数的 32 位元化。这一章82 页中倒有 61 页程式码 (包括 WINIO 和它的两个呼叫范例),如果你从不曾看过听过 WINIO,显然这一章难以下咽,下面两篇文章对 WINIO 的设计原理有详细说明 :

○ Call Standard C I/O Functions from Your Windows Code Using the WINIO Library (MSJ, 1991/07)

○ Porting DOS Programs to Protected-Mode Windows with the WINDOS Library (MSJ,1991/09)

其实 main()、printf()、gets() 等标准的 C 程式一样可以在 Windows NT 中跑,它自动继承一个 console,程式所有的 I/O 都将在这个 console 所控制的视窗中执行。这听起来就像在 DOS Box 中执行 DOS 程式没有两样,但这只是 Windows NT console I/O 的一小部份功能而已。Win32 有一组 Console API,提供开发所谓character-mode 程式的必要函式,我们既拥有类似 printf() 的方便,又做出不折不扣的 32 位元程式。

好,那麽你会问有了 Console API 又何需把WINIO 移植到 NT 上 ? 一来这是作者的一个移植示范,二来不管 QuickWin 或 EasyWin 或 Console API,没有一个适用於 Win32s 环境,独独 32 位元的 WINIO 可以,所以它还是有些实质用途的。关於 Console API,PC-Mag 有一篇 Exploring the Win32 Console API (Ray Duncan,1992.11.24) 也很好。

说到这里我又想补充一点,许多我们习惯的 Windows 程式写法,特别是为我们带来不解或不便的,像 MakeProcInstance()、GlobalLock()/GlobalUnlock()、Medium Model 神话、LibMain()/WEP(),在编译器不断演进的过程中其实已经成为天宝当年的老写法了,已经没有存在的必要。Windows 程式甚至可以不要 WinMain() 而以 main() 做为进入点。这些特质可能因编译器的不同而互异,我刚刚说的是Microsoft 开发工具,有兴趣的话你可以看 MSDN 的 The C/C++ Compiler Learns New Tricks 一文。(也许编译器手册上也有这些资讯,手册太多了我不敢说我没有漏看)。

第五章涵盖 Windows NT 的多工多线特徵,以及影响所及的程式写法。Process 是一个可执行档在记忆体中的映像 (image),自己拥有一个独立的位址空间;Thread 则是在该位址空间中的一个执行单位。强制性多工对於那种「在一个以上的 thread 中处理同一份资料 (变数)」的情况特别有深远的影响。Windows 3.x 虽然没有强制性多工 (如果我们把眼光放在System VM 中而不是 VM 与 VM 之间的话),同样的问题依然可能发生在 DLL,因为 DLL 可以提供 task (EXEs) 之间的共用资料。这也就是为什麽利用 DLL 供应共用性资料的作法并不被鼓励,你得小心 !

在强制性多工多线环境下,Scheduler 随时会进来,全域变数很容易造成困扰。以这个例子来说 :

int count = 0;
DATABLK aData[MAXDAT];

1 RETCODE MultiThreadFunction(DATABLK *pData)
2 {
3 if (count == MAXDAT) return(FULL);
4 aData[count] = *pData;
5 count++;
6 return OK;
7 }

如果 thread 在 line4 执行後被切断 (强制性多工环境下任何两个指令间都有可能 被中断),会导至第二个 thread 把资料贮存在第一个 thread 的资料上。更糟的是(和上例无关) 如果第一个 thread 已经把阵列用光了,第二个 thread 可能会用到阵列以外的记忆体。指标乱指会造成什麽後果 ? 所有 C 程式员都知道其严重性。这些问题的发生都是因为全域变数被各 thread 共享,所以能够避免使用全域变数就尽量避免;如果不能避免,Windows NT 提供数种 Synchronization objects: Events、Semaphore、Mutex、Critical Section。这些名词意义以及技术会让只具 DOS 基础的人充满挫折感。

谈到多工,IPC (行程通讯) 就不能不表。关於这部份本章有三个范例程式,一是 CALLIPC,使用 DLL shared data 做 IPC (只能针对静态资料);一是 MEMSHARE,使用 "shared memory-mapped file" 做 IPC (可以针对动态配置的记忆体);一是 DDEIPC,使用 DDEML 做 IPC。程式都小小的,饶富趣味,针对每个程式你可以 (也只能) 执行两个个体 (instance),每个个体都会出现一个画有格子状的视窗;当你在个体 A 移动滑鼠,原本是应该在游标位置所在的格子中涂上黑色,现在却是在个体 B 的对映格子中涂色。这种带有视觉效果之 IPC 范例的教学效果和吸引力好极了。

我总觉得,同样是写应用软体,Windows 程式员对作业系统认知程度的多寡,所带来在程式发展上的影响,远比 DOS 程式员要来的重的多。所以国外杂志常说Windows 程式员的薪水高,环境挑,想来不是没有道理。程式写到一个程度,没有适度的作业系统基础就难再进步,这也是为什麽我一再强调要注意 prcess、thread、memory management、virtual machine 等基础知识。很抱歉我总是以DOS 程式员的心情来看学习历程,懂 UNIX 或 X Window 的人应该已经有了这些知识。

第六章介绍 Windows NT 的新事物,包括 Registry 和 SEH 和 VM,以及 GDI++,Memory-Mapped File I/O,Unicode、File System、Networking、Security。范围这麽广,总共 110 页的这一章只能蜻蜓点水,在几个点上稍做停留。若要做主题浏览,本章尚可,若要更深入些,就得看稍後介绍的下一本书了。

System Registry 是 Windows 3.1 的 REG.DAT 的扩充,并不存在於 Win32s 中。原先 REG.DAT 只是为了贮存 OLE 的资讯,以及 ProgMan 和 FileMan 的副档名相关资讯 (像是 .TXT 对映到 NotePad.EXE);到了 NT,所有原放於 CONFIG.SYS,AUTOEXEC.BAT,WIN.INI,SYSTEM.INI 的资讯都被放在这具有层层阶级的资料库中了,甚至应用程式也被鼓励把自己私有的执行环境描述在其中。关於这个主题,A Look at the Windows System Registry (PC-Mag,1993.01.12) 以及Exploring Windows System Registry Performance Data (PC-Mag,1993.01.26) 也是两篇好文章,都是 Ray Duncan 的作品。

你觉得介绍书又介绍文章有点儿画蛇添足吗 ? 文章精简而且独立性强,比较不会带来压迫感。杂志专栏作家的文笔、思路结构、技术实力通常都非常好,本身也多有写书经验。我总喜欢先看文章再看书。

第七章有 42 页介绍 Win32s。任何关於 Win32s 的文章,一定会出现 thunk 这个字,但是你查不到这个字的意义。这个术语最初是发展编译器的人用的,意思是

"a piece of code,generated by the compiler,which evaluated an l-value or r-value of a parameter"

而当 Windows 使用 thunk 这个字,它的意义是

"an address that can be called by a process that refers to a system generated piece of code which does something, and then calls a real function address"

thunk 的行为像在截取程式不同部位发出的函式呼叫动作,有时候用来决定位址,有时候用来转换位址等等。Win32s 组成份子 (各 DLLs、EXEs) 中,最重要的部份我们就称之为 "thunking layer"。关於Thunking,我看过最深入的一篇文章是 At Last-Write Bona Fide 32-bit Programs that Run on Windows 3.1 Using Win32s (Andrew Schulman,MSJ,1993/04)。在这篇文章中,作者解释 32 位元的好处,以及 Win32s 的好处。
其实 Win32s 并不是第一个企图进入 32 位元的 Windows 程式,第一个有此打算的是 Microsoft 的 WINMEM32.LIB (1991/03 的 MSJ 有一篇 Porting 32 bit AP to Windows 3.1 with the WINMEM32 Lib)。MetaWare 和 Rational Systems 两家公司也有 32 位元的
Windows Extender (它对 Windows 的关系相当於 32 位元 DOS Extender 相对於 MS-DOS 的关系)。某些 32 位元产品如 Mathematica (Wolfram Research 出品) 也已经出现在市场上。

但不同於这些已经建立的 32 位元 Windows 程式,Win32s 承诺的是
标准性,因为它使用与 Windows NT 相同的 Win32 API,并且,当然
也因为它来自於 Microsoft。事实上 Win32s 可以说是 Windows
作业系统在 32 位元的扩充部份,绝不只是一个补充过渡品。每一份
Windows 3.1 都已经知道了什麽是 Win32s : Windows 3.1 的 KRNL386
内含一个 ExecPE() 函式,只要使用者想在 Windows 3.1 中执行一个
Win32 PE (Portable Executable) 档案,ExecPE() 就会搜寻并企图
载入一个 W32SYS.DLL 档案。此档案是 Win32s 的组成份子,从这里
开始引爆整个 Win32s 子系统的活动。

下图是这篇文章的附图。此图将 Win32s 子系统的组成份子以及它们之间
的关系表现的非常好。

图 MSJ 1993.04

本网站略



如果曾经使用过或考虑过 DOS Extender,是否你想过这个问题 :「我原先的 DOS 程式内呼叫了买来的 LIB (没有原始码),现在为了加入 DOS Extender,重新编译的程式如何和这买来的 LIB 再次成功联结」? 这问题同样出现在 Win32s 身上,但这次有解答,Universal Thunks 可以完成使命。这也是唯一一个存在於 Win32s 而不存在於 Win32 的特质 (Win32 根本不需要什麽 thunking)。本章有一个小范例就是以 32 位元程式呼叫目前在 Win32s 中还没有支援的 MCI (多媒体控制介面),最终由 16 位元 MMSTSTEM.DLL 服务 (内含 MCI)。这个例子借助 Win32s API 中的 Universal Thunking Mechanism 提供的标准介面,在 32 位元应用程式与16 位元 DLL 之间搭起一座鹊挢。简单地说,我们要为 32 位元程式制作一个32 位元的 "interface DLL",它透过系统提供的 "stepdown thunk" 与另一端的 16 位元 "interface DLL" 搭上,再由 16 位元的 interface DLL 呼叫 16 位元 DLL 函式 :

32 位元应用程式


32 位元 interface DLL (程式员负责完成)


stepdown thunk (系统内建)


16 位元 interface DLL (程式员负责完成)


16 位元 DLL

MSJ 在 1993.11 有一篇文章 Mix 16-bit and 32-bit Code in Your Applications with the Win32s Universal Thunk 很深入,作者是 Walter Oney。怀疑 thunking 有什麽用吗 ? 难说,视你依赖一个无原始码之 DLL 的强烈程度而定。

再回到书上来。第八章又对移植性耳提面命一番。其中有个小节提到 MFC (Microsoft Fundation Class)。说真的,要有跨平台的移植性,高效率的软体生产力,MFC 应该是很好的投资;Borland 的 OWL 是同类级产品,应该也不错。厂商广告说的天花乱坠,让人有「以 MFC 撰写程式很简单」的想法,再进而联想到「任何生手都可以利用各种 Wizards (或 Experts) 轻轻松松写 Windows 程式」,真是胡说,依我看 ClassLib 只能帮助有相当功力的 Windows 程式员。下个月我们好好谈谈 MFC。

 

背景资料 :
书名 Advanced Windows NT
(The Developer's Guide to
the Win32 Application Programming Interface)

作者 Jeffrey Richter
出版 Microsoft Press
页数 11 章,700 页
售价 US$ 39.95
磁片 Yes

Introduction
1. Processes and Threads
2. Memory Management with Heaps
3. Virtual Memory Management
4. Memory-Mapped Files
5. Thread Synchronization
6. The Win32 SubSystem Environment
7. Dynamic-Link Libraries
8. Thread-Local Storage
9. File Systems and File I/O
10. Structured Exception Handling
11. Unicode
Appendix : Message Crackers

advanced-win-v1.jpg (16246 bytes)

本书基本上以作业系统观念为主,辅以范例验证之。从章名可以发现,都是作业系统的大题目。Richter 另有一本Windows 3.1 : A Developer's Guide,在我心目中与经典作Programming Windows 3.1 等量齐观;他设计范例程式的点子和技巧都是一流的。

别忽略最前面的 Introduction,这里有名词解释,解释的是 Win32,Win32s 和 Windows NT (许多人不能够十分清楚它们的关系与异同)。本书读者群设定在具备 16 位元 Windows 程式经验者,范例程式以 C 写成。Richter 说他自己发展大计划时用的是 C++,但他不愿意丧失最大的客户群。老实说我也很想知道台湾有多少人真正在工作上应用 C++ 语言。这里也提到全书程式的发展和测试环境,以及磁片的安装方式。

第一章 Processes and Threads,介绍应用於 process 和 thread 建立与维护的各种 API 函式。作者以某些关键性 API 函式叁数的详细说明,帮助我们更了解作业系统。光 CreateProcess() 的 10 个叁数就足足用掉 13 页。

在 16 位元 Windows 中要做到多工,必须不断呼叫 PeekMessage(),而在耗时甚久的档案动作中,偏偏 PeekMessage() 又只在读档 (或写档) 动作之间才发生效用,所以常常发生的一种情况是,使用者由於没有立即看见反应而拼命按[Cancel] 钮,每一次按钮都被 Windows 记录下来;当软体真的处理了第一个按钮动作,剩馀的左键 click 动作就作用在当时萤幕上的位置。会不会因而引起档案未贮存就离开的这种断肠情况呢 ? 谁知道 !! 看你被记录下来的 click 动作到底落在萤幕上的哪里罗。如果你将耗时的程式码集中放在某个 thread 执行,UI 码放在 primary thread 执行,那麽 [Cancel] 按钮永远有最高优先权,可以即时反应。我们再不需要把PeekMessage() 撒落的满地都是了。

如果你把印表工作放在某个 thread,允许使用者在primary thread 中继续它的编辑排版工作,那麽 primary thread 更改的资料在 printing thread 中该当如何 ? 解决方法是把档案拷贝一份到暂时档中供应给 printing thread,而让 primary thread 在原档案工作。这类问题前面我也说了好几次,都是强制性多工多线环境带来的副作用,它可是两面刀呢。

Win32 允许应用程式设定 thread 的执行优先权,从 1 (最低) 到 31 (最高)。如果优先权高的 thread 有 event 等待处理,优先权低的 thread 永远只能等待。那麽优先权 1 的 thread 岂不永无翻身之日 ? 别担心,绝大部份的 threads 都把它们分得的 CPU 时间拿来睡觉,所以,低优先权的 thread 仍有可为。

这一章描述 process 的生命周期以及 NT 如何管理它,同时也叙述了 NT 的Object Manager (process 也是一种 object)。本章也讨论了 process 对 thread 的管理。科班出身的你认为这都是作业系统课程里的基本东西,有什麽好再强调的 ? 可是软体人力中有许多并非资讯背景的工程师,我在工研院机械所的同事(开发 DOS 环境下的 CAD/CAM 软体) 有数学、工工、土木、机械、教育背景、就是没有一个人是资讯本科。数量庞大的 PC 软体人员需要更多关於多工作业的观念与经验。这些高阶性能过去处在一个遥远的国度中,在空调洁净一尘不染的大型主机上;现在 Windows NT (以及即将来临的 Windows 4.0 ) 带它们破窗而入到我们凌乱而生活化的书房里。

第二章介绍 NT 新的 Heap 记忆体管理。16 位元 Windows 的记忆体管理大致分为 Global Heap 和 Local Heap 两种,各有其使用时机以及优缺点,Richter 建议如果程式不考虑回溯相容於 16 位元 Windows 的话,就直接使用新的 Heap API。有一个范例是在 C++ object 中设计 new 和 delete 两个运算子的overloading 函式,那麽就可以在配置记忆体和释放记忆体时用到 Heap API 的好处。

第三章介绍虚拟记忆体。这份能力在 Windows 3.x 加强模式已有,Win32 提供让程式员能够直接叁与其事的能力。这章介绍的是 Windows NT 记忆体管理的基石,上一章的 Heap manager 系架构在本章的 VM manager 之上。

第四章介绍 Memory-Mapped Files。这是一个非常重要的技术,是 NT 管理 App 和 DLL 的重要基石。基本想法是你可以在打开档案後取得一个映射到档案的指标,然後就可以把档案内容当做在记忆体中一样地运作,不必困扰应该先配置多少记忆体以及如何把内容分段读进来分段处理等 "buffering" 琐事。NT 本身利用这技术做三件事情 :

o. NT 利用此技术载入并执行 EXEs 或 DLLs,系统会注意所有关於 paging、buffering、caching 事情。

o. NT 以此技术存取记忆体中的资料 (把它视为档案),应用程式不必自己做buffering。

o. 这是 Windows NT 唯一用来在 processes 之间共享资料的方法。两个 processes 使用同名的 Memory-Mapped Files 就可以共享其中资料。我们熟知的在Windows 3.1 中使用於 DDE 或 DDEML 的「GMEM_DDESHARE 记忆体」现已不复能用,因为NT 中每一个 process 都有自己的位址空间。

第五章有 115 页讨论 Thread Synchronization。在强制性多工环境中当多个 threads 同时进行时,有时难免要令某个 thread 等一等,直到符合某种情况再继续进行。如何使各个 thread 的活动同步化,不至於失去控制,成为强制性多工非常重要的课题。本章讨论的是 NT 提供的 Synchronization object 中的四种 : critical section、mutexes,semaphores,events。

第六章介绍 Win32 SubSystem。前面我曾说作者在设计范例程式时有很好的点子和技术,在这一章中表露无遗。NT 在输入系统方面有一个很大的改进 : 每一个 thread 自有一个 local input state,本章讨论为什麽 Microsoft 要加上它,以及应用程式如何从中获利。

第七章介绍 DLL。DLL 地位最大的改变是,在 16 位元 Windows 中它被视为作业系统的一部份,一旦载入,任何程式都可以呼叫它提供的 API 函式;在 32 位元 Windows NT 中 DLL 载入後却只属於某个process 所有,换句话说别的 process 想呼叫它是不能够的。但这并不意味 NT 可能载入两份完全相同的 DLL,如果两个 process 使用同一个 DLL,实际的一份码会以 memory mapped file 的方式映射到两个 process 各自拥有的虚拟位址空间中,每一个 process 维护自己对该 DLL 的使用次数。现在可以体会memory mapped file 以及虚拟位址空间的重要了吧,不弄懂那些东西你就不知道我现在说些什麽。

想以 DLL 做为资料交换场所吗 ? 16 位元 Windows 中的确有这种作法,藉 DLL 的全域变数或 LocalAlloc() 空间做为共享区域。但这在 NT 行不通,原因很明显 : DLL 载入之後属於 process 所有,而不同的 process 有自己的位址空间。

DLL 的撰写方式也有重大变革。以前必须联结 LibEntry.OBJ,这是组合语言完成的码;NT 既然宣称跨平台,组合语言的必要性就应该降到最低;在 Win32 中你的 DLL 只要像 EXE 一样地联结就好,不要什麽特殊手续。LibMain() 和 WEP() 已不需要,取而代之的是 DllEntryPoint() :

BOOL WINAPI DllEntryPoint(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved)
{

switch (fdwReason) {
case DLL_PROCESS_ATTACH:
// DLL
被映射到 process
//
位址空间
break;
case DLL_THREAD_ATTACH:
//
一个 thread 产生了
break;
case DLL_PROCESS_DETACH:
//
一个 thread 结束了
break;
case DLL_THREAD_DETACH:
// DLL
被解除映射
break;
}
return(TRUE);

}

如果你看到别人的 DLL 进入点是 DllMain() 而不是 DllEntryPoint(),那是为了 C runtime 函式库而又做的一点小设计,书中有说明。

有一件事值得在此披露。当一个 DLL 呼叫 GlobalAlloc() 时,获得之记忆体空间的拥有者是当时呼叫 DLL 的那个 task,而不是 DLL 本身。更进一步说,如果这个 DLL 被移出系统之外 (例如 task 呼叫了 FreeLibrary()),而该 task 依然活着,那麽这块空间仍然存在没有被摧毁。想想这情况 :

 

wpe27.jpg (7726 bytes)

 

其中 blockA 属於 taskA 拥有。当 taskA 结束,blockA 也被系统收回,此时如果 taskB 仍然呼叫 DLL 函式去处理 blockA (别忘了 taskA 和taskB 的程式码相同),可能导至当机。但是如果配置记忆体时指定 GMEM_SHARE 呢 ? 情况就大不相同,blockA 将因此属於 DLL 而不是 taskA,所以 taskB 可以安心享用。如果一块 GMEM_SHARE block 不是透过 DLL 配置而是 task 自行配置,它将属於 module 所有。

以上这些情况是 16 位元 Windows 中很重要的观念。至於 Windows NT,由於 DLL 附属於每个 process 的私有位址空间中,所以记忆体不论如何配置、由谁配置,都只属於 process 拥有;GMEM_SHARE 不复存在於 Windows NT 中。第七章的最後一节讲的就是这些。

第八章介绍 Thread-Local Storage (TLS),这又是 multithreading 带来的副作用。想想看标准的 C runtime 函式库 strtok(),通常这函式的用法是先呼叫一次,取得第一个单元 (token) 的起始位址,然後再持续呼叫 (叁数是 NULL) 以取得下一个单元的起始位址 :

1. char *ptr;
2. ptr = strtok("FEB.01,1994",".,");
3. while(ptr != NULL)
4. {
5. printf("ptr = %s \n",ptr);
6. ptr = strtok(NULL,".,");
7. }

如果我们把第一次得到的位址放在 static 变数中,却没想到在进入 while() 之前第二个 thread 插进来又呼叫了第二行的 strtok(),资料就被盖掉了。解决之道是 C 的 runtime 函式库必须改写 (使用 TLS) 以顺应multithreading (这是 Microsoft 或 Borland 或 Watcom 的事,和我们无关),而程式员也要注意尽量少用全域变数 (这一点已经三令五申过)。本章介绍的TLS 技术对 DLL 尤其重要,因为 DLL 往往不知道自己将来是如何被联结使用,因此以 TLS 贮存资料确保安全非常有必要。

第九章讨论应用程式如何操作 Windows NT 支援的四种档案系统 : FAT,CDFS,HPFS 和 NTFS。细部动作包括对磁碟目录结构的产生和删除,对档案的开与关,对资料的读与写等等。可惜没有一张搔到痒处的档案系统架构图 --- 像描述 DOS FAT 层层结构一目了然的那种。

第十章的 SEH 是个崭新玩意儿。支援 Win32 的 C 编译器有三个新的关键字 try、except、finally,用以实现"termination handling" 和 "exception handling"。所谓的 exception 就是一个你并不预期的事件,例如动用到一个不合法的记忆体位址或是把数值除以 0。exception handling 允许应用程式捕捉硬体或软体的 exception,使应用程式本身更稳健些;termination handling 则保证程式的清除善後工作(cleanup) 即使在发生了 exception 情况下也一定会执行。Win32 的 SEH 并不是 C++ 语言的 Exceptions Handling,关於後者你可以叁考 PC Magazine 在 1993.12.21 的 Making Exceptions With C++ (Kaare Christian) 一文,而前者你可以叁考 MSJ 在 1994.01 的 Clearer, More Comprehensive Error Processing with Win32 Structured Exception Handling 一文。如果使用过 C 语言的 setjump()、longjump() 函式,以及 Windows API 中的catch()、throw() 函式,你应该比较能消化这一章。

十一章的 Unicode 算是全书技术中最简单的了。为分辨 Wide Char 或 ANSI Char,我们可以大量使用巨集,这技俩很像为了让程式在不同编译器之下过关而使用巨集一样。作者把全书程式为 Unicode 而重新改写,花了四个小时,不算多,显示事情似乎颇为单纯。关於 Unidcode,PC-Magazine 的 [Environment] 专栏 (Charles Petzold 主持) 也有三篇深入的文章 :

⊙ 1993.10.26 Move over, ASCII! Unicode is here.

⊙ 1993.11.09 Unicode, Wide Characters, and C.

⊙ 1993.11.23 Viewing a Unicode TrueType Font Under Windows NT.

这些文章的技术性与本章无分轩轾,但 Petzold 的广泛度更能带我们翱翔知识的领域。他在第一篇一开始先以轻松的笔调告诉我们编码语言,世界各语系特色,然後才导入到 Unicode 标准,以及 Windows NT 的支援;文章一开始先给个楔子: "A great concept deserves a great name, and that name is Unicode". 气势磅礴,沛然欲出。我总认为技术性文章仍然比的出文笔高下,拿这两份资料一比,Petzold 比 Richter 高明些。

我想所谓幽默绝不是几首四不像打油诗加几句流行歌词,再加几句街头俚语;幽默应该是像这种由文字带来的内心莞尔,你不见得会笑,但就是有轻松愉快的感觉。我所喜欢的幽默,是能使我发笑五秒钟而沉思十分钟的那一种。

Richter 的文笔其实也不恶,第十章一开始他说:『闭上眼睛想像一下,有没有可能你的程式完全不会失败 ? 当然,如果永远有足够的记忆体,没有人丢给你不合法的指标,并且你要的档案总是存在的话。如果我们能这麽假设我们的环境,写程式可多麽赏心悦目,程式很容易被写,被读,被了解,再不需庸人自扰的这里放一个if 那里摆一个 goto,你的程式一根肠子通到底』。如果他直接了当这麽写 : 『Microsoft 在 Windows NT 中实作出 SEH 的主要动机是为了使作业系统的开发更容易,应用程式更稳健 ...』,显然我们少了些阅读乐趣。我想你应该能了解我想强调什麽,能遇着一位有文采而且知识渊博的作家,带领我们遨游知识瀚海,真是快意不过。有句话说 : 五岳归来不看山,黄山归来不看岳,当你读过这种文字,其他人的作品味同嚼蜡,索然无趣。

Richter 在 MSJ 发表过下列文章,这些内容都被收录在本书中 :

⊙ 1993/10 Coordinate Win32 Threads Using Manual-Reset and Auto-Reset Events

⊙ 1993/08 Synchronizing Win32 Threads Using Critical Sections, Semaphores, and Mutexes.

⊙ 1993/07 Creating, Managing, and Destroying Processes and Threads under Windows NT

⊙ 1993/04 Memory-Mapped Files in Windows NT Simplify File Manipulation and Data Sharing

⊙ 1993/03 An Introduction to Win32 Heap and Virtual Memory Management Routines

 

背景资料 :
书名 Inside Windows NT
作者 Helen Custer
出版 Microsoft Press
页数 9 章,385 页
售价 US$ 24.95
磁片 No.

1. The Mission
2. System Overview
3. The Object Manager and Object Security
4. Processes and Threads
5. Windows And the Protected Subsystems
6. The Virtual Memory Manager
7. The Kernel
8. The I/O System
9. Networking

inside-winnt-v1.jpg (13583 bytes)


NRP 出版社有一本同名的书,讲的是使用层面,我介绍的这本是 Microsoft Press 出版,请注意。稍早时候,本书可说是 Windows NT 系统观念的最佳书籍,书店有缺货现象。本书出版日期更在 Windows NT 上市之前,作者花了相当多的时间与 NT 小组成员沟通,封面并有 NT 小组领导人David N.Cutler 的「背书」,以及他的序言。Cutler 在序中说 :『虽然 NT 是我们的设计,Helen 却是能够捕捉其精华本质并且使它更容易被了解的人。关於此点,我们欠她一份情』。

Helen 这本书的目的并不是要教导作业系统原理,也不是要给作业系统工程师看的,她设定的读者是一般大众的我们,是「对电脑有些认识,希望了解这个系统的内部设计以便写出更好的应用软体,或希望因此降低黑盒子如作业系统者所带来的神秘」。这本书学不到如何写 Win32 程式。

本书的整体性评论是 : 有许多好图,学习 Windows NT 的系统架构应该从这本书开始。对於不是那麽想深入这领域而只是希望 keep 住新技术的人,这本书够了。

从 Windows 3.1 到 NT,够我们用功好些时间。可是我们还有的要奋斗的呢,OLE2、Chicago、Cairo 接踵而来。我真的希望如果你想赶上时代,及早行动时犹未晚。我周遭朋友谈的都是 Windows,看的都是 Windows,写的都是 Windows,杂志的问卷调查却发现台湾的 DOS 与 Windows 使用比例相差十分悬殊,这份结果从电脑书籍的销售比例上也得到验证。有时我真怀疑难道我生活在象牙塔里吗 ? 谁告诉我这个答案 ?

也许你会奇怪侯捷竟然把 OLE2 和上述作业系统相提并论,那是因为 OLE2 已经快接近半个作业系统的难度和大小了。下个月我为你介绍 Inside OLE2Inside Visual C++ 两本书。

 

carton-chicago.jpg (37888 bytes)

蓬莱此去无多路,不知老侯平安否


侯捷 2010-07-15 08:32:57

[新一篇] 精采絕倫 彩色書與電子書

[舊一篇] 具有革命精神的MFC 和 OLE2
回頂部
寫評論


評論集


暫無評論。

稱謂:

内容:

驗證:


返回列表