Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Consume a C++/WinRT Runtime Component in a Win32 Application - Part 1

5.00/5 (8 votes)
12 Jan 2022CPOL8 min read 12.8K  
Introduction to C++/WinRT
Part 1 of how to consume a C++/WinRT runtime component in a Win32 app

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:

C++
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

C#
// Class with properties in C#
class DemoStudent
{
    // default ctor
    public DemoStudent() { }

    //properties
    public string FirstName { get; set; }
    public string LastName  { get; set; }

    //other code
    ................
    ................
}

Declaring the same class in Standard C++ 17

C++
// Class without properties in standard C++ 17
class DemoStudent
{
    public:

    //default ctor
    DemoStudent() = default;

    //default dtor
    virtual ~DemoStudent() = default;

    // getter/setter or accessor/mutator if you prefer
    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;

    //other code
   ................
   ................

   private:

   std::wstring m_name;
   std::wstring m_last_name;

    //other code
   ................
   ................
};

Deviating instead a little from the standard C++ and using Microsoft Visual C++, we can define properties with an extension of the C++ language:

C++
// Class with properties in standard C++ 17 thanks to Visual C++ extension
 class DemoStudent()
 {
 public:

 //Default ctor/dtor
 DemoStudent() = default;
 virtual ~DemoStudent() = default;

 //getters/setters
  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;}

 // Properties declaration:
 // FirstName
 // LastName
 __declspec(property(get = get_FirstName, put = put_FirstName)) std::wstring FirstName;
 __declspec(property(get = get_LastName, put = put_LastName)) std::wstring LastName;

      //other code
     ................
     ................

 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

C++
// file DemoStudent.idl
// Interface declaration in MIDL 3.0
namespace UWPUtils
{
    [default_interface]
    runtimeclass DemoStudent
    {        
        //default ctor
        DemoStudent();
        
        //Properties
        String FirstName; //implicit get;set;
        String LastName;  //implicit get;set;
       
        //other code
        ................
        ................        
    }
}
  
// file DemoStudent.h 
// Class implementation with properties in C++/WinRT      
namespace winrt::UWPUtils::implementation
{
    struct DemoStudent : DemoStudentT<demostudent>
    {
    //default ctor
    DemoStudent() = default;

    //get
    hstring FirstName();
    //set
    void FirstName(hstring const& value);

    //get
    hstring LastName();
    //set
    void LastName(hstring const& value);

    //other code
    ................
    ................
    };   
}

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.

C++
// Curiously Recurring Template Pattern (CRTP) o F-bound polymorphism pattern sample
// A derived class inherits from a template class with itself as template argument

// Template base class
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!";
	    }
};

// CRTP derived class
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; }

};
    //------------------------------------------------

// main
int main()
{
auto d = std::make_unique<derived>();

std::wstring str = d->Run();

std::wcout << str << std::endl;

d = nullptr;
d.reset();

return 0;

};

    //------------------------------------------------
    //output:
    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:

Image 1

C++
// file DemoStudent.h
#pragma once
#include "DemoStudent.g.h"

// Note: Remove this static_assert after copying these generated source files to your project.
// This assertion exists to avoid compiling these generated source files directly.
//static_assert(false, "Do not compile generated C++/WinRT source files directly");

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="">
    {
    };
}
C++
// file DemoStudent.cpp
#include "pch.h"
#include "DemoStudent.h"
#include "DemoStudent.g.cpp"

// Note: Remove this static_assert after copying these generated source files to your project.
// This assertion exists to avoid compiling these generated source files directly.
//static_assert(false, "Do not compile generated C++/WinRT source files directly");

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

Image 2

C++
//file DemoStudent.g.h

// WARNING: Please don't edit this file. It was generated by C++/WinRT v2.0.201113.7

#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
C++
// file DemoStudent.g.cpp

// WARNING: Please don't edit this file. It was generated by C++/WinRT v2.0.201113.7

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>())
    {
    }
}
C++
// file module.g.cpp

// WARNING: Please don't edit this file. It was generated by C++/WinRT v2.0.201113.7

#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 strings.

Finally, we find the ActivateInstance method that depending on the class passed with the argument T by calling the template function make<T> returns:

  1. 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.
  2. 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)