Error setting comptr to winrt::UserControl::Tag()

352 Views Asked by At

Update: I used Richard's suggestion to fix the setting of Tag. But I am having some issues using the getter for Tag and using the .as/try_as operator on it.

class DerivedController : public winrt::implements<DerivedController, Controller> {
 public:
    DerivedController() {}

    virtual ~DerivedController() {}

    static winrt::com_ptr<DerivedController> from(const winrt::FrameworkElement& control) {
        return control ? control.Tag().try_as<DerivedController>() : nullptr;
    }
}

This is the error I get:

1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(8013): error C2440: 'static_cast': cannot convert from 'winrt::impl::producer<D,I,void> *' to 'D *'
1>        with
1>        [
1>            D=`anonymous-namespace'::DerivedController,
1>            I=winrt::Windows::Foundation::IInspectable
1>        ]
1>        and
1>        [
1>            D=`anonymous-namespace'::DerivedController
1>        ]
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(8013): note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(8012): note: while compiling class template member function 'D &winrt::impl::produce_base<D,I,void>::shim(void) noexcept'
1>        with
1>        [
1>            D=`anonymous-namespace'::DerivedController,
1>            I=winrt::Windows::Foundation::IInspectable
1>        ]
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(7777): note: see reference to function template instantiation 'D &winrt::impl::produce_base<D,I,void>::shim(void) noexcept' being compiled
1>        with
1>        [
1>            D=`anonymous-namespace'::DerivedController,
1>            I=winrt::Windows::Foundation::IInspectable
1>        ]
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(7737): note: see reference to class template instantiation 'winrt::impl::produce_base<D,I,void>' being compiled
1>        with
1>        [
1>            D=`anonymous-namespace'::DerivedController,
1>            I=winrt::Windows::Foundation::IInspectable
1>        ]
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(7777): note: see reference to class template instantiation 'winrt::impl::produce<D,winrt::Windows::Foundation::IInspectable>' being compiled
1>        with
1>        [
1>            D=`anonymous-namespace'::DerivedController
1>        ]
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(4088): note: see reference to function template instantiation 'D *winrt::get_self<To,winrt::Windows::Foundation::IInspectable>(const I &) noexcept' being compiled
1>        with
1>        [
1>            D=`anonymous-namespace'::DerivedController,
1>            To=`anonymous-namespace'::DerivedController,
1>            I=winrt::Windows::Foundation::IInspectable
1>        ]
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(2387): note: see reference to function template instantiation 'winrt::com_ptr<D> winrt::impl::as<To,winrt::impl::IUnknown>(From *)' being compiled
1>        with
1>        [
1>            D=`anonymous-namespace'::DerivedController,
1>            To=`anonymous-namespace'::DerivedController,
1>            From=winrt::impl::IUnknown
1>        ]
1>note: see reference to function template instantiation 'winrt::com_ptr<D> winrt::Windows::Foundation::IUnknown::as<`anonymous-namespace'::DerivedController>(void) const' being compiled
1>        with
1>        [
1>            D=`anonymous-namespace'::DerivedController
1>        ]
1>note: see reference to class template instantiation 'winrt::com_ptr<D>' being compiled
1>        with
1>        [
1>            D=Controller
1>        ]
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(10615): note: see reference to class template instantiation 'winrt::com_ptr<winrt::impl::IContextCallback>' being compiled (compiling source file
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(10349): note: see reference to class template instantiation 'winrt::com_ptr<winrt::impl::IServerSecurity>' being compiled
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(10308): note: see reference to class template instantiation 'std::chrono::time_point<winrt::clock,winrt::Windows::Foundation::TimeSpan>' being compiled
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(6462): note: see reference to class template instantiation 'winrt::com_ptr<winrt::impl::IMarshal>' being compiled
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\cppwinrt\winrt/base.h(210): note: see reference to class template instantiation 'std::array<uint8_t,8>' being compiled

Controller is constructor as:

auto controller = winrt::make<Controller>().as<Controller>();

DerivedController is constructed as:

DerivedController dController{};

DerivedController used to be like this in c++/cx:

ref class DerivedController sealed : public Controller {
    internal : explicit DerivedController(Windows::UI::Xaml::FrameworkElement ^ &control)    
        : Controller(control) {}   

    static DerivedController ^   
        from(Windows::UI::Xaml::FrameworkElement ^ control) {   
            return control ? dynamic_cast<SvgCanvasController ^>(control->Tag) : nullptr;   
        }
}

Not sure what I am doing wrong, the error seems to be related to how the classes are defined. Would appreciate any thoughts on it!

Original: In C++/CX, I used to be able to do:

ref class Controller {
    Controller() {
        container = ref new UserControl();
        container->Tag = this;
        ...
        ...
    }
}

When I try to convert this to C++/WinRT, direct conversion would like this:

class Controller : public winrt::implements<Controller, winrt::Windows::Foundation::IInspectable> {
    Controller::Controller() {
        winrt::UserControl container;
1===>    container.Tag(this);
        ...
        ...
    }
}

Controller is a hand-authored class (no idls) whose definition looks like this:

class Controller : public winrt::implements<Controller, winrt::Windows::Foundation::IInspectable> 
{
    ...
    ...
    ...
}

But I get an error at (1):

Error   C2664   'void winrt::impl::consume_Windows_UI_Xaml_IFrameworkElement<D>::Tag(const winrt::Windows::Foundation::IInspectable &) const': cannot convert argument 1 from 'Controller *' to 'const winrt::Windows::Foundation::IInspectable &'
  1. Is it possible to set a Com pointer to Tag using some interop with ABI?
  2. Is there anything else I am missing?
  3. Is this the right approach? Or is there a way around it?
2

There are 2 best solutions below

3
On BEST ANSWER
  1. Is it possible to set a Com pointer to Tag using some interop with ABI?

If you want to convert the code container->Tag=this; in C++/CX to the corresponding code in C++/WinRT, you could try to pass *this as the parameter instead of using interop with ABI , like this:

class Controller : public winrt::implements<Controller, winrt::Windows::Foundation::IInspectable>
{
    public:
        UserControl container;
        Controller::Controller()
        {
            container.Tag(*this);
        }
        ……
};
  1. Is there anything else I am missing?

You did a good job, but maybe we can pay attention to the defference of this between C++/CX and C++/WinRT. Here we use *this instead this.

  1. Is this the right approach? Or is there a way around it?

If you want to get the object added into Tag property, then the approach is right. You can try this code:

Controller cc{};
UserControl uc = cc.container;
auto item1 = uc.Tag();
        
auto item2=  item1.try_as<Controller>();
auto item3 = item2.get();
UserControl uc2 = item3->container;
if (uc = uc2)
{
   int t = 1;
}

Update:

You can use static_cast to implement the conversion from Controller pointer to DerivedController pointer and change the return type of from method as DerivedController* like this:

static DerivedController* from(winrt::Windows::UI::Xaml::FrameworkElement const& control) 
{
    auto con = static_cast<winrt::Windows::UI::Xaml::FrameworkElement>(control);
    if (con != nullptr)
    {
        auto it1=con.Tag();
        auto it2 = it1.try_as<Controller>();
        Controller* cc = it2.get();
        DerivedController* der = static_cast< DerivedController* >(cc);
        return der;
    }
    return nullptr;
}

Then call the from method:

Controller cc{};
DerivedController dController{};
DerivedController* aa = dController.from(cc.container);
 
0
On

You've gotten most of the way there, but it's time to dig into what actually happens under the hood when calling as<To>()/try_as<To>(). You may already know that they are a wrapper for QueryInterface. The only difference between the two is that as<To>() converts QueryInterface failure into an exception, while try_as<To>() returns null on failure. (For brevity, I'll refer to just as<To>() for the remainder)

So now we know that we're calling QueryInterface, but it takes a GUID, not a template type argument. C++/WinRT translates this for us with a type trait, winrt::guid_of<T>, that associates a uuid with a type. When the type To is a projected interface, or a raw ABI interface declared with the uuid declspec, the "type-to-guid" mapping behaves pretty much how one would expect.

But what happens when type To is an implementation type derived from winrt::implements? There are two extra steps that come into play here.

The default interface

Step 1 is that guid_of<T> returns the guid of T's default interface. Interfaces are their own default interface - default_interface<T> is the same as T. For runtimeclasses, the default interface is an attribute in the winmd, and can be manually specified in MIDL3, but the typical case is that the default interface of Widget is IWidget.

The default interface of winrt::implements

Step 2 is that for a type T derived from winrt::implements, the default interface of T is the default interface of the second type supplied to the template (the first type after the CRTP type). For example,

struct Base : implements<Base, IBase> {};
// default_interface<Base> == default_interface<IBase> == IBase

strict Derived1 : implements<Derived1, IDerived, Base> {};
// default_interface<Derived1> == IDerived

struct Derived2 : implements<Derived2, Base, IDerived> {};
// default_interface<Derived2> == IBase

struct Derived1 : implements<Derived1, Base> {};
// default_interface<Derived1> == IBase

This is important to remember.

In the example above, note that Derived2 and Derived 3 have the same default interface as Base. (Derived3 above is even worse, and a bit of an antipattern - Derived3 doesn't implement any interfaces of its own, and so there's no need to use implements here.) This means that as<Derived2>() isn't going to behave the way we might think - C++/WinRT is going to QueryInterface for IBase, and not IDerived, which makes Base and Derived indistinguishable for the purposes of as<T>(). This would lead to runtime ambiguity and all sorts of mayhem if this successfully compiled.

However, as<Derived2>() won't compile, because of one last wrinkle: as<To>() needs a return type. Again, for projected interfaces/runtimeclasses, this is simple: return To. For a raw ABI interface, this is also simple: return com_ptr<T>.

The catch

But for implements, we need to remember a detail from QueryInterface, the returned pointer must point to the requested interface's vtable (which might be offset in the object's memory layout). At the risk of oversimplifying, implements accomplishes this with a template type produce<D, I> for each implemented interface (produce<Derived1, IDerived> points at Derived1's vtable for IDerived). So, the final step is that as<To>() casts this ABI offset interface pointer into a produce<To, default_interface<To>> pointer, so that the compiler is able to apply the correct offset to safely get back a To pointer.

If we take the example code, this means that calling as<Derived1>() will cast to produce<Derived1, IDerived>*, which can get you back to Derived1. But calling as<Derived2>() will attempt to cast to produce<Derived2, IBase>*, which has no path back to Derived2, because IBase wasn't one of the interfaces implemented by Derived2. Put another way, C++/WinRT will take you from IBase to Base, because Base implements IBase via the implements type, but it won't take you from IBase to IDerived2.

Conclusion

If your implementation type doesn't have a default interface (or doesn't have a unique one), you're going to have a bad time trying to use it with as<T>()/try_as<T>().

At this point, I'm hoping you can see why calling as<DerivedController>() isn't quite working for you: it doesn't have its own default interface, making it ambiguous with Controller when it comes to as<T>()/QueryInterface. The reason this worked in C++/CX is it automagically generated a default interface for ref classes. If you want your type to be reachable via as<T>(), it needs to have a unique default interface.