Windows API 向 C++ 开发人员提出了一项挑战。组成 API 的众多库大都表现为 C 语言风格的函数和句柄或是 COM 风格的接口。这些用起来都不太方便,需要进行一定的封装或间接操作。
C++ 开发人员的难题是如何确定合理的封装级别。与 MFC 和 ATL 这样的库一起成长起来的开发人员可能倾向于将所有内容都包装为类和成员函数,因为这是他们长久以来依靠的 C++ 库所表现出的模式。也有些开发人员可能对任何形式的封装都嗤之以鼻,而只是直接使用原始函数、句柄和接口。可以说这部分开发人员不是真正的 C++ 开发人员,而只是有身份问题的 C 开发人员。我相信,现在的 C++ 开发人员有着更为自然的中间立场。
我在 MSDN 杂志 重新开始了我的专栏,我在此将向您展示如何使用 C++0x(很可能会命名为 C++ 2011),以及如何使用 Windows API 将本机 Windows 软件开发从黑暗时代解救出来。接下来几个月,我将带您更深入体验 Windows 线程池 API。在这个过程中,您会看到如何编写极具可扩展性的应用程序,而无需使用花哨的新语言以及复杂或昂贵的运行时。您只需要有优秀的 Visual C++ 编译器、Windows API 和掌握技巧的愿望就足够了。
像所有好项目一样,良好的基础是成功的一半。那么,我要如何“包装”Windows API 呢?我不想在后面每个专栏中拘泥于这些细节,因此打算在本专栏中讲清楚建议的做法,在以后依此执行。有关 COM 风格接口的问题暂不做讨论,因为下面几个专栏还用不到这种接口。
Windows API 由很多库组成,这些库公开一组 C 语言风格的函数,以及一个或多个称为句柄的不透明指针。这些句柄通常表示库或系统资源。有相应的函数可用于创建、操作和释放使用句柄的资源。例如,CreateEvent 函数创建一个事件对象,并返回一个到该事件对象的句柄。要释放该句柄并告知系统您已使用完该事件对象,只需将该句柄传递给 CloseHandle 函数即可。如果同一事件对象再无其他现用句柄,系统将销毁该对象:
auto h = CreateEvent( ...
);
CloseHandle(h);
复制代码
C++ 新手
如果您是初次接触 C++ 2011,我要指出一点:auto 关键字会告知编译器根据初始化表达式推断变量类型。这在您不知道表达式类型时非常有用,在进行元编程或只想保存一些击键时,常常会出现这种情况。
但您几乎在任何时候都不应编写这样的代码。毫无疑问,C++ 提供的最有价值功能就是类的功能。模板很酷,标准模板库 (STL) 很神奇,但如果没有类,C++ 中的一切都毫无意义。C++ 程序简明可靠的优点要归功于类。我说的不是虚函数、继承和其他花哨的功能。我说的只是构造函数和析构函数。这往往就是您所需要的一切,还有,这不需要付出任何成本。在实践中,您需要了解异常处理的开销,本专栏末尾将讨论这个问题。
要驯服 Windows API 并使其为 C++ 开发人员所用,需要一个封装句柄的类。是的,您所喜爱的 C++ 库已经有一个句柄包装,但它是完全为 C++ 2011 设计的吗?您能放心地将这些句柄存储在 STL 容器中,然后在程序中传递它们并跟踪其所有者吗?
C++ 类是完美的句柄抽象。请注意,我没有说“对象”。要记住,句柄是对象在程序中的代表,往往不是对象本身。需要看管的是句柄,而不是对象。Windows API 对象与 C++ 类之间若存在一对一关系有时会非常方便,不过这是另外一个问题。
虽然句柄一般是不透明的,但仍会存在不同类型的句柄,而且往往有微妙的语义区别,这就有必要使用类模板以常规方式对句柄充分进行包装。需要使用模板参数来指定句柄类型以及句柄的具体特性或特征。
在 C++ 中,特征类通常用于提供关于给定类型的信息。这样,我可以为多个句柄编写一个类模板,并为 Windows API 中不同类型的句柄提供不同的特征类。句柄的特征类还需要定义句柄的释放方式,以使句柄类模板能够根据需要自动释放句柄。例如,下面就是事件句柄的一个特征类:
struct handle_traits
{
static HANDLE invalid() throw()
{
return nullptr;
}
static void close(HANDLE value) throw()
{
CloseHandle(value);
}
};
复制代码
因为 Windows API 中的很多库共享这些语义,所以它们不单用于事件对象。如您所见,特征类只包含静态成员函数。如此一来,编译器即可轻松嵌入代码而不引入任何开销,同时为元编程提供极大的灵活性。
无效函数返回无效句柄的值。这个值通常为 nullptr,是 C++ 2011 中的一个新关键字,表示 null 指针值。不同于传统的同类值,nullptr 是强类型的,因此适用于模板和函数重载。无效句柄有时也定义为非 nullptr 的值,这就会导致特征类中包含无效函数。close 函数封装关闭或释放句柄的机制。
给出了特征类的轮廓,我可以继续并开始定义句柄类模板,如图 1 所示。
图 1 句柄类模板
template <typename Type, typename Traits>
class unique_handle
{
unique_handle(unique_handle const &);
unique_handle & operator=(unique_handle const &);
void close() throw()
{
if (*this)
{
Traits::close(m_value);
}
}
Type m_value;
public:
explicit unique_handle(Type value = Traits::invalid()) throw() :
我已将 copy 构造函数和 copy 赋值运算符声明为私有,并且保持它们未实现。这会阻止编译器自动生成它们,因为它们很少适合句柄。Windows API 允许复制特定类型的句柄,但这是与 C++ copy 语义非常不同的概念。
构造函数的值参数依靠特征类提供默认值。析构函数调用私有的 close 成员函数,该函数又依靠特征类根据需要关闭句柄。这样,我就得到了一个堆栈友好且异常安全的句柄。
不过这些还不够。close 成员函数依靠 Boolean 转换来确定是否需要关闭句柄。虽然 C++ 2011 引入了显式转换函数,但在 Visual C++ 还没有这样的函数,因此我使用一种通用的 Boolean 转换方法来避免编译器在正常情况下允许的令人担心的隐式转换: