
基础知识
1.一般将进程定义为一个正在运行的程序的一个实例,有两部分构成:
a.一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程体积信息的地方。
b.一个地址空间,其中包含所有可执行文件(executable)或DLL模块的代码和数据。此外,它还包含动态内存分配,比如线程堆栈和堆的分配。
2.进程是有“惰性”的。进程要做任何事情,都必须让一个线程在它的上下文中运行。如果没有线程要执行进程地址空间包含的代码,进程就失去了继续存在的理由。这是系统会自动销毁进程及其地址空间。
3.对于所有要运行的线程,操作系统会轮流为每个线程调度一些CPU时间。它会采取循环(round-robin,轮询或轮流)方式,为每个线程都分配时间片(称为“量”或者“量程”,即quantum),从而营造出所有线程都在“并发”运行的假象。
4.Windows支持两种类型的应用程序:GUI程序和CUI程序。前者是“图形用户界面”(Graphical User Interface)的简称,后者是“控制台用户界面”(Console User Interface)的简称。
5.Windows应用程序可以使用以下两种入口函数:_tWinMain()和_tmain()。
6.加载到进程地址空间的每一个可执行文件或者DLL文件都被赋予了一个独一无二的实例句柄。可执行文件的实例被当作(w)WinMain函数的第一个参数hInstanceExe传入。在需要加载资源的函数调用中,一般都要提供此句柄的值。
7.为了知道一个可执行文件或DLL文件被加载到进程地址空间的什么位置,可以使用如下的GetModuleHandle函数来返回一个句柄/基地址:
HMODULE GetModuleHandle(PCTSTR pszModule);
8.每个进程都有一个与它关联的环境块(environment block),这是在进程地址空间内存分配的一块内存。
9.进程中的线程可以在主机的任何CPU上执行。然而,也可以强迫线程在可用CPU的一个子集上运行,这称为“处理器关联性”(processor affinity)。
10.如果不提供完整的路径名,各种Windows函数会在当前驱动器的当前目录查找文件和目录。
11.可以用GetVersion函数获取用户运行的Windows系统版本。
Continue Reading…

基础知识
1.理解Windows应用程序编程接口(application programming interface,API),首先需要探讨一下内核对象(kernel object)及其句柄(handle)。在系统和我们写的应用程序中,内核对象用于管理进程、线程和文件等诸多种类的大量资源。
2.每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问。这个内存块是一个数据结构,其成员维护着与对象相关的信息。少数成员(安全描述符和使用计数等)是所有对象都有的,但其他大多数成员都是不同类型的对象持有的。
3.内核对象的数据结构只能有操作系统访问,所以应用程序不能在内存中定位这些数据结构并直接修改内容。Microsoft有意强化了这个限制,所以Microsoft能自由地添加、删除和修改这些结构中的成员,同时不会干扰任何应用程序的正常运行。
4.利用Windows提供的一组函数,这组函数会以恰当的方式来操纵这些结构。我们始终可以使用这些函数来访问这些内核对象。调用一个会创建内核对象的函数以后,函数会返回一个句柄(handle),它标识了所创建的对象。
5.内核对象的所有者是操作系统内核而不是进程。如果进程调用一个函数来创建内核对象,进程终止运行时内核对象并不一定会销毁。每个对象都包含一个使用计数(usage count),是每个内核对象类型都有的一个数据成员。初次创建一个对象时,使用计数为1,另一个进程获得现有内核对象访问后,使用计数递增。进程终止后操作系统会自动递减该对象的使用计数。一旦对象的使用计数变为0,操作系统内核就会销毁该对象。
6.内核对象可以用一个安全描述符(security descriptor,SD)来保护。安全描述符描述了谁(通常是对象的创建者)拥有对象;哪些组和用户被允许访问或使用此对象;哪些组和用户被拒绝访问此对象。
7.一个进程初始化时,系统将会为它分配一个句柄表(handle table)。首次初始化的时候,其句柄表为空。当进程内的一个线程调用一个会创建内核对象的函数时,内核将为这个对象分配并初始化一个内存块。然后,内核扫描进程的句柄表,查找一个空白的记录项(empty entry),对其进行初始化。用于创建内核对象的任何函数都会返回一个与线程相关的句柄,这个句柄可由同一个进程中运行的所有线程使用。
8.句柄被设计成“与进程相关的”(process-relative),其中最重要的是原因是健壮性/可靠性,还有安全性。
9.只有在进程之间有一个父-子关系的时候,才可以使用对象句柄继承。只有句柄才是可以继承的,对象本身不能继承。
10.为了使子进程得到它想要的一个内核对象的句柄值,最常见的方式是将句柄值作为命令行参数传给子进程。子进程初始化代码将解析命令行(通常调用_stscanf_s),并提取句柄值。子进程获得句柄值后,就会拥有和父进程一样的内核对象访问权限。句柄继承之所以能够实现,唯一的原因就是“共享的内核对象”的句柄值在父进程和子进程中是完全一样的。这正是父进程能将句柄值作为命令行参数来传递的原因。
11.跨进程边界共享内核对象的方法有:使用对象句柄继承、为对象命名、复制对象句柄。
12.终端服务(Terminal Service)情况不同,正在运行终端服务的计算机中,有多个用于内核对象的命名空间。其中一个是全局命名空间,所有客户端都能访问的内核对象都放在这个命名空间中。这命名空间主要由服务使用。
13.想确保我们的应用程序创建的内核对象名称永远不会和其他应用程序名称冲突,或者想确保它们免遭劫持,可以定义一个自定义的前缀,并把它作为自己的专有命名空间使用,这和使用Global和Local前缀是相似的。
Continue Reading…

基础知识
1.自Windows NT起,Windows的所有版本都完全用Unicode来构建。也就是说,所有核心函数(创建窗口、显示文本、进行字符串处理等等)都需要Unicode字符串。
2.在Windows Vista以及后续系统版本中,每个Unicode字符都使用UTF-16编码,UTF全称是Unicode Transformation Format(Unicode转换格式)。UTF-16将每个字符编码为2个字节(或者说16位)。
3.C运行库中任何修改字符串的函数都存在一个安全隐患:如果目标字符串缓冲区不够大,无法容纳所生成的字符串,就会导致内存中的数据被破坏(memory corruption)。
4.strcpy和wcscpy函数(以及其他大多数字符串处理函数)的问题在于,它们没有收到指定了缓冲区最大长度的参数。
5.为了写安全的代码,我们应该放弃这些熟悉的、能修改字符串的C运行库函数(不过,strlen、wcslen和_tcslen等函数是没有问题的,因为它们不会修改传入的字符——即使它们假设字符串是以0来终止的,而这个假设有时并不一定成立)。
6.新的安全字符串函数,在应用程序中包含StrSafe.h时,String.h也会被包含进来。C运行库中现有的字符串处理函数(如_tcscpy宏后的那些函数)已被标记为废弃不用。现有的每一个函数(如_tcscpy或_tcscat),都有一个对应的新版本的函数。前面的名字相同,但最后添加了一个_s(代表secure)后缀。
Continue Reading…