Introduction
Based on C++ Standard 17, C++/WinRT is currently the most modern technology for creating applications (C++) in a Windows environment. The fact that it is compatible with C++ Standard 17 means that it can also be used for classic C++ applications which will have access to the most modern operating system APIs.
We can create, therefore, "mixed" solutions composed of UWP-C++/WinRT -Win32 -.NET projects that interact with each other allowing us to take advantage of the best of the various technologies.
Until some time ago, all this was not possible except with COM or through the creation of proxies in C++ / CX (C++ Extended), a C++ dialect created by Microsoft with a syntax similar to the C# language.
C++/CX has been set aside once and for all and from now on with C++/WinRT, we return to using C++ Standard.
What Exactly is C++/WinRT
Language Implementation & Language Projection
First of all, it is necessary to clarify what WinRT language projection is.
We can define the language projection as the ability of the operating system to expose the Windows Runtime APis in a homogeneous (indeed identical) and automatic way, regardless of which programming language has been used to implement the applications or the APIs themselves.
From a technical point of view, the operating system reads metadata and through COM activates the correct language projection.
The idea behind it is similar to that of the Component Object Model (COM), in fact, even in COM a component once published, regardless of the language in which it was written, can be used transparently by applications written in any compatible COM language. So in WinRT as well as in COM, applications can be written in various languages (language implementation: C++, C#, VB, Delphi, etc.).
Each language, or rather every compiler, in this case produces not only DLL.s or EXE but also .winmd files (WinMD) or Windows Metadata, which contain the description, in machine code, of the implemented types (metadata).
The metadata, when required, are read and used by software development tools and / or by the runtime operating system by activating the language projection specific to a given language; so, in the case of C++, when the operating system finds C++/WinRT code (compiled into a .winmd file) it will trigger language projection for the C++ language.
The same thing happens, of course, for each respective language that was used at the time of application development.
Technically, C++/WinRT is a C++ template library (standard 17) for windows Runtime Platform based on the CRTP (curiously recurring template pattern) pattern and distributed entirely in header files.
The purpose of a Windows Runtime Component, normally, is to be "consumed" by various applications; This feature unites WinRT components to COM objects. (By normally, I mean that this is the natural purpose, however nothing prevents you from creating a WinRT component and consuming it exclusively within a single application; obviously in this case, it would not be necessary to create a WinRT component).
The fact that a WinRT Component can be consumed by applications written in various languages implies, as is the case in COM, the need to describe in a standardized universal language the types, properties, classes, methods, etc.
The "place" where this description, once again, as for COM, will be written, is one or more IDL files.
It follows that, even in C++/WinRT, a runtime class that must be consumed, in addition to ours, also by other apps, must be declared in an .idl file.
This is necessary for the types declared in the .idl files to be then exported by the MIDL tool to the compiled .winmd file.
Initial Structure of the IDL File in C++/WinRT
Creating a new WinRT Component project in Visual Studio 2019, we also find an IDL file: Class.idl; the content of which is as follows:
namespace UWPUtils
{
[default_interface]
runtimeclass Class
{
Class();
Int32 MyProperty;
}
}
In the declaration of the class, we can note that it is decorated with the attribute [default_interface]
and defined with the keyword runtimeclass
(whose meaning is obvious).
Also in the .idl file, we see that there is a default constructor; for each constructor declared in the .idl file, the compiler generates the implementation types and projected types (implementation type and projected type), in practice, it writes a stub for our sources already beautiful and ready to use.
The constructors defined in the .idl file will be used by applications when they consume the runtime class outside of their own compilation unit.
Properties, or Not Properties, That Is the Question
About Properties
In the initial .idl file proposed by Visual Studio, in addition to the default constructor, a property (MyProperty
) is defined. However, it does not specify whether this property is read only or not.
When not specified, the MIDL tool will produce the respective get
and set
methods (that is, get
and set
are implicit).
Those who know the standard C++ may be perplexed, because more than a C++ application, this .idl file seems to refer to some other language (Java or C# in the first place); in fact, in the standard C++ language, it is not possible to define properties in a class as we understand them in C#, Java, Delphi and so on, accompanied by the relative getters and setters; therefore in C++/WinRT for each property described in the IDL file, a get
and set
method will be produced by the MIDL tool (see examples below).
Declaring a C# Class
class DemoStudent
{
public DemoStudent() { }
public string FirstName { get; set; }
public string LastName { get; set; }
................
................
}
Declaring the same class in Standard C++ 17
class DemoStudent
{
public:
DemoStudent() = default;
virtual ~DemoStudent() = default;
void set_FirstName(const std:wstring& name);
const std::wstring get_FirstName() const;
void set_LastName(const std:wstring& last_name);
const std::wstring get_LastName() const;
................
................
private:
std::wstring m_name;
std::wstring m_last_name;
................
................
};
Deviating instead a little from the standard C++ and using Microsoft Visual C++, we can define properties with an extension of the C++ language:
class DemoStudent()
{
public:
DemoStudent() = default;
virtual ~DemoStudent() = default;
std::wstring get_FirstName(){ return m_FirstName;}
void put_FirstName(std::wstring fname){ m_FirstName = fname;}
std::wstring get_LastName(){ return m_LastName;}
void put_LastName( std::wstring lname){ m_LastName = lname;}
__declspec(property(get = get_FirstName, put = put_FirstName)) std::wstring FirstName;
__declspec(property(get = get_LastName, put = put_LastName)) std::wstring LastName;
................
................
private:
std::wstring m_FirstName;
std::wstring m_LastName;
};
Properties in C++/WinRT
In C++/WinRT, the properties instead of in the classes are defined in the respective .idl file and in compilation MIDL produces setters and getters.
C++/WinRT Class with Properties
namespace UWPUtils
{
[default_interface]
runtimeclass DemoStudent
{
DemoStudent();
String FirstName; String LastName;
................
................
}
}
namespace winrt::UWPUtils::implementation
{
struct DemoStudent : DemoStudentT<demostudent>
{
DemoStudent() = default;
hstring FirstName();
void FirstName(hstring const& value);
hstring LastName();
void LastName(hstring const& value);
................
................
};
}
namespace winrt::UWPUtils::factory_implementation
{
struct DemoStudent : DemoStudentT<demostudent, implementation::demostudent="">
{
};
}
Structure of a Runtime Class
In the .h file above, we can see that the runtime class is actually a struct
; the difference as it is known is that in a struct
, the members and methods are public
by default.
We can also see that our runtime class (struct DemoStudent
above) inherits from a template base class (DemoStudentT
) that has the runtime class itself as its argument.
As we know, in C++ when a class inherits a template class in whose argument(s) there is the derived class itself, we are faced with the case of Curiously Recurring Template Pattern (CRTP) or F-bound polymorphism pattern (see an example below) and C++/WinRT is based precisely on this idiom.
template <class t="">
class BaseT
{
public:
BaseT() = default;
virtual ~BaseT() = default;
std::wstring Run() {
static_cast<t*>(this)->Execute();
return L"Method Execute called from base!";
}
};
class Derived : public BaseT<derived>
{
public:
Derived() = default;
virtual ~Derived() = default;
void Execute() { result = L"Method Execute called from derived!"; Print(); }
private:
std::wstring result = L"";
void Print() { std::wcout << result << std::endl; }
};
int main()
{
auto d = std::make_unique<derived>();
std::wstring str = d->Run();
std::wcout << str << std::endl;
d = nullptr;
d.reset();
return 0;
};
Method Execute called from derived!
Method Execute called from base!
Implementation types As already mentioned, in our case, it is necessary to declare in the .idl file our runtime class, done this at the time of build the toolchain (midl.exe and cppwinrt.exe) generates an implementation type for us.
This is the stub of our struct
to which I referred above.
The stub struct
generated by Visual Studio, based on the runtime class that we declared in the .idl file is ready to use and is saved one part in a .h file and the other in .cpp files; we can want to copy / paste the two files from the sources folder to that of our project.
The two generated files are in the folder .. project\Generated Files\sources\ the content of which is:
#pragma once
#include "DemoStudent.g.h"
namespace winrt::UWPUtils::implementation
{
struct DemoStudent : DemoStudentT<demostudent>
{
DemoStudent() = default;
hstring FirstName();
void FirstName(hstring const& value);
hstring LastName();
void LastName(hstring const& value);
};
}
namespace winrt::UWPUtils::factory_implementation
{
struct DemoStudent : DemoStudentT<demostudent, implementation::demostudent="">
{
};
}
#include "pch.h"
#include "DemoStudent.h"
#include "DemoStudent.g.cpp"
namespace winrt::UWPUtils::implementation
{
hstring DemoStudent::FirstName()
{
throw hresult_not_implemented();
}
void DemoStudent::FirstName(hstring const& value)
{
throw hresult_not_implemented();
}
hstring DemoStudent::LastName()
{
throw hresult_not_implemented();
}
void DemoStudent::LastName(hstring const& value)
{
throw hresult_not_implemented();
}
}
The rest of the type implementation generated by Visual Studio (assuming the file is called demostudent) as in the example is in the files:
- DemoStudent.g.h
- DemoStudent.g.cpp
- module.g.cpp
#pragma once
#include "winrt/UWPUtils.h"
namespace winrt::UWPUtils::implementation
{
template <typename d="">
struct __declspec(empty_bases) DemoStudent_base : implements<d, uwputils::demostudent="">
{
using base_type = DemoStudent_base;
using class_type = UWPUtils::DemoStudent;
using implements_type = typename DemoStudent_base::implements_type;
using implements_type::implements_type;
hstring GetRuntimeClassName() const
{
return L"UWPUtils.DemoStudent";
}
};
}
namespace winrt::UWPUtils::factory_implementation
{
template <typename d="">
struct __declspec(empty_bases)
DemoStudentT : implements<d, windows::foundation::iactivationfactory="">
{
using instance_type = UWPUtils::DemoStudent;
hstring GetRuntimeClassName() const
{
return L"UWPUtils.DemoStudent";
}
auto ActivateInstance() const
{
return make<t>();
}
};
}
#if defined(WINRT_FORCE_INCLUDE_DEMOSTUDENT_XAML_G_H) || __has_include("DemoStudent.xaml.g.h")
#include "DemoStudent.xaml.g.h"
#else
namespace winrt::UWPUtils::implementation
{
template <typename d="">
using DemoStudentT = DemoStudent_base<d, i...="">
}
#endif
void* winrt_make_UWPUtils_DemoStudent()
{
return winrt::detach_abi
(winrt::make<winrt::uwputils::factory_implementation::demostudent>());
}
WINRT_EXPORT namespace winrt::UWPUtils
{
DemoStudent::DemoStudent() :
DemoStudent(make<uwputils::implementation::demostudent>())
{
}
}
#include "pch.h"
#include "winrt/base.h"
void* winrt_make_UWPUtils_DemoStudent();
bool __stdcall winrt_can_unload_now() noexcept
{
if (winrt::get_module_lock())
{
return false;
}
winrt::clear_factory_cache();
return true;
}
void* __stdcall winrt_get_activation_factory([[maybe_unused]] std::wstring_view const& name)
{
auto requal = [](std::wstring_view const& left, std::wstring_view const& right) noexcept
{
return std::equal(left.rbegin(), left.rend(), right.rbegin(), right.rend());
};
if (requal(name, L"UWPUtils.DemoStudent"))
{
return winrt_make_UWPUtils_DemoStudent();
}
return nullptr;
}
int32_t __stdcall WINRT_CanUnloadNow() noexcept
{
#ifdef _WRL_MODULE_H_
if (!::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().Terminate())
{
return 1;
}
#endif
return winrt_can_unload_now() ? 0 : 1;
}
int32_t __stdcall WINRT_GetActivationFactory(void* classId, void** factory) noexcept try
{
std::wstring_view const name{ *reinterpret_cast<winrt::hstring*>(&classId) };
*factory = winrt_get_activation_factory(name);
if (*factory)
{
return 0;
}
#ifdef _WRL_MODULE_H_
return ::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().
GetActivationFactory(static_cast<hstring>(classId),
reinterpret_cast<::IActivationFactory**>(factory));
#else
return winrt::hresult_class_not_available(name).to_abi();
#endif
}
catch (...) { return winrt::to_hresult();
}
Well in short, it must be admitted that at first glance, the code created by Visual Studio could be intimidating, in reality, it is nothing complicated.
Starting from the file DemoStudent.g.h, at the beginning after the namespace, we find the declaration of a variadic template class, that is, a struct
(in this case, but also a function can be variadic template) that supports an arbitrary number of arguments (just like the printf
function of C) whose name is DemoStudent_base
.
This is our basic class (struct
).
The struct
DemoStudent_base
inherits the struct winrt::implements
, also a variadic template class (struct
), which has among other arguments, UWPUtils::D emoStudent
.
The struct
winrt::implements
is the base struct
from which each of our runtime classes or activation factories, directly or indirectly descend; this implements various fundamental interfaces including IUnknown
, IInspectable
, etc.
The following code is a series of using new_type_name =
or aliases of existing types (C++ 17) and use of namespaces.
Below, we find a GetRuntimeClassName()
method that returns an hstring
value.
hstring (winrt::hstring)
is a struct
that contains a series of UNICODe UTF-16 characters; that is, text string
s.
Finally, we find the ActivateInstance
method that depending on the class passed with the argument T
by calling the template function make<T>
returns:
- if we are creating a
winrt
component that is consumed by apps outside the compilation unit in which component is implemented: returns (project) the default interface of the implemented type. - if we are implementing and consuming a runtime class within the same build unit: an instance of the projected type returns.
The same goes for the DemoStudentT
struct
, but this time in the namespace factory_implementation
.
Finally, in the implementation namespace, an alias is declared for the struct DemoStudent_base
with the directive (C++ 17) using DemoStudentT = DemoStudent_base<D, I... >
.
Go to Part 2 for Template classes/functions, variadic templates insight.
History
- 13th January, 2022: Initial version