I got the following C++/CX code that works as intended but would like to convert it to C++/WinRT code:
namespace My.Custom.Stuff
{
[Windows::Foundation::Metadata::WebHostHidden]
public ref class BaseClass : public Windows::UI::Xaml::Controls::SwapChainPanel
{
public:
BaseClass();
void PublicMethod();
protected:
static ::DirectX::XMFLOAT3 ProtectedMethod();
};
}
namespace My.Custom.Stuff
{
[Windows::Foundation::Metadata::WebHostHidden]
public ref class SubClass sealed : public BaseClass
{
public:
SubClass();
void UseProtectedMethod()
{
::DirectX::XMFLOAT3 value = ProtectedMethod()
// ...
}
};
}
However, the problem that I encounter is as follows: BaseClass
contains a protected method that returns a type that cannot be mapped to a corresponding Windows Runtime type. In C++/CX this is not a problem because ProtectedMethod
is not mapped at all. If I use my Windows Runtime component ProtectedMethod
is simply not exposed which is what I want.
However, that method should be a member of BaseClass
because multiple other classes like SubClass
use the method when implementing their public methods. I have come up with the following C++/WinRT MIDL code:
namespace My.Custom.Stuff
{
unsealed runtimeclass BaseClass : Windows.UI.Xaml.Controls.SwapChainPanel
{
BaseClass();
void PublicMethod();
// This does not work
protected DirectX.XMFLOAT3 RgbFromBrush(IInspectable brush);
}
}
import "BaseClass.idl";
namespace My.Custom.Stuff
{
runtimeclass SubClass : BaseClass
{
SubClass();
void UseProtectedMethod();
}
}
The problem is that if I define ProtectedMethod
that way it won't work because ::DirectX::XMFLOAT3
is no Windows Runtime type. If I use any other return type that protected method gets mapped. However, it should not be visible when using that Windows Runtime component and of course I should not have to change its return type.
How can I achieve what I do with C++/CX using C++/WinRT?
Edit
Compiling the MIDL code results in something like this:
#include "BaseClass.g.h"
namespace winrt::My::Custom::Stuff::implementation
{
struct BaseClass : BaseClassT<BaseClass>
{
BaseClass() = default;
// ...
};
}
namespace winrt::My::Custom::Stuff::factory_implementation
{
struct BaseClass : BaseClassT<BaseClass, implementation::BaseClass>
{
};
}
I thought it might be possible to simply add the protected method as follows:
namespace winrt::My::Custom::Stuff::implementation
{
struct BaseClass : BaseClassT<BaseClass>
{
BaseClass() = default;
protected:
static ::DirectX::XMFLOAT3 ProtectedMethod();
};
}
However, attempting to use ProtectedMethod
in SubClass
results in the following error:
error C2039: 'ProtectedMethod': is not a member of 'winrt::My::Custom::Stuff::BaseClass'
Here's how I'm using it:
#include "SubClass.g.h"
namespace winrt::My::Custom::Stuff::implementation
{
struct SubClass : SubClassT<SubClass>
{
SubClass() = default;
void UseProtectedMethod()
{
::DirectX::XMFLOAT3 value = ProtectedMethod();
}
};
}
namespace winrt::My::Custom::Stuff::factory_implementation
{
struct SubClass : SubClassT<SubClass, implementation::SubClass>
{
};
}
When converting from C++/CX to C++/WinRT, only public methods should go into your IDL file, as those are the only methods comprising the WinRT API surface.
Methods that are private/protected/internal are not part of the WinRT API surface, and so they should not go into the IDL.
The usage code you posted shouldn't compile, because your C++ definition of implementation::SubClass
is missing a template parameter. Since the IDL/WinRT definition has SubClass
deriving from BaseClass
, you need to supply BaseClass
's implementation type to SubClassT
, and if you examine the contents of the cppwinrt-generated "SubClass.h", you'll see this happening. Once you declare your implementation of SubClass
correctly, you will have access to the protected methods on BaseClass
.
I just tried this out successfully, and it looks like this:
namespace RuntimeComponent1
{
[default_interface]
unsealed runtimeclass BaseClass
{
BaseClass();
void PublicMethod();
}
}
#pragma once
#include "BaseClass.g.h"
struct non_winrt_type
{
};
namespace winrt::RuntimeComponent1::implementation
{
struct BaseClass : BaseClassT<BaseClass>
{
BaseClass() = default;
void PublicMethod() {}
protected:
non_winrt_type ProtectedMethod()
{
return {};
}
};
}
namespace winrt::RuntimeComponent1::factory_implementation
{
struct BaseClass : BaseClassT<BaseClass, implementation::BaseClass>
{
};
}
import "BaseClass.idl";
namespace RuntimeComponent1
{
[default_interface]
runtimeclass SubClass : BaseClass
{
SubClass();
void UseProtectedMethod();
}
}
#pragma once
#include "SubClass.g.h"
#include "BaseClass.h"
namespace winrt::RuntimeComponent1::implementation
{
// Notice how BaseClass is used here.
// This was copied directly from the generated boilerplate SubClass.h
struct SubClass : SubClassT<SubClass, RuntimeComponent1::implementation::BaseClass>
{
SubClass() = default;
void UseProtectedMethod()
{
auto result = ProtectedMethod();
}
};
}
namespace winrt::RuntimeComponent1::factory_implementation
{
struct SubClass : SubClassT<SubClass, implementation::SubClass>
{
};
}
I attempted to recreate your sample and it worked as you described it. However, this is what I also attempted in my migration project and it just didn't work. So, I went out looking for the difference. In my migration project I have 4 nested namespaces, e.g.
My.Custom.Super.WindowsRuntimeComponent
. Your sample works well if I have 3 or less namespaces but with 4 namespaces the tooling starts changing the name of the generated fileBaseClass.g.h
toMy.Custom.Super.WindowsRuntimeComponent.BaseClass.g.h
. But that currently happens in my newly created sample only, not in my migration projectSomething strange is going on. My migration project only compiles if the
::implementation
part of the namespace is not added beforeBaseClass
instruct BaseClass : BaseClassT<BaseClass, implementation::BaseClass>
. More digging is required to get to the bottom of this...Alright, so besides the name changes of the generated files I missed adding
#include BaseClass.h
inSubClass.h
. Instead I removed the::implementation
part of the namespace. Correcting this and factoring in the changed filenames solves my problem. Thanks a lot for your help, Ryan!