作为一个Wi n d o w s 软件开发人员,你经常需要创建、打开和操作各种内核对象。系统要创 建和操作若干类型的内核对象,比如存取符号对象、事件对象、文件对象、文件映射对象、 I / O 完成端口对象、作业对象、信箱对象、互斥对象、管道对象、进程对象、信标对象、线程 对象和等待计时器对象等。这些对象都是通过调用函数来创建的。例如,C r e a t e F i l e M a p p i n g 函 数可使系统能够创建一个文件映射对象。每个内核对象只是内核分配的一个内存块,并且只能 由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息。有些数据成 员(如安全性描述符、使用计数等)在所有对象类型中是相同的,但大多数数据成员属于特定 的对象类型。例如,进程对象有一个进程I D 、一个基本优先级和一个退出代码,而文件对象则 拥有一个字节位移、一个共享模式和一个打开模式。 由于内核对象的数据结构只能被内核访问,因此应用程序无法在内存中找到这些数据结构 并直接改变它们的内容。M i c r o s o f t 规定了这个限制条件,目的是为了确保内核对象结构保持状 态的一致。这个限制也使M i c r o s o f t 能够在不破坏任何应用程序的情况下在这些结构中添加、删 除和修改数据成员。 如果我们不能直接改变这些数据结构,那么我们的应用程序如何才能操作这些内核对象 呢?解决办法是,Wi n d o w s 提供了一组函数,以便用定义得很好的方法来对这些结构进行操作。 这些内核对象始终都可以通过这些函数进行访问。当调用一个用于创建内核对象的函数时,该 函数就返回一个用于标识该对象的句柄。该句柄可以被视为一个不透明值,你的进程中的任何 线程都可以使用这个值。将这个句柄传递给Wi n d o w s 的各个函数,这样,系统就能知道你想操 作哪个内核对象。本章后面还要详细讲述这些句柄的特性。 为了使操作系统变得更加健壮,这些句柄值是与进程密切相关的。因此,如果将该句柄值 传递给另一个进程中的一个线程(使用某种形式的进程间的通信)那么这另一个进程使用你的 进程的句柄值所作的调用就会失败。在3 . 3 节“跨越进程边界共享内核对象”中,将要介绍3 种 机制,使多个进程能够成功地共享单个内核对象。这是核心编程中的,希望能对此问题有所帮助
句柄类(handle classes ) C + +中的存取控制允许将实现与接口部分分开,但实现的隐藏是不完全的。编译器必须知 道一个对象的所有部分的声明,以便创建和管理它。我们可以想象一种只需声明一个对象的公 共接口部分的编程语言,而将私有的实现部分隐藏起来。但C + +在编译期间要尽可能多地做静 态类型检查。这意味着尽早捕获错误,也意味着程序具有更高的效率。然而这对私有的实现部 分来说带来两个影响:一是即使程序员不能轻易地访问实现部分,但他可以看到它;二是造成 一些不必要的重复编译。 1 可见的实现部分 有些项目不可让最终用户看到其实现部分。例如可能在一个库的头文件中显示一些策略信息,但公司不想让这些信息被竞争对手获得。比如从事一个安全性很重要的系统(如加密算 法),我们不想在文件中暴露任何线索,以防有人破译我们的代码。或许我们把库放在了一个 “有敌意”的环境中,在那里程序员会不顾一切地用指针和类型转换存取我们的私有成员。在 所有这些情况下,就有必要把一个编译好的实际结构放在实现文件中,而不是让其暴露在头 文件中。 2 减少重复编译 在我们的编程环境中,当一个文件被修改,或它所依赖的文件包含的头文件被修改时,项 目负责人需要重复编译这些文件。这意味着无论何时程序员修改了一个类,无论是修改公共的 接口部分,还是私有的实现部分,他都得再次编译包含头文件的所有文件。对于一个大的项目 而言,在开发初期这可能非常难以处理,因为实现部分可能需要经常改动;如果这个项目非常 大,用于编译的时间过多就可能妨碍项目的完成。 解决这个问题的技术有时叫句柄类(handle classes )。有关实现的 任何东西都消失了,只剩一个单一的指针“s m i l e ”。该指针指向 一个结构,该结构的定义与其所有的成员函数的定义一样出现在实现文 件中。这样,只要接口部分不改变,头文件就不需变动。而实现部分可 以按需要任意更动,完成后只要对实现文件进行重新编译,然后再连接 到项目中。 这里有个说明这一技术的简单例子。头文件中只包含公共的接口和一个简单的没有完全指 定的类指针。 //HANDLE.H--Handle classes #ifndef HANDEL_H_ #define HANDEL_H_class handle{ struct cheshire;//Class declaration only cheshire* smile; public: void initialize(); void cleanup(); int read(); void chang(int); }; #endif // HANDEL_H_ 这是所有客户程序员都能看到的。这行 struct cheshire; 是一个没有完全指定的类型说明或类声明(一个类的定义包含类的主体)。它告诉编译器, cheshire 是一个结构的名字,但没有提供有关该结构的任何东西。这对产生一个指向结构的指 针来说已经足够了。但我们在提供一个结构的主体部分之前不能创建一个对象。在这种技术里, 包含具体实现的结构主体被隐藏在实现文件中。 这是Thinking in C++中的描述。我认为很清楚。具体参阅该书第三章。
建和操作若干类型的内核对象,比如存取符号对象、事件对象、文件对象、文件映射对象、
I / O 完成端口对象、作业对象、信箱对象、互斥对象、管道对象、进程对象、信标对象、线程
对象和等待计时器对象等。这些对象都是通过调用函数来创建的。例如,C r e a t e F i l e M a p p i n g 函
数可使系统能够创建一个文件映射对象。每个内核对象只是内核分配的一个内存块,并且只能
由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息。有些数据成
员(如安全性描述符、使用计数等)在所有对象类型中是相同的,但大多数数据成员属于特定
的对象类型。例如,进程对象有一个进程I D 、一个基本优先级和一个退出代码,而文件对象则
拥有一个字节位移、一个共享模式和一个打开模式。
由于内核对象的数据结构只能被内核访问,因此应用程序无法在内存中找到这些数据结构
并直接改变它们的内容。M i c r o s o f t 规定了这个限制条件,目的是为了确保内核对象结构保持状
态的一致。这个限制也使M i c r o s o f t 能够在不破坏任何应用程序的情况下在这些结构中添加、删
除和修改数据成员。
如果我们不能直接改变这些数据结构,那么我们的应用程序如何才能操作这些内核对象
呢?解决办法是,Wi n d o w s 提供了一组函数,以便用定义得很好的方法来对这些结构进行操作。
这些内核对象始终都可以通过这些函数进行访问。当调用一个用于创建内核对象的函数时,该
函数就返回一个用于标识该对象的句柄。该句柄可以被视为一个不透明值,你的进程中的任何
线程都可以使用这个值。将这个句柄传递给Wi n d o w s 的各个函数,这样,系统就能知道你想操
作哪个内核对象。本章后面还要详细讲述这些句柄的特性。
为了使操作系统变得更加健壮,这些句柄值是与进程密切相关的。因此,如果将该句柄值
传递给另一个进程中的一个线程(使用某种形式的进程间的通信)那么这另一个进程使用你的
进程的句柄值所作的调用就会失败。在3 . 3 节“跨越进程边界共享内核对象”中,将要介绍3 种
机制,使多个进程能够成功地共享单个内核对象。这是核心编程中的,希望能对此问题有所帮助
C + +中的存取控制允许将实现与接口部分分开,但实现的隐藏是不完全的。编译器必须知
道一个对象的所有部分的声明,以便创建和管理它。我们可以想象一种只需声明一个对象的公
共接口部分的编程语言,而将私有的实现部分隐藏起来。但C + +在编译期间要尽可能多地做静
态类型检查。这意味着尽早捕获错误,也意味着程序具有更高的效率。然而这对私有的实现部
分来说带来两个影响:一是即使程序员不能轻易地访问实现部分,但他可以看到它;二是造成
一些不必要的重复编译。
1 可见的实现部分
有些项目不可让最终用户看到其实现部分。例如可能在一个库的头文件中显示一些策略信息,但公司不想让这些信息被竞争对手获得。比如从事一个安全性很重要的系统(如加密算
法),我们不想在文件中暴露任何线索,以防有人破译我们的代码。或许我们把库放在了一个
“有敌意”的环境中,在那里程序员会不顾一切地用指针和类型转换存取我们的私有成员。在
所有这些情况下,就有必要把一个编译好的实际结构放在实现文件中,而不是让其暴露在头
文件中。
2 减少重复编译
在我们的编程环境中,当一个文件被修改,或它所依赖的文件包含的头文件被修改时,项
目负责人需要重复编译这些文件。这意味着无论何时程序员修改了一个类,无论是修改公共的
接口部分,还是私有的实现部分,他都得再次编译包含头文件的所有文件。对于一个大的项目
而言,在开发初期这可能非常难以处理,因为实现部分可能需要经常改动;如果这个项目非常
大,用于编译的时间过多就可能妨碍项目的完成。
解决这个问题的技术有时叫句柄类(handle classes )。有关实现的
任何东西都消失了,只剩一个单一的指针“s m i l e ”。该指针指向
一个结构,该结构的定义与其所有的成员函数的定义一样出现在实现文
件中。这样,只要接口部分不改变,头文件就不需变动。而实现部分可
以按需要任意更动,完成后只要对实现文件进行重新编译,然后再连接
到项目中。
这里有个说明这一技术的简单例子。头文件中只包含公共的接口和一个简单的没有完全指
定的类指针。
//HANDLE.H--Handle classes
#ifndef HANDEL_H_
#define HANDEL_H_class handle{
struct cheshire;//Class declaration only
cheshire* smile;
public:
void initialize();
void cleanup();
int read();
void chang(int);
};
#endif // HANDEL_H_
这是所有客户程序员都能看到的。这行
struct cheshire;
是一个没有完全指定的类型说明或类声明(一个类的定义包含类的主体)。它告诉编译器,
cheshire 是一个结构的名字,但没有提供有关该结构的任何东西。这对产生一个指向结构的指
针来说已经足够了。但我们在提供一个结构的主体部分之前不能创建一个对象。在这种技术里,
包含具体实现的结构主体被隐藏在实现文件中。
这是Thinking in C++中的描述。我认为很清楚。具体参阅该书第三章。
总之,handle用来唯一的标志一个对象。按函数的声明用合适的handle做参数去调用函数就得了。
请问你有Thinking in C++的中文电子版吗?在哪里能下载呢?谢谢你!
To CandyCat(Candy):
你说的那些是属于“内核对象”,但是象窗口、菜单这些东西是属于“用户对象”的,他们的管理和访问控制是完全不同的。 HANDLE具体是什么东西,取决与对象的存储方式。学过数据结构的人都应该清楚,多个同类对象的存储方式最常用的有两种:数组和链表。这两种方式各有优缺点,具体我就不再说了。根据《核心编程》这本书的说法(我没试过),内核对象似乎是采用数组方式,因此HANDLE就是数组索引。而对于用户对象,根据我的分析和一些书上写的,应该采用的是链表方式(因其动态性能较好)。所以在这里,HANDLE就是对应结构的指针了。 关于数组和链表,都是老土的东西了,如果你还是不懂的话我可没办法了。 :)
你们怎么懂这么多:)
不过建议买本书看,电子版看起来太累。