How do I convert from the C++/WinRT implementation type to the C++/WinRT projection type?

Raymond Chen

Raymond

Working with C++/WinRT projections is relatively straightforward. Everything is a smart pointer type, and you invoke methods on them in the usual way, and the only methods you can invoke are the ones in the interface definition.

For concreteness, let’s suppose you have this class interface definition:

namespace Sample
{
    runtimeclass Class
    {
        Class();
        void DoSomething();
    };
};

And you implemented it like this:

namespace winrt::Sample::implementation
{
    struct Class : ClassT<Class>
    {
        Class();
        void DoSomething();

        void ImplementationMethod();
        int implementation_value = 42;
    };
}

Working from the projection side, here’s what you can and cannot do:

winrt::Sample::Class c;   // can construct it
c.DoSomething();          // can invoke interface method
c.ImplementationMethod(); // not allowed
c.implementation_value++; // not allowed

On the other hand, working with C++/WinRT implementations is a bit gnarlier, because you are now straddling two worlds: You have the simple projection world, and you have the ugly implementation world.

For convenience, I’m going to omit the winrt:: namespace qualifier from everything.

First, we have the object creation patterns:

// Create an object from the implementation,
// returning the projection.
Sample::Class c =
    winrt::make<implementation::Class>();

// Create an object from the implementation,
// returning a com_ptr to the implementation.
com_ptr<implementation::Class> c =
    winrt::make_self<implementation::Class>();

And then we have the object conversion patterns:

// Go from the implementation to the projection.
implementation::Class* p;
Sample::Class c = *p;

// Go from the implementation's com_ptr to the projection.
com_ptr<implementation::Class> ptr;
Sample::Class c = *ptr;

The implementation class contains conversions to its projected runtime class as well as all of its interfaces. So you just need to dereference the raw pointer or com_ptr, and then let the conversion kick in.

There’s a gotcha here: You have to make sure not to copy the object by mistake!

implementation::Class* p;
auto o = *p; // bad idea
if (case1) {
  Sample::Class c = o;
} else {
  IInspectable i = o;
}

This code wants to factor out the dereference operator and then perform the conversion later, depending on what case it is in. The problem is that o is going to be a copy of the object, which is going to cause all sorts of problems, because the object is managed by its internal reference count, and copying it into a local is going to cause all sorts of problems: The copy will get a copy of the reference count, which is now incorrect. And the copy is going to destruct when it goes out of scope, regardless of its actual reference count. Fortunately, the debug build will catch this problem with the error

error C2259: 'winrt::Sample::implementation::Class': cannot instantiate abstract class due to following members:
'void winrt::impl::root_implements<D,winrt::Sample::Class>::use_make_function_to_create_this_object(void)': is abstract

Next time, we’ll look at the harder job of getting from the projection back to the implementation.

2 comments

Comments are closed. Login to edit/delete your existing comments

    • Avatar
      紅樓鍮

      Kenny’s article

      winrt::implements defines an additional pure virtual function in debug builds. As long as you don’t decide to aim a gun at your feet and override that function in your implementation type (which inherits from winrt::implements), your implementation type is abstract and cannot be constructed.

      On the other hand, instead of newing your implementation type directly, winrt::make<T> news a type that inherits from your implementation type and does nothing other than filling in that hole in winrt::implements, which makes it identical to your implementation type except being constructible.

      If you delete the copy constructor, then you cannot copy-construct it but you can still default-construct it:

      implementation::Class o;
      

      Now, you cannot delete the default constructor as well if your runtime class is default constructible in the IDL since the C++ default constructor is what the IDL default constructor gets projected as!
      Even if the situation allows you to delete the default constructor, it still won’t prevent other forms of construction:

      implementation::Class { 1337 };
      implementation::Class { std::forward<Args>(args)... };
      

      The pure virtual function trick is much more robust, which prevents the class from being constructed from any combination of arguments.