《Windows核心编程》读书笔记

第一章 对程序错误的处理

 

1.这儿讲到了Windows函数如何进行错误处理,也就是采用最后错误代码的方式。但是,我们却经常不知道如何对错误进行处理。比如,界面层调用业务层接口函数时,如果出现错误情况,该咋办?目前我提供的业务层接口函数都是void类型,出现什么错误,就在业务模块中直接打印信息告诉用户,感觉好像不爽,但如果采用错误码方式,感觉具体的错误信息又无法返回给界面层。

 

对于收到的信息有问题,是否有必要告知用户具体的失败细节?

 

另外就是在通信中,如何传递操作结果?比如,某个操作失败后,你总要告诉用户具体是怎么失败的吧?

 

参考一下Windows API的错误处理方式及错误代码列表,看能否解决上面的问题。

 

 

2.看了Windows API函数的返回值类型仅仅只有几种简单的,难道对于Windows API函数,它怎么就没有返回过比如CMap之类的复杂东西,它究竟是如何解决这类需求的?

 

 

3.验证一下错误代码是否是和线程相关?究竟放在线程本地存储的那个地方的?最好能把它找出来

 

4.参考一下TCE中的错误处理方式,看看有什么缺点?是否可以通过最后错误代码的方式解决?

定义自己的错误代码值?

 

5.参考书上写一个能够获取最后错误代码文本描述的工具函数,写程序经常用到

 

6.当想只产生一个程序实例时,这儿是通过根据窗口类和标题查找到主窗口,然后发送一个自定义消息给它,调用在处理中调用SetForgroundWindow让其激活。我应该总结一套这种做法出来

 

7.FormatMessage好像可以从咱们的DLL中获取错误描述,如何办到?

 

8.再参考MSDN,STL中的错误处理机制,看看实践中的代码,以解决目前如何处理错误问题?

 

 

2Unicode

 

2.为什么我们的操作系统内部已经采用Unicode了,我们还老是采用多字节字符集?

答:其实还是我们的习惯问题。用Unicode多好,只要是开发好的程序,移植到任何环境都可以正确显示,因为各种语言的操作系统都是支持Unicode编码的。我们一定要形成一种观念:Unicode字符串比ASCI先进的。观念纠正了,自然而然我们就愿意把我们的程序设置成Unicode了。

 

3._UNICODE宏用于C运行期头文件,而UNICODE宏则用于Windows头文件。当编译源代码模块时,通常必须同时定义这两个宏。

答:当在工程设置中选中使用Unicode时,编译器确实同时设置了_UNICODE和UNICODE宏。

 

4.如果要创建其他软件开发人员将要使用的动态链接库(DLL),请考虑使用下面的方法。在DLL中提供两个输出函数。一个是A N S I版本,另一个是Unicode版本。在A N S I版本中,只需要分配内存,执行必要的字符串转换,并调用该函数的Unicode版本。

答:我们在写DLL时,都没有这样做,看来不太好,得改才专业。

 

5.一定要熟练在ASCII和Unicode之间转换

答:

这两个函数参数看起来比较多,感觉使用很复杂,实际上挺简单的。参数CodePage, dwFlags绝大部分情况下都是如下值。

 

功能1:求至少需要多少宽字符空间才能存放得下转换后的内容,-1表示所有,后面NULL和0表示不关心

int nLen = MultiByteToWideChar(CP_ACP, 0, "聂富华", -1, NULL, 0);//返回4

 

如果我们不能确定要多少空间才能存放转换后的内容的话,则先像上面那样求得需要空间,然后分配空间,这次将最后两个参数正常传进去就行了。

 

对于Unicode往ASCI转,道理一样。例如,求至少需要多少宽字符空间才能存放得下转换后的内容

int nLen = WideCharToMultiByte(CP_ACP, 0, L"聂富华", -1, NULL, 0,NULL, NULL); // 返回7然而然工程设置中就都选Unicode了。

 

你会发现,WideCharToMultiByte函数接受的参数比MultiByteToWideChar函数要多2个,即pDefaultChar和pfUsedDefaultChar。只有当WideCharToMultiByte函数遇到一个宽字节字符,而该字符在uCodePage参数标识的代码页中并没有它的表示法时,WideCharToMultiByte函数才使用这两个参数。如果宽字节字符不能被转换,该函数便使用pDefaultChar参数指向的字符。如果该参数是NULL(这是大多数情况下的参数值),那么该函数使用系统的默认字符。该默认字符通常是个问号。这对于文件名来说是危险的,因为问号是个通配符。

pfUsedDefaultChar参数指向一个布尔变量,如果宽字符串中至少有一个字符不能转换成等价多字节字符,那么函数就将该变量置为TRUE。如果所有字符均被成功地转换,那么该函数就将该变量置为FALSE。当函数返回以便检查宽字节字符串是否被成功地转换后,可以测试该变量。同样,通常为该测试传递NULL

 

6.资源(字符串表、对话框模板和菜单等)中的字符串值总是写作Unicode字符串。例如,如果在编译源代码模块时没有定义UNICODE,调用LoadString实际上就是调用LoadStringA函数。这时LoadStringA就从你的资源中读取字符串,并将该字符串转换成ANSI字符串。ANSI形式的字符串将从该函数返回给你的应用程序。

 

7.如何理解Code Page,Locale,Language之间的关系?

 

Code Page其实就是字符集。当我们的程序不是Unicode程序时,程序要正确显示,系统中必须要首先安装与开发程序时相同的字符集类型,并且将其设置为系统当前字符集。如果是Unicode程序,那就没关系,都能正常显示,因为操作系统天生就支持Unicode字符集。

 

由See Code Page Identifiers for a list of ANSI and other code pages. 这句话我们可以知道,ASCI代码页不是某一个具体的代码页,我们可以把它就看做是多字节的意思,即ASCI代码页是诸如简体中文,繁体中文,日文等多字节代码页的总称。

 

The GetACP function retrieves the current ANSI code-page identifier forthe system.

我测试了一下,返回的是936,即

936 ANSI/OEM - Simplified Chinese (PRC, Singapore)

 

也就是说,我的操作系统默认的多字节代码页是简体中文,这也解释了上面为什么总是传CP_ACP,实际上就是取系统默认的多字节代码页。

 

通过控制面板我们可以看到,Locale(位置)和代码页是没有直接关系的。而选择的默认语言,才和代码页有直接关系,一个语言就对应一个代码页,选择不同的语言类型,当前系统的代码页也会跟着变化。比如,我将以前选择的中文(中国)修改为中文(香港特别行政区)之后,重启系统让其生效,再次调用GetACP,发现当前代码页已经变成950了,即繁体中文。好多程序已经不能正常显示,因为程序开发时编译到程序中的936代码页,现在却按950代码页去解析,当然不行了。而操作系统和部分程序是正常的,说明是基于Unicode开发的。再次证明Unicode比ASCI强,更能适应变化。现在还有唯一一点不明白的是,为什么语言下拉列表和代码页转换表中不是一一对应的,我怎么知道那种语言对应哪个代码页?难道每次都先设置当前语言,然后调用GetACP获取当前系统的代码页吗?呵呵,不过后面这点疑惑不影响大局了,我只要知道选当前语言会影响当前代码页就行了。

 

 

8.如何能够很容易地对ANSI和Unicode字符和字符串进行混合和匹配?

答:

 

char  szA[100];

WCHAR szW[100];

 

// Converts Unicode string to ANSI

sprintf(szA, "%S", L"Unicode Str");

 

// Converts ANSI string to Unicode

swprintf(szW, L"%S", "ANSI Str");

 

上面的代码经过验证,确实可行的。规律就是格式化字符串肯定和函数名是一致的,如果字符串一致则小写s,不一致则大写S。

其实,通过上面的办法,同样可以在Unicode和ASCI之间转换,呵呵,办法多了,反而有时一样都不会用。

 

 

3章内核对象

 

1.什么是内核对象?

我觉得这一章只要理解什么是内核对象就完全足够了!!

每个内核对象只是内核分配的一个内存块,和我们分配内存无异,并且只能由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息。

 

2.内核对象由内核所拥有(换句话说就是内核对象由操作系统去管理维护),而不是由进程所拥有。换句话说,如果你的进程调用了一个创建内核对象的函数,然后你的进程终止运行,那么内核对象不一定被撤消。在大多数情况下,对象将被撤消,但是如果另一个进程正在使用你的进程创建的内核对象,那么该内核知道,在另一个进程停止使用该对象前不要撤消该对象,必须记住的是,内核对象的存在时间可以比创建该对象的进程长。

 

3.内核对象都具有使用计数和安全性属性。

 

4.如何判断一个对象是GUI对象还是内核对象?

答:用于创建用户对象或GDI对象的函数都没有PSECURITY_ATTRIBUTES参数。而内核对象都有该参数。

 

5.当一个进程被初始化时,系统要为它分配一个内核句柄表。

答:用来维护句柄(用户程序需要用它来操作内核对象)与内核对象的真正内存地址之间的对应关系,否则怎么可能通过句柄就能操作到内核对象呢,是吧,呵呵。

 

6.为什么我们要共享内核对象?

答:例如

a.文件映射对象使你能够在同一台机器上运行的两个进程之间共享数据块。

b. 邮箱和指定的管道使得应用程序能够在连网的不同机器上运行的进程之间发送数据块。

c. 互斥对象、信标和事件使得不同进程中的线程能够同步它们的连续运行,这与一个应用程序在完成某项任务时需要将情况通知另一个应用程序的情况相同。

 

7.如何共享内核对象?

a.继承(1.最常用的方法是将句柄值作为一个命令行参

数传递给子进程 2. 方法之一是让父进程等待子进程完成初始化(使用第9章介绍的Wa i t F o r I n p u I d l e函数),然后,父进程可以将一条消息发送或展示在子进程中的一个线程创建的窗口中。3.另一个方法是让父进程将一个环境变量添加给它的环境程序块。)

b.创建有名字的内核对象

c.通过DuplicateHandle函数

 

第4章 进程

 

1.进程的组成

