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

C++/WinRT Runtime Component in Win32 Applications - Part 4

5.00/5 (2 votes)
14 Jan 2022CPOL3 min read 6.2K   207  
Implementing a Win32 C++ Application that consumes a C++/WinRT Runtime Component.

Image 1

Image 2

(above the WinRT Ticket machine in action in a Win32 Application)

We open in Visual Studio the C++ Win32 project created earlier (see Part 3). Our sample application will simulate the operation of a queue cutting system, the issuance of tickets will not take place as a result of choosing a real user at the Totem, but will be managed by the Win32 client application, it will be sufficient to click on the New button in the ribbon control.

Importing a C++/WinRT Component into a Win32 Desktop Application

We saw, in Part 1, that C++/WinRT is based on C++ standard 17, so fully compatible with every C++ 17 application. You probably already have in mind that it will be enough to add one or more includes in the C++ Win32 project and end.

Well, this is indeed possible, but it is not what we want. What we want is for our Win32 C++ application to consume the C++/WinRT component and not to incorporate it into it; that is, our app must, at runtime, load the component and invoke its classes, properties and events.

To do this, you will need to import the WinRT component types into your Win32 project in some way.

Importing C++/WinRT Types into a C++/Win32

We've saw, in Part 3, that Visual Studio does not (at least currently) directly import a .winmd file into a Win32 C++ project. However, there is a workaround: if we define the dependency of the Win32 project, on the WinRT component, the cppwinrt.exe tool, produces for us all the needed code so that we can define the types of the WinRT component in the Win32 app. However, the cppwinrt.exe tool is part of the C++/Winrt compilation toolchain, so definitely not present by default in a C++/Win32 project, therefore, to have the tool available, we will have to add, via NuGet, to the Win32 project, two components:

  • Microsoft.Windows.CppWinRT
  • VC WinRT Forwarders

First of all, we must define the dependency on the WinRT component: In the C++ project Win32, we do Add -> New Item.. and add a Property Sheet file.

We modify the contents of the file in the following way: In Solution Explorer, we go to Property Manager, select the Win32 project with right click and click on the menu item Add Existing Property Sheet...

XML
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup />
  <ItemDefinitionGroup />
  <ItemGroup />
  <ItemGroup>
    <Reference Include="$(SolutionDir)\$(Configuration)\TicketMachine\TicketMachine.winmd">
      <IsWinMDFile>true</IsWinMDFile>
    </Reference>
    <ReferenceCopyLocalPaths Include=
     "$(SolutionDir)\$(Configuration)\TicketMachine\TicketMachine.dll">
      <IsWinMDFile>false</IsWinMDFile>
    </ReferenceCopyLocalPaths>
  </ItemGroup>
</Project>

Image 3

We select the file we have created.

Image 4

Let's add a manifest file to the Win32 project: and modify it as follows:

XML
    <?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>

  <file name="TicketMachine.dll">
    <activatableClass
        name="TicketMachine.Service"
        threadingModel="both"
        xmlns="urn:schemas-microsoft-com:winrt.v1" />
        <activatableClass
        name="TicketMachine.Desk"
        threadingModel="both"
        xmlns="urn:schemas-microsoft-com:winrt.v1" />
    <activatableClass
        name="TicketMachine.Operator"
        threadingModel="both"
        xmlns="urn:schemas-microsoft-com:winrt.v1" />
    <activatableClass
        name="TicketMachine.Ticket"
        threadingModel="both"
        xmlns="urn:schemas-microsoft-com:winrt.v1" />
    <activatableClass
        name="TicketMachine.OperationsQueue"
        threadingModel="both"
        xmlns="urn:schemas-microsoft-com:winrt.v1" />
    <activatableClass
        name="TicketMachine.OperationsLog"
        threadingModel="both"
        xmlns="urn:schemas-microsoft-com:winrt.v1" />
  </file>

</assembly>

As we can see in the manifest file, we must set the name of the component file (.dll), the names of the classes that we want to load at runtime (defined in the .idl file of the component) and the threading model.

Open NuGet and add the two WinRT components:

Image 5

