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.

  • @Jim

    Compiler can alter the semantic meaning of & and * operators depending on the type of the expression they are bound to.

    I also have trouble with

       ref class aa {};

    instead of

       class aa : public cxobject {};

    Using the latter form would also make declarations valid with only operators & and *.

    The only thing that remains is how to visually distinguish between cx and non-cx types? Why not introduce a convention to prefix types with 'cx' with a warning if it's not?

    P.S. I generally dislike GC and prefer managing resources myself. Hiding refcounting like this seems quite dangerous as you can easily end up with cyclic connections even between simple objects... Was all of this really the right choice or few steps back in the evolution process?

  • @cppguy:

    "standard C++ would require users to write much more code to achieve same goals."

    We have talked about this already, remember?

    blogs.msdn.com/.../10209291.aspx

    Right off the bat, with the current fixed API for WinRT which - if Microsoft really cared as they say they care - no doubt could have been amended just a little bit to make its use from C++ more natural, what's so different between the following two pieces of code?

    // C++/CX

    Window::Current->Content = ref new MainPage();

    Window::Current->Activate();

    and

    // ISO C++

    ComPtr<IWindow> spCurrentWindow;

    WinRT::Create<IWindowStatics>(RuntimeClass_Windows_UI_Xaml_Window)->get_Current(&spCurrentWindow);

    WinRT::Check(spCurrentWindow->put_Content(MainPage::Create()));

    WinRT::Check(spCurrentWindow->Activate());

    Two lines instead of four, but the four lines are standard-conformant and give you control over everything. The four lines can be made shorter, too (I just had an idea on how I could use just three).

    No, standard C++ would NOT require users to write much more code to achieve same goals, IF ONLY Microsoft tried.

  • In fact, why couldn't we simply write the above snippet as:

    // ISO C++

    Window::Current->Content = new MainPage();

    Window::Current->Activate();

    ??

    What is stopping Microsoft from creating an ISO C++ library around WinRT with a class named 'Window' and a static member of that class named 'Current', etc? In fact, with all the metadata it looks like the source code for such a library can be generated automatically. Why not do this? The benefits are easy: standard-compliant code instead of just another Microsoft language extension, which means instant support for traditional C++ tools, no surprises for C++ developers, etc, etc. Standards are kind of important, don't you think? You don't break them just because you can, right? Especially if this does not give you much of anything in return (the ISO C++ code above is exactly the same as the C++/CX code save for non-conformant 'ref' in the latter). Ah, well, I forgot that we are talking about Microsoft here...

  • @PleaseFixYourBugs

    > Two lines instead of four, but the four lines are standard-conformant and give you control over everything.

    I don't think GUI development is area where low-level control is important somehow. Really important things imo are high level of abstraction, WYSIWYG interface editing and simplicity. Standard C++ doesn't have built-in COM support, no dllexport analog, no properties, no reflection, no metadata (winmd-like), it doesn't even have interface classes (whose closest analog is VC-specific __interface) and didn't know anything about multi-threading before C++11... Can C++ WinRT-comparable framework be implemented in standard-comformant C++? not really. Even Qt uses custom language extensions.

  • @Jim Springfield

    Thanks for the answers!

    I'll be awaiting the further information on C++/CX <-> Excel interoperability with interest!

  • I totally agree that we could write a tool that takes a .winmd file (WinRT metadata) and generates a standard C++ wrapper around it.  We did something very much like that with #import of TLBs.  While that can give a very clean consumption model of WinRT it really doesn't do anything for authoring.  In general, I do like a library approach where it works, but in this case it doesn't provide the benefits we want.  In theory, you could write a new language that spits out metadata, wrappers, and standard C++ code, but I don't think that is an improvement over what we did.  Keep in mind that it is allowable under the standard for vendors to provide extensions to the language.  We have been very careful to not change the meaning of standard C++ code and to ensure that our extensions are truly extensions.  Our goal is to make it very easy for C++ developers to take advantage of the new API in Windows8.  I believe this new model is a vast improvement over classic COM and we wanted our compiler to be able to consume it cleanly.

  • @cppguy:

    "Really important things imo are high level of abstraction, WYSIWYG interface editing and simplicity. Standard C++ doesn't have built-in COM support, no dllexport analog, no properties, no reflection, no metadata (winmd-like), it doesn't even have interface classes (whose closest analog is VC-specific __interface) and didn't know anything about multi-threading before C++11..."

    Abstraction - ISO C++ allows one to have as much abstraction as he wants.

    WYSIWYG - if someone can have a WYSIWYG editor spitting C++/CX, someone can have the same WYSIWYG editor spitting ISO C++. This is not rocket science.

    Standard C++ has no built-in COM support - that's why we are talking about libraries.

    Standard C++ has no dllexport - that's not necessary. Existing conventions on laying out objects in memory and existing calling conventions are enough.

    Standard C++ has no properties - fine. Don't use properties, use methods. Before you say so, same for events.

    Standard C++ has no reflection - meh. It is *very* easy to add C++/CX-style "reflection" as a library feature.

    Standard C++ has no metadata - so? If you want to have means of reading or writing that metadata - let's have a library for that. What's wrong with this?

    Standard C++ doesn't even have interface classes - and?

    Standard C++ didn't know anything about multi-threading before C++11 - so? C++11 is an approved standard now, so we can use that. If you don't want to use the threading support in C++11, let's, well, use a library. Why not?

    In sum, your arguments are totally unconvincing.

    I seriously don't get *WHERE* C++/CX is so better than ISO C++. Jim Springfield implies this is only the case on the consumer side, but this just is not the case, there are many things that can be done on the authoring side as well.

    @Jim:

    "While that can give a very clean consumption model of WinRT it really doesn't do anything for authoring."

    There are plenty of things you can do for authoring, too. What exact code you have problems authoring? Post it and I or someone else will show you how it could look in ISO C++. Seriously, the line you are trying is a non-argument and I don't believe for a second that you don't know it.

  • Typo:

    Jim Springfield implies this is only the case on the consumer side ... --> Jim Springfield implies the relative parity of C++/CX and ISO C++ in terms of ease of use, etc, only holds for the consumer side ...

    Sorry for that.

  • Here is a pretty simple example:

    ref interface IBar

    {

    int GetValue();

    };

    ref interface IFoo : public IBar

    {

    float GetFloatValue();

    };

    ref class MyClass : IFoo

    {

    public:

    MyClass(int i) : v(i) {}

    int GetValue() {return v;}

    float GetFloatValue() {return (float)v;}

    void SetValue(int i) {v = i;}

    static int GetStaticValue() {return 0;}

    int v;

    };

    Here is the C++ code this generates under the covers:

    [uuid("<guidhash_of_IBar>")]

    __interface IBar : public IInspectable

    {

    HRESULT GetValue(int* v);

    };

    // Note: there is no way to express that IFoo requires IBar

    [uuid("<guidhash_of_IFoo>")]

    __interface IFoo : public IInspectable

    {

    HRESULT GetFloatValue(float* v);

    };

    [uuid("<guidhashof___MyClass>")]

    __interface __MyClassInterface : public IInspectable

    {

    HRESULT SetValue(int v);

    };

    [uuid("")]

    __interface IMyClass_Factory

    {

    HRESULT GetStaticValue(int* v);

    HRESULT Create(int i, __MyClassInterface** pp);

    }

    class __MyClass : public IFoo, public IBar, __MyClassInterface

    {

    public:

    //

    // Implement IUnknown and IInspectable

    // This could come from base class or template, etc.

    //

    __MyClass(int i) : v(i) {}

    HRESULT GetValue(int* v)

    {

    try

    {

    *v = __GetValue();

    }

    catch(Exception& e)

    {

    return MapExceptionToHRESULT(e);

    }

    return S_OK;

    }

    int __GetValue() {return v;}

    HRESULT GetFloatValue(float* v)

    {

    try

    {

    *v = __GetFloatValue();

    }

    catch(Exception& e)

    {

    return MapExceptionToHRESULT(e);

    }

    return S_OK;

    }

    float __GetFloatValue() {return v;}

    HRESULT SetValue(int i)

    {

    try

    {

    __SetValue(i);

    }

    catch(Exception& e)

    {

    return MapExceptionToHRESULT(e);

    }

    return S_OK;

    }

    void __SetValue(int i) {v = i;}

    static int __GetStaticValue() {return 0;}

    int v;

    };

    class __MyClassFactory : public IMyClass_Factory

    {

    public:

    //

    // Implement IUnknown, IActivationFactory

    // This could come from base class or template, etc.

    //

    HRESULT GetStaticValue(int* v)

    {

    try

    {

    *v = __MyClass::__GetStaticValue();

    }

    catch(Exception& e)

    {

    return MapExceptionToHRESULT(e);

    }

    return S_OK;

    }

    HRESULT Create(int i, __MyClassInterface** pp);

    {

    try

    {

    *pp = new __MyClass(i);

    }

    catch(Exception& e)

    {

    return MapExceptionToHRESULT(e);

    }

    return S_OK;

    }

    }

    Also, the compiler autogenerates the metadata and the guids.  A much more complicated example would involve properties, events, marshaling, and inheritance.

    From a pure code perspective, I agree that everything could be written in C++, or C, or ASM and that can be made easier with libraries, templates, macros, etc.  I would argue that the code at the top (i.e. "ref interface" and "ref class" is much closer to what a C++ developer would like to write.  The level of abstraction is right and the programmer shouldn't have to worry what the underlying ABI is.  There is a lot of magic in C++ that the compiler takes care of with vtable layout, inheritance, virtual inheritance, member pointers, etc, that users shouldn't have to worry about or write.  We are targeting a specific ABI and trying to map C++ onto that as best we can.

  • @Jim Springfield:

    I would have to argue that the proper translation of that code into standard C++ is as follows:

    a) Change "ref interface" to "struct".

    b) Change "ref class" to "class".

    c) Add `virtual ... = 0;' to the member function declarations in the base classes.

    d) -- and I cannot possibly stress this enough -- _remove any requirement that MyClass be a COM class._

    That said, even your translation given above seems easily improvable: e.g., by replacing "Exception &e" in the boilerplate with "Exception const &e", and appending the clause "catch(...) { return E_FAIL; }". It also doesn't look like IUnknown et al. are actually implemented, which seems like a much easier task for a code generator than for a human.

    Also, would "boost::bind(&IBar::GetValue, &obj_of_type_MyClass)" work? How about "[obj1, &obj2]() { return obj1.GetValue() + obj2.GetValue(); }"? Or "std::vector<decltype(obj3)> objs"? (If ref types effectively have a deleted placement new, substitute pointers as appropriate. (But do they?))

    "Keep in mind that it is allowable under the standard for vendors to provide extensions to the language. We have been very careful [...] to ensure that our extensions are truly extensions."

    I'm not actually convinced of that. At the very least, shouldn't "ref" then be spelled "__ref"?

  • Ray, "ref" is a context-sensitive keyword (like C++11 "final" and "override"). That's how it avoids stomping over std::ref() from <functional>.

    Strictly conforming ISO C++ code can't say "ref class", so C++11 1.4 [intro.compliance]/8 permits this extension: "A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this International Standard. Having done so, however, they can compile and execute such programs." By default, "ref class" triggers a diagnostic. /ZW enables WinRT extensions and silences diagnostics accordingly.

  • @Jim:

    Here you go:

    struct IBar : public IInspectable

    {

    virtual int GetValue() = 0;

    };

    struct IFoo : public IBar

    {

    virtual float GetFloatValue() = 0;

    };

    struct IMyClassStatic : public IInspectable

    {

    virtual int GetStaticValue() = 0;

    };

    struct MyClassStatic : public WinRtBase, public IMyClassStatic

    {

    int GetStaticValue() {return 0;}

    WINRT_INTERFACE(IMyClassStatic)

    };

    class MyClass : public WinRtBase, public IFoo

    {

    public:

    MyClass(int i) : v(i) {}

    int GetValue() {return v;}

    float GetFloatValue() {return (float)v;}

    void SetValue(int i) {v = i;}

    int v;

    WINRT_INTERFACE(IFoo)

    };

    WINRT_FACTORY(MyClass, MyClassStatic)

    The WinRtBase is an imaginary base class not unlike COM base classes in MFC / ATL / whatever, the WINRT_INTERFACE and WINRT_FACTORY are macros, also pretty standard. As soon as we have variadic templates, we could write WINRT_INTERFACES which would take a list of interfaces (perhaps there is a way to implement this now as well). Exceptions cross module boundaries. If this is a problem, let's discuss exactly why and I will amend the design.

    So, what exactly did C++/CX buy you? Automatic wrapping of extensions into HRESULTs? That's a *very* questionable feature. Not having to write an interface and class for static members? That's more of a gimmick than anything else. Not having to use macros? Meh, there's not a lot of them, they are clear and readily understandable. What's more important, macros are part of the language and your 'ref' things aren't.

  • > d) -- and I cannot possibly stress this enough -- _remove any requirement that MyClass be a COM class._

    And loose ability to use this class from languages other than C++ and with other compilers, cause of non-standartized name mangling.

  • By the way, if we go with what Ray says and stop tying everything to COM, we could of course get rid of all macros and instead publish the .h file with the declarations of interfaces and classes. We would need some means to use the C++ classes and interfaces designed in such a way from other languages, but that can easily be done by emitting and using metadata, which is a small part of what you had to do when implementing C++/CX.

    @cppguy:

    "And loose ability to use this class from languages other than C++ and with other compilers, cause of non-standartized name mangling."

    As I say above, let's use metadata. Long term, let's push for standardized name mangling. C++/CX and COM is a crutch here. I would argue that at the very least plain C++ and COM is a better crutch.

  • @STL:  'By default, "ref class" triggers a diagnostic. /ZW enables WinRT extensions and silences diagnostics accordingly.'

    If "ref class" does trigger a diagnostic by default, that would make me feel much better.  Will VC11 ship with the default being to not have /ZW set?  Otherwise, it seems like it would be out of conformance with the clause you cited.

Page 2 of 4 (49 items) 1234