答:概念性,对编程实践无意义

 

2.但是,人们很少知道这样一个情况,即可以从应用程序中全部删除/ S U B S Y S T E M链接程序开关。当这样做的时候,链接程序能够自动确定应用程序应该连接到哪个子系统。当进行链接时,链接程序要查看代码中存在4个函数(Wi n M a i n、w Wi n M a i n、m a i n或w m a i n)中的哪一个。然后确定可执行程序应该是哪一个子系统,并且确定可执行程序中应该嵌入哪个C / C + +启动函数。

 

3.启动函数的功能

答:概念性

 

4.许多应用程序在全局变量中保存( w ) Wi n M a i n的h i n s t E x e参数,这样,它就很容易被所有可执行文件的代码访问。

答:确实是这样,如果不保存的话,很难再有其他办法获取实例地址了。其实有时还可以通过GetModuleHandle获取到,更绝的是还可以根据某个地址来获取某个实例的地址,例如,VERIFY(GetModuleHandleEx(4, (LPCTSTR)(DWORD)(void*)fun, &hModule));方法是你可以任取一个函数,如果是全局的直接就可以转,如果是成员函数可能需要先定义一个函数指针,让其指向一个函数后才能转,这个函数一定就位于exe或DLL加载的空间中,呵呵。

 

注意,不要把实例地址和句柄混了。

 

5.伪句柄

答:摘自MSDN GetCurrentProcess函数的说明

A pseudo handle is a special constant, currently (HANDLE)-1, that isinterpreted as the current process handle. For compatibility with futureoperating systems, it is best to call GetCurrentProcess instead of hard-codingthis constant value. The calling process can use a pseudo handle to specify itsown process whenever a process handle is required. Pseudo handles are notinherited by child processes.

注意两点,一,伪句柄是一个常量 二,当需要一个进程句柄的时候,能使用这个伪句柄代表当前进程句柄。

 

6.C o m m a n e L i n e To A rg v W负责在内部分配内存。大多数应用程序不释放该内存,它们在进程运行终止时依靠操作系统来释放内存。这是完全可行的。如果想要自己来释放内存,正确的方法是像下面这样调用H e a p F r e e函数:

int nArgNums;

LPWSTR* ppArgs = CommandLineToArgv(GetCommondLineW(), &nArgNums);

HeapFree(GetProcessHeap(), 0, ppArgs);

答:感觉这个函数在处理命令行时挺有用的,不用自己去切分参数了。特别是在WinMain函数中。

 

 

第三部分内存管理

1 3Wi n d o w s的内存结构

这一章主要介绍了进程的虚拟地址空间的概念、它的分区情况、如何使用这些虚拟地址空间。最后还介绍了一下数据对齐。

 

 

1、进程的地址空间在分区时,主要跟什么有关系?

答:操作系统(比如32位Windows 2000)和硬件平台(x86处理器、Alpha处理器)。

 

2、虚拟地址空间如何划分?

答:NULL指针分区、用户方式分区、64K-KB禁止进入分区、内核方式分区

 

3、多年来,编程人员一直强烈要求扩大用户方式的地址空间。真的吗?如何扩大用户方式分区?

答:张尹上次好像说过TE的就不够,但是我还没遇到过。

 

 

4、查一下MSDN上的/LARGEADDRESSAWARE相关知识

答:

 

5、将虚拟地址转换成物理存储器地址的流程图?

答:

 

13.8 数据对齐的重要性

数据对齐并不是操作系统的内存结构的一部分,而是CPU结构的一部分。

当CPU访问正确对齐的数据时,它的运行效率最高。当数据大小的数据模数的内存地址是0时,数据是对齐的。例如,WORD值应该总是从被2除尽的地址开始,而DWORD值应该总是从被4除尽的地址开始,如此等等。当CPU试图读取的数据值没有正确对齐时, CPU可以执行两种操作之一。即它可以产生一个异常条件,也可以执行多次对齐的内存访问,以便读取完整的未对齐数据值。

为了使应用程序获得最佳的运行性能,编写的代码必须使数据正确地对齐。

下面让我们更加深入地说明x86 CPU是如何进行数据对齐的。X86 CPU的EFLAGS寄存器中包含一个特殊的位标志,称为AC(对齐检查的英文缩写)标志。按照默认设置,当CPU首次加电时,该标志被设置为0。当该标志是0时,CPU能够自动执行它应该执行的操作,以便成功地访问未对齐的数据值。然而,如果该标志被设置为1,每当系统试图访问未对齐的数据时,CPU就会发出一个INT17H中断。x 8 6的Windows 2000和Windows 98版本从来不改变这个CPU标志位。因此,当应用程序在x86处理器上运行时,你根本看不到应用程序中出现数据未对齐的异常条件。

现在让我们来看一看Alpha CPU的情况。Alpha CPU不能自动处理对未对齐数据的访问。当未对齐的数据访问发生时,CPU就会将这一情况通知操作系统。这时,Windows 2000将会确定它是否应该引发一个数据未对齐异常条件。它也可以执行一些辅助指令,对问题默默地加以纠正,并让你的代码继续运行。按照默认设置,当在Alpha计算机上安装Windows2000时,操作系统会对未对齐数据的访问默默地进行纠正。然而,可以改变这个行为特性。当引导Windows 2000时,系统就会在注册表中查找的这个关键字:

HKEY_LOCAL_MACHINE\CurrentControlSet\Control\SessionManager

在这个关键字中,可能存在一个值,称为EnableAlignmentFaultExceptions。如果这个值不存在(这是通常的情况),Windows2000会默默地处理对未对齐数据的访问。如果存在这个值,系统就能获取它的相关数据值。如果数据值是0,系统会默默地进行访问的处理。如果数据值是1,系统将不执行默默的处理,而是引发一个未对齐异常条件。几乎从来都不需要修改该注册表值的数据值,因为如果修改有些应用程序能够引发数据未对齐的异常条件并终止运行。

如果不使用AXPAlign实用程序,仍然可以让系统为进程中的所有线程默默地纠正对未对齐数据的访问,方法是让进程的线程调用SetErrorMode函数.

当然,无论在哪个CPU 平台上运行,都可以调用SetErrorMode函数,传递SEM_NOALIGNMENTFAULTEXCEPT标志。但是,结果并不总是相同。如果是x86系统,该标志总是打开的,并且不能被关闭。如果是Alpha系统,那么只有当EnableAlignmentFaultExceptions注册表值被设置为1时,才能关闭该标志。

 

......这是因为x86 CPU本身正在进行调整,因此没有通知操作系统。由于是x86 CPU而不是操作系统来进行这种调整,因此访问x86计算机上的未对齐数据对性能产生的影响并不像需要用软件(Windows 2000操作系统代码)来进行数据对齐调整的CPU那样大。

可以看到,在Alpha CPU上使用__unaligned关键字,生成的CPU指令要多两倍以上。编译器添加的指令比CPU跟踪未对齐数据的访问并让操作系统来纠正未对齐数据的效率要高得多。

最后要说明的是,x86 CPU上运行的Visual C/C++编译器不支持__unaligned关键字。我想Microsoft公司也许认为这没有必要,因为CPU本身进行未对齐数据的纠正速度很快。

 

 

MSDN

 

Instead, the system maintains a page map for each process, which is aninternal data structure used to translate virtual addresses into correspondingphysical addresses. Each time a thread references an address, the systemtranslates the virtual address to a physical address.

 

Only processes that have theIMAGE_FILE_LARGE_ADDRESS_AWARE flag set in the image header have access tomemory above 2 gigabytes (GB).

 

The process address space can be adjusted between2 GB and 3 GB by using the /USERVA switch in boot.ini. The system address spaceadjusts as needed.

 

To increase the size of physical storage, thesystem uses the disk for additional storage.

 

The pages of a process's virtual address space canbe in one of the following states:

答:Free、Reserved、Committed

 

The system initializes and loads each committedpage into physical memory only during the first attempt to read or write tothat page.

 

Named file mapping provides an easy way to createa block of shared memory.

 

Memory that belongs to a process is implicitly protectedby its private virtual address space. In addition, Windows provides memoryprotection by using the virtual memory hardware. The implementation of thisprotection varies with the processor, for example, code pages in the addressspace of a process can be marked read-only and protected from modification byuser-mode threads.

 

为什么要把Reserved和Committed区分开?

答:The page has been reserved for future use.

从16章的堆栈,我们就可以得出分开是有用的。有时我们可能使用的最大虚拟空间是确定的,比如堆栈默认为1M,但是实际可能用不了那么多,要是你直接就都提交物理内存,显然影响性能嘛,所以就出现了这种分开的方式,先把空间占住(不耗性能),然后再按需提交物理内存(耗性能)。在EasySip导入功能中,我可能只知道我最多可以容纳多少条数据,但文件中具体有多少条我不读是不知道的,这个场景中,采用保留和提交的分开的方式再适合不过了。我先保留最大条数数据所需要的虚拟空间,然后像堆栈那样按需提交物理内存,多帅啊!

 

As an alternative to dynamic allocation, theprocess can simply commit the entire region instead of only reserving it.However, committing the region consumes physical storage that might not beneeded, making it unavailable for use by other processes.

 

 

Data Execution Prevention (DEP) is a system-levelmemory protection feature that is built into Microsoft® Windows® starting with Windows XP andWindows Server 2003. DEP enables the system to mark one or more pages of memoryas non-executable. Marking memory regions as non-executable means that codecannot be run from that region of memory, which makes it harder for theexploitation(n.非法利用) of buffer overruns.

DEP prevents code from being run from data pagessuch as the default heap, stacks, and memory pools. If an application attemptsto run code from a data page that is protected, a memory access violationexception occurs, and if the exception is not handled, the calling process isterminated.

 

Heap allocations made by calling the malloc andHeapAlloc functions are non-executable.

Applications cannot run code from the defaultprocess heap or the stack.

 

You should also attempt to control the layout ofyour application's virtual memory and create executable regions. Theseexecutable regions should be located in a lower memory space thannon-executable regions. By locating executable regions below non-executable regions,you can help prevent a buffer overflow from overflowing into the executablearea of memory.

 

