Visual C++ 2015 引入更新的 C++ 特性到 Windows API 已翻译 100%

oschina 投递于 2015/01/04 12:35 (共 24 段, 翻译完成于 01-26)
阅读 7484
收藏 35
4
加载中

Visual C++ 2015 is the culmination of a huge effort by the C++ team to bring modern C++ to the Windows platform. Over the last few releases, Visual C++ has steadily gained a rich selection of modern C++ language and library features that together make for an absolutely amazing environment in which to build universal Windows apps and components. Visual C++ 2015 builds on the remarkable progress introduced in those earlier releases and provides a mature compiler that supports much of C++11 and a subset of C++ 2015. You might argue about the level of completeness, but I think it’s fair to say that the compiler supports the most important language features, enabling modern C++ to usher in a new era of library development for Windows. And that really is the key. As long as the compiler supports the development of efficient and elegant libraries, developers can get on with building great apps and components.

Rather than giving you a boring list of new features or pro­viding a high-level whirlwind tour of capabilities, I’ll instead walk you through the development of some traditionally complex code that is now, frankly, quite enjoyable to write, thanks to the maturity of the Visual C++ compiler. I’m going to show you something that’s intrinsic to Windows and at the heart of practically every significant current and future API.

已有 1 人翻译此段
我来翻译

It’s somewhat ironic that C++ is finally modern enough for COM. Yes, I’m talking about the Component Object Model that has been the foundation for much of the Windows API for years, and continues as the foundation for the Windows Runtime. While COM is undeniably tied to C++ in terms of its original design, borrowing much from C++ in terms of binary and semantic conventions, it has never been entirely elegant. Parts of C++ that were not deemed portable enough, such as dynamic_cast, had to be eschewed in favor of portable solutions that made C++ implementations more challenging to develop. Many solutions have been provided over the years to make COM more palatable for C++ developers. The C++/CX language extension is perhaps the most ambitious thus far by the Visual C++ team. Ironically, these efforts to improve Standard C++ support have left C++/CX in the dust and really make a language extension redundant.

To prove this point I’m going to show you how to implement IUnknown and IInspectable entirely in modern C++. There is nothing modern or appealing about these two beauties. IUnknown continues to be the central abstraction for prominent APIs like DirectX. And these interfaces—with IInspectable deriving from IUnknown—sit at the heart of the Windows Runtime. I’m going to show you how to implement them without any language extensions, interface tables or other macros—just efficient and elegant C++ with oodles of rich type information that lets the compiler and developer have a great conversation about what needs to be built.

已有 1 人翻译此段
我来翻译

The main challenge is to come up with a way to describe the list of interfaces that a COM or Windows Runtime class intends to implement, and to do so in a way that’s convenient for the developer and accessible to the compiler. Specifically, I need to make this list of types available such that the compiler can interrogate and even enumerate the interfaces. If I can pull that off I might be able to get the compiler to generate the code for the IUnknown QueryInterface method and, optionally, the IInspectable GetIids method, as well. It’s these two methods that pose the biggest challenge. Traditionally, the only solutions have involved language extensions, hideous macros or a lot of hard-to-maintain code.

Both method implementations require a list of interfaces that a class intends to implement. The natural choice for describing such a list of types is a variadic template:

template <typename ... Interfaces>
class __declspec(novtable) Implements : public Interfaces ...
{
};
已有 1 人翻译此段
我来翻译

The novtable __declspec extended attribute keeps any constructors and destructors from having to initialize the vfptr in such abstract classes, which often means a significant reduction in code size. The Implements class template includes a template parameter pack, thus making it a variadic template. A parameter pack is a template parameter that accepts any number of template arguments. The trick is that parameter packs are normally used to allow functions to accept any number of arguments, but in this case I’m describing a template whose arguments will be interrogated purely at compile time. The interfaces will never appear in a function parameter list.

One use of those arguments is already plain to see. The param­eter pack expands to form the list of public base classes. Of course, I’m still responsible for actually implementing those virtual functions, but at this point I can describe a concrete class that implements any number of interfaces:

class Hen : public Implements<IHen, IHen2>
{
};
已有 1 人翻译此段
我来翻译

Because the parameter pack is expanded to designate the list of base classes, it’s equivalent to what I might have written myself, as follows:

class Hen : public IHen, public IHen2
{
};

The beauty of structuring the Implements class template in this way is that I can now insert the implementation of various boilerplate code into the Implements class template while the developer of the Hen class can use this unobtrusive abstraction and largely ignore the magic behind it all.

So far, so good. Now I’ll consider the implementation of IUnknown itself. I should be able to implement it entirely inside the Implements class template, given the type of information the compiler now has at its disposal. IUnknown provides two facilities that are as essential to COM classes as oxygen and water are to humans. The first and perhaps simpler of the two is reference counting and is the means by which COM objects track their lifetime. COM prescribes a form of intrusive reference counting whereby each object is responsible for managing its own lifetime based on its awareness of how many outstanding references exist. This is in contrast to a reference counting smart pointer such as the C++11 shared_ptr class template, where the object has no knowledge of its shared ownership. You might argue about the pros and cons of the two approaches, but in practice the COM approach is often more efficient and it’s just the way COM works so you have to deal with it. If nothing else, you’ll likely agree that it’s a horrible idea to wrap a COM interface inside a shared_ptr!

已有 1 人翻译此段
我来翻译

I’ll begin with the only runtime overhead introduced by the Implements class template:

protected:
  unsigned long m_references = 1;
  Implements() noexcept = default;
  virtual ~Implements() noexcept
  {}

The defaulted constructor isn’t really overhead in itself; it simply ensures the resulting constructor—which will initialize the reference count—is protected rather than public. Both the reference count and the virtual destructor are protected. Making the reference count accessible to derived classes allows for more complex class composition. Most classes can simply ignore this, but do notice that I’m initializing the reference count to one. This is in contrast to popular wisdom that suggests the reference count should initially be zero because no references have been handed out yet. That approach was popularized by ATL and undoubtedly influenced by Don Box’s Essential COM, but it’s quite problematic, as a study of the ATL source code can well attest. Starting with the assumption that the ownership of the reference will immediately be assumed by a caller or attached to a smart pointer provides for a far less error-prone construction process.

已有 1 人翻译此段
我来翻译

A virtual destructor is a tremendous convenience in that it allows the Implements class template to implement the reference counting rather than forcing the concrete class itself to provide the implementation. Another option would be to use the curiously recurring template pattern to avoid the virtual function. Normally I’d prefer such an approach, but it would complicate the abstraction slightly, and because a COM class by its very nature has a vtable, there’s no compelling reason to avoid a virtual function here. With these primitives in place, it becomes a simple matter to implement both AddRef and Release inside the Implements class template. First, the AddRef method can simply use the InterlockedIncrement intrinsic to bump up the reference count:

virtual unsigned long __stdcall AddRef() noexcept override
{
  return InterlockedIncrement(&m_references);
}
已有 1 人翻译此段
我来翻译

That’s largely self-explanatory. Don’t be tempted to come up with some complex scheme whereby you might conditionally replace the InterlockedIncrement and InterlockedDecrement intrinsic functions with the C++ increment and decrement operators. ATL attempts to do so at a great expense of complexity. If efficiency is your concern, rather spend your efforts avoiding spurious calls to AddRef and Release. Again, modern C++ comes to the rescue with its support for move semantics and its ability to move the ownership of references without a reference bump. Now, the Release method is only marginally more complex:

virtual unsigned long __stdcall Release() noexcept override
{
  unsigned long const remaining = InterlockedDecrement(&m_references);
  if (0 == remaining)
  {
    delete this;
  }
  return remaining;
}

The reference count is decremented and the result is assigned to a local variable. This is important as this result should be returned, but if the object were to be destroyed it would then be illegal to refer to the member variable. Assuming there are no outstanding references, the object is simply deleted via a call to the aforementioned virtual destructor. This concludes reference counting, and the concrete Hen class is still as simple as before:

class Hen : public Implements<IHen, IHen2>
{
};
已有 1 人翻译此段
我来翻译

Now it’s time to consider the wonderful world of QueryInterface. Implementing this IUnknown method is a nontrivial exercise. I cover this extensively in my Pluralsight courses and you can read about the many weird and wonderful ways to roll your own implementation in “Essential COM” (Addison-Wesley Professional, 1998) by Don Box. Be warned that while this is an excellent text on COM, it’s based on C++98 and doesn’t represent modern C++ in any way. For the sake of space and time, I’ll assume you have some familiarity with the implementation of QueryInterface and focus instead on how to implement it with modern C++. Here’s the virtual method itself:

virtual HRESULT __stdcall QueryInterface(
  GUID const & id, void ** object) noexcept override
{
}

Given a GUID identifying a particular interface, QueryInterface should determine whether the object implements the desired interface. If it does, it must increment the reference count for the object and then return the desired interface pointer via the out parameter. If not, it must return a null pointer. Therefore, I’ll start with a rough outline:

*object = // Find interface somehow
if (nullptr == *object)
{
  return E_NOINTERFACE;
}
static_cast<::IUnknown *>(*object)->AddRef();
return S_OK;
已有 1 人翻译此段
我来翻译

So QueryInterface first attempts to find the desired interface somehow. If the interface isn’t supported, the requisite E_NO­INTERFACE error code is returned. Notice how I’ve already taken care of the requirement to clear the resulting interface pointer on failure. You should think of QueryInterface very much as a binary operation. It either succeeds in finding the desired interface or not. Don’t be tempted to get creative here and only conditionally respond favorably. Although there are some limited options allowed by the COM specification, most consumers will simply assume that the interface isn’t supported, regardless of what failure code you might return. Any mistakes in your implementation will undoubtedly cause you no end of debugging misery. QueryInterface is too fundamental to mess around with. Finally, AddRef is called through the resulting interface pointer again to support some rare but permissible class composition scenarios. Those aren’t explicitly supported by the Implements class template, but I’d rather set a good example here. It’s important to keep in mind that the reference-counting operations are interface-specific rather than object-specific. You can’t simply call AddRef or Release on any interface belonging to an object. You must honor the COM rules governing object identity, otherwise you risk introducing illegal code that will break in mysterious ways.

已有 1 人翻译此段
我来翻译
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(23)

sgknight
sgknight
写的太长了。没功夫看。
哆啦比猫
哆啦比猫
对不起,我已经全面运用 c++14 了,你特么 c++11 才跟上?
0Scn
0Scn
rime(好用)啊,g++492好用,VS2015赶紧把C++ 1y补上。
kerriganA
kerriganA
好吧,这一个东西太重了,装一个visual studio 2013都要很长的时间啊。。操 。。
alien_hjy
alien_hjy
太長不看。。。反正我這種懶人,就算win上也只用mingw。。。
BadFish
BadFish
6.0经典啊,写出的程序完全向上兼容,到10版本的时候帮助文档是个坑,10以后写出的程序向下兼容性很差(装了运行库也不行),现在虚拟机只用6.0和08版本
tufengwei
tufengwei

引用来自“uudiin”的评论

垃圾,不要误导开发者了
你自己不就是垃圾么
tufengwei
tufengwei

引用来自“uudiin”的评论

垃圾,不要误导开发者了
你自己不就是垃圾么
tufengwei
tufengwei

引用来自“MarvinGuo”的评论

玩过一段时间的C++/CX,只是最后看到这货还用注册表,当时就没兴趣了。

引用来自“甘薯”的评论

凡是依赖注册表这种垃圾东西的应用一律恶心到死.

引用来自“Cath”的评论

用注册表是啥意思?

引用来自“Simmery”的评论

至少也得整得像java或golang一样用环境变量吧。注册表这样的东西就是垃圾
注册表比linux上安装—个程序带进—堆依赖组件强—万俗
wolf2999
wolf2999
这版本更新得好快,还在用2012呢。
返回顶部
顶部