Inside the C++/CX Design

Inside the C++/CX Design

Rate This
  • Comments 49

Jim SpringfieldHello. This is Jim Springfield, an architect on the Visual C++ team.

Today, I want to give some insight into the new language extensions, officially called C++/CX, which was designed to support the new API model in Windows 8. If you attended //BUILD/, watched some of the sessions online, or have been playing with the prerelease of Visual Studio, you probably have seen some of the “new” syntax. For anyone who is familiar with C++/CLI (i.e. the language extensions we provide for targeting the CLR), the syntax shouldn’t seem much different.

Please note, however, that while the C++/CX syntax is very similar to C++/CLI, the underlying implementation is very different, it does not use the CLR or a garbage collector, and it generates completely native code (x64, x64, ARM depending on target).

Early on in the design of our support for Windows 8, we looked at many different ideas including a pure library approach as well as various ways to integrate support in the language. We have a long history of supporting COM in the Visual C++ team. From MFC to ATL to #import to attributed ATL. We also have a good bit of experience at targeting the CLR including the original managed extensions, C++/CLI, and the IJW support for compiling native code to MSIL. Our design team consisted of seven people and included people who worked on these and who have lots of experience in libraries, compiler implementation, and language design.

We actually did develop a new C++ template library for Windows 8 called WRL (Windows Runtime Library) that does support targeting Windows 8 without language extensions. WRL is quite good and it can be illuminating to take a look at it and see how all of the low-level details are implemented. It is used internally by many Windows teams, although it does suffer from many of same problems that ATL does in its support of classic COM.

  1. Authoring of components is still very difficult. You have to know a lot of the low-level rules about interfaces.
  2. You need a separate tool (MIDL) to author interfaces/types.
  3. There is no way to automatically map interfaces from low-level to a higher level (modern) form that throws exceptions and has real return values.
  4. There is no unification of authoring and consumption patterns.

With some of the new concepts in the Windows Runtime, these drawbacks become even more difficult than in classic COM/ATL. Interface inheritance isn’t vtable-based like it is in classic COM. Class inheritance is based on a mechanism similar to aggregation but with some differences including support for private and protected interfaces. We quickly realized that although there is a need for a low-level tool like WRL, for the vast majority of uses, it is just too hard to use and we could do a lot better while still preserving performance and providing a lot of control.

The #import feature that was available in VC6 provides a good mechanism for consuming COM objects that have a type library. We thought about providing something similar for the Windows Runtime (which uses a new .winmd file), but while that could provide a good consumption experience, it does nothing for authoring. Given that Windows is moving to a model where many things are asynchronous, authoring of callbacks is very important and there aren’t many consumption scenarios that wouldn’t include at least some authoring. Also, authoring is very important for writing UI applications as each page and user-defined control is a class derived from an existing Runtime class.

The design team spent a lot of time discussing what consumption of Windows Runtime components should look like. We decided early on that we should expose classes and interfaces at a higher level than what the ABI defines. Supporting modern C++ features such as exceptions was deemed to be important as well as mapping the Runtime definition of inheritance (both for interfaces and classes) to C++ in such a way that it was natural. It quickly became clear that we would need some new type categories to represent these as we couldn’t change what the existing C++ ABI meant. We went through a lot of different names and it wasn’t until we decided to use the ^ that we also decided to use ref class to indicate the authoring of a Windows Runtime class.

We also spent a lot of time exploring various approaches to how to hold onto a pointer to a WinRT class or interface. Part of this decision was also how to tell the difference between a low-level version of an interface and the high-level version of the interface. We had a lot of different proposals including just using a *, using * with a modifier, and using various other characters such as the ‘@’ symbol. In the original extensions we did for managed code, we in fact did use a * with a modifier (__gc). We realized we would have many of the same problems if we followed that route. Some of the breakthroughs came when we started thinking about what the type of a pointer dereference would be. This made us realize that what we were doing was similar to what we did when C++/CLI was designed. At one point, someone suggested “Why don’t we just use the ^ symbol?” After the laughter died down, it started making a lot of sense. On design point after design point, we often came to the same design decision we had made for C++/CLI.