Save the Visual Studio solution, from the Project menu, go to Project Build Order..., make sure that the Win32 application is compiled after the WinRT component and recompile the solution.

As a result, we will have that the Win32 project now depends on the WinRT component, in addition, the cppwinrt tool.exe has produced for us the code that allows us to implement the types of the TicketMachine component. The files produced by the cppwinrt tool.exe we find them in the GeneratedFiles folder of the Win32 project.

Now you can, by adding the include file TicketMachine.h in our project, consume the component.

C++
#pragma once
#include "CWindow.h"
#include "MessageHandlerT.h"
............
............
#include "winrt/TicketMachine.h"

We define in the CFrameWindow class of the framework, (see part 3), the following members:

C++
    class CFrameWindow :
        public CWindow
    {

....................
....................
....................

private:
static winrt::TicketMachine::Ticket m_ticket;
static winrt::TicketMachine::OperationsQueue m_operationsQueue;
winrt::TicketMachine::OperationsLog m_operationsLog;
       
//C++/WinRT Component event handlers
winrt::event_token m_TicketAddedEventTokenArgs;
winrt::event_token m_OperationCompleteEventTokenArgs;

//Items in Queue collection
static std::vector <winrt::ticketmachine::operationsqueue> m_opQueue;

//Items Log collection
std::vector<winrt::ticketmachine::operationslog> m_opLog;

static void InitializeServices();
static void InitializeDesks();
static void InitializeOperators();

static bool AddOperationsQueue(static winrt::TicketMachine::OperationsQueue const& q, 
                               static winrt::TicketMachine::Ticket const& t);

public:
std::vector<winrt::ticketmachine::operationsqueue> GetOperationsQueue();
std::vector<winrt::ticketmachine::operationslog> GetOperationsLog();

static void CreateNewTicket();

    ....................
    ....................
    ....................

    };

In the constructor, we implement handlers for events:

C++
....................
....................
....................
....................

///OnStartQueue Event handler
m_TicketAddedEventTokenArgs = m_operationsQueue.OnQueueItemAdded([]
(const auto&, winrt::TicketMachine::StartQueueEventArgs args)
{
	bool isAdded = args.ItemAdded();

	if (isAdded)
	{		
		struct _tagOperationQueue
		{
			LPWSTR ticketNumber;
			LPWSTR serviceCode;
			LPWSTR serviceName;

			LPWSTR deskNumber;
			LPWSTR deskCode;
			LPWSTR deskName;
			
			LPWSTR ticketCreationDate;
			LPWSTR ticketBarcode;
			int32_t ticketMaxNumber;			
		};
		_tagOperationQueue OQ;
		OQ.serviceCode = (LPWSTR)m_operationsQueue.ServiceCode().c_str();
		OQ.serviceName = (LPWSTR)m_operationsQueue.ServiceName().c_str();

		OQ.deskNumber = (LPWSTR)m_operationsQueue.DeskNumber().c_str();
		OQ.deskCode = (LPWSTR)m_operationsQueue.DeskCode().c_str();
		OQ.deskName = (LPWSTR)m_operationsQueue.DeskName().c_str();


		OQ.ticketNumber = (LPWSTR)m_operationsQueue.TicketNumber().c_str();
		OQ.ticketBarcode = (LPWSTR)m_operationsQueue.TicketBarcode().c_str();
		OQ.ticketMaxNumber = m_operationsQueue.MaxQueueItems();
		OQ.ticketCreationDate = (LPWSTR)(m_operationsQueue.CreationDate().DateToString() + L" " +
			                             m_operationsQueue.CreationDate().TimeToString()).c_str();
		
		SendMessage((HWND)m_clientWnd->GetChildHandle(), WM_NEW_TICKET, 
                    (WPARAM)(pCFrameWnd->hwndFrame), reinterpret_cast<LPARAM>(&OQ));
    }
    });

    ///OnOperationCompleted Event handlers
    m_OperationCompleteEventTokenArgs = m_operationsQueue.OnOperationCompleted([]
    (const auto&, winrt::TicketMachine::CloseOperationEventArgs args)
    {
    bool isCompleted = args.OperationClosed();

    if (isCompleted)
    {
    SendMessage((HWND)m_clientWnd->GetChildHandle(), WM_OPERATION_CLOSED, 
               (WPARAM)(pCFrameWnd->hwndFrame), (LPARAM)(NULL));

    }

    });

    ....................
    ....................

    InitializeServices();

    InitializeDesks();

    InitializeOperators();
}

