⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 windows-core-program-chapter1to8.txt

📁 关于vc方面的编程指导书
💻 TXT
📖 第 1 页 / 共 5 页
字号:

                见原书P38的程序

另外,你也可以编写该函数的ANSI版本,这样,它就根本不执行转换字符串的实际操作。相反,你可以编写该函数的ANSI版本,这样它就将ANSI字符串转换成Unicode字符串,将Unicode字符串传递给StringReverseW函数,然后将转换后的字符串重新转换成ANSI字符串。该函数类似下面的样子:

                见原书P39的程序

最后,在你用动态连接库分配的头文件中,你可以象下面这样建立这两个函数的原型:

                 见原书P40的程序










第3章 内核对象


在我们介绍Windows API的时候,首先要讲述内核对象以及它们的句柄。本章将要介绍一些比较抽象的概念,我们并不讨论某个特定内核对象的特性。相反我们只是介绍适用于所有内核对象的特性。
我喜欢首先介绍一个比较具体的问题,不过准确地理解内核对象对于想要成为一名Windows软件开发能手来说是至关重要的。内核对象可以供系统和我们编写的应用程序用来管理各种各样的资源,比如进程,线程和文件等。本章讲述的概念也会出现在本书的其他各章之中。但是我觉得,在你开始使用实际的函数来操作内核对象之前,你是无法深刻理解本章讲述的有些内容的。因此当你阅读本书的其他章节时,你可能需要经常回过来参考本章的内容。

3.1 什么是内核对象
作为一个Windows软件开发人员,你经常需要创建、打开和操作各种内核对象。系统要创建和操作若干类型的内核对象,比如存取符号对象,事件对象,文件对象,文件映射对象,I/O完成端口对象,作业对象,信箱对象,互斥对象,管道对象,进程对象,信标对象,线程对象和等待记时器对象等。这些对象都是通过调用函数来创建的。例如,CreateFileMapping函数可使系统能够创建一个文件映射对象。每个内核对象只是内核分配的一个内存块,并且只能由该内核访问。该内存块是一种数据结构,它的成员负责维护关于该对象的各种信息。有些数据成员(如安全性描述符、使用计数等)在所有对象类型中是相同的,不过大多数数据成员属于特定的对象类型。例如,进程对象有一个进程ID,一个基本优先级和一个退出代码,而文件对象则拥有一个字节位移,一个共享模式和一个打开模式。
由于内核对象的数据结构只能被内核访问,因此应用程序无法在内存中找到这些数据结构并直接改变它们的内容。Microsoft规定了这个限制条件,目的是为了确保内核对象结构保持状态的一致。这个限制也使Microsoft能够在不破坏任何应用程序的情况下在这些结构中添加、删除和修改数据成员。
如果我们不能直接改变这些数据结构,那么我们的应用程序如何才能操作这些内核对象呢?解决办法是,Windows提供了一组函数,以便用定义得很好的方法来对这些结构进行操作。这些内核对象始终都可以通过这些函数进行访问。当你调用一个用于创建内核对象的函数时,该函数就返回一个用于标识该对象的句柄。该句柄可以被视为一个不透明值,你的进程中的任何线程都可以使用这个值。你将这个句柄传递给Windows的各个函数,这样,系统就能知道你想操作哪个内核对象。本章的后面部分我们还要详细讲述这些句柄的特性。
为了使操作系统变得更加健壮,这些句柄值是与进程密切相关的。因此,如果你将该句柄值传递给另一个进程中的一个线程(使用某种形式的进程间的通信方式)那么这另一个进程使用你的进程的句柄值所作的调用就会失败。在本章结尾处的“共享跨越进程边界的内核对象”这一节中,我们将要介绍3种机制,使多个进程能够成功地共享单个内核对象。