Many of the concepts we were trying to express were already present in the C++/CLI syntax. Given that reference counting is a form of garbage collection, using ^ to represent a “refcounted” pointer in ZW fits quite well. Dereferencing of a ^ yields a %, also like C++/CLI. While many concepts are expressed the same way, there are a few areas where we decided to deviate from C++/CLI. For example, in C++/CX, the default interface on a class is specified through an attribute in the interface list while in C++/CLI it is an attribute on the class itself.

In C++/CX we have a much better story than C++/CLI when it comes to interoperating references types with regular types. In C++/CLI, managed objects can move around in memory as the garbage collector runs. This means you can’t get the real address of a member (without pinning) or even embed anything except primitive types (i.e. int) into your class. You also cannot put a ^ into a native class or struct. In C++/CX, objects do not move around in memory and thus all of these restrictions are gone. You can put any type into a ref class and you can put a ^ anywhere. This model is much friendlier to normal C++ types and gives the programmer more flexibility in C++/CX.

We will be providing more insight into our design over the coming months. If there are specific things you would like to know more about, please let us know.

  • On which system can you program in ISO C++ (not C) so that you can call directly other applications and components in the system? Even inside one C++ application it is getting messy, as soon as you have 2 shared libraries compiled with different CRT. When you want to talk with the operating system and other applications, not necessary written in C++ (yes, we are not alone), the situation complicates even more. The only approach I know about, is to expose a flat C API, please spread the word if there are other ways. So, when you have to talk to Windows (at this point you are by the way in platform dependent area) you were used to call the flat C API or use some libs to encapsulate it in C++. In these circumstances, I find the augmented C++ an elegant solution, because these extensions are written in order to allow you to talk easily to Windows (again, platform dependent anyway), and not to solve the general C++ ABI incompatibility issues.

  • I am afraid the C++ community is making the language too complex. I think C++ is steering into the wrong direction. C++ is more and more a language of those who developed this language. It is not simple, it is not easy to use, it is getting more and more ugly...

    Simplicity is beauty! Keep C++ as simple as possible!

  • @John Burton:

    "So if I write "Type^ something" then I can think of that as a shortcut for writing somethign like std::shared_ptr<Type> something"? (I realise it's not a shared_ptr, it's a com wrapper, but is the basic concept right? That it's largely a neat syntax for defining a reference counted pointer rather than anything very new?)"

    Yes, you understand it right. There are some smarts in the compiler in that using Type^ over a smart pointer may in a few limited cases save you a pair of calls to AddRef / Release, but that's it, there are no other differences.

  • PleaseFixYourBugs:

    "The main point is to use plain ISO C++ without any language extensions. If we absolutely have to expose C++ classes for use in other languages [...] let's use metadata. It's not important whether metadata is generated by a C++ compiler or a separate tool, what's important is that it is generated out of plain ISO C++."

    Well done! I think this is what a lot of us die-hard C++ coders are thinking at this stage. Brilliantly summarized.

    Jim Springfield:

    It's interesting to see how, throughout this discussion, the focus tends to shift from 'this fundamental concept is difficult to express in standard C++ 11' to 'we came up with this WinRT ABI thing derived from COM, now it's etched in stone, and, you know what, some things in there can't be nicely expressed in standard C++'.

    Let's turn this around a bit, shall we? Why on earth did you have to come up with this COM *** child in the first place? HRESULTs?!... What year is this? 1995?

    Let's think about it: What is the best language for writing native apps in Windows? Why of course, it's Python... I mean, C#... no, it's actually JavaSc... oh, wait...

    Yeah, OK, it's C++. So, how about defining an ABI that is standard-C++-friendly in the first place, and let the other languages worry about how to map to it?

    You're worried about custom name mangling? - specify the Visual C++ one as the standard one for WinRT, and make it public. If any other C++ compiler wants to target WinRT, they have to implement it when generating WinRT code.

    You're worried about exceptions being thrown across module boundaries? - same answer: specify the Visual C++ way as the standard for WinRT, publish it, and let's move on to more interesting topics.

    For goodness sake, it's your OS, you're calling the shots as far as the binary interfaces are concerned. Use that to make things better on the language side (and easier for you), not worse.

    A simple

    #pragma WinRT

    at the beginning of the .h file declaring the stuff that needs to be exported through WinRT should be enough for authoring WinRT types. The rest should be standard C++; yeah, with some under-the-covers support from the compiler, but __no_ugly_syntax_extensions.

Page 4 of 4 (49 items) 1234