Implementation of member functions:

....................
....................
....................
....................

void Win32Framework::CFrameWindow::InitializeServices()
	{
		struct _tagServicesType
		{
			LPWSTR serviceCode;
			LPWSTR serviceName;
		};

		_tagServicesType SRV{ 0 };

		winrt::TicketMachine::Service srv;
		Collection<winrt::ticketmachine::service> _srv;

		auto _services = _srv.GetItems(srv, false);

		size_t index = 0;

		do {
			
			SRV.serviceCode = (LPWSTR)_services[index].ServiceCode().c_str();
			SRV.serviceName = (LPWSTR)_services[index].ServiceName().c_str();
			++index;

			SendMessage((HWND)m_clientWnd->GetChildHandle(), WM_INITIALIZE_SERVICES, 
                        (WPARAM)(pCFrameWnd->hwndFrame), reinterpret_cast<lparam>(&SRV));

		} while (index <= (_services.size() - 1));
	}

	void Win32Framework::CFrameWindow::InitializeDesks()
	{
		struct _tagDeskType
		{
			LPWSTR deskNumber;
			LPWSTR deskCode;			
			LPWSTR deskName;
		};

		_tagDeskType DSK{ 0 };

		winrt::TicketMachine::Desk dsk;
		Collection<;winrt::TicketMachine::Desk> _dsk;

		auto _desks = _dsk.GetItems(dsk, false);

		size_t index = 0;

		do {
			
			DSK.deskNumber = (LPWSTR)_desks[index].DeskNumber().c_str();
			DSK.deskCode = (LPWSTR)_desks[index].DeskCode().c_str();
			DSK.deskName = (LPWSTR)_desks[index].DeskName().c_str();
			++index;

			SendMessage((HWND)m_clientWnd->GetChildHandle(), WM_INITIALIZE_DESKS, 
                       (WPARAM)(pCFrameWnd->hwndFrame), reinterpret_cast<lparam>(&DSK));

		} while (index <= (_desks.size() - 1));
	}

	void Win32Framework::CFrameWindow::InitializeOperators()
	{
		struct _tagOperatorType
		{			
			LPWSTR firstName;
			LPWSTR lastName;
			LPWSTR badgeCode;
		};

		_tagOperatorType OPR{ 0 };

		winrt::TicketMachine::Operator opr;
		Collection<twinrt::TicketMachine::Operator> _opr;

		auto _operators = _opr.GetItems(opr, false);

		size_t index = 0;

		do {
				
			OPR.firstName = (LPWSTR)_operators[index].FirstName().c_str();
			OPR.lastName = (LPWSTR)_operators[index].LastName().c_str();
			OPR.badgeCode = (LPWSTR)_operators[index].BadgeCode().c_str();
			
			++index;

			SendMessage((HWND)m_clientWnd->GetChildHandle(), WM_INITIALIZE_OPERATORS, 
                       (WPARAM)(pCFrameWnd->hwndFrame), reinterpret_cast<LPARAM>(&OPR));

		} while (index <= (_operators.size() - 1));
	}

void Win32Framework::CFrameWindow::CreateNewTicket()
	{	
		m_ticket.Create();

		bool insElement = AddOperationsQueue(m_operationsQueue,m_ticket);

		if (insElement)
		{
			//Raise the insert new ticket in queue event
			m_operationsQueue.OperationItemAdded(true);
		}
}
	
....................
....................
....................
....................

The rest of the work is more dedicated to the creation of the GUI of the demo application which in any case I will not deal with here so as not to dwell and because it is out of context. When the work is finished, we will have the result shown at the top of the page.

History

  • 14th January, 2022: Initial version

License

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