3.1.1 内核对象的使用计数
内核对象由内核所拥有,而不是由进程所拥有。换句话说,如果你的进程调用了一个创建内核对象的函数,然后你的进程终止运行,那么内核对象不一定被撤消。在大多数情况下,对象将被撤消,但是如果另一个进程正在使用你的进程创建的内核对象,那么该内核知道,在另一个进程停止使用该对象前不要撤消该对象,必须记住的是,内核对象的存在时间可以比创建该对象的进程长。
内核知道有多少进程正在使用某个内核对象,因为每个对象包含一个使用计数。使用计数是所有内核对象类型常用的数据成员之一。当一个对象刚刚创建时,它的使用计数被置为1。然后,当另一个进程访问一个现有的内核对象时,使用计数就递增1。当进程终止运行时,内核就自动确定该进程仍然打开的所有内核对象的使用计数。如果内核对象的使用计数降为0,内核就撤消该对象。这样可以确保在没有进程引用该对象时系统中不保留任何内核对象。
3.1.2 安全性
内核对象能够得到安全描述符的保护。安全描述符用于描述谁创建了该对象,谁能够访问或使用该对象,谁无权访问该对象。安全描述符通常在编写服务器应用程序时使用,如果你编写客户机端的应用程序,那么你可以忽略内核对象的这个特性。

Windows
98 根据原来的设计,Windows 98并不用作服务器端的操作系统。为此,
          Microsoft公司没有在Windows 98中配备安全特性。不过,如果你现在
          为Windows 98设计软件,你在实现你的应用程序时仍然应该了解有关的
          安全问题,并且使用相应的访问信息,以确保它能在Windows 2000上正
          确地运行。

用于创建内核对象的函数几乎都有一个指向SECURITY_ATTRIBUTES结构的指针,作为其参数,下面显示了CreateFileMapping函数的指针:

            见原书P43的程序(1)

大多数应用程序只是为该参数传递NULL,这样就可以创建带有默认安全性的内核对象。默认安全性意味着对象的管理小组的任何成员和对象的创建者都拥有对该对象的全部访问权,而其他所有人均无权访问该对象。但是,你可以指定一个SECURITY_ATTRIBUTES结构,对它进行初始化,并为该参数传递该结构的地址。SECURITY_ATTRIBUTES结构类似下面的样子:

            见原书P43的程序(2)

尽管该结构称为SECURITY_ATTRIBUTES,但是它包含的与安全性有关的成员实际上只有一个,即lpSecurityDescriptor。如果你想要限制人们对你创建的内核对象的访问,你必须创建一个安全性描述符,然后象下面这样对SECURITY_ATTRIBUTES结构进行初始化:

             见原书P44的程序(1)

由于bInheritHandle这个成员与安全性毫无关系,因此我准备推迟到本章后面部分讲述继承性的这一节中再介绍bInheritHandle这个成员。
当你想要获得对相应的一个内核对象的访问权(而不是创建一个新对象)时,你必须设定要对该对象执行什么操作。例如,如果我想要访问一个现有的文件映射内核对象,以便读取它的数据,那么我应该调用下面这个OpenfileMapping函数:

              见原书P44的程序(2)

通过将FILE_MAP_READ作为第一个参数传递给OpenFileMapping,我指明我打算在获得对该文件映象的访问权后读取该文件,OpenFileMapping函数在返回一个有效的句柄值之前,首先执行一次安全检查。如果我(已登录用户)被允许访问现有的文件映象内核对象,OpenFileMapping就返回一个有效的句柄。但是,如果我被拒绝访问该对象,OpenFileMapping将返回NULL,而调用GetLastError函数则返回5(ERROR_ACCESS_DENIED),同样,大多数应用程序并不使用该安全性,因此我不想进一步讨论这个问题。