Copy-on-write protection is an optimization thatallows multiple processes to map their virtual address spaces such that theyshare a physical page until one of the processes modifies the page. This ispart of a technique called lazy evaluation, which allows the system to conservephysical memory and time by not performing an operation until absolutelynecessary.

 

Applications developed for the Microsoft® Windows Server™ familycontinue to grow, both in terms of size and performance demands.

The following features enable applications toaccess more memory:

4GT RAM Tuning

Physical Address Extension

Address Windowing Extensions

Large Page Support

 

For applications that are memory-intensive, suchas database management systems (DBMS), the use of a larger virtual addressspace can provide considerable performance benefits. However, the file cache,paged pool, and non-paged pool are smaller, which can adversely affectapplications with heavy networking or I/O. Therefore, you might want to testyour application under load, and examine the performance counters to determinewhether your application benefits from the larger address space.

Operating systems based on Microsoft® Windows NT® provide applications with a 4gigabyte (GB) virtual address space. The virtual address space is divided sothat 2-GB is available to the application and the other 2-GB is available onlyto the system.

The 4GTRAM Tuning feature increases the memory that is available to theapplication up to 3-GB, and reduces the amount available to the system tobetween 1 and 2-GB, but the administrator can change this by using the /USERVAswitch. This benefits applications that run on computers with more than 2-GB ofphysical memory.

To enable this feature, add the /3GB switch to theBoot.ini file. This enables applications to use the first 3-GB of the addressspace on the following systems:

 

ThePhysical Address Extension (PAE) enables applications toaddress more than 4 GB of physical memory. It is supported by Intel processors.The following systems can use PAE to take advantage of physical memory beyond 4GB:

Windows Server 2003 Enterprise Edition

Windows Advanced Server, Limited Edition

Windows 2000 Datacenter Server

Windows 2000 Advanced Server

To enable PAE, you must use the /PAE switch in theBoot.ini file.

With PAE enabled, the operating system moves fromtwo-level linear address translation to three-level address translation. Theextra layer of translation provides access to physical memory beyond 4 GB.Instead of a linear address being split into three separate fields for indexinginto memory tables, it is split into four separate fields; a 2-bit field, two9-bit fields, and a 12-bit field that corresponds to the page size implementedby Intel Architecture (4 KB).

 

Large-pagesupport enables server applications to establish large-page memory regions,which is particularly useful on 64-bit Windows. Each large-page translation usesa single translation buffer inside the CPU. The size of this buffer istypically three orders of magnitude larger than the native page size; thisincreases the efficiency of the translation buffer, which can increaseperformance for frequently accessed memory.

To use large-page support, do the following:

Obtain the SE_LOCK_MEMORY_PRIVILEGE privilege bycalling the AdjustTokenPrivileges function. For more information, see ChangingPrivileges in a Token.

Retrieve the minimum large-page size by callingthe GetLargePageMinimum function.

Include the MEM_LARGE_PAGES value when calling theVirtualAlloc function. The size and alignment must be a multiple of thelarge-page minimum.

The following are restrictions when using largepages:

These memory regions may be difficult to obtainafter the system has been running for a long time because the space for eachlarge page must be contiguous, but the memory may have become fragmented.

The memory is always read-write and fullycacheable.

Large-page allocations are not subject to joblimits.

WOW64 on Intel Itanium-based systems does notsupport 32-bit applications that use this feature. The applications should berecompiled as native 64-bit applications.

 

Heapfragmentation(n.碎化) is when available memory is broken into small,non-contiguous blocks. When this occurs, memory allocation can fail even thoughthere is enough memory in the heap to satisfy the request, because no one blockof memory is large enough to satisfy the allocation request.

For applications that have a low memory usage, thestandard heap is adequate(足够的); allocations willnot fail due to heap fragmentation. However, if the application allocatesmemory frequently and uses a variety of allocation sizes, memory allocation canfail due to heap fragmentation.

Windows XP and Windows Server 2003 introduce the low-fragmentation heap (LFH).This mechanism is built on top of the existing heap, but as the name implies,it reduces fragmentation of the heap. Applications that allocate large amountsof memory in various allocation sizes should use the LFH. Note that the LFH canallocate blocks up to 16 KB. For blocks greater than this, the LFH uses thestandard heap.

To use the LFH in your application, call theHeapCreate or GetProcessHeap function to obtain a handle to a standard heap.Then call the HeapSetInformation function to enable the LFH. If the callsucceeds, memory is allocated and freed in the LFH when you call the heap API.Otherwise, the memory is allocated in the standard heap. Note that it is not possibleto enable the LFH if you are using certain gflags options related to the heap.

The LFH avoids fragmentation by managing allallocated blocks in 128 predetermined different block-size ranges. Each of the128 size ranges is called a bucket. When an application needs to allocatememory from the heap, the LFH chooses the bucket that can allocate the smallestblock large enough to contain the requested size. The smallest block that canbe allocated is 8 bytes.

 

The CoTaskMemAlloc function has the advantage ofworking well in either C, C++, or Visual Basic. It is also the only way toshare memory in a COM-based application, since MIDL uses CoTaskMemAlloc andCoTaskMemFree to marshal memory.(需要#include <objbase.h>

 

A guard page provides a one-shot alarm for memorypage access. This can be useful for an application that needs to monitor thegrowth of large dynamic data structures. For example, there are operatingsystems that use guard pages to implement automatic stack checking.

 

If a program attempts to access an address withina guard page, the system raises a STATUS_GUARD_PAGE_VIOLATION (0x80000001)exception. The system also clears the PAGE_GUARD modifier, removing the memorypage's guard page status. The system will not stop the next attempt to accessthe memory page with a STATUS_GUARD_PAGE_VIOLATION exception.

 

///

AllocateUserPhysicalPages

略,因为我还暂时用不到AWE。

 

Address Windowing Extensions

Address Windowing Extensions(AWE) is a set of extensions that allows an application to quickly manipulate physical memory greater than4GB. Certain data-intensive applications, such as database managementsystems and scientific and engineering software, need access to very largecaches of data. In the case of very large data sets, restricting the cache tofit within an application's 2GB of user address space is a severe restriction.In these situations, the cache is too small to properly support theapplication.

AWE solves this problem byallowing applications to directly address huge amounts of memory while continuing to use 32-bit pointers.AWE allows applications to have data caches larger than 4GB (where sufficientphysical memory is present). AWE uses physical nonpaged memory and window viewsof various portions of this physical memory within a 32-bit virtual addressspace.

(也就是说,在32位系统中,假如我的RAM有8G,但实际上我最多只能利用4G,通过AWE我们就能利用后面的4G内存。看了MSDN的例子,大概是这样一个流程,首先利用AllocateUserPhysicalPages在4G之外的地方分配物理内存,然后用VirtualAlloc分配一个虚拟地址出来,最后用MapUserPhysicalPages将这个虚拟地址和前面分配出来的物理内存映射起来,这样,操作虚拟地址就相当于操作物理内存了。)

......allows user applications to use up to 64 GBof physical non-paged memory in a 32-bit virtual address space on 32-bitplatforms.

 

Another pertinent feature is the 4 GB Tuning (4GT) feature of Windows 2000 Advanced Server on the IA32 platform, which allows3 GB of memory for user space rather than the usual 2 GB.

 

CopyMemory

简单,同memcpy

 

CreateMemoryResourceNotification

The CreateMemoryResourceNotification functioncreates a memory resource notification object.(也就是当内存使用达到某个水平的时候可以得到通知,要是CPU占用率也有这个功能就好了,那样我们就不用开一个线程去不断查询了,查询方式可能浪费CPU,太土了!)

HANDLE CreateMemoryResourceNotification(

 MEMORY_RESOURCE_NOTIFICATION_TYPE NotificationType

);

Applications can use memory resource notificationevents to scale the memory usage as appropriate. If available memory is low,the application can reduce its working set. If available memory is high, theapplication can allocate more memory.

 

Any thread of the calling process can specify thememory resource notification handle in a call to theQueryMemoryResourceNotification function or one of the wait functions. The state of the objectis signaled when the specified memory condition exists. This is a system-wide event, so allapplications receive notification when the object is signaled. Note thatthere is a range of memory availability where neither theLowMemoryResourceNotification or HighMemoryResourceNotification object issignaled. In this case, applications should attempt to keep the memory useconstant.

 

The default level of available memory that signalsa low-memory-resource notification event is approximately 32 MB per 4 GB, to a maximumof 64 MB. The default level that signals a high-memory-resource notificationevent is three times thedefault low-memory value.

 

Use the CloseHandle function to close the handle.The system closes the handle automatically when the process terminates. Thememory resource notification object is destroyed when its last handle has beenclosed.

 

To compile an application that uses this function,define the _WIN32_WINNT macro as 0x0501 or later. For more information, seeUsing the Windows Headers.

 

 

FillMemory

简单,同memset

 

 

FreeUserPhysicalPages

略,因为我还暂时用不到AWE。

 

 

GetLargePageMinimum

The GetLargePageMinimum functionretrieves the minimum size of a large page.

large page是什么?

答:

注意:Requires Windows Server 2003(在我的XP上提示无法定位程序输入点 GetLargePageMinimum 于动态链接库KERNEL32.dll上)

 

GetProcessHeap

例 HANDLE hHeap = GetProcessHeap();// 0x00150000

为什么要从堆中分配,而不是直接用VirtualAlloc分配?

从进程堆分配与从我们自己创建的堆中分配有什么差异?

堆都有哪些类型?

 

注意:每个进程至少有一个有效的堆,即进程堆。

 

GetProcessHeaps

The GetProcessHeaps function obtains handles toall of the heaps that are valid for the calling process.

void CjjjteDlg::OnBnClickedButton1()

{

    HANDLE hHeap = GetProcessHeap(); //0x00150000

 

    const int NUM = 4;

    HANDLE pHandle = new HANDLE[NUM];

    DWORD dwHeaps = GetProcessHeaps(NUM,pHandle);

    if ( dwHeaps > NUM)

    {

        delete pHandle;

        pHandle = new HANDLE[dwHeaps];

        GetProcessHeaps(dwHeaps,pHandle); //0x00150000 0x00250000 0x00260000 0x00380000 0x003a0000 0x003b0000

        // 默认就产生了这么多,都是谁干的啊,会不会是C运行时库,STL这些干的? 呵呵

    }

    delete []pHandle;

}

 

GetWriteWatch

The GetWriteWatch function retrieves the addressesof the pages that are written to in a region of virtual memory.

上面这句话容易理解错,通过

    LPVOID pv6 = VirtualAlloc(NULL,4096 + 100, MEM_RESERVE|MEM_COMMIT|MEM_WRITE_WATCH,PAGE_READWRITE); //0x003e0000

    memset((char*)pv6 + 4096, '\0',100);

 

    LPVOID pvPage[8];

    DWORD dwCount = 8;

    ULONG dwGranularity;

    int nret = GetWriteWatch(0, pv6,4096 + 100, pvPage, &dwCount, &dwGranularity);// dwCount == 0x00000001、pvPage[0] == 0x003e1000

首先是保留并提交了两页的虚拟内存(分别为0x003e0000,0x003e1000),我们往第二页中写入了数据,所以GetWriteWatch返回的值是0x003e1000,也就是第二页的开始地址。

综上,GetWriteWatch的功能是获取一片虚拟内存中哪些页被写入过,并把写入过的页的开始地址记入PVOID类型的数组中。(理解了这个其他的就都好理解了)

 

When you call the VirtualAlloc function to reserveor commit memory, you can specify MEM_WRITE_WATCH. This value causes the systemto keep track of the pages that are written to in the committed memory region.You can call the GetWriteWatch function to retrieve the addresses of the pagesthat have been written to since the region has been allocated or thewrite-tracking state has been reset.

To reset the write-tracking state, set theWRITE_WATCH_FLAG_RESET value in the dwFlags parameter. Alternatively, you cancall the ResetWriteWatch function to reset the write-tracking state. However,if you use ResetWriteWatch, you must ensure that no threads write to the regionduring the interval between the GetWriteWatch and ResetWriteWatch calls.Otherwise, there may be written pages that you do not detect.

The GetWriteWatch function can be useful toprofilers, debugging tools, or garbage collectors.

 

 

GlobalAlloc

以前总是见到先GlobalAlloc,然后GlobalLock才能使用分配的内存,我还以为这两个函数必须配对使用呢,看起来好像不是,例如

HANDLE h = GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT,100);

返回的句柄值实际上就是分配内存所在地址。

 

也就是说,只有分配的是GMEM_MOVEABLE属性的内存,因为(Allocates movablememory. Memory blocks are never moved in physical memory, but they can be movedwithin the default heap.)才必须用GlobalLock锁住后再使用。

 

Theglobal and local functions supported for porting from 16-bit code, ormaintaining source code compatibility with 16-bit Windows(注意其存在的主要价值). The global and local functions areslower than other memory management functions and do not provide as manyfeatures(注意其缺点). Therefore, new applicationsshould use the heap functions(我们应该怎样选择).However, the global functions are still used with DDE and the clipboardfunctions(确实,以前在UCC见过。思考:假如我不用global去分配会出现什么情况?).

 

Windows memory management does not provide aseparate local heap and global heap, as 16-bit Windows does(也就是说,在16位Windows中,global和local是不同的堆,而目前都是同一个堆,通过

    HANDLE hHandle1 = GlobalAlloc(GMEM_FIXED,1024); // 0x001613e0

    HANDLE hHandle2 = LocalAlloc(GMEM_FIXED,  1024); //0x00162240

可以确认确实是同一个堆,因为两次分配的内存挨得那么近,明显是在一个堆中的). As aresult, there is no difference between the memory objects allocated by theGlobalAlloc and LocalAlloc functions(即最多只需要学一类函数就行了).In addition, the change from a 16-bit segmented memory model to a 32-bit virtual memory model has made someof the related global and local functions and their options unnecessary ormeaningless. For example, there are no longer near and far pointers, becauseboth local and global allocations return 32-bit virtual addresses(也就是说16位中global和local返回的指针都可能是不同的).

 

Memory objects allocated by GlobalAlloc andLocalAlloc are in private, committed pages with read/write access that cannotbe accessed by other processes. Memory allocated by using GlobalAlloc withGMEM_DDESHARE is not actually shared globally as it is in 16-bit Windows(估计在16位中,global和local的差别之一是global的是可以被其他进程共享的,而local的只能本进程用).This value has no effect and is available only for compatibility. Applicationsrequiring shared memory for other purposes must use file-mapping objects.Multiple processes can map a view of the same file-mapping object to providenamed shared memory(告诉我们目前怎么实现内存共享).

 

Memory allocations are limited only by theavailable physical memory, including storage in the paging file on disk(如何判断一块100M的内存属于哪个堆呢?). When you allocate fixedmemory, GlobalAlloc and LocalAlloc return a pointer that the calling processcan immediately use to access the memory. When you allocate moveable memory,the return value is a handle. To get a pointer to a movable memory object, usethe GlobalLock and LocalLock functions.(注意,If zero isspecified, the default is GMEM_FIXED.(为啥要搞个moveablememory?)

 

The actual size of the memoryallocated can be larger than the requested size. To determine the actual numberof bytes allocated, use the GlobalSize or LocalSize function. If the amountallocated is greater than the amount requested, the process can use the entireamount.(还真麻烦,比malloc,new屁事多多了,用它还不如用malloc,new呢)

 

The GlobalReAlloc and LocalReAlloc functionschange the size or the attributes of a memory object allocated by GlobalAllocand LocalAlloc. The size can increase or decrease.(和realloc类似,我们有这种重分配的使用场景吗?)

 

The GlobalFree and LocalFreefunctions release memory allocated by GlobalAlloc, LocalAlloc, GlobalReAlloc,or LocalReAlloc. To discard the specified memory object without invalidatingthe handle, use the GlobalDiscard or LocalDiscard function. The handle can beused later by GlobalReAlloc or LocalReAlloc to allocate a new block of memoryassociated with the same handle.(就仅仅为了使用同一个句柄!麻烦不啊!)

 

To return information about a specified memoryobject, use the GlobalFlags or LocalFlags function. The information includesthe object's lock count and indicates whether the object is discardable or hasalready been discarded.(有必要搞lock count吗?答:参考下面的GlobalFree)To return a handle to the memory object associated with a specifiedpointer, use the GlobalHandle or LocalHandle function.

 

 

GlobalDiscard

相当于释放内存。略

 

GlobalFlags

注意:The lock count of memory objects allocated withGMEM_FIXED is always zero.

 

GlobalFree

The GlobalFree function frees the specified globalmemory object and invalidates its handle.

为什么跟内核对象一样搞个独立的句柄出来玩?

答:可以考虑一下内核对象为啥要搞个句柄计数,还不是为了在进程间共享的时候能容易管理资源的释放。这儿估计也是一样,因为在16位中,global出来的内存块是可以进程间共享的。

 

GlobalHandle

对GMEM_MOVEABLE的内存对象才有意义,如果是GMEM_FIXED的,句柄和指针本来就是相等的。

 

GlobalLock

lock计数加1。略

 

Memory objects allocated with GMEM_FIXED alwayshave a lock count of zero. For these objects, the value of the returned pointeris equal to the value of the specified handle.(还真如我前面的猜测)

 

GlobalMemoryStatus

The GlobalMemoryStatus function obtainsinformation about the system's current usage of both physical and virtualmemory.

 

To obtain information about the extended portionof the virtual address space, or if your application may run on computers withmore than 4 GB of main memory, use the GlobalMemoryStatusEx function.

注意,这个Global和GlobalAlloc不是同类,这儿真的是全局,而后者是globalheap!

 

GlobalMemoryStatusEx

Notice that Task Manager returns essentially thesame type of information as does a call to GlobalMemoryStatusEx.

 

GlobalReAlloc

If GlobalReAlloc fails, the original memory is notfreed, and the original handle and pointer are still valid.

 

GlobalSize

To verify that the specified object's memory blockhas not been discarded, use the GlobalFlags function before calling GlobalSize.

 

GlobalUnlock

lock计数减1。

If the memory object is already unlocked,GlobalUnlock returns FALSE and GetLastError reports ERROR_NOT_LOCKED.

 

HeapAlloc

The HeapAlloc function allocates a block of memoryfrom a heap. The allocated memory is not movable.

 

Memory allocated by HeapAlloc is not movable. The address returned by HeapAlloc isvalid until the memory block is freed or reallocated; the memory block does notneed to be locked(注意,从返回的类型LPVOID看也是和HGLOBAL、HLOCAL不一样的,当然不可能锁了).Because the system cannot compact a private heap, it can become fragmented(因为系统不能压缩一个私有堆,它可能出现内存碎片).

 

Applications that allocate large amounts of memoryin various allocation sizes can use the low-fragmentation heap to reduce heap fragmentation.

 

Serialization ensures mutual exclusion(互斥) when two or morethreads attempt to simultaneously allocate or free blocks from the same heap.There is a small performance cost to serialization, but it must be usedwhenever multiple threads allocate and free memory from the same heap(既然那么少的性能损失,我看啥时候都没有必要HEAP_NO_SERIALIZE了). Setting theHEAP_NO_SERIALIZE value eliminates mutual exclusion on the heap. Withoutserialization, two or more threads that use the same heap handle might attemptto allocate or free memory simultaneously, likely causing corruption in theheap. The HEAP_NO_SERIALIZE value can, therefore, be safely used only in thefollowing situations:

The process has only one thread.

The process has multiple threads, but only onethread calls the heap functions for a specific heap.

The process has multiple threads, and theapplication provides its own mechanism for mutual exclusion to a specific heap.

 

 

HeapCompact

The HeapCompact function attempts to compact aspecified heap. It compacts the heap by coalescing(v.结合)adjacent free blocks of memory and decommitting large free blocks of memory.

注意返回值,If thefunction succeeds, the return value is the size of the largest committed freeblock in the heap, in bytes.

There is no guarantee that an application can successfullyallocate a memory block of the size returned by HeapCompact. Other threads orthe commit threshold might prevent such an allocation.

上面才说了不能压缩的,怎么还有压缩函数?

 

HeapCreate

(针对HEAP_NO_SERIALIZE)Alternatively,you can specify this option on individual heap function calls. (也就是说你可以还有一次机会在HeapAlloc中指定,可以达到同样效果)

HANDLE HeapCreate(

  DWORDflOptions,

  SIZE_TdwInitialSize,

  SIZE_TdwMaximumSize

);

If dwMaximumSize is 0, the heap is growable. (当然你也可以搞成不能增长的,但是应用场景在哪儿呢?即增长和不增长有什么差别?)

 

/***

*_heap_init() - Initialize theheap

*

*Purpose:

*       Setup the initial C library heap.

*

*       NOTES:

*       (1) This routine should only be calledonce!

*       (2) This routine must be called beforeany other heap requests.

*

*Entry:

*       <void>

*Exit:

*       Returns 1 if successful, 0 otherwise.

*

*Exceptions:

*       If heap cannot be initialized, theprogram will be terminated

*       with a fatal runtime error.

*

*******************************************************************************/

 

int __cdecl_heap_init (

        int mtflag

        )

{

#if defined_M_AMD64 || defined_M_IA64

        // HEAP_NO_SERIALIZE is incompatible with the LFH heap

        mtflag = 1;

#endif  /* defined _M_AMD64|| defined _M_IA64 */

        //  Initialize the"big-block" heap first.

        if ( (_crtheap = HeapCreate( mtflag? 0 : HEAP_NO_SERIALIZE,

                                     BYTES_PER_PAGE, 0 )) == NULL)

            return 0;

__forceinline void * __cdecl _heap_alloc (size_tsize)

 

{

#ifndef _WIN64

    void *pvReturn;

#endif  /* _WIN64 */

 

    if (_crtheap == 0) {

        _FF_MSGBANNER();   /* write run-time error banner */

        _NMSG_WRITE(_RT_CRT_NOTINIT);  /* write message */

        __crtExitProcess(255);  /* normally_exit(255) */

    }

 

#ifdef _WIN64

    return HeapAlloc(_crtheap, 0, size ?size : 1);

#else  /* _WIN64 */

    if (__active_heap ==__SYSTEM_HEAP) {

        return HeapAlloc(_crtheap, 0, size ?size : 1);

    } else

    if ( __active_heap== __V6_HEAP ) {

        if (pvReturn = V6_HeapAlloc(size)){

            return pvReturn;

        }

    }

#ifdef CRTDLL

    else if ( __active_heap == __V5_HEAP)

    {

        if (pvReturn = V5_HeapAlloc(size)){

            return pvReturn;

        }

    }

#endif  /* CRTDLL */

 

    if (size == 0)

        size = 1;

 

    size = (size + BYTES_PER_PARA - 1) & ~(BYTES_PER_PARA- 1);

 

    return HeapAlloc(_crtheap, 0, size);

 

#endif  /* _WIN64 */

}

看,C运行时库中的内存分配其实本质上还是调用Heap系列函数,它只不过是做了一层封装,方便跨平台移植和使用方便(比如,我们不用自己去调用HeapCreate、HeapDestroy了吧,这样就减轻了我们需要在各个模块中创建自己的堆的麻烦,把这些事都交给C运行时库一个模块去管理,多简单啊)。所以,绝大部分情况下,我们最好是调用C运行时库中的malloc,估计当在特定情况下,比如对性能有特定要求时,我们才有必要调用HeapCreate创建自己的堆,毕竟堆管理的内存块越多,速度越慢!

 

HeapDestroy

 

HeapFree

 

HeapLock

If the function succeeds, the calling thread ownsthe heap lock. Only the calling thread will be able to allocate or releasememory from the heap. The execution of any other thread of the calling processwill be blocked if that thread attempts to allocate or release memory from theheap. Such threads will remain blocked until the thread that owns the heap lockcalls the HeapUnlock function.(那么麻烦,而malloc和new都是不需要锁的,我干嘛用这么复杂的东西啊?)

 

The HeapLock function is primarily useful forpreventing the allocation and release of heap memory by other threads while thecalling thread uses the HeapWalk function.(参考HeapWalk)

 

Each call to HeapLock must be matched by acorresponding call to the HeapUnlock function. Failure to call HeapUnlock willblock the execution of any other threads of the calling process that attempt toaccess the heap.(什么情况下HeapUnlock会失败呢?)

 

还要注意一点: HeapLockk和GlobalLock的Lock意义(针对被锁的对象)上有差别吗?

 

HeapQueryInformation

Windows NT 4.0 SP4 and later provide a fast memoryallocation mechanism called look-aside lists. General pool allocations can varyin size, but a look-aside list contains only fixed-sized blocks. Therefore,look-aside lists are faster because the system does not search for free memorythat fits allocations of varying sizes. In addition, access to look-aside listsis generally synchronized using fast atomic processor exchange instructionsinstead of mutexes or spinlocks. Look-aside lists can be created by the systemor drivers. They can be allocated from paged or nonpaged pool.(有好的应用场景吗?)

 

To enable the low-fragmentation heap, use theHeapSetInformation function.(看来还只有调用HeapSetInformation才能开启这个特性了)

 

 

HeapReAlloc

重分配以前我理解得太复杂了,我就说嘛,为什么内存增大了地址还能不变呢?显然不可能嘛!实际上,理解成先释放再分配更容易理解些,好处是本来我们需要至少写两行代码的,现在只需要调用一个函数(即一行代码)了,还有一个差异是如果新分配的内存可以在以前的地方申请,那么返回的地址和以前是一样的,而写两行就没有这个保证。例

    HANDLE hHeap = HeapCreate(0, 2048, 0);

    LPVOID lp = HeapAlloc(hHeap, 0,256);

 

    // 因为比256小,所以系统尽量在原来的地方分配,所以lp1 == lp成立

    // 同时也导致我以前的误解,呵呵

    LPVOID lp1 = HeapReAlloc(hHeap,0, lp, 128);

    ASSERT(lp1 == lp);

 

    // 因为比256大,所以系统只能到另外的地方申请去,当然lp2 != lp

    LPVOID lp2 = HeapReAlloc(hHeap,0, lp, 8096);

    ASSERT(lp2 != lp);

所有的重分配,比如realloc,GlobalReAlloc等应该都是这个理解。

 

HeapSetInformation

HeapCompatibilityInformation Enablesheap features. At this time, only the Low-fragmentation Heap (LFH) issupported.

 

HeapSize

The HeapSize function retrieves the size of amemory block allocated from a heap by the HeapAlloc or HeapReAlloc function.(也就是说,Heap也不是跟C运行时库中的malloc一样,申请多少就分配多少)

 

HeapUnlock

 

HeapValidate

The HeapValidate function validates the specifiedheap. The function scans all the memory blocks in the heap, and verifies thatthe heap control structures maintained by the heap manager are in a consistentstate. You can also use the HeapValidate function to validate a single memoryblock within a specified heap, without checking the validity of the entireheap.

何时需要验证堆啊?

 

HeapWalk

HeapWalk can fail in a multithreaded applicationif the heap is not locked during the heap enumeration. Use the HeapLock andHeapUnlock functions to control heap locking during heap enumeration.

因为需要调用多次HeapWalk才能遍历完,所以就算你像HeapAlloc一样在其内部加锁也是没法实现一次性遍历的,没办法,只好显式地在外面加锁了。

 

IsBadCodePtr

The IsBadCodePtr function determines whether thecalling process has read access to the memory at the specified address.

BOOL IsBadCodePtr( FARPROC lpfn);

不知道上面的指定地址和IsBadReadPtr中的指定内存范围有什么区别,从函数声明看,好像是应该传入函数地址,但是怎么知道这个函数占多少内存呢?

 

IsBadReadPtr

同下

 

IsBadStringPtr

This function is typically used when working withpointers returned from third-party libraries, where you cannot determine thememory management behavior in the third-party DLL.

 

In a preemptive multitasking environment, it ispossible for some other thread to change the process's access to the memorybeing tested. Even when the function indicates that the process has read accessto the specified memory, you should use structured exception handling whenattempting to access the memory. Use of structured exception handling enablesthe system to notify the process if an access violation exception occurs,giving the process an opportunity to handle the exception.(对这四个函数都适用)

 

IsBadWritePtr

同上

 

LocalAlloc

LocalDiscard

LocalFlags

LocalFree

LocalHandle

LocalLock

LocalReAlloc

LocalSize

LocalUnlock

略,同global function

 

MapUserPhysicalPages

略,因为我还暂时用不到AWE。

 

MapUserPhysicalPagesScatter

略,因为我还暂时用不到AWE。

 

MoveMemory

同memmove

 

QueryMemoryResourceNotification

The QueryMemoryResourceNotification functionretrieves the state of the specified memory resource object.

BOOL QueryMemoryResourceNotification(

  HANDLEResourceNotificationHandle,

  PBOOLResourceState

);

Unlike the wait functions,QueryMemoryResourceNotification does not block the calling thread. Therefore,it is an efficient way to check the state of physical memory before proceedingwith an operation.

 

ResetWriteWatch

The ResetWriteWatch function resets thewrite-tracking state for a region of virtual memory. Subsequent calls to theGetWriteWatch function only report pages that are written to since the resetoperation.

 

If you use ResetWriteWatch, you must ensure thatno threads write to the region during the interval between the GetWriteWatchand ResetWriteWatch calls. Otherwise, there may be written pages that you notdetect.

 

SecureZeroMemory

The SecureZeroMemory function fills a block ofmemory with zeros. It is designed to be a more secure version of ZeroMemory.

A code example of this scenario is in thefollowing section. If ZeroMemory were called in this example instead ofSecureZeroMemory, the compiler could optimize the call because the szPassword bufferis not read from before it goes out of scope. The password would remain on theapplication stack where it could be captured in a crash dump or probed by a malicious(怀恶意的) application.

WCHARszPassword[MAX_PATH];

 

//Retrieve the password

if(GetPasswordFromUser(szPassword, MAX_PATH))   

   UsePassword(szPassword);

// Clearthe password from memory

SecureZeroMemory(szPassword,sizeof(szPassword));

实现代码如下:

FORCEINLINE

PVOID

RtlSecureZeroMemory(

    IN PVOID ptr,

    IN SIZE_T cnt

    )

{

    volatile char *vptr = (volatile char *)ptr;

    while (cnt) {

        *vptr = 0;

        vptr++;

        cnt--;

    }

    return ptr;

}

而用来实现ZeroMemory的memset函数实现如下:

void * __cdecl memset (

        void *dst,

        int val,

        size_t count

        )

{

        void *start = dst;

        while (count--) {

               *(char *)dst= (char)val;

               dst = (char*)dst + 1;

        }

 

        return(start);

}

体会一下volatile的作用?

 

VirtualAlloc

The VirtualAlloc function reserves or commits aregion of pages in the virtual address space of the calling process. Memoryallocated by this function is automatically initialized to zero, unlessMEM_RESET is specified.

 

例:

    LPVOID  pv = VirtualAlloc(NULL, 32, MEM_COMMIT,PAGE_READWRITE);

    memset(pv, 0, si.dwPageSize);

    // memset(pv, 0, si.dwPageSize + 1); 证明确实是按页分配的

 

例:

    void* pv1 = malloc(32);  // 0x003a9548

    void* pv2 = malloc(32);  // 0x003a95a8

 

    HANDLE hHeap = HeapCreate(0, 1024 * 1024, 0);

    LPVOID pv3 = HeapAlloc(hHeap, 0,32);  //0x0114fea0

    LPVOID pv4 = HeapAlloc(hHeap, 0,32);  //0x0114fed8

 

    LPVOID  pv5 = VirtualAlloc(NULL, 32, MEM_COMMIT,PAGE_READWRITE); //0x01150000

    LPVOID  pv6 = VirtualAlloc(NULL, 32, MEM_COMMIT,PAGE_READWRITE); //0x01160000

由此我们可以看出三者的关系,首先,VirtualAlloc是最靠近底层的,它是按页进行的,所以在分配大块内存的时候速度应该是最快的。但是,它也有缺点,因为我们分配内存不可能每次都是大块大块的,实际上,我们经常都在分配小块内存,要是都用VirtualAlloc的话,岂不是浪费,内存还不被你一会儿就分完了。所以,堆出现了。堆在内部肯定还是调用VirtualAlloc分配内存的,但是它可以避免浪费,比如它用VirtualAlloc分配了一个页的内存,当我们多次分配小块内存时,都可以在那一页中分配,它就是做这个管理的。最后,分配内存是最常见的行为了,我们的程序都分为许多独立的模块,那样我们就必须经常创建各自的堆,以便分配小块内存,要是我们能够提供一个统一的接口直接分配内存(都在一个堆中分配),这样岂不是就免去了创建堆的麻烦?!malloc正是这个统一的接口,它存在于C运行时库(动态链接库)中,在内部它通过HeapCreate创建一个堆,以后所有的内存分配它就都在这个堆中进行,当然分配时它直接调用HeapAlloc就行了(注:我估计,当一个堆管理的小块内存太多的时候,分配的速度可能会变慢(但我没法证明),所以,当你对性能要求很高的时候,也可以创建自己的堆,当然,此时你不能用malloc了,呵呵)

 

注意,一开始分配内存的时候仅仅是指为虚拟地址分配了页文件而不是RAM,因为RAM要等到具体操作虚拟地址(比如strcpy)的时候才会提交,并且一操作完可能就被交换出去了。

 

VirtualAllocEx

The VirtualAllocEx function reserves or commits aregion of memory within the virtual address space of a specified process.

LPVOID VirtualAllocEx(

  HANDLE hProcess,

  LPVOIDlpAddress,

  SIZE_TdwSize,

  DWORDflAllocationType,

  DWORDflProtect

);

 

VirtualFree

 

VirtualFreeEx

The VirtualFreeEx function releases, decommits, orreleases and decommits a region of memory within the virtual address space of aspecified process.

BOOL VirtualFreeEx(

  HANDLE hProcess,

  LPVOIDlpAddress,

  SIZE_TdwSize,

  DWORDdwFreeType

);

 

VirtualLock

The VirtualLock function locks the specifiedregion of the process's virtual address space into physical memory(注意,是RAM哦,相当于显式提交RAM,VirtualAlloc仅仅是提交页文件,即磁盘文件),ensuring that subsequent access to the region will not incur a page fault.

BOOL VirtualLock(

  LPVOIDlpAddress,

  SIZE_TdwSize

);

All pages in the specified region must becommitted. Memory protected with PAGE_NOACCESS cannot be locked.

 

Locking pages into memory may degrade theperformance of the system by reducing the available RAM and forcing the systemto swap out other critical pages to the paging file. Each version of Windowshas a limit on the maximum number of pages a process can lock. This limit isintentionally small to avoid severe performance degradation. Applications thatneed to lock larger numbers of pages must first call theSetProcessWorkingSetSize function to increase their minimum and maximum workingset sizes. The maximum number of pages that a process can lock is equal to thenumber of pages in its minimum working set minus a small overhead.

 

Pages that a process has locked remain in physicalmemory until the process unlocks them or terminates.

 

To unlock a region of locked pages, use theVirtualUnlock function. Locked pages are automatically unlocked when theprocess terminates.

 

This function is not like the GlobalLock orLocalLock function in that it does not increment a lock count and translate ahandle into a pointer. There is no lock count for virtual pages.

 

VirtualProtect

 

The PAGE_GUARD protection modifier establishesguard pages. Guard pages act as one-shot access alarms. For more information,see Creating Guard Pages.

 

It is best to avoid using VirtualProtect to changepage protections on memory blocks allocated by GlobalAlloc, HeapAlloc, orLocalAlloc, because multiple memory blocks can exist on a single page. The heapmanager assumes that all pages in the heap grant at least read and writeaccess.(再次证明堆是分配小块内存的,和VirtualAlloc中的分析完全一致)

 

VirtualProtectEx

The VirtualProtectEx function changes theprotection on a region of committed pages in the virtual address space of a specified process.

 

VirtualQuery

SIZE_T VirtualQuery(

  LPCVOIDlpAddress,

 PMEMORY_BASIC_INFORMATION lpBuffer,

  SIZE_TdwLength

);

VirtualQuery provides information about a regionof consecutive(连续的) pages beginning at a specified address that share thefollowing attributes:

The state of all pages is the same (MEM_COMMIT,MEM_RESERVE, MEM_FREE, MEM_PRIVATE, MEM_MAPPED, or MEM_IMAGE).

If the initial page is not free, all pages in theregion are part of the same initial allocation of pages.

The access granted to all pages is the same(PAGE_READONLY, PAGE_READWRITE, PAGE_NOACCESS, PAGE_WRITECOPY, PAGE_EXECUTE,PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_EXECUTE_WRITECOPY, PAGE_GUARD,or PAGE_NOCACHE).

 

VirtualQueryEx

The VirtualQueryEx function provides informationabout a range of pages within the virtual address space of a specified process.

 

VirtualUnlock

The VirtualUnlock function unlocks a specified rangeof pages in the virtual address space of a process, enabling the system to swapthe pages out to the paging file if necessary.

 

Calling VirtualUnlock on a range of memory that isnot locked releases the pages from the process's working set.

working set指什么?

答:

 

ZeroMemory

在VS2005中,可见

#define MoveMemory RtlMoveMemory

#define CopyMemory RtlCopyMemory

#define FillMemory RtlFillMemory

#define ZeroMemory RtlZeroMemory

#define RtlMoveMemory(Destination,Source,Length) memmove((Destination),(Source),(Length))

#define RtlCopyMemory(Destination,Source,Length) memcpy((Destination),(Source),(Length))

#define RtlFillMemory(Destination,Length,Fill) memset((Destination),(Fill),(Length))

#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))

由此可见,这类函数都是直接被定义为C运行时库函数,所以完全没有任何区别,我原先还以为性能上可能有区别呢,没有!我们完全可以直接使用C运行时库函数,免得多记几个API函数,呵呵!

 

1 6章线程的堆栈

每当创建一个线程时,系统就会为线程的堆栈(每个线程有它自己的堆栈)保留一个堆栈空间区域,并将一些物理存储器提交给这个已保留的区域。按照默认设置,系统保留1MB的地址空间并提交两个页面的内存。

何时我们需要修改默认设置?如何修改?

答:

方法一:/STACK:reserve[,commit]

方法二:在创建线程时指定

 

记住堆栈扩展过程及堆栈溢出过程?(看书即可明白,下面仅仅是一些重点或引子)

答:

每当线程试图访问保护页面(VirtualAlloc时带PAGE_GUARD保护属性)中的存储器时,系统就会得到关于这个情况的通知。作为响应,系统将提交紧靠保护页面下面的另一个存储器页面。然后,系统从当前保护页面中删除保护页面的保护标志,并将它赋予新提交的存储器页面。这种方法使得堆栈存储器只有在线程需要时才会增加。(Anyattempt to access a guard page causes the system to raise aSTATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. 为什么是通过异常通知,而不是每次访问的时候计算?答:我估计是因为这样逻辑简单,再加上STATUS_GUARD_PAGE_VIOLATION可能有CPU支持,所以效率是其他方式没法比的

 

如你预计的那样,从地址0x08002000开始的页面的保护属性已经被删除,物理存储器被提交给从0x08001000地址开始的页面。它们的差别是,系统并不将保护属性应用于新的物理存储器页面(0x08001000)。这意味着该堆栈已保留的地址空间区域包含了它能够包含的全部物理存储器。最底下的页面总是被保留的,从来不会被提交。

 

当系统将物理存储器提交给0x08001000地址上的页面时,它必须再执行一个操作,即它要引发一个EXCEPTION_STACK_OVERFLOW异常处理(在WinNT.h文件中定义为0xC00000FD)。通过使用结构化异常处理( SEH),你的程序将能得到关于这个异常处理条件的通知,并且能够实现适度恢复。关于SEH的详细说明,请参见第23、2 4和2 5章的内容。本章结尾处的Summation示例应用程序将展示如何对堆栈溢出进行适度恢复。

 

如果在出现堆栈溢出异常条件之后,线程继续使用该堆栈,那么在0x08001000地址上的页面中的全部内存均将被使用,同时,该线程将试图访问从0x08000000开始的页面中的内存。当该线程试图访问这个保留的(未提交的)内存时,系统就会引发一个访问违规异常条件。

这儿纠正一个错误观念:以前以为只要程序溢出就必须得立刻结束,从这儿来看不是这样的,只要我们用SEH(就像Summation示例那样)捕获异常,程序还是有办法继续运行的,直到出现访问违规异常!

 

为什么堆栈区域的最后一个页面始终被保留着。这样做的目的是为了防止不小心改写进程使用的其他数据。

我们写程序怎么就没有考虑这么周到呢,从来没说加点什么保护之类的?!

 

16.1 Windows 98下的线程堆栈

略,谁还用Windows 98啊?!

 

16.2 C/C++运行期库的堆栈检查函数

void SomeFunction()

{

   intnValues[4000];

       nValues[0] = 0;

}

如果初次访问堆栈是在低于保护页面的一个地址上进行的(如上面这个代码中的赋值行所示),那么线程将访问已经保留的内存并且引发访问违规。为了确保能够成功地编写上面所示的函数,编译器将插入对C运行期库的堆栈检查函数的调用。

也就是说,编译器将插入依次访问每个页的代码,也许就是给每一页第一个字节赋零而已。这个知识点对实践没有什么用,因为都是编译器负责了,但是我们要知道有这回事。在VS2005中调用SomeFunction函数,结果发现确实先调用了如下代码:

       page    ,132

       title   chkstk - C stack checkingroutine

;***

;chkstk.asm - C stack checking routine

;

;      Copyright (c) Microsoft Corporation. All rights reserved.

;

;Purpose:

;       Providessupport for automatic stack checking in C procedures

;       whenstack checking is enabled.

;

;*******************************************************************************

 

.xlist

        includecruntime.inc

.list

 

; size of a page of memory

 

_PAGESIZE_     equ     1000h

 

 

        CODESEG

 

page

;***

;_chkstk - check stack upon procedure entry

;

;Purpose:

;       Providestack checking on procedure entry. Method is to simply probe

;       eachpage of memory required for the stack in descending order. This

;       causesthe necessary pages of memory to be allocated via the guard

;       pagescheme, if possible. In the event of failure, the OS raises the

;      _XCPT_UNABLE_TO_GROW_STACK exception.

;

;      NOTE:  Currently, the (EAX <_PAGESIZE_) code path falls through

;       to the"lastpage" label of the (EAX >= _PAGESIZE_) code path.  This

;       issmall; a minor speed optimization would be to special case

;       this uptop.  This would avoid the painfulsave/restore of

;       ecx andwould shorten the code path by 4-6 instructions.

;

;Entry:

;       EAX =size of local frame

;

;Exit:

;       ESP =new stackframe, if successful

;

;Uses:

;       EAX

;

;Exceptions:

;      _XCPT_GUARD_PAGE_VIOLATION - May be raised on a page probe. NEVER TRAP

;                                    THIS!!!! Itis used by the OS to grow the

;                                    stack ondemand.

;      _XCPT_UNABLE_TO_GROW_STACK - The stack cannot be grown. More precisely,

;                                    the attemptby the OS memory manager to

;                                    allocate another guardpage in response

;                                    to a_XCPT_GUARD_PAGE_VIOLATION has

;                                    failed.

;

;*******************************************************************************

 

public _alloca_probe

 

_chkstk proc

 

_alloca_probe   =  _chkstk

 

       push    ecx

 

; Calculate new TOS.

 

        lea     ecx, [esp] + 8 - 4      ; TOS before entering function + size forret value

        sub     ecx, eax                ; new TOS

 

; Handle allocation size that results in wraparound.

; Wraparound will result in StackOverflow exception.

 

        sbb     eax, eax                ; 0 if CF==0, ~0 if CF==1

        not     eax                     ; ~0 if TOS did notwrapped around, 0 otherwise

        and     ecx, eax                ; set to 0 if wraparound

 

        mov     eax, esp                ; current TOS

        and     eax, not ( _PAGESIZE_ - 1) ; Round down tocurrent page boundary

 

cs10:

        cmp     ecx, eax                ; Is new TOS

        jb      short cs20              ; in probed page?

        mov     eax, ecx                ; yes.

        pop     ecx

       xchg    esp, eax                ; update esp

        mov     eax, dword ptr [eax]    ; get return address

        mov     dword ptr [esp], eax    ; and put it at new TOS

        ret

 

; Find next lower page and probe

cs20:

        sub     eax, _PAGESIZE_         ; decrease by PAGESIZE

       test    dword ptr [eax],eax     ; probe page.

        jmp     short cs10

 

_chkstk endp

 

        end

 

学会Exception-Handler Syntax?

答:

///

 

LONG WINAPI FilterFunc(DWORD dwExceptionCode) {

 

  return((dwExceptionCode == STATUS_STACK_OVERFLOW)

      ?EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH);

}

 

///

 

// The separate thread that is responsible forcalculating the sum.

// I use a separate thread for the following reasons:

//    1. Aseparate thread gets its own 1 MB of stack space.

//    2. Athread can be notified of a stack overflow only once.

//    3. Thestacks storage is freed when the thread exits.

DWORD WINAPI SumThreadFunc(PVOID pvParam) {

 

   // Theparameter pvParam, contains the number of integers to sum.

   UINT uSumNum= PtrToUlong(pvParam);

 

   // uSumcontains the summation of the numbers from 0 through uSumNum.

   // If the sumcannot be calculated, a sum of UINT_MAX is returned.

   UINT uSum =UINT_MAX;

 

   __try {

      // Tocatch the stack overflow exception, we must

      // executethe Sum function while inside an SEH block.

      uSum =Sum(uSumNum);

   }

   __except(FilterFunc(GetExceptionCode())) {

      // If weget in here, its because we have trapped a stack overflow.

      // We cannow do whatever is necessary to gracefully continue execution

      // Thissample application has nothing to do, so no code is placed

      // in thisexception handler block.

   }

 

   // The threads exit code is the sum of thefirst uSumNum

   // numbers,or UINT_MAX if a stack overflow occurred.

   return(uSum);

}

__except后面的括号中可以填三个值:EXCEPTION_EXECUTE_HANDLER、EXCEPTION_CONTINUE_SEARCH、EXCEPTION_CONTINUE_EXECUTION,参考一下MSDN很容易就明白了。

 

1 9DLL基础

 

Windows中最重要的3个DLL是:Kernel32.dll,User32.dll,GDI32.dll。

 

为什么要使用DLL的一些原因?

 

如何为应用程序创建DLL?

 

19.3.2 创建用于非VisualC++工具的DLL

本质在于微软的编译器经常改变函数的名字,比如最常见的两种函数调用习惯比较如下:

The __stdcall calling convention is used to call Win32 APIfunctions.The callee cleans the stack, so the compiler makes vararg functions__cdecl.(也就是说__stdcall这种调用习惯不能用在变参函数上,以前还没注意过,呵呵)

Argument-passing order               Right to left.

Stack-maintenance responsibility     Called function pops its own argumentsfrom the stack.

Name-decoration convention           An underscore (_) is prefixed to the name. The name isfollowed by the at sign (@) followed by      the number of bytes (in decimal) in the argument list. Therefore, thefunction declared as int func( int a, double b ) is decorated as follows:_func@12

 

This isthe default calling convention for C and C++ programs. Becausethe stack is cleaned up by the caller, it can do vararg functions. The __cdeclcalling convention creates larger executables than __stdcall, because itrequires each function call to include stack cleanup code. The following listshows the implementation of this calling convention.

 

Argument-passing order               Right to left

Stack-maintenance responsibility     Calling function pops the arguments fromthe stack

Name-decoration convention           Underscore character (_) is prefixed to names, except whenexporting __cdecl functions that use C linkage.(经验证,__cdecl和__stdcall在名字修饰上的差异在于,前者加了extern"C"后,不再修饰函数名,这也是为什么平常我们虽然没有使用

.def,但是用GetProcAddress照样找得到函数的原因。)

原来这其中还有这么多机关,现在想起来就害怕!

 

使用Microsoft的VisualStudio的DumpBin.exe实用程序(带有-exports开关),你能够看到DLL的输出节是个什么样子。

实际上,用DEPENDS.EXE看更简单,通过帮助中的Whyuse Dependency Walker?我们可以知道,DEPENDS.EXE的功能比DumpBin.exe大多了。

 

当链接程序进行输入符号的转换时,它就将一个称为输入节的特殊的节嵌入产生的可执行模块。输入节列出了该模块需要的DLL模块以及由每个DLL模块引用的符号。使用VisualStudio的DumpBin.exe实用程序(带有-imports开关),能够看到模块的输入节的样子。

同样,通过DEPENDS.EXE看输入节更简单。原来

显示的就是父模块针对当前选中模块的输入节内容,简单一点说就是父模块调用了当前模块的哪些函数。(以前一直没搞清楚这个窗口显示的是什么,现在终于懂了,呵呵,现在明白了为什么上面一个窗口中的函数是下面一个窗口中函数的子集了吧,有export才能import嘛),还有

红色图标表示在当前模块中没有定位到父模块所期望的函数。具体参考DEPENDS.EXE的帮助,如

The Parent Import Function ListView displays the list of parent import functions for the currently selectedmodule in the Module Dependency Tree View. Parent import functions are functions that are actually called in thegiven module by the parent module.

 

 

加载程序的搜索顺序?

 

为什么平常我们还是不太习惯把一些功能封装到DLL中去?

答:主要还是建DLL有点麻烦,设置输出目录、定义#define XXXXX_API __declspec(dllexport)等。实际上,到了VS2005后,解决方案方式和向导生成的代码已经帮我们做了很多事情,已经比较简单了,以后可以考虑多用用DLL。

 

第五部分结构化异常处理

23章结束处理程序

 

如果说这种直接的编程环境只是一个美妙的梦想的话,那么结构化异常处理(SEH)就会给你一个现实的惊喜。使用SEH的好处就是当你编写程序时,只需要关注程序要完成的任务。如果在运行时发生什么错误,系统会发现并将发生的问题通知你。

利用SEH,你可以完全不用考虑代码里是不是有错误,这样就把主要的工作同错误处理分离开来。这样的分离,可以使你集中精力处理眼前的工作,而将可能发生的错误放在后面处理。

看看平常我们用了多少结构化异常处理,以及一些书上的建议,能够用返回值处理的绝不用异常处理机制,我敢断定世界上没有有这么好的事!

 

微软在Windows中引入SEH的主要动机是为了便于操作系统本身的开发。操作系统的开发人员使用SEH,使得系统更加强壮。我们也可以使用SEH,使我们的自己的程序更加强壮。

记住上面的抽象的好处没用,还不如记住一些使用结构化异常的一些惯用场景。

 

使用SEH所造成的负担主要由编译程序来承担,而不是由操作系统承担。当异常块(exceptionblock)出现时,编译程序要生成特殊的代码。编译程序必须产生一些表(table)来支持处理SEH的数据结构。编译程序还必须提供回调(callback)函数,操作系统可以调用这些函数,保证异常块被处理。编译程序还要负责准备栈结构和其他内部信息,供操作系统使用和参考。在编译程序中增加SEH支持不是一件容易的事。不同的编译程序厂商会以不同的方式实现SEH,这一点并不让人感到奇怪。幸亏我们可以不必考虑编译程序的实现细节,而只使用编译程序的SEH功能。

 

注意 不要将结构化异常处理同C++的异常处理相混淆。C++异常处理是一种不同形式的异常处理,其形式是使用C++关键字catch和throw。微软的VisualC++也支持C++的异常处理,并且在内部实现上利用了已经引入到编译程序和Windows操作系统的结构化异常处理的功能。

两者具体有什么关系?局部对象在函数退出时会析构函数会被调用会不是用的是结构化异常机制呢?

 

SEH实际包含两个主要功能:结束处理(terminationhandling)和异常处理(exceptionhandling)。

 

一个结束处理程序能够确保去调用和执行一个代码块(结束处理程序,terminationhandler),而不管另外一段代码(保护体,guardedbody)是如何退出的(不论你在保护体中使用return,还是goto,或者是longjump,或者continue、break)

 

读者可能要问编译程序是如何保证在try块可以退出之前执行finally块的。当编译程序检查源代码时,它看到在try块中有return语句。这样,编译程序就生成代码将返回值(本例中是5)保存在一个编译程序建立的临时变量中。编译程序然后再生成代码来执行finally块中包含的指令,这称为局部展开。更特殊的情况是,由于try块中存在过早退出的代码,从而产生局部展开,导致系统执行finally块中的内容。在finally块中的指令执行之后,编译程序临时变量的值被取出并从函数中返回。

看一下汇编代码,研究一下究竟是如何实现的?

 

可以看到,要完成这些事情,编译程序必须生成附加的代码,系统要执行额外的工作。在不同的CPU上,结束处理所需要的步骤也不同。例如,在Alpha处理器上,必须执行几百个甚至几千个CPU指令来捕捉try块中的过早返回并调用finally块。在编写代码时,就应该避免引起结束处理程序的try块中的过早退出,因为程序的性能会受到影响。本章后面,将讨论__leave程序的SEH功能来捕捉常见的事情要更有效。

注意当控制流自然地离开try块并进入finally块(就像在Funcenstein1中)时,进入finally块的系统开销是最小的。在x86CPU上使用微软的编译程序,当执行离开try块进入finally块时,只有一个机器指令被执行,读者可以在自己的程序中注意到这种系统开销。当编译程序要生成额外的代码,系统要执行额外的工作时(如同在Funcenstein2中),系统开销就很值得注意了。

 

现在我们来考察另外的情况,这里可以真正显示结束处理的价值。

场景一(确保资源得到释放):

DWORD Funcfurter1()

{

    DWORD dwTemp;

 

    // 1. Do any processing here

    // ...

 

    __try

    {

        // 2. Request permission to access protected data,and thenuse it.

        WaitForSingleObject(g_hSem,INFINITE);

 

        dwTemp = Funcinator(g_dwProtectedData);

    }

    __finally

    {

        // 3. Allow others to use protected data.

        ReleaseSemaphore(g_hSem,1, NULL);

    }

 

    // 4. Continue processing

    return (dwTemp);

}

现在假想一下,try块中的Funcinator函数调用包含一个错误,会引起一个无效内存访问。如果没有SEH,在这种情况下,将会给用户显示一个很常见的ApplicationError对话框。当用户忽略这个错误对话框,该进程就结束了。当这个进程结束(由于一个无效内存访问),信标仍将被占用并且永远不会被释放,这时候,任何等待信标的其他进程中的线程将不会被分配CPU时间。但若将对ReleaseSemaphore的调用放在finally块中,就可以保证信标获得释放,即使某些其他函数会引起内存访问错误。

如果不考虑到多进程,确实释放不释放都没多大关系,但是到了多进程中,确实是一个使用异常处理机制的场景(比如EasySip中,可惜我们做到,呜呜)。经验证,发生异常后,确实还能执行__finally块。

void CggggguDlg::OnBnClickedButton1()

{

    __try

    {

        *((int*)NULL) = 0; // 发生异常

 

    }

    __finally

    {

        // 点击应用程序错误对话框中确定后,真的可以在程序退出之前弹出消息框

        AfxMessageBox(_T("I can do whether or no!"));

    }

}

 

尽管结束处理程序可以捕捉try块过早退出的大多数情况,但当线程或进程被结束时,它不能引起finally块中的代码执行。当调用ExitThread或ExitProcess时,将立即结束线程或进程,而不会执行finally块中的任何代码。另外,如果由于某个程序调用TerminateThread或TerminateProcess,线程或进程将死掉,finally块中的代码也不执行。某些C运行期函数(例如abort)要调用ExitProcess,也使finally块中的代码不能执行。虽然没有办法阻止其他程序结束你的一个线程或进程,但你可以阻止你自己过早调用ExitThread和ExitProcess。

 

场景二(简化复杂的编程问题,也就是在EasySip中实现从文件读取呼叫数据时相同的问题,申请资源多但返回路径多,各种错误检查使这个函数非常难以阅读,也使这个函数难以理解、维护和修改。):

例子 略

解决办法:利用__finally将资源释放集中处理。

 

上面解决办法的问题是系统开销。就像前面讨论的,应该尽可能避免在try块中使用return语句。为了帮助避免在try块中使用return语句,微软在其C/C++编译程序中增加了另一个关键字__leave。

也就是在原来应该return的地方用__leave替换,并记录返回值,不管怎样,都让其在函数最后return返回值。例子略。

这个方案和大雄说的goto方案哪个好些?

答:

 

 

AbnormalTermination

这个内部函数只在finally块中调用,返回一个Boolean值。指出与finally块相结合的try块是否过早退出。换句话说,如果控制流离开try块并自然进入finally块,AbnormalTermination将返回FALSE。如果控制流非正常退出try块-通常由于goto、return、break或continue语句引起的局部展开,或由于内存访问违规或其他异常引起的全局展开-对AbnormalTermination的调用将返回TRUE。

Abnormal termination of a __try block causes thesystem to search backward through all stack frames to determine whether anytermination handlers must be called. This can result in the execution ofhundreds of instructions, so it is important to avoid abnormal termination of a__try block due to a return, goto, continue, or break statement.

To avoid abnormal termination, execution shouldcontinue to the end of the block. You can also execute the __leave statement.The __leave statement allows for immediate termination of the __try blockwithout causing abnormal termination and its performance penalty. Check yourcompiler documentation to determine if the __leave statement is supported.

书上仅仅说用__leave去提高性能,但是没有说清楚为什么。原来,通过return等离开try块都被认为是Abnormaltermination,而通过__leave则被认为是自然离开try块,不用去搜索termination handlers。要是没搞清楚这个道理,在try块中到处都是return,没问题的代码都被搞出问题来了,呵呵!

 

27章硬件输入模型和局部输入状态

本章将讨论系统的硬件输入模型。重点将考察按键和鼠标事件是如何进入系统并发送给适当的窗口过程的。微软设计输入模型的一个主要目标就是为了保证一个线程的动作不要对其他线程的动作产生不好的影响。

 

27.1 原始输入线程

鼠标和键盘事件->设备驱动程序->系统硬件输入队列(System hardware inputqueue, SHIQ)->原始输入线程(raw input thread,RIT)->VIQ

 

鼠标消息

 

按键硬件事件

 

......另外,RIT还要负责处理特殊的键组合,如Alt+Tab、Alt+Esc和Ctrl+Alt+Del等。因为RIT在内部处理这些键组合,就可以保证用户总能用键盘激活窗口。应用程序不能够拦截和废弃这些键组合。当用户按动了某个特殊的键组合时,RIT激活选定的窗口,并将窗口的线程连接到RIT。Windows也提供激活窗口的功能,使窗口的线程连接到RIT。

 

27.2 局部输入状态

 

 

GetWindowThreadProcessId

The GetWindowThreadProcessId function retrieves theidentifier of the thread that created the specified window and, optionally, theidentifier of the process that created the window.

 

SetFocus

 

GetFocus

 

SetActiveWindow

 

GetActiveWindow

 

BringWindowToTop

 

AllowSetForegroundWindow

 

SetForegroundWindow

 

LockSetForegroundWindow

 

GetAsyncKeyState

 

ClipCursor

 

SetCapture

 

ReleaseCapture

 

AttachThreadInput

 

GetKeyState

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值