JULY 2011
VOLUME 26 NUMBER 07
Windows with C - C and the Windows API
By Kenny Kerr | July 2011
The Windows API presents a challenge to the C developer. The various libraries that make up the API are, for the most part, exposed either as C-style functions and handles or COM-style interfaces. Neither of these is very convenient to work with and requires some level of encapsulation or indirection.
The challenge for the C developer is to determine the level of encapsulation. Developers who grew up with libraries like MFC and ATL may be inclined to wrap everything up as classes and member functions, because thatrsquo;s the pattern exhibited by the C libraries theyrsquo;ve relied on for so long. Other developers may scoff at any sort of encapsulation and just use the raw functions, handles and interfaces directly. Arguably these other developers arenrsquo;t really C developers, but simply C developers with identity issues. I believe therersquo;s a more natural middle ground for the contemporary C developer.
As I restart my column here at MSDN Magazine, Irsquo;ll show you how you can use C 0x, or C 2011 as it will likely be named, along with the Windows API to lift the art of native Windows software development out of the dark ages. For the next few months Irsquo;m going to take you through an extended tour of the Windows Thread Pool API. Follow along and yoursquo;ll discover how to write amazingly scalable applications without the need for fancy new languages and complicated or costly runtimes. All yoursquo;ll need is the excellent Visual C compiler, the Windows API and a desire to master your craft.
As with all good projects, some groundwork is needed to get off to a good start. How, then, am I going to “wrap” the Windows API? Rather than bog down every subsequent column with these details, Irsquo;m going to spell out my recommended approach in this column and simply build on this going forward. Irsquo;ll leave the issue of COM-style interfaces for the time being, as that wonrsquo;t be needed for the next few columns.
The Windows API consists of many libraries that expose a set of C-style functions and one or more opaque pointers called handles. These handles usually represent a library or system resource. Functions are provided to create, manipulate and release the resources using handles. As an example, the CreateEvent function creates an event object, returning a handle to the event object. To release the handle and tell the system yoursquo;re done using the event object, simply pass the handle to the CloseHandle function. If there are no other outstanding handles to the same event object, the system will destroy it:
auto h = CreateEvent( ... );
CloseHandle(h);
New to C
If yoursquo;re new to C 2011, I should point out that the auto keyword tells the compiler to deduce the type of variable from the initialization expression. This is useful when you donrsquo;t know the type of an expression, as is often the case in metaprogramming, or when you just want to save some keystrokes.
But you should almost never write code like this. Undoubtedly, the single most valuable feature C offers is that of the class. Templates are cool, the Standard Template Library (STL) is magical, but without the class nothing else in C makes sense. The class is what makes C programs succinct and reliable. Irsquo;m not talking about virtual functions and inheritance and other fancy features. Irsquo;m just talking about a constructor and a destructor. Often thatrsquo;s all you need, and guess what? It doesnrsquo;t cost you anything. In practice, you need to be aware of the overhead imposed by exception handling, and Irsquo;ll address that at the end of this column.
To tame the Windows API and make it accessible to modern C developers, a class that encapsulates a handle is needed. Yes, your favorite C library may already have a handle wrapper, but was it designed from the ground up for C 2011? Can you reliably store these handles in an STL container and pass them around your program without losing track of who owns them?
The C class is the perfect abstraction for handles. Note I didnrsquo;t say “objects.” Remember that the handle is the objectrsquo;s representative within your program, and is most often not the object itself. The handle is what needs shepherding—not the object. It may sometimes be convenient to have a one-to-one relationship between a Windows API object and a C class, but thatrsquo;s a separate issue.
Even though handles are typically opaque, there are still different types of handles and, often, subtle semantic differences that necessitate a class template to adequately wrap handles in a general way. Template parameters are needed to specify the handle type and the specific characteristics or traits of the handle.
In C , a traits class is commonly used to provide information about a given type. In this way I can write a single class template for handles and provide different traits classes for the different types of handles in the Windows API. A handlersquo;s traits class also needs to define how a handle is released so that the handle class template can automatically release it if needed. As such, herersquo;s a traits class for event handles:
struct handle_traits
{
static HANDLE invalid() throw()
{
return nullptr;
}
static void close(HANDLE value) throw()
{
CloseHandle(value);
}
};
Because many libraries in the Windows API share these semantics, they can be used for more than just event objects. As you can see, the traits class consists only of static member functions. The result is that the compiler can easily inline the code and no overhead is introduced, while providing a great deal of flexibility for metaprogramming.
The invalid function returns the value of an invali
全文共17871字,剩余内容已隐藏,支付完成后下载完整资料
C 与 Windows API
Kenny Kerr 2011年7月
第26卷07号
Windows API向C 开发人员提出了一项挑战。组成API的众多库大都表现为C语言风格的函数和句柄或是COM风格的接口。这些用起来都不是很方便,需要进行一定的封装或间接操作。
C 开发人员的难题在于如何确定合理的封装级别。伴随着MFC(微软基础类库,是微软公司提供的一个类库,以C 类的形式封装了Windows API,并且包含一个应用程序框架)和ATL(动态模板库,是一种微软程序库,支持利用C 语言编写ASP代码以及其它ActiveX程序)这样的库一起成长起来的开发人员可能倾向于将所有内容都包装为类和成员函数,因为这是他们长久以来依靠的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 lt;typename Type, typename Traitsgt;class unique_handle
{
unique_handle(unique_handle const amp;);
unique_handle amp; operator=(unique_handle const amp;);
void close() throw()
{
if (*this)
{
Traits::close(m_value);
}
}
Type m_value;
public:
explicit unique_handle(Type value = Traits::invalid()) throw() :
m_value(value)
{
}
~unique_handle() throw()
{
close();
}
我将它命名为unique_handle,因为它与标准的unique_ptr类模板有几分神似。很多库还使用着相同的句柄类型和语义,因此我们有必要为最常用的情况提供一个typedef,就简单地叫它 handle 就好了:
typedef unique_handlelt;HANDLE, handle_traitsgt; handle;
现在,我可以创建一个事件对象,并将其声明为“handle”,如下所示:
handle h(CreateEvent( ...
));
我已经将copy构造函数和copy赋值运算符声明为私有,并且保持它们未实现。这样会阻止编译器自动生成它们,因为这些函数和运算符几乎不适合句柄。Windows API中允许复制特定类型的句柄,但这与C 中的copy语义有着非常不同的概念。
构造函数的值参数依靠特征类来提供默认值。析构函数调用私有的close成员函数,该函数又依靠特征类,根据需要关闭句柄。这样,我就得到了一个堆栈友好而且异常安全的句柄。
不过仅仅有这些还不够。close成员函数依靠Boolean转换来确定是否需要关闭句柄。虽然C 2011中引入了显式转换函数,但在Visual C 中还没有这样的函数,因此我在这里使用了一种通用的Boolean转换方法,来避免编译器在正常情况下允许一些令人担心的隐式转换:
private:
struct boolean_struct { int member; };
typedef int boolean_struct::* boolean_type;
bool operator==(unique_handle const amp;);
bool operator!=(unique_handle const amp;);
public:
operator boolean_type() const throw()
{
return Traits::invalid() != m_value ?
amp;boolean_struct::member : nullptr;
}
这也就意味着我现在可以简单地测试句柄是否有效,而不会允许一些危险的转换在暗地里里偷偷进行:
unique_handlelt;SOCKET, socket_traitsgt; socket;
unique_handlelt;HANDLE, handle_traitsgt; event;
if (socket amp;amp; event) {} // Are both valid?if (!event) {} // Is event invalid?int i = socket; // Compiler error!if (socket == event) {} // Compiler error!
使用更加明显的布尔运算符会允许最后两个错误在暗地里发生。虽然如此,这的确会允许两个套接字之间的比较,因此需要显式实现相等运算符,或将它们声明为私有并保持未实现。
unique_handle拥有句柄的方式与标准unique_ptr类模板拥有对象并通过指针管理对象的方式比较相似。因此,可以通过提供我们所熟悉的get、reset以及release成员函数来管理基础句柄。get函数的编写非常简单:
Type get() const throw()
{
return m_value;
}
相比较而言,reset函数稍微有一些复杂,但我们已经讨论过它的构建基础:
bool reset(Type value = Traits::invalid()) throw()
{
if (m_value != value)
{
close();
m_value = value;
}
return *this;
}
我冒昧对unique_ptr提供的reset函数模式做了一些轻微的改动,使其返回一个布尔值,指示对象是否已用有效句柄进行了重置。这样可以方便进行错误处理,稍候我们再来讨论这个问题。release函数的编写现在来看是显而易见的:
Type release() throw()
{
auto value = m_value;
m_value = Traits::invalid();
return value;
}
复制与移动
最后是考虑复制与移动语义。因为我已经禁止了句柄的复制语义,所以应该允许移动语义。如果想要在STL容器中存储句柄,这一点就会变得非常重要。这些容器在传统上依赖于复制语义,但随着C 2011的引入,他们开始支持移动语义。
我将不会在这里对于移动语义和rvalue引用进行详细的介绍。我只是告诉大家,这其中的基本理念是允许对象值以开发人员可预测的、并且对于库作者和编译器一致的方式在对象之间进行传递。
在C 2011之前,开发人员不得不寄希望于使用各种复杂的技巧来避免语言(广义上讲为 STL)对复制对象的过度喜爱。编译器常常会创建一个对象副本,然后会立即销毁原始对象。而使用移动语义,开发人员就可以声明一个对象将不再使用,其值移至别处,通常伴随有尽可能少的指针交换。
在某些较为特殊的情况下,开发人员需要明确指出这一点;但大多数情况下,编译器可以利用移动感知对象,并执行前所未有的超高效优化。而这其中蕴含的好消息,是您自己的类启用移动语义将会变得非常简单。就像复制依赖于复制构造函数和复制赋值运算
全文共7793字,剩余内容已隐藏,支付完成后下载完整资料
资料编号:[10965],资料为PDF文档或Word文档,PDF文档可免费转换为Word
以上是毕业论文外文翻译,课题毕业论文、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。