Windows
98 虽然许多应用程序不需要考虑安全性问题,但是Windows的许多函
          数要求你传递必要的安全访问信息。为Windows 98设计的若干应用程序
          在Windows 2000上无法正确地运行,因为在实现这些应用程序时没有对
          安全问题给于足够的考虑。
              例如,假设一个应用程序在开始运行时要从注册表的子关键字中读
          取一些数据。为了正确地进行这项操作,你的代码应该调用
          RegOpenKeyEx,传递KEY_QUERY_VALUE,以便获得必要的访问权。
              但是,许多应用程序原先是为Windows 98开发的,当时没有考虑到
          运行Windows 2000的需要。由于Windows 98没有解决注册表的安全问
          题,因此软件开发人员常常要调用RegOpenKeyEx函数,传递
          KEY_All_ACCESS,作为必要的访问权。开发人员这样做的原因是,它
          是一种比较简单的解决方案,并且意味着开发人员不必考虑究竟需要什
          么访问权。问题是注册表的子关键字可以被用户读取,但是不能写入。
          因此,当该应用程序现在放在Windows 2000上运行时,用
          KEY_ALL_ACCESS调用RegOpenKeyEx就会失败,而且,没有相应的
          错误检查方法,应用程序的运行就会产生不可预料的结果。

除了内核对象外,你的应用程序也可以使用其他类型的对象,如菜单,窗口,鼠标光标,刷子和字体等。这些对象属于用户对象或图形设备接口(GDI)对象,而不是内核对象。当你初次着手为Windows编程时,如果你想要将用户对象或GDI对象与内核对象区分开来,你一定会感到不知所措。比如,图标究竟是用户对象还是内核对象呢?若要确定一个对象是否属于内核对象,最容易的方法是观察创建该对象所用的函数。创建内核对象的所有函数几乎都有一个参数,你可以用来设定安全属性的信息,这与前面讲到的CreateFileMapping函数是相同的。
用于创建用户对象或GDI对象的函数都没有PSECURITY_ATTRIBUTES参数。例如,让我们来看一看下面这个CreateIcon函数:

            见原书P45的程序

3.2 进程的内核对象句柄表
当一个进程被初始化时,系统要为它分配一个句柄表。该句柄表只用于内核对象,不用于用户对象或GDI对象。句柄表的详细结构和管理方法并没有具体的资料说明。通常我并不介绍操作系统中没有文档资料的那些部分。不过,在这种情况下,我会进行例外处理,因为我认为,作为一个称职的Windows程序员,他必须懂得如何管理进程的句柄表。由于这些信息没有文档资料,因此我不能保证所有的详细信息都正确无误,同时,在Windows 2000、Windows 98和Windows CE中,它们的实现方法是不同的。为此,请认真阅读下面介绍的内容,以加深你的理解,而不是学习系统是如何进行操作的。
表3-1显示了进程的句柄表的样子。你可以看到,它只是个数据结构的数组。每个结构都包含一个指向内核对象的指针,一个访问屏蔽和一些标志。

                  表3-1   进程的句柄结构

     索引    内核对象内存块          访问屏蔽               标志
                 的指针        (标志位的DWORD)   (标志位的DWORD)
     1       0x????????             0x????????               0x????????
     2       0x????????             0x????????               0x????????

3.2.1 创建内核对象
当进程初次被初始化时,它的句柄表是空的。然后,当进程中的线程调用创建内核对象的函数时,比如CreateFileMapping,内核就为该对象分配一个内存块,并对它初始化。这时,内核对进程的句柄表进行扫描,找出一个空项。由于表3-1中的句柄表是空的,内核便找到索引1位置上的结构并对它进行初始化。该指针成员将被设置为内核对象的数据结构的内存地址,访问屏蔽设置为全部访问权,同时,各个标志也作了设置。(关于标志,我们将在本章后面部分的继承性一节中介绍。)
下面列出了用于创建内核对象的一些函数(但这决不是个完整的列表):

            见原书P47的程序

用于创建内核对象的所有函数均返回与进程相关的句柄,这些句柄可以被在相同进程中运行的任何或所有进程成功地加以使用。该句柄值实际上是放入进程的句柄表中的索引,

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -