Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Det här avsnittet visar en fallstudie av portning av ett av de Universal Windows Platform (UWP) appexemplen från C# till C++/WinRT. Du kan skaffa dig praktisk erfarenhet av portering genom att följa genomgången och själv portera exemplet allteftersom.
Note
Källkoden som portas är en UWP C#-app.
Målkoden C++/WinRT i den här artikeln är skriven för WinUI 3 (Windows App SDK). Oavsett var UWP-källan använder API:er som skiljer sig åt i WinUI 3 (till exempel Windows.UI.Core.CoreDispatcher jämfört med Microsoft.UI.Dispatching.DispatcherQueue), visar den här artikeln uttryckligen rätt WinUI 3-motsvarighet i C++/WinRT-utdata. Du kan använda kodmönstren från kolumnen C++/WinRT direkt i WinUI 3-appen.
En omfattande katalog med teknisk information som ingår i portning till C++/WinRT från C#finns i det tillhörande ämnet Flytta till C++/WinRT från C#.
Ett kort förord om C#- och C++-källkodsfiler
I ett C#-projekt är dina källkodsfiler främst .cs filer. När du flyttar till C++, kommer du att märka att det finns fler typer av källkodsfiler att vänja sig vid. Anledningen är att göra med skillnaden mellan kompilatorer, hur C++-källkod återanvänds och begreppen att deklarera och definiera en typ och dess funktioner (dess metoder).
En funktionsdeklaration beskriver bara funktionens signatur (dess returtyp, dess namn och dess parametertyper och namn). En funktionsdefinition innehåller funktionens brödtext (dess implementering).
Det är lite annorlunda när det gäller typer. Du definierar en typ genom att ange dess namn och genom att (minst) bara deklarera alla dess medlemsfunktioner (och andra medlemmar). Det stämmer att du kan definiera en typ även om du inte definierar dess medlemsfunktioner.
- Vanliga C++-källkodsfiler är
.h(dot aitch) och.cppfiler. En.hfil är en rubrikfil och definierar en eller flera typer. Du kan definiera medlemsfunktioner i en headerfil, men det är vanligtvis det som en.cpp-fil är till för. För en hypotetisk C++-typ av MyClass definierar du myclass iMyClass.hoch definierar dess medlemsfunktioner iMyClass.cpp. För att andra utvecklare ska kunna återanvända dina klasser delar du bara.hut filer och objektkod. Du skulle hålla dina.cppfiler hemliga eftersom implementeringen utgör din immateriella egendom. - Förkompilerad rubrik (
pch.h). Vanligtvis finns det en uppsättning huvudfiler som du inkluderar i ditt program, och du ändrar inte dessa filer så ofta. Så i stället för att bearbeta innehållet i den uppsättningen med rubriker varje gång du kompilerar kan du aggregera dessa rubriker till en fil, kompilera den en gång och sedan använda utdata från det förkompileringssteget varje gång du skapar. Det gör du via en fördefinierad rubrikfil (vanligtvis med namnetpch.h). -
.idlfiler. Dessa filer innehåller Gränssnittsdefinitionsspråk (IDL). Du kan betrakta IDL som headerfiler för Windows Runtime-typer. Vi pratar mer om IDL i avsnittet IDL för MainPage-typen.
Ladda ned och testa Urklippsexemplet
Gå till webbsidan för Clipboard-exemplet och klicka på Ladda ned ZIP. Packa upp den nedladdade filen och ta en titt på mappstrukturen.
- C#-versionen av exempelkällkoden finns i mappen med namnet
cs. - C++/WinRT-versionen av exempelkällkoden finns i mappen med namnet
cppwinrt. - Andra filer – som används av både C#-versionen och C++/WinRT-versionen – finns i mapparna
sharedochSharedContent.
Genomgången i det här avsnittet visar hur du kan återskapa C++/WinRT-versionen av Urklippsexemplet genom att portera det från C#-källkoden. På så sätt kan du se hur du kan portera dina egna C#-projekt till C++/WinRT.
För att få en känsla för vad exemplet gör öppnar du C#-lösningen (\Clipboard_sample\cs\Clipboard.sln), ändrar konfigurationen efter behov (kanske till x64), skapar och kör. Exemplets eget användargränssnitt (UI) vägleder dig genom dess olika funktioner, steg för steg.
Tip
Rotmappen för exemplet som du laddade ned kan ha namnet Clipboard i stället Clipboard_sampleför . Men vi fortsätter att referera till den mappen Clipboard_sample för att skilja den från den C++/WinRT-version som du kommer att skapa i ett senare steg.
Skapa en tom app med namnet Urklipp
Note
Information om hur du installerar och använder VSIX (C++/WinRT Visual Studio Extension) och NuGet-paketet (som tillsammans tillhandahåller projektmall och byggstöd) finns i Visual Studio support för C++/WinRT.
Påbörja portningsprocessen genom att skapa ett nytt C++/WinRT-projekt i Microsoft Visual Studio. Skapa ett nytt projekt med hjälp av projektmallen Tom app, Paketerad (WinUI 3 i Desktop) för C++. Ange dess namn till Urklipp och (så att mappstrukturen matchar genomgången) kontrollera att Placera lösning och projekt i samma katalog är avmarkerat.
Som utgångspunkt, kontrollera att det här nya, tomma projektet går att bygga och köra.
Package.appxmanifest, och resursfiler
Om C#- och C++/WinRT-versionerna av exemplet inte behöver installeras sida vid sida på samma dator kan de två projektens apppakets manifestkällfiler (Package.appxmanifest) vara identiska. I så fall kan du bara kopiera Package.appxmanifest från C#-projektet till C++/WinRT-projektet, så är du klar.
För att de två versionerna av exemplet ska samexistera behöver de olika identifierare. I så fall öppnar Package.appxmanifest du filen i en XML-redigerare i C++/WinRT-projektet och antecknar dessa tre värden.
- I elementet /Package/Identity noterar du värdet för attributet Namn . Det här är paketnamnet. För ett nyskapat projekt tilldelas projektet ett initialt värde i form av en unik GUID.
- I elementet /Package/Applications/Application noterar du värdet för ID-attributet . Det här är program-ID:t.
- I elementet /Package/mp:PhoneIdentity noterar du värdet för attributet PhoneProductId . För ett nyskapat projekt ställs detta återigen in på samma GUID som paketnamnet är inställt på.
Kopiera Package.appxmanifest sedan från C#-projektet till C++/WinRT-projektet. Slutligen kan du återställa de tre värden som du antecknade. Eller så kan du redigera de kopierade värdena för att göra dem unika och/eller lämpliga för programmet och för din organisation (som du vanligtvis skulle göra för ett nytt projekt). Till exempel kan vi i det här fallet, i stället för att återställa värdet för paketnamnet, helt enkelt ändra det kopierade värdet från Microsoft.SDKSamples.Clipboard.CS till Microsoft.SDKSamples.Clipboard.CppWinRT. Och vi kan lämna program-ID:t inställt på App. Så länge antingen paketnamnet eller program-ID:t skiljer sig har de två programmen olika programanvändarmodell-ID:t (AUMID). Och det är vad som krävs för att två appar ska installeras sida vid sida på samma dator.
I den här genomgången är det klokt att göra några andra ändringar i Package.appxmanifest. Det finns tre förekomster av textsträngen Clipboard C# Sample. Ändra det till C++/WinRT-exempel i Urklipp.
I C++/WinRT-projektet Package.appxmanifest är filen och projektet nu osynkroniserade med avseende på de tillgångsfiler som de refererar till. Åtgärda detta genom att först ta bort tillgångarna från C++/WinRT-projektet genom att välja alla filer i Assets mappen (i Prieskumník riešení i Visual Studio) och ta bort dem (välj Ta bort i dialogrutan).
C#-projektet refererar till tillgångsfiler från en delad mapp. Du kan göra samma sak i C++/WinRT-projektet, eller så kan du kopiera filerna som vi gör i den här genomgången.
Gå till mappen \Clipboard_sample\SharedContent\media. Välj de sju filer som C#-projektet innehåller (microsoft-sdk.png, , smalltile-sdk.pngsplash-sdk.png, squaretile-sdk.png, storelogo-sdk.png, tile-sdk.pngoch windows-sdk.png), kopiera dem och klistra in dem i \Clipboard\Clipboard\Assets mappen i det nya projektet.
Högerklicka på Assets mappen (i Prieskumník riešení i C++/WinRT-projektet) >Lägg till>befintligt objekt... och gå till \Clipboard\Clipboard\Assets. I filväljaren väljer du de sju filerna och klickar på Lägg till.
Package.appxmanifest är nu synkroniserad igen med projektets tillgångsfiler.
MainPage, inklusive de funktioner som konfigurerar exemplet
Urklippsexemplet, som alla Universal Windows Platform (UWP) appexempel, består av en samling scenarier som användaren kan gå igenom en i taget. Samlingen scenarier i ett visst exempel konfigureras i exemplets källkod. Varje scenario i samlingen är ett dataobjekt som lagrar en rubrik, samt typen av klass i projektet som implementerar scenariot.
Om du tittar i källkodsfilen i SampleConfiguration.cs C#-versionen av exemplet visas två klasser. Det mesta av konfigurationslogik finns i klassen MainPage , som är en partiell klass (den utgör en fullständig klass när den kombineras med markering i MainPage.xaml och imperativ kod i MainPage.xaml.cs). Den andra klassen i den här källkodsfilen är Scenario med dess egenskaper Title och ClassType .
Under de kommande underavsnitten ska vi titta på hur du portar MainPage och Scenario.
IDL för MainPage-typen
Nu ska vi börja det här avsnittet med att kort prata om Gränssnittsdefinitionsspråk (IDL) och hur det hjälper oss när vi programmerar med C++/WinRT. IDL är en typ av källkod som beskriver den anropsbara ytan för en Windows Runtime typ. Den anropsbara (eller offentliga) ytan av en typ projiceras ut i världen, så att typen kan användas. Den beräknade delen av typen står i kontrast till den faktiska interna implementeringen av typen, som naturligtvis inte är anropsbar och inte offentlig. Det är bara den beräknade delen som vi definierar i IDL.
Efter att ha skapat IDL-källkoden (i en .idl fil) kan du sedan kompilera IDL:t till maskinläsbara metadatafiler (kallas även Windows metadata). Dessa metadatafiler har tillägget .winmd, och här är några av deras användningsområden.
- En
.winmdkan beskriva Windows Runtime-typerna i en komponent. När du refererar till en Windows Runtime komponent (WRC) från ett programprojekt läser programprojektet Windows metadata som tillhör WRC (metadata kan finnas i en separat fil eller paketeras i samma fil som SJÄLVA WRC) så att du kan använda WRC-typerna inifrån programmet. - En
.winmdkan beskriva de Windows Runtime typerna i en del av ditt program så att de kan användas av en annan del av samma program. Till exempel en Windows Runtime-typ som används av en XAML-sida i samma app. - För att göra det enklare för dig att arbeta med Windows Runtime-typer (inbyggda eller från tredje part) använder C++/WinRT-byggsystemet
.winmd-filer för att generera omslutande typer som representerar de projicerade delarna av dessa Windows Runtime-typer. - För att göra det enklare för dig att implementera dina egna Windows Runtime typer omvandlar C++/WinRT-byggsystemet din IDL till en
.winmdfil och använder den sedan för att generera omslutningar för projektionen, samt för att basera implementeringen på (vi kommer att prata mer om dessa stubs senare i det här avsnittet).
Den specifika version av IDL som vi använder med C++/WinRT är Microsoft Interface Definition Language 3.0. I resten av det här avsnittet av ämnet ska vi granska C# MainPage-typen i detalj. Vi bestämmer vilka delar av den som ska ingå i projektionen av typen C++/WinRT MainPage (det vill säga i dess anropsbara eller offentliga yta) och som bara kan vara en del av implementeringen. Den skillnaden är viktig eftersom när vi kommer att skapa vår IDL (vilket vi ska göra i avsnittet efter den här) kommer vi bara att definiera de anropsbara delarna där.
C#-källkodsfilerna som tillsammans implementerar MainPage-typen är: MainPage.xaml (som vi kommer att porta snart, genom att kopiera den), MainPage.xaml.csoch SampleConfiguration.cs.
I C++/WinRT-versionen tar vi med vår MainPage-typ i källkodsfiler på ett liknande sätt. Vi tar logiken i MainPage.xaml.cs och översätter den till största delen till MainPage.h och MainPage.cpp. Och för logiken i SampleConfiguration.csöversätter vi det till SampleConfiguration.h och SampleConfiguration.cpp.
Klasserna i ett C#-Universal Windows Platform (UWP)-program är naturligtvis Windows Runtime typer. Men när du skapar en typ i ett C++/WinRT-program kan du välja om den typen är en Windows Runtime typ eller en vanlig C++-klass/struct/uppräkning.
Alla XAML-sidor i vårt projekt måste vara en Windows Runtime typ, så MainPage måste vara en Windows Runtime typ. I C++/WinRT-projektet är MainPage redan en Windows Runtime typ, så vi behöver inte ändra den aspekten av det. Närmare bestämt är det en runtime-klass.
- Mer information om huruvida du bör definiera en körningsklass för en viss typ finns i artikeln Skapa API:er med C++/WinRT.
- I C++/WinRT finns den interna implementeringen av en körningsklass och de planerade (offentliga) delarna av den i form av två olika klasser. Dessa kallas för implementeringstypen och den planerade typen. Du kan lära dig mer om dem i avsnittet som nämndes i föregående punkt, och även i Förbruka API:er med C++/WinRT.
- Om du vill ha mer information om anslutningen mellan körningsklasser och IDL (
.idlfiler) kan du läsa och följa med i avsnittet XAML-kontroller; binda till en C++/WinRT-egenskap. Det avsnittet går igenom processen för att skapa en ny runtimeklass, där det första steget är att lägga till ett nytt Midl File (.idl)-objekt i projektet.
För MainPage har vi faktiskt den nödvändiga MainPage.idl filen redan i C++/WinRT-projektet. Det beror på att projektmallen skapade den åt oss. Men senare i den här genomgången kommer vi att lägga till ytterligare .idl filer i projektet.
Vi kommer snart att se en lista över exakt vilken IDL vi behöver lägga till i den befintliga MainPage.idl filen. Innan dess måste vi resonera lite om vad som behöver finnas i IDL:n och vad som inte behöver det.
För att avgöra vilka medlemmar i MainPage som vi behöver deklarera i MainPage.idl (så att de blir en del av MainPage-körningsklassen) och som helt enkelt kan vara medlemmar i implementeringstypen MainPage ska vi göra en lista över medlemmarna i klassen C# MainPage. Vi hittar dessa medlemmar genom att titta i MainPage.xaml.cs och i SampleConfiguration.cs.
Vi hittar totalt tolv protected fält och private metoder. Och vi hittar följande public medlemmar.
- Standardkonstruktorn
MainPage(). - De statiska fälten Aktuell och FEATURE_NAME.
- Egenskaperna IsClipboardContentChangedEnabled och Scenarios.
- Metoderna BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications och NotifyUser.
Det är de public medlemmar som är kandidater för att deklarera i MainPage.idl. Så låt oss undersöka var och en och se om de behöver vara en del av MainPage-körningsklassen eller om de bara behöver vara en del av implementeringen.
- Standardkonstruktorn
MainPage(). För en XAML-sida är det normalt att deklarera en standardkonstruktor i sin IDL. På så sätt kan XAML UI-ramverket aktivera typen. - Det statiska fältet Current används från de enskilda XAML-sidorna för scenarierna för att komma åt applikationens instans av MainPage. Eftersom Current inte används för att samverka med XAML-ramverket (och det används inte heller i kompileringsenheter) kan vi reservera det så att det endast är medlem av implementeringstypen. Med dina egna projekt i fall som detta kan du välja att göra det. Men eftersom fältet är en instans av den projicerade typen känns det logiskt att deklarera det i IDL. Så det är vad vi ska göra här (och att göra det gör också koden något renare).
- Det är ett liknande fall för det statiska FEATURE_NAME-fältet , som används inom MainPage-typen . Om du väljer att deklarera den i IDL blir koden något renare.
- Egenskapen IsClipboardContentChangedEnabled används endast i klassen OtherScenarios . Så under porteringen förenklar vi det lite och gör det till ett privat fält i runtime-klassen OtherScenarios. För att den inte ska hamna i IDL.
- Egenskapen Scenarios är en samling av objekt av typen Scenario (en typ som vi nämnt tidigare). Vi ska prata om Scenario i nästa underavsnitt, så låt oss lämna egenskapen Scenarios tills dess också.
- Metoderna BuildClipboardFormatsOutputString, DisplayToast och EnableClipboardContentChangedNotifications är verktygsfunktioner som känns mer att göra med exemplets allmänna tillstånd än om huvudsidan. Så under porten omstrukturerar vi dessa tre metoder till en ny verktygstyp med namnet SampleState (som inte behöver vara en Windows Runtime typ). Av den anledningen kommer dessa tre metoder inte att gå i IDL.
- Metoden NotifyUser anropas från de enskilda XAML-sidorna för scenarierna på den instans av MainPage som returneras från det statiska fältet Current. Eftersom (som redan nämnts) Aktuell är en instans av den planerade typen måste vi deklarera NotifyUser i IDL. NotifyUser tar en parameter av typen NotifyType. Vi pratar om det i nästa underavsnitt.
Alla medlemmar som du vill databind till måste också deklareras i IDL (oavsett om du använder {x:Bind} eller {Binding}). Mer information finns i Databindning.
Vi gör framsteg: vi utvecklar en lista över vilka medlemmar som ska läggas till och vilka som inte ska läggas till i MainPage.idl filen. Men vi måste fortfarande diskutera egenskapen Scenarios och typen NotifyType . Så låt oss göra det härnäst.
IDL för typerna Scenario och NotifyType
Klassen Scenario definieras i SampleConfiguration.cs. Vi har ett beslut att fatta om hur du porterar den klassen till C++/WinRT. Som standard skulle vi förmodligen göra det till en vanlig C++ struct. Men om Scenario används mellan binärfiler eller för att samverka med XAML-ramverket måste det deklareras i IDL som en Windows Runtime typ.
När vi studerar C#-källkoden upptäcker vi att Scenario används i den här kontexten.
<ListBox x:Name="ScenarioControl" ... >
var itemCollection = new List<Scenario>();
int i = 1;
foreach (Scenario s in scenarios)
{
itemCollection.Add(new Scenario { Title = $"{i++}) {s.Title}", ClassType = s.ClassType });
}
ScenarioControl.ItemsSource = itemCollection;
En samling scenarioobjekt tilldelas egenskapen ItemsSource för en ListBox (som är en objektkontroll). Eftersom Scenariobehöver samverka med XAML måste det vara en Windows Runtime typ. Det måste därför definieras i IDL. Att definiera typen Scenario i IDL gör att C++/WinRT-byggsystemet genererar en källkodsdefinition av Scenario åt dig i en bakomliggande huvudfil (vars namn och plats inte är viktiga för den här genomgången).
Och du kommer ihåg att MainPage.Scenarios är en samling scenarioobjekt , som vi just har sagt måste finnas i IDL. Därför måste även MainPage.Scenarios deklareras i IDL.
NotifyType är en enum deklarerad i C#:s MainPage.xaml.cs. Eftersom vi skickar NotifyType till en metod som tillhör MainPage-körningsklassen måste NotifyType också vara en Windows Runtime typ och den måste definieras i .MainPage.idl
Nu ska vi lägga till de nya typerna och den nya medlemmen i Mainpage som vi har valt att deklarera i IDL i MainPage.idlfilen. Samtidigt tar vi bort platshållarmedlemmarna i Mainpage från IDL:n som Visual Studio-projektmallen gav oss.
Så, i ditt C++/WinRT-projekt öppnar du MainPage.idl och redigerar det så att det ser ut som i listan nedan. Observera att en av redigeringarna är att ändra namnområdesnamnet från Urklipp till SDKTemplate. Om du vill kan du bara ersätta hela innehållet i MainPage.idl med följande kod. En annan justering att notera är att vi ändrar namnet på Scenario::ClassType till Scenario::ClassName.
// MainPage.idl
namespace SDKTemplate
{
struct Scenario
{
String Title;
Microsoft.UI.Xaml.Interop.TypeName ClassName;
};
enum NotifyType
{
StatusMessage,
ErrorMessage
};
[default_interface]
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
MainPage();
static MainPage Current{ get; };
static String FEATURE_NAME{ get; };
static Windows.Foundation.Collections.IVector<Scenario> scenarios{ get; };
void NotifyUser(String strMessage, NotifyType type);
};
}
Note
Mer information om innehållet i en .idl fil i ett C++/WinRT-projekt finns i Microsoft Interface Definition Language 3.0.
Med ditt eget portningsarbete kanske du inte vill eller behöver ändra namnområdets namn som vi gjorde ovan. Vi gör det här bara för att standardnamnområdet för C#-projektet som vi porterar är SDKTemplate. medan namnet på projektet och sammansättningen är Urklipp.
Men allteftersom vi fortsätter med porteringen i den här genomgången kommer vi att ändra varje förekomst av namnområdesnamnet Clipboard i källkoden till SDKTemplate. Det finns också ett ställe i C++/WinRT-projektegenskaperna där namnet på namnområdet Clipboard förekommer, så vi tar tillfället i akt att ändra det nu.
I Visual Studio anger du värdet SDKTemplate för projektegenskapen >>Root Namespace för C++/WinRT-projektet.
Spara IDL och generera stub-filer igen
Ämnet XAML-kontroller; bind till en C++/WinRT-egenskap introducerar begreppet stub-filer och visar en genomgång av dem i praktiken. Vi nämnde även stubs tidigare i det här avsnittet när vi nämnde att C++/WinRT-byggsystemet omvandlar innehållet i dina .idl filer till Windows metadata, och sedan från dessa metadata genererar ett verktyg med namnet cppwinrt.exe stubs som du kan basera implementeringen på.
Varje gång du lägger till, tar bort eller ändrar något i din IDL och skapar uppdaterar byggsystemet stub-implementeringarna i dessa stubs-filer. Så varje gång du ändrar din IDL och skapar rekommenderar vi att du visar dessa stubs-filer, kopierar eventuella ändrade signaturer och klistrar in dem i projektet. Vi ger mer information och exempel på exakt hur du gör det om en stund. Men fördelen med att göra detta är att ge dig ett felfritt sätt att alltid veta vilken form din implementeringstyp ska vara och vad signaturen för dess metoder ska vara.
Nu i genomgången är vi klara med att redigera MainPage.idl filen för tillfället, så du bör spara den nu. Projektet går inte att bygga klart för tillfället, men det är ändå bra att köra en build nu eftersom det genererar om stubbfilerna för MainPage. Skapa projektet nu och ignorera eventuella byggfel.
För det här C++/WinRT-projektet genereras stub-filerna i \Clipboard\Clipboard\Generated Files\sources mappen. Du hittar dem där när den partiella byggprocessen har avslutats (återigen, som förväntat, kommer byggprocessen inte att lyckas helt. Men steget som vi är intresserade av – att generera stubbar – kommer att ha slutförts). Filerna vi är intresserade av är MainPage.h och MainPage.cpp.
I dessa två stub-filer ser du nya stub-implementeringar av medlemmarna i MainPage som vi har lagt till i IDL (aktuell och FEATURE_NAME, till exempel). Du vill kopiera de stub-implementeringarna till filerna MainPage.h och MainPage.cpp som redan finns i projektet. På samma sätt som vi gjorde med IDL tar vi bort platshållarmedlemmarna i Mainpage från de befintliga filerna som Visual Studio projektmallen gav oss (dummyegenskapen MyProperty och händelsehanteraren med namnet ClickHandler).
Faktum är att den enda medlemmen i den aktuella versionen av MainPage som vi vill behålla är konstruktorn.
När du har kopierat de nya medlemmarna från stub-filerna, tagit bort de medlemmar som vi inte vill ha och uppdaterat namnområdet MainPage.h bör filerna och MainPage.cpp i projektet se ut som kodlistorna nedan. Observera att det finns två MainPage-typer . En i implementeringsnamnområdet och en till i factory_implementation namnrymd. Den enda ändring vi har gjort i factory_implementation en är att lägga till SDKTemplate i dess namnområde.
// MainPage.h
#pragma once
#include "MainPage.g.h"
namespace winrt::SDKTemplate::implementation
{
struct MainPage : MainPageT<MainPage>
{
MainPage();
static SDKTemplate::MainPage Current();
static hstring FEATURE_NAME();
static Windows::Foundation::Collections::IVector<SDKTemplate::Scenario> scenarios();
void NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type);
};
}
namespace winrt::SDKTemplate::factory_implementation
{
struct MainPage : MainPageT<MainPage, implementation::MainPage>
{
};
}
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"
namespace winrt::SDKTemplate::implementation
{
MainPage::MainPage()
{
InitializeComponent();
}
SDKTemplate::MainPage MainPage::Current()
{
throw hresult_not_implemented();
}
hstring MainPage::FEATURE_NAME()
{
throw hresult_not_implemented();
}
Windows::Foundation::Collections::IVector<SDKTemplate::Scenario> MainPage::scenarios()
{
throw hresult_not_implemented();
}
void MainPage::NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
throw hresult_not_implemented();
}
}
För strängar använder C# System.String. Se metoden MainPage.NotifyUser för ett exempel. I vår IDL deklarerar vi en sträng med Sträng och när cppwinrt.exe verktyget genererar C++/WinRT-kod åt oss använder det typen winrt::hstring . Varje gång vi stöter på en sträng i C#-kod portar vi den till winrt::hstring. Mer information finns i Stränghantering i C++/WinRT.
En förklaring av parametrarna const& i metodsignaturerna finns i Parameteröverföring.
Uppdatera alla återstående namnområdesdeklarationer/referenser och skapa
Innan du skapar C++/WinRT-projektet letar du upp eventuella deklarationer av (och referenser till) Clipboard-namnområdet och ändrar dem till SDKTemplate.
-
MainPage.xamlochApp.xaml. Namnområdet visas i värdena för attributenx:Classochxmlns:local. -
App.idl. -
App.h. -
App.cpp. Det finns tvåusing namespace-direktiv (sök efter delsträngenusing namespace Clipboard) och två kvalificeringar av typen MainPage (sök efterClipboard::MainPage). De behöver förändras.
Eftersom vi tog bort händelsehanteraren från MainPage går du också till MainPage.xaml och tar bort knappelementet från markering.
Spara alla filer. Rensa lösningen (Skapa>en ren lösning) och skapa den sedan. Efter att ha följt alla ändringar hittills, precis som de skrivits, förväntas bygget lyckas.
Implementera MainPage-medlemmar som vi deklarerade i IDL
Konstruktorn, Current och FEATURE_NAME
Här är relevant kod (från C#-projektet) som vi behöver porta.
<!-- MainPage.xaml -->
...
<TextBlock x:Name="SampleTitle" ... />
...
// MainPage.xaml.cs
...
public sealed partial class MainPage : Page
{
public static MainPage Current;
public MainPage()
{
InitializeComponent();
Current = this;
SampleTitle.Text = FEATURE_NAME;
}
...
}
...
// SampleConfiguration.cs
...
public partial class MainPage : Page
{
public const string FEATURE_NAME = "Clipboard C# sample";
...
}
...
Snart kommer vi att återanvända MainPage.xaml i sin helhet (genom att kopiera den). För tillfället (nedan) lägger vi tillfälligt till ett TextBlock-element med lämpligt namn i MainPage.xaml C++/WinRT-projektet.
FEATURE_NAME är ett statiskt fält i MainPage (ett C#- const fält är i stort sett statiskt i sitt beteende), definierat i SampleConfiguration.cs. För C++/WinRT, i stället för ett (statiskt) fält, gör vi det till C++/WinRT-uttrycket för en skrivskyddad (statisk) egenskap. C++/WinRT-sättet att uttrycka en egenskaps getter är som en funktion som returnerar egenskapsvärdet och inte tar några parametrar (en accessor). Så blir C#-fältet FEATURE_NAME till den statiska åtkomstfunktionen FEATURE_NAME i C++/WinRT (i det här fallet, som returnerar strängliteralen).
För övrigt skulle vi göra samma sak om vi portade en skrivskyddad C#-egenskap. För en skrivbar egenskap i C# uttrycks en setter i C++/WinRT som en void-funktion som tar egenskapens värde som parameter (en mutator). I båda fall, om C#-fältet eller egenskapen är statisk, är C++/WinRT-åtkomstmetoden och/eller ändringsmetoden också det.
Current är ett statiskt fält (inte ett konstant) fält i MainPage. Återigen gör vi det C++/WinRT-uttryck som motsvarar detta till en skrivskyddad egenskap, och den gör vi åter statisk. Medan FEATURE_NAME är konstant, är Aktuell det inte. Så i C++/WinRT behöver vi ett bakgrundsfält, och vår accessor returnerar det. Så i C++/WinRT-projektet deklarerar vi i MainPage.h ett privat statiskt fält med namnet current, vi definierar/initierar aktuellt i MainPage.cpp (eftersom det har statisk lagringstid) och vi kommer åt det via en offentlig statisk accessor-funktion med namnet Current.
Konstruktorn själv utför ett par tilldelningar, som är enkla att porta.
I C++/WinRT-projektet lägger du till ett nytt Visual C++>Code>C++-filobjekt (.cpp) med namnet SampleConfiguration.cpp.
Redigera MainPage.xaml, MainPage.h, MainPage.cppoch SampleConfiguration.cpp för att matcha listorna nedan.
<!-- MainPage.xaml -->
...
<StackPanel ...>
<TextBlock x:Name="SampleTitle" />
</StackPanel>
...
// MainPage.h
...
namespace winrt::SDKTemplate::implementation
{
struct MainPage : MainPageT<MainPage>
{
...
static SDKTemplate::MainPage Current() { return current; }
...
private:
static SDKTemplate::MainPage current;
...
};
...
}
// MainPage.cpp
...
namespace winrt::SDKTemplate::implementation
{
SDKTemplate::MainPage MainPage::current{ nullptr };
MainPage::MainPage()
{
InitializeComponent();
MainPage::current = *this;
SampleTitle().Text(FEATURE_NAME());
}
...
}
// SampleConfiguration.cpp
#include "pch.h"
#include "MainPage.h"
using namespace winrt;
using namespace SDKTemplate;
hstring implementation::MainPage::FEATURE_NAME()
{
return L"Clipboard C++/WinRT Sample";
}
Se också till att ta bort befintliga funktionskroppar från MainPage.cpp för MainPage::Current() och MainPage::FEATURE_NAME(), eftersom vi nu definierar dessa metoder någon annanstans.
Som du ser deklareras MainPage::current som av typen SDKTemplate::MainPage, som är den projicerade typen. Det är inte av typen SDKTemplate::implementation::MainPage, som är implementeringstypen. Den projicerade typen är den som är utformad för att användas antingen inom projektet för samverkan med XAML eller över binärgränser. Implementeringstypen är det du använder för att implementera de funktioner som du har exponerat på din projicerade typ. Eftersom deklarationen av MainPage::current (i MainPage.h) visas inom implementeringsnamnområdet (winrt::SDKTemplate::implementation) skulle en okvalificerad MainPage ha hänvisat till implementeringstypen. Därför kvalificerar vi oss med SDKTemplate:: för att vara tydliga med att vi vill att MainPage::current ska vara en instans av den projicerade typen winrt::SDKTemplate::MainPage.
I konstruktorn finns det några punkter relaterade till MainPage::current = *this; som förtjänar en förklaring.
- När du använder pekaren
thisinuti en medlem av implementeringstypen är pekarenthisnaturligtvis en pekare till implementeringstypen. - Om du vill konvertera pekaren
thistill motsvarande projicerade typ avrefererar du den. Förutsatt att du genererar din implementeringstyp från IDL (som vi har här) har implementeringstypen en konverteringsoperator som konverteras till den planerade typen. Det är därför uppgiften här fungerar.
Mer information om dessa detaljer finns i Instansiera och returnera implementeringstyper och gränssnitt.
I konstruktorn finns SampleTitle().Text(FEATURE_NAME());också . Delen SampleTitle() är ett anrop till en enkel accessorfunktion med namnet SampleTitle, som returnerar den TextBlock som vi lade till i XAML. När du x:Name ett XAML-element genererar XAML-kompilatorn en åtkomstmetod åt dig som är uppkallad efter elementet. Delen .Text(...) anropar funktionen Text mutator i TextBlock-objektet som SampleTitle-accessorn returnerade. Och FEATURE_NAME() anropar vår statiska MainPage::FEATURE_NAME accessor-funktion för att returnera strängliteralen. Helt och hållet anger den kodraden egenskapen Text för TextBlock med namnet SampleTitle.
Observera att eftersom strängar i Windows Runtime använder breda tecken, lägger vi till bredteckenkodningsprefixet L framför en strängliteral när vi porterar den. Därför ändrar vi (till exempel) "en strängliteral" till L"a string literal". Se även Breda strängliteraler.
Scenarier
Här är den relevanta C#-koden som vi behöver porta.
// MainPage.xaml.cs
...
public sealed partial class MainPage : Page
{
...
public List<Scenario> Scenarios
{
get { return this.scenarios; }
}
...
}
...
// SampleConfiguration.cs
...
public partial class MainPage : Page
{
...
List<Scenario> scenarios = new List<Scenario>
{
new Scenario() { Title = "Copy and paste text", ClassType = typeof(CopyText) },
new Scenario() { Title = "Copy and paste an image", ClassType = typeof(CopyImage) },
new Scenario() { Title = "Copy and paste files", ClassType = typeof(CopyFiles) },
new Scenario() { Title = "Other Clipboard operations", ClassType = typeof(OtherScenarios) }
};
...
}
...
Från vår tidigare undersökning vet vi att den här samlingen av scenarioobjekt visas i en ListBox. I C++/WinRT finns det gränser för vilken typ av samling som vi kan tilldela egenskapen ItemsSource för en objektkontroll. Samlingen måste vara antingen en vektor eller en observerbar vektor, och dess element måste vara något av följande:
- Antingen runtime-klasser eller,
- IInspectable.
När det gäller IInspectable, om elementen inte själva är runtimeklasser, måste de vara av en typ som kan boxas och unboxas till och från IInspectable. Och det innebär att de måste vara Windows Runtime-typer (se Omvandla värden till och från IInspectable).
I den här fallstudien gjorde vi inte Scenario till en körningsklass. Det är dock fortfarande ett rimligt alternativ. Och det kommer att finnas fall i ditt eget portningsarbete där en runtime-klass definitivt kommer att vara det rätta valet. Om du till exempel behöver göra elementtypen observerbar (se XAML-kontroller, binda till en C++/WinRT-egenskap) eller om elementet behöver ha metoder av någon annan anledning, och det är mer än bara en uppsättning datamedlemmar.
Eftersom vi i den här genomgången inte använder en runtime-klass för typen Scenario, så måste vi tänka på boxning. Om vi hade gjort Scenario till en vanlig C++ struct, skulle vi inte kunna boxa det. Men vi deklarerade Scenario som struct i IDL, och därför kan vi boxa in det.
Vi måste välja mellan att boxa Scenario i förväg eller att vänta tills vi precis ska tilldela dem till ItemsSource och boxa dem precis när det behövs. Här följer några saker att tänka på när det gäller dessa två alternativ.
- Boxning i förväg. För det här alternativet är vår datamedlem en samling IInspectable som är redo att tilldelas till användargränssnittet. Vid initialisering paketerar vi in Scenario-objekten i den datamedlemmen. Vi behöver bara en kopia av den samlingen, men vi måste avboxa ett element varje gång vi behövde läsa dess fält.
- Boxning precis i tid. För det här alternativet är vår datamedlem en samling av Scenario. När det blir dags att tilldela i användargränssnittet paketerar vi Scenario-objekten från datamedlemmen i en ny samling av IInspectable. Vi kan läsa fälten för elementen i datamedlemmen utan att packa upp, men vi behöver två kopior av samlingen.
Som du kan se väger för- och nackdelarna för en liten samling som den här i stort sett upp varandra. Så för den här fallstudien går vi med alternativet just-in-time.
Medlemmen scenarios är ett fält i MainPage som definieras och initieras i SampleConfiguration.cs. Och Scenarios är en skrivskyddsegenskap på MainPage, definierad i MainPage.xaml.cs (och implementerad så att den helt enkelt returnerar fältet scenarios). Vi ska göra något liknande i C++/WinRT-projektet. men vi gör de två medlemmarna statiska (eftersom vi bara behöver en instans i programmet och så att vi kan komma åt dem utan att behöva en klassinstans). Och vi kallar dem scenariosInner respektive scenarios. Vi deklarerar scenariosInner i MainPage.h. Och eftersom den har statisk lagringstid definierar/initierar vi den i en .cpp fil (SampleConfiguration.cppi det här fallet).
Redigera MainPage.h och SampleConfiguration.cpp matcha listorna nedan.
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
static Windows::Foundation::Collections::IVector<Scenario> scenarios() { return scenariosInner; }
...
private:
static winrt::Windows::Foundation::Collections::IVector<Scenario> scenariosInner;
...
};
// SampleConfiguration.cpp
...
using namespace Windows::Foundation::Collections;
...
IVector<Scenario> implementation::MainPage::scenariosInner = winrt::single_threaded_observable_vector<Scenario>(
{
Scenario{ L"Copy and paste text", xaml_typename<SDKTemplate::CopyText>() },
Scenario{ L"Copy and paste an image", xaml_typename<SDKTemplate::CopyImage>() },
Scenario{ L"Copy and paste files", xaml_typename<SDKTemplate::CopyFiles>() },
Scenario{ L"History and roaming", xaml_typename<SDKTemplate::HistoryAndRoaming>() },
Scenario{ L"Other Clipboard operations", xaml_typename<SDKTemplate::OtherScenarios>() },
});
Se också till att ta bort den befintliga funktionstexten från MainPage.cpp för MainPage::scenarios(), eftersom vi nu definierar den metoden i huvudfilen.
Som du ser initierar vi i SampleConfiguration.cpp den statiska datamedlemmen scenariosInner genom att anropa en C++/WinRT-hjälpfunktion med namnet winrt::single_threaded_observable_vector. Den funktionen skapar ett nytt Windows Runtime samlingsobjekt åt oss och returnerar det som ett IObservableVector-gränssnitt. Eftersom samlingen i det här exemplet inte är observerbar (det behöver den inte vara, eftersom den inte lägger till eller tar bort element efter initieringen), kunde vi i stället ha valt att anropa winrt::single_threaded_vector. Den funktionen returnerar samlingen som ett IVector-gränssnitt .
Mer information om samlingar och bindning till dem finns i XAML-objektkontroller, bind till en C++/WinRT-samling och samlingar med C++/WinRT.
Initieringskoden som du precis har lagt till refererar till typer som ännu inte finns i projektet (till exempel winrt::SDKTemplate::CopyText. För att åtgärda detta ska vi gå vidare och lägga till fem nya tomma XAML-sidor i projektet.
Lägg till fem nya tomma XAML-sidor
Lägg till ett nytt Visual C++>Tom sida (C++/WinRT)-objekt i projektet (se till att det är objektmallen Tom sida (C++/WinRT) och inte Tom sida-mallen). Ge den namnet CopyText. Den nya XAML-sidan definieras i namnområdet SDKTemplate , vilket är vad vi vill ha.
Upprepa ovanstående process ytterligare fyra gånger och namnge XAML-sidorna CopyImage, , CopyFilesHistoryAndRoamingoch OtherScenarios.
Nu kan du skapa igen, om du vill.
NotifyUser
I C#-projektet hittar du implementeringen av metoden MainPage.NotifyUser i MainPage.xaml.cs.
MainPage.NotifyUser har ett beroende av MainPage.UpdateStatus, och den metoden har i sin tur beroenden för XAML-element som vi ännu inte har portat. Så tills vidare skapar vi bara en stubb för metoden UpdateStatus i C++/WinRT-projektet, och vi porterar den senare.
Här är den relevanta C#-koden som vi behöver porta.
// MainPage.xaml.cs
...
public void NotifyUser(string strMessage, NotifyType type)
if (Dispatcher.HasThreadAccess)
{
UpdateStatus(strMessage, type);
}
else
{
var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => UpdateStatus(strMessage, type));
}
private void UpdateStatus(string strMessage, NotifyType type) { ... }{
...
NotifyUser skickar uppdateringar av användargränssnittet till huvudtråden. I WinUI 3 används Microsoft. UI. Dispatching.DispatcherQueue i stället för den äldre CoreDispatcher. När du vill använda en typ från en Windows eller Microsoft namnområde i C++/WinRT måste du inkludera motsvarande C++/WinRT-namnområdeshuvudfil (mer information om detta finns i Kom igång med C++/WinRT). I det här fallet är rubriken winrt/Microsoft.UI.Dispatching.h, som du kommer att se i kodlistan nedan, och vi kommer att inkludera den i pch.h.
UpdateStatus är privat. Så vi ska göra det till en privat metod för vår MainPage-implementeringstyp . UpdateStatus är inte avsett att anropas i körningsklassen, så vi deklarerar den inte i IDL.
När du har portat MainPage.NotifyUser och tagit bort MainPage.UpdateStatus är det här vad vi har i C++/WinRT-projektet. Efter den här kodlistan ska vi undersöka en del av informationen.
// pch.h
...
#include <winrt/Microsoft.UI.Dispatching.h>
...
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
void NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type);
...
private:
void UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type);
...
};
// MainPage.cpp
...
void MainPage::NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
if (DispatcherQueue().HasThreadAccess())
{
UpdateStatus(strMessage, type);
}
else
{
DispatcherQueue().TryEnqueue([strMessage, type, this]()
{
UpdateStatus(strMessage, type);
});
}
}
void MainPage::UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
throw hresult_not_implemented();
}
...
I C# kan du använda punktnotation för att komma åt nästlade egenskaper. Därför kan typen C# MainPage komma åt sin egen Dispatcher-egenskap med syntaxen Dispatcher. Och C# kan ytterligare pricka in det värdet med syntax som Dispatcher.HasThreadAccess. I C++/WinRT implementeras egenskaper som accessorfunktioner, så syntaxen skiljer sig bara när du lägger till parenteser för varje funktionsanrop.
| C# | C++/WinRT |
|---|---|
Dispatcher.HasThreadAccess |
DispatcherQueue().HasThreadAccess() |
När C#-versionen av NotifyUseranropar Dispatcher.RunAsync använder WinUI 3-motsvarigheten DispatcherQueue.TryEnqueue. C++/WinRT-versionen implementerar återanropsdelegaten som en lambda-funktion. I C++/WinRT samlar vi in de två parametrar som vi ska använda, samt pekaren this (eftersom vi ska anropa en medlemsfunktion). Det finns mer information om hur du implementerar ombud som lambdas och kodexempel i avsnittet Hantera händelser med hjälp av ombud i C++/WinRT.
Implementera återstående MainPage-medlemmar
Nu ska vi göra en fullständig lista över medlemmarna i MainPage (implementerade över MainPage.xaml.cs och SampleConfiguration.cs) så att vi kan se vilka som vi har portat hittills och vilka som ännu inte har gjort det.
| Medlem | Access | Status |
|---|---|---|
| MainPage-konstruktor | public |
Porterad |
| Aktuell egenskap | public |
Porterad |
| FEATURE_NAME egenskap | public |
Porterad |
| Egenskapen IsClipboardContentChangedEnabled | public |
Ej påbörjad |
| Egenskap för scenarier | public |
Porterad |
| BuildClipboardFormatsOutputString metoden | public |
Ej påbörjad |
| DisplayToast-metod | public |
Ej påbörjad |
| EnableClipboardContentChangedNotifications-metoden | public |
Ej påbörjad |
| NotifyUser-metoden | public |
Porterad |
| OnNavigatedTo-metoden | protected |
Ej påbörjad |
| isApplicationWindowActive-fält | private |
Ej påbörjad |
| needToPrintClipboardFormat-fält | private |
Ej påbörjad |
| scenarier fält | private |
Porterad |
| Button_Click-metoden | private |
Ej påbörjad |
| DisplayChangedFormats metod | private |
Ej påbörjad |
| Footer_Click metod | private |
Ej påbörjad |
| HandleClipboardChanged-metod | private |
Ej påbörjad |
| OnClipboardChanged-metod | private |
Ej påbörjad |
| OnWindowActivated-metod | private |
Ej påbörjad |
| ScenarioControl_SelectionChanged-metoden | private |
Ej påbörjad |
| UpdateStatus-metod | private |
Utstubbad |
Vi kommer att prata om de ännu ej porterade medlemmarna i de närmaste underavsnitten.
Note
Då och då kommer vi att stöta på referenser i källkoden till UI-element i XAML-markering (i MainPage.xaml). När vi kommer till dessa referenser kommer vi tillfälligt att arbeta runt dem genom att lägga till enkla platshållarelement i XAML. På så sätt fortsätter projektet att byggas efter varje underavsnitt. Alternativet är att lösa referenserna genom att kopiera hela innehållet i MainPage.xaml från C#-projektet till C++/WinRT-projektet nu. Men om vi gör det dröjer det länge innan vi kan stanna till och bygga på nytt (vilket kan dölja eventuella stavfel eller andra misstag vi gör under tiden).
När vi är klara med att portera den imperativa koden för Klassen MainPage kopierar vi innehållet i XAML-filen och är övertygade om att projektet fortfarande kommer att byggas.
IsClipboardContentChangedEnabled
Det här är en get-set C#-egenskap som standard är false. Den är medlem i MainPage och definieras i SampleConfiguration.cs.
För C++/WinRT behöver vi en åtkomstfunktion, en mutatorfunktion och en underliggande datamedlem i form av ett fält. Eftersom IsClipboardContentChangedEnabled representerar tillståndet för ett av scenarierna i exemplet, i stället för tillståndet för Själva MainPage , skapar vi de nya medlemmarna med en ny verktygstyp som heter SampleState. Och vi implementerar det i vår SampleConfiguration.cpp källkodsfil och gör medlemmarna static (eftersom vi bara behöver en instans i programmet och så att vi kan komma åt dem utan att behöva en klassinstans).
Som komplement till vår SampleConfiguration.cpp i C++/WinRT-projektet lägger du till ett nytt objekt av typen Visual C++>Kod>Rubrikfil (.h) med namnet SampleConfiguration.h. Redigera SampleConfiguration.h och SampleConfiguration.cpp matcha listorna nedan.
// SampleConfiguration.h
#pragma once
#include "pch.h"
namespace winrt::SDKTemplate
{
struct SampleState
{
static bool IsClipboardContentChangedEnabled();
static void IsClipboardContentChangedEnabled(bool checked);
private:
static bool isClipboardContentChangedEnabled;
};
}
// SampleConfiguration.cpp
...
#include "SampleConfiguration.h"
...
bool SampleState::isClipboardContentChangedEnabled = false;
...
bool SampleState::IsClipboardContentChangedEnabled()
{
return isClipboardContentChangedEnabled;
}
void SampleState::IsClipboardContentChangedEnabled(bool checked)
{
if (isClipboardContentChangedEnabled != checked)
{
isClipboardContentChangedEnabled = checked;
}
}
Återigen måste ett fält med static lagring (till exempel SampleState::isClipboardContentChangedEnabled) definieras en gång i programmet och en .cpp fil är en bra plats för det (SampleConfiguration.cpp i det här fallet).
BuildClipboardFormatsOutputString
Den här metoden är en offentlig medlem i MainPage och definieras i SampleConfiguration.cs.
// SampleConfiguration.cs
...
public string BuildClipboardFormatsOutputString()
{
DataPackageView clipboardContent = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
StringBuilder output = new StringBuilder();
if (clipboardContent != null && clipboardContent.AvailableFormats.Count > 0)
{
output.Append("Available formats in the clipboard:");
foreach (var format in clipboardContent.AvailableFormats)
{
output.Append(Environment.NewLine + " * " + format);
}
}
else
{
output.Append("The clipboard is empty");
}
return output.ToString();
}
...
I C++/WinRT gör vi BuildClipboardFormatsOutputString till en offentlig statisk metod för SampleState. Vi kan göra det static eftersom det inte har åtkomst till några instansmedlemmar.
För att använda typerna Clipboard och DataPackageView i C++/WinRT behöver vi inkludera huvudfilen för namnrymden Windows i C++/WinRT, winrt/Windows.ApplicationModel.DataTransfer.h.
I C# är egenskapen DataPackageView.AvailableFormats en IReadOnlyList, så vi kan komma åt egenskapen Count för det. I C++/WinRT returnerar accessorfunktionen DataPackageView::AvailableFormats en IVectorView, som har en storleksåtkomstfunktion som vi kan anropa.
Om du vill porta användningen av C# System.Text.StringBuilder-typen använder vi standardtypen C++ std::wostringstream. Den typen är en utdataström för breda strängar (och för att kunna använda den sstream måste vi inkludera huvudfilen). I stället för att använda en Tilläggsmetod som du gör med en StringBuilder använder du infogningsoperatorn (<<) med en utdataström, till exempel wostringstream. Mer information finns i iostream-programmering och formatering av C++/WinRT-strängar.
C#-koden konstruerar en StringBuilder med nyckelordet new . I C# är objekt som standard referenstyper, deklarerade på heapen med new. I modern standard-C++ är objekt värdetyper som standard, deklarerade i stacken (utan att använda new). Så porterar vi StringBuilder output = new StringBuilder(); till C++/WinRT genom att helt enkelt använda std::wostringstream output;.
Nyckelordet C# var ber kompilatorn att härleda en typ. Du portar var till auto i C++/WinRT. Men i C++/WinRT finns det fall där (för att undvika kopior) du vill ha en referens till en uppskjuten (eller härledd) typ, och du uttrycker en lvalue-referens till en härledd typ med auto&. Det finns också fall där du vill ha en särskild typ av referens som binder korrekt oavsett om den initieras med en lvalue eller med ett rvalue. Och du uttrycker det med auto&&. Det är det formulär som du ser används i loopen for i den portade koden nedan. En introduktion till lvalues och rvalues finns i Värdekategorier och referenser till dem.
Redigera pch.h, SampleConfiguration.hoch SampleConfiguration.cpp för att matcha listorna nedan.
// pch.h
...
#include <sstream>
#include "winrt/Windows.ApplicationModel.DataTransfer.h"
...
// SampleConfiguration.h
...
struct SampleState
{
static hstring BuildClipboardFormatsOutputString();
...
}
...
// SampleConfiguration.cpp
...
using namespace Windows::ApplicationModel::DataTransfer;
...
hstring SampleState::BuildClipboardFormatsOutputString()
{
DataPackageView clipboardContent{ Clipboard::GetContent() };
std::wostringstream output;
if (clipboardContent && clipboardContent.AvailableFormats().Size() > 0)
{
output << L"Available formats in the clipboard:";
for (auto&& format : clipboardContent.AvailableFormats())
{
output << std::endl << L" * " << std::wstring_view(format);
}
}
else
{
output << L"The clipboard is empty";
}
return hstring{ output.str() };
}
Note
Syntaxen i kodraden DataPackageView clipboardContent{ Clipboard::GetContent() }; använder en funktion i den moderna standarden C++ som kallas enhetlig initiering, med dess karakteristiska användning av klammerparenteser i stället för ett = tecken. Den syntaxen gör det tydligt att initiering snarare än tilldelning sker. Om du föredrar den form av syntax som ser ut som tilldelning (men faktiskt inte är det) kan du ersätta syntaxen ovan med motsvarande DataPackageView clipboardContent = Clipboard::GetContent();. Det är dock en bra idé att bli bekväm med båda sätten att uttrycka initiering, eftersom du sannolikt kommer att se båda används ofta i koden du stöter på.
DisplayToast
DisplayToast är en offentlig statisk metod för klassen C# MainPage och du hittar den definierad i SampleConfiguration.cs. I C++/WinRT gör vi det till en offentlig statisk metod för SampleState.
Vi har redan gått igenom de flesta detaljer och tekniker som är relevanta vid portering av den här metoden. Ett nytt objekt att notera är att du porterar en C#-ordagrann strängliteral (@) till en standardliteral för C++ -råsträng (LR).
När du refererar till typerna ToastNotification och XmlDocument i C++/WinRT kan du antingen kvalificera dem efter namnområdesnamn eller redigera SampleConfiguration.cpp och lägga till using namespace direktiv som i följande exempel.
using namespace Windows::UI::Notifications;
Du har samma val när du hänvisar till typen XmlDocument och varje gång du hänvisar till någon annan Windows Runtime-typ.
Förutom dessa objekt följer du bara samma vägledning som du gjorde tidigare för att utföra följande steg.
- Deklarera metoden i
SampleConfiguration.hoch definiera den iSampleConfiguration.cpp. - Redigera
pch.hför att inkludera nödvändiga C++/WinRT-Windows namnområdeshuvudfiler. - Skapa C++/WinRT-objekt på stacken, inte på heapen.
- Ersätt anrop till get-åtkomstmetoder för egenskaper med funktionsanropssyntax (
()).
En mycket vanlig orsak till kompilator-/länkfel är att glömma att inkludera C++/WinRT-Windows namnområdeshuvudfiler som du behöver. Mer information om ett möjligt fel finns i C3779: Varför ger kompilatorn mig felet "consume_Something: funktion som returnerar "auto" kan inte användas innan den definieras?.
Om du vill följa genomgången och portera DisplayToast själv kan du jämföra dina resultat med koden i C++/WinRT-versionen i ZIP-filen med källkoden till Clipboard-exemplet som du laddade ned.
Aktivera aviseringar om ändrat urklippsinnehåll
EnableClipboardContentChangedNotifications är en offentlig statisk metod i klassen C# MainPage och definieras i SampleConfiguration.cs.
// SampleConfiguration.cs
...
public bool EnableClipboardContentChangedNotifications(bool enable)
{
if (IsClipboardContentChangedEnabled == enable)
{
return false;
}
IsClipboardContentChangedEnabled = enable;
if (enable)
{
Clipboard.ContentChanged += OnClipboardChanged;
Window.Current.Activated += OnWindowActivated;
}
else
{
Clipboard.ContentChanged -= OnClipboardChanged;
Window.Current.Activated -= OnWindowActivated;
}
return true;
}
...
private void OnClipboardChanged(object sender, object e) { ... }
private void OnWindowActivated(object sender, WindowActivatedEventArgs e) { ... }
...
I C++/WinRT gör vi det till en offentlig statisk metod för SampleState.
I C# använder du operatorsyntaxen += och -= för att registrera och återkalla ombud för händelsehantering. I C++/WinRT har du flera syntaktiska alternativ för att registrera/återkalla ett ombud, enligt beskrivningen i Hantera händelser med hjälp av ombud i C++/WinRT. Men i allmänhet går det till så att du registrerar och avregistrerar med anrop till ett par funktioner som är uppkallade efter händelsen. För att registrera skickar du din delegat till registreringsfunktionen och får en återkallelsetoken tillbaka (en winrt::event_token). Om du vill återkalla skickar du den token till återkallningsfunktionen. I det här fallet är handern statisk och (som du kan se i följande kodlista) är funktionsanropssyntaxen enkel.
Liknande token används faktiskt, i bakgrunden, i C#. Men språket gör den informationen implicit. C++/WinRT gör det explicit.
Objekttypen visas i C#-händelsehanterarsignaturerna. På C#-språket är objektet ett alias för .NET System.Object-typen. Motsvarigheten i C++/WinRT är winrt::Windows::Foundation::IInspectable. Så du ser IInspectable i C++/WinRT-händelsehanterare.
Redigera SampleConfiguration.h och SampleConfiguration.cpp matcha listorna nedan.
// SampleConfiguration.h
...
static bool EnableClipboardContentChangedNotifications(bool enable);
...
private:
...
static event_token clipboardContentChangedToken;
static event_token activatedToken;
static void OnClipboardChanged(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
static void OnWindowActivated(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::WindowActivatedEventArgs const& e);
...
// SampleConfiguration.cpp
...
using namespace Windows::Foundation;
using namespace Microsoft::UI;
using namespace Microsoft::UI::Xaml;
...
event_token SampleState::clipboardContentChangedToken;
event_token SampleState::activatedToken;
...
bool SampleState::EnableClipboardContentChangedNotifications(bool enable)
{
if (isClipboardContentChangedEnabled == enable)
{
return false;
}
IsClipboardContentChangedEnabled(enable);
if (enable)
{
clipboardContentChangedToken = Clipboard::ContentChanged(OnClipboardChanged);
activatedToken = Window::Current().Activated(OnWindowActivated);
}
else
{
Clipboard::ContentChanged(clipboardContentChangedToken);
Window::Current().Activated(activatedToken);
}
return true;
}
void SampleState::OnClipboardChanged(IInspectable const&, IInspectable const&){}
void SampleState::OnWindowActivated(IInspectable const&, WindowActivatedEventArgs const& e){}
Låt själva ombuden för händelsehantering (OnClipboardChanged och OnWindowActivated) vara stubs för tillfället. De finns redan på vår lista över medlemmar till port, så vi kommer till dem i senare underavsnitt.
OnNavigatedTo
OnNavigatedTo är en skyddad metod för klassen C# MainPage och definieras i MainPage.xaml.cs. Här är det, tillsammans med XAML ListBox som den refererar till.
<!-- MainPage.xaml -->
...
<ListBox x:Name="ScenarioControl" ... />
...
// MainPage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Populate the scenario list from the SampleConfiguration.cs file
var itemCollection = new List<Scenario>();
int i = 1;
foreach (Scenario s in scenarios)
{
itemCollection.Add(new Scenario { Title = $"{i++}) {s.Title}", ClassType = s.ClassType });
}
ScenarioControl.ItemsSource = itemCollection;
if (Window.Current.Bounds.Width < 640)
{
ScenarioControl.SelectedIndex = -1;
}
else
{
ScenarioControl.SelectedIndex = 0;
}
}
Det är en viktig och intressant metod, eftersom det är här vår samling scenarioobjekt tilldelas användargränssnittet. C#-koden skapar en System.Collections.Generic.List med Scenario-objekt och tilldelar den till egenskapen ItemsSource hos en ListBox (som är en kontroll för objektsamlingar). Och i C# använder vi stränginterpolation för att skapa rubriken för varje scenarioobjekt (observera användningen av $ specialtecknet).
I C++/WinRT gör vi OnNavigatedTo till en publik metod i MainPage. Och vi lägger till ett tomt ListBox-element i XAML-koden så att kompileringen lyckas. Efter kodexemplet går vi igenom några av detaljerna.
<!-- MainPage.xaml -->
...
<StackPanel ...>
...
<ListBox x:Name="ScenarioControl" />
</StackPanel>
...
// MainPage.h
...
void OnNavigatedTo(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
...
// MainPage.cpp
...
using namespace winrt::Microsoft::UI::Xaml;
using namespace winrt::Microsoft::UI::Xaml::Navigation;
...
void MainPage::OnNavigatedTo(NavigationEventArgs const& /* e */)
{
auto itemCollection = winrt::single_threaded_observable_vector<IInspectable>();
int i = 1;
for (auto s : MainPage::scenarios())
{
s.Title = winrt::to_hstring(i++) + L") " + s.Title;
itemCollection.Append(winrt::box_value(s));
}
ScenarioControl().ItemsSource(itemCollection);
if (Window::Current().Bounds().Width < 640)
{
ScenarioControl().SelectedIndex(-1);
}
else
{
ScenarioControl().SelectedIndex(0);
}
}
...
Återigen anropar vi funktionen winrt::single_threaded_observable_vector, men den här gången för att skapa en samling IInspectable. Det var en del av det beslut vi tog att utföra boxningen av våra scenarioobjekt på just-in-time-basis.
Och i stället för C#:s användning av stränginterpolation här använder vi en kombination av funktionen to_hstring och sammanfogningsoperatornför winrt::hstring.
isApplicationWindowActive
I C#är isApplicationWindowActive ett enkelt privat bool fält som tillhör klassen MainPage och definieras i SampleConfiguration.cs. Standardvärdet är false. I C++/WinRT gör vi det till ett privat statiskt fält i SampleState (av de skäl som vi redan har beskrivit) i SampleConfiguration.h filerna och SampleConfiguration.cpp med samma standard.
Vi har redan sett hur du deklarerar, definierar och initierar ett statiskt fält. För en uppdatering kan du se tillbaka på vad vi gjorde med fältet isClipboardContentChangedEnabled och göra samma sak med isApplicationWindowActive.
needToPrintClipboardFormat
Samma mönster som isApplicationWindowActive (se rubriken direkt före den här).
Button_Click
Button_Click är en privat metod (händelsehantering) i klassen C# MainPage och definieras i MainPage.xaml.cs. Här visas den, tillsammans med XAML-SplitView som den hänvisar till, och den ToggleButton som den är registrerad med.
<!-- MainPage.xaml -->
...
<SplitView x:Name="Splitter" ... />
...
<ToggleButton Click="Button_Click" .../>
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Splitter.IsPaneOpen = !Splitter.IsPaneOpen;
}
Och här är motsvarigheten, portad till C++/WinRT. Observera att i C++/WinRT-versionen är public händelsehanteraren (som du kan se deklarerar du den före deklarationerna private:). Det beror på att en händelsehanterare som är registrerad i XAML-markering, som den här är, måste vara public i C++/WinRT för att XAML-markering ska få åtkomst till den. Om du å andra sidan registrerar en händelsehanterare i imperativ kod (som vi gjorde i MainPage::EnableClipboardContentChangedNotifications tidigare) behöver händelsehanteraren inte vara public.
<!-- MainPage.xaml -->
...
<StackPanel ...>
...
<SplitView x:Name="Splitter" />
</StackPanel>
...
// MainPage.h
...
void Button_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
...
// MainPage.cpp
void MainPage::Button_Click(Windows::Foundation::IInspectable const& /* sender */, Microsoft::UI::Xaml::RoutedEventArgs const& /* e */)
{
Splitter().IsPaneOpen(!Splitter().IsPaneOpen());
}
DisplayChangedFormats
I C#är DisplayChangedFormats en privat metod som tillhör klassen MainPage och definieras i SampleConfiguration.cs.
private void DisplayChangedFormats()
{
string output = "Clipboard content has changed!" + Environment.NewLine;
output += BuildClipboardFormatsOutputString();
NotifyUser(output, NotifyType.StatusMessage);
}
I C++/WinRT gör vi det till ett privat statiskt fält i SampleState (det har inte åtkomst till några instansmedlemmar) i SampleConfiguration.h filerna och SampleConfiguration.cpp . C#-koden för den här metoden använder inte System.Text.StringBuilder; men det gör tillräckligt med strängformatering att för C ++/WinRT-versionen är detta ett annat bra ställe att använda std::wostringstream.
I stället för den statiska egenskapen System.Environment.NewLine , som används i C#-koden, infogar vi standard-C++ std::endl (ett nytt radtecken) i utdataströmmen.
// SampleConfiguration.h
...
private:
static void DisplayChangedFormats();
...
// SampleConfiguration.cpp
void SampleState::DisplayChangedFormats()
{
std::wostringstream output;
output << L"Clipboard content has changed!" << std::endl;
output << BuildClipboardFormatsOutputString().c_str();
MainPage::Current().NotifyUser(output.str(), NotifyType::StatusMessage);
}
Det finns en liten ineffektivitet i utformningen av C++/WinRT-versionen ovan. Först skapar vi en std::wostringstream. Men vi anropar också metoden BuildClipboardFormatsOutputString (som vi portade tidigare). Den metoden skapar sin egen std::wostringstream. Och den förvandlar sin ström till en winrt::hstring och returnerar det. Vi anropar funktionen hstring::c_str för att omvandla den som returnerade hstring tillbaka till en C-sträng och sedan infogar vi den i vår ström. Det skulle vara effektivare att bara skapa en std::wostringstream och skicka (en referens till) runt, så att metoderna kan infoga strängar i den direkt.
Det är vad vi gör i C++/WinRT-versionen av Urklipps exempelkällkod (i zip-filen som du laddade ned). I den källkoden finns det en ny privat statisk metod med namnet SampleState::AddClipboardFormatsOutputString, som tar och fungerar på en referens till en utdataström. Och sedan omstruktureras metoderna SampleState::D isplayChangedFormats och SampleState::BuildClipboardFormatsOutputString för att anropa den nya metoden. Det är funktionellt likvärdigt med kodlistorna i det här avsnittet, men det är mer effektivt.
Footer_Click
Footer_Click är en asynkron händelsehanterare som tillhör klassen C# MainPage och definieras i MainPage.xaml.cs. Kodlistan nedan är funktionellt likvärdig med metoden i källkoden som du laddade ned. Men här har jag brutit ut den från en rad till fyra rader, så att det blir lättare att se vad den gör och därmed hur vi ska portera den.
async void Footer_Click(object sender, RoutedEventArgs e)
{
var hyperlinkButton = (HyperlinkButton)sender;
string tagUrl = hyperlinkButton.Tag.ToString();
Uri uri = new Uri(tagUrl);
await Windows.System.Launcher.LaunchUriAsync(uri);
}
Även om metoden tekniskt sett är asynkron, gör den ingenting efter await, så den behöver await inte (eller nyckelordet async ). Det använder förmodligen dem för att undvika IntelliSense-meddelandet i Visual Studio.
Motsvarande C++/WinRT-metod är också asynkron (eftersom den anropar Launcher.LaunchUriAsync). Men det behöver varken co_await eller returnera ett asynkront objekt. Information om co_await och asynkrona objekt finns i Samtidighet och asynkrona åtgärder med C++/WinRT.
Nu ska vi prata om vad metoden gör. Eftersom det här är en händelsehanterare för click-händelsen för en Hyperlänkknapp är objektet med namnet avsändaren i själva verket en Hyperlänkknapp. Därför är typkonverteringen säker (vi kan också ha uttryckt den här konverteringen som sender as HyperlinkButton). Därefter hämtar vi värdet för egenskapen Tag (om du tittar på XAML-markeringen i C#-projektet ser du att detta är inställt på en sträng som representerar en webb-URL). Även om egenskapen FrameworkElement.Tag (Hyperlänkbutton är ett FrameworkElement) är av typen objekt, kan vi i C# stringifiera den med Object.ToString. Från den resulterande strängen skapar vi ett URI-objekt . Och slutligen (med hjälp av Shell) startar vi en webbläsare och navigerar till URL:en.
Här är metoden som portats till C++/WinRT (återigen expanderad för tydlighetens skull), varefter är en beskrivning av informationen.
// pch.h
...
#include "winrt/Windows.System.h"
...
// MainPage.h
...
void Footer_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
...
// MainPage.cpp
...
using namespace winrt::Windows::Foundation;
using namespace winrt::Microsoft::UI::Xaml::Controls;
...
void MainPage::Footer_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const&)
{
auto hyperlinkButton{ sender.as<HyperlinkButton>() };
hstring tagUrl{ winrt::unbox_value<hstring>(hyperlinkButton.Tag()) };
Uri uri{ tagUrl };
Windows::System::Launcher::LaunchUriAsync(uri);
}
Som alltid skapar vi händelsehanteraren public. Vi använder funktionen as på avsändarobjektet för att konvertera den till HyperlänkKnapp. I C++/WinRT är egenskapen Tag en IInspectable (motsvarigheten till Object). Men det finns ingen Tostring på IInspectable. I stället måste vi packa upp IInspectable till ett skalärt värde (en sträng, i det här fallet). Mer information om boxning och avboxning finns i Boxnings- och avboxningsvärden till IInspectable.
De två sista raderna upprepar portningsmönster som vi har sett tidigare, och de ekar i stort sett C#-versionen.
HandleClipboardChanged
Det finns inget nytt med att portera den här metoden. Du kan jämföra C#- och C++/WinRT-versionerna i zip-filen i Urklipps exempelkällkod som du laddade ned.
OnClipboardChanged och OnWindowActivated
Hittills har vi bara tomma stubs för dessa två händelsehanterare. Men att portera dem är enkelt, och det ger inte upphov till något nytt att diskutera.
ScenarioControl_SelectionChanged
Det här är en annan privat händelsehanterare som tillhör klassen C# MainPage och som definieras i MainPage.xaml.cs. I C++/WinRT gör vi det offentligt och implementerar det i MainPage.h och MainPage.cpp.
För den här metoden behöver vi MainPage::navigate, som är ett privat booleskt fält, initierat till false. Och du behöver en ram i MainPage.xamlmed namnet ScenarioFrame. Men förutom dessa detaljer avslöjar portning av den här metoden inga nya tekniker.
Om du, i stället för att portera för hand, kopierar kod från C++/WinRT-versionen i ZIP-filen med källkoden till Clipboard sample som du laddade ned, kommer du att se att MainPage::NavigateTo används där. Just nu behöver du bara omstrukturera innehållet i NavigateTo till ScenarioControl_SelectionChanged.
UpdateStatus
Vi har bara en stub hittills för MainPage.UpdateStatus. Att porta dess implementering täcker återigen till stor del sådant som redan är känt. En ny punkt att notera är att i C# kan vi jämföra en sträng med String.Empty, I C++/WinRT anropar vi i stället funktionen winrt::hstring::empty . En annan är att nullptr är standard-C++-motsvarigheten till C#:s null.
Du kan genomföra resten av porteringen med hjälp av tekniker som vi redan har gått igenom. Här är en lista över de typer av saker du behöver göra innan den portade versionen av den här metoden kompileras.
- Lägg till en Border med namnet StatusBorder i
MainPage.xaml. - I
MainPage.xamllägger du till en TextBlock med namnet StatusBlock. - Lägg till en StackPanel med namnet StatusPanel i
MainPage.xaml. - I
pch.hlägger du till#include "winrt/Microsoft.UI.Xaml.Media.h". - I
pch.hlägger du till#include "winrt/Microsoft.UI.Xaml.Automation.Peers.h". - Så här lägger du
MainPage.cpptillusing namespace winrt::Microsoft::UI::Xaml::Media;. - Så här lägger du
MainPage.cpptillusing namespace winrt::Microsoft::UI::Xaml::Automation::Peers;.
Kopiera XAML och de format som krävs för att slutföra portningen av MainPage
För XAML är det idealiska fallet att du kan använda samma XAML-markering i ett C# och ett C++/WinRT-projekt. Och exemplet Clipboard är ett sådant fall.
I filen Styles.xaml har Urklippsexemplet en XAML ResourceDictionary med formatmallar som tillämpas på knappar, menyer och andra gränssnittselement i programmets användargränssnitt. Sidan Styles.xaml sammanfogas till App.xaml. Och sedan har vi standardstartpunkten MainPage.xaml för användargränssnittet, som vi redan har sett en kort stund. Nu kan vi återanvända dessa tre .xaml filer, oförändrade, i C++/WinRT-versionen av projektet.
Precis som med tillgångsfiler kan du välja att referera till samma delade XAML-filer från flera versioner av ditt program. I den här genomgången kopierar vi filer till C++/WinRT-projektet för enkelhetens skull och lägger till dem på det sättet.
Gå till \Clipboard_sample\SharedContent\xaml mappen, välj och kopiera App.xaml och MainPage.xamloch klistra sedan in de två filerna i \Clipboard\Clipboard mappen i C++/WinRT-projektet och välj att ersätta filer när du uppmanas att göra det.
I C++/WinRT-projektet i Visual Studio klickar du på Visa alla filer för att aktivera det. Lägg nu till en ny mapp direkt under projektnoden och ge den Stylesnamnet . I Utforskaren \Clipboard_sample\SharedContent\xaml navigerar du till mappen, väljer och kopierar Styles.xamloch klistrar in den i mappen \Clipboard\Clipboard\Styles som du nyss skapade. Tillbaka i Prieskumník riešení i C++/WinRT-projektet högerklickar du på Styles mappen >Lägg till>befintligt objekt... och navigerar till \Clipboard\Clipboard\Styles. I filväljaren väljer du Styles och klickar på Lägg till.
Lägg till en ny mapp i C++/WinRT-projektet, direkt under projektnoden och med namnet Styles. Gå till \Clipboard_sample\SharedContent\xaml mappen, välj och kopiera Styles.xamloch klistra in den \Clipboard\Clipboard\Styles i mappen i ditt C++/WinRT-projekt. Högerklicka på Styles mappen (i Prieskumník riešení i C++/WinRT-projektet) >Lägg till>befintligt objekt... och gå till \Clipboard\Clipboard\Styles. I filväljaren väljer du Styles och klickar på Lägg till.
Klicka på Visa alla filer igen för att inaktivera det.
Nu har vi portat MainPage och om du har följt stegen kommer ditt C++/WinRT-projekt nu att bygga och köra.
Konsolidera dina .idl filer
Förutom standardstartpunkten MainPage.xaml för användargränssnittet har Urklippsexemplet fem andra scenariospecifika XAML-sidor, tillsammans med motsvarande kod bakom filer. Vi kommer att återanvända den faktiska XAML-markeringen för alla dessa sidor, oförändrat, i C++/WinRT-versionen av projektet. Och vi ska titta på hur du portar koden bakom i de kommande huvudavsnitten. Men innan det ska vi prata om IDL.
Det är värdefullt att samla IDL:en för dina runtime-klasser i en enda IDL-fil. Mer information om det värdet finns i Att dela upp runtime-klasser i Midl-filer (.idl). Sedan konsoliderar vi innehållet i CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idloch OtherScenarios.idl genom att flytta den IDL:en till en enda fil med namnet Project.idl (och sedan ta bort de ursprungliga filerna).
Vi gör det, men vi tar även bort den automatiskt genererade dummyegenskapen (Int32 MyProperty;och dess implementering) från var och en av dessa fem XAML-sidtyper.
Lägg först till ett nytt Midl File-objekt (.idl) i C++/WinRT-projektet. Ge den namnet Project.idl. Ersätt hela innehållet i Project.idl med följande kod.
// Project.idl
namespace SDKTemplate
{
[default_interface]
runtimeclass CopyFiles : Microsoft.UI.Xaml.Controls.Page
{
CopyFiles();
}
[default_interface]
runtimeclass CopyImage : Microsoft.UI.Xaml.Controls.Page
{
CopyImage();
}
[default_interface]
runtimeclass CopyText : Microsoft.UI.Xaml.Controls.Page
{
CopyText();
}
[default_interface]
runtimeclass HistoryAndRoaming : Microsoft.UI.Xaml.Controls.Page
{
HistoryAndRoaming();
}
[default_interface]
runtimeclass OtherScenarios : Microsoft.UI.Xaml.Controls.Page
{
OtherScenarios();
}
}
Som du ser, är det bara en kopia av innehållet i de enskilda .idl-filerna, allt inom ett och samma namnområde och med MyProperty borttaget från varje körningsklass.
I Prieskumník riešení i Visual Studio väljer du alla ursprungliga IDL-filer (CopyFiles.idl, , CopyImage.idlCopyText.idl, HistoryAndRoaming.idloch ) och OtherScenarios.idlRedigera>Ta bort dem (välj Ta bort i dialogrutan).
Slutligen – och för att fullborda borttagningen av MyProperty – tar du i filerna .h och .cpp för var och en av samma fem XAML-sidtyper bort deklarationerna och definitionerna för åtkomstfunktionen int32_t MyProperty() och mutatorfunktionen void MyProperty(int32_t).
För övrigt är det alltid en bra idé att låta namnet på dina XAML-filer matcha namnet på den klass som de representerar. Om du till exempel har x:Class="MyNamespace.MyPage" i en XAML-markeringsfil ska den filen ha namnet MyPage.xaml. Detta är inte ett tekniskt krav, men om du inte behöver jonglera olika namn för samma artefakt blir projektet mer begripligt och underhållsbart och enklare att arbeta med.
CopyFiles
I C#-projektet implementeras XAML-sidtypen CopyFiles i källkodsfilerna CopyFiles.xaml och CopyFiles.xaml.cs . Låt oss ta en titt på var och en av medlemmarna i CopyFiles i tur och ordning.
rootPage
Det här är ett privat fält.
// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
MainPage rootPage = MainPage.Current;
...
}
...
I C++/WinRT kan vi definiera och initiera det så här.
// CopyFiles.h
...
struct CopyFiles : CopyFilesT<CopyFiles>
{
...
private:
SDKTemplate::MainPage rootPage{ MainPage::Current() };
};
...
Återigen (precis som med MainPage::current) deklareras CopyFiles::rootPage som av typen SDKTemplate::MainPage, som är den projicerade typen och inte implementeringstypen.
CopyFiles (konstruktorn)
I C++/WinRT-projektet har typen CopyFiles redan en konstruktor som innehåller den kod vi vill ha (den anropar bara InitializeComponent).
CopyButton_Click
C# CopyButton_Click-metoden är en händelsehanterare, och från nyckelordet async i dess signatur kan vi se att metoden utför asynkront arbete. I C++/WinRT implementerar vi en asynkron metod som en coroutine. En introduktion till samtidighet i C++/WinRT, tillsammans med en beskrivning av vad en coroutine är, finns i Samtidighet och asynkrona åtgärder med C++/WinRT.
Det är vanligt att vilja schemalägga ytterligare arbete efter att en coroutine har slutförts, och i sådana fall returnerar coroutinen någon typ av asynkront objekt som man kan vänta på och som eventuellt rapporterar förloppet. Men dessa överväganden gäller vanligtvis inte för en händelsehanterare. Så när du har en händelsehanterare som utför asynkrona åtgärder kan du implementera den som en koroutin som returnerar winrt::fire_and_forget. Mer information finns i Fire and forget.
Även om tanken med en fire-and-forget-korutin är att du inte behöver bry dig om när den slutförs, fortsätter arbetet fortfarande i bakgrunden (eller är suspenderat i väntan på att återupptas). Du kan se från C#-implementeringen att CopyButton_Click beror på pekaren this (den kommer åt instansdatamedlemmen rootPage). Därför måste vi vara säkra på att this-pekaren (en pekare till ett CopyFiles-objekt) överlever korutinen CopyButton_Click. I en situation som det här exempelprogrammet, där användaren navigerar mellan användargränssnittssidor, kan vi inte styra livslängden för dessa sidor direkt. Om CopyFiles-sidan skulle förstöras (genom att man navigerar bort från den) medan CopyButton_Click fortfarande körs på en bakgrundstråd, är det inte säkert att få åtkomst till rootPage. För att korutinen ska fungera korrekt måste den hämta en stark referens till this-pekaren och behålla den referensen under hela korutinens livstid. Mer information finns i Starka och svaga referenser i C++/WinRT.
Om du tittar i C++/WinRT-versionen av exemplet på CopyFiles::CopyButton_Click ser du att det är gjort med en enkel deklaration på stacken.
fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
auto lifetime{ get_strong() };
...
}
Nu ska vi titta på de andra aspekterna av den porterade koden som är anmärkningsvärda.
I koden instansierar vi ett FileOpenPicker-objekt och två rader senare kommer vi åt objektets FileTypeFilter-egenskap . Returtypen för den egenskapen implementerar en IVector med strängar. Och för den IVectorn anropar vi metoden IVector<T>.ReplaceAll(T[]). Det intressanta här är värdet som vi skickar till den metoden, där metoden förväntar sig en array. Här är kodraden.
filePicker.FileTypeFilter().ReplaceAll({ L"*" });
Värdet som vi skickar ({ L"*" }) är en standardlista för C++- initiering. Den innehåller ett enskilt objekt, i det här fallet, men en initialiserarlista kan innehålla valfritt antal kommaavgränsade objekt. De delar av C++/WinRT som gör det möjligt att skicka en initialiserarlista till en metod som denna förklaras i standardinitieringslistor.
Vi porterar nyckelordet C# await till co_await i C++/WinRT. Här är exemplet från koden.
auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };
Tänk sedan på den här raden med C#-kod.
dataPackage.SetStorageItems(storageItems);
C# kan implicit konvertera IReadOnlyList<StorageFile> som representeras av storageItems till den IEnumerable<IStorageItem> som förväntas av DataPackage.SetStorageItems. Men i C++/WinRT behöver vi uttryckligen konvertera från IVectorView<StorageFile> till IIterable<IStorageItem>. Så vi har ett annat exempel på as-funktionen i praktiken.
dataPackage.SetStorageItems(storageItems.as<IVectorView<IStorageItem>>());
Där vi använder nyckelordet null i C# (till exempel Clipboard.SetContentWithOptions(dataPackage, null)), använder nullptr vi i C++/WinRT (till exempel Clipboard::SetContentWithOptions(dataPackage, nullptr)).
PasteButton_Click
Detta är en annan händelsehanterare i form av en brand-och-glöm-coroutine. Nu ska vi titta på de aspekter av den porterade koden som är anmärkningsvärda.
I C#-versionen av exemplet fångar vi undantag med catch (Exception ex). I den portade C++/WinRT-koden visas uttrycket catch (winrt::hresult_error const& ex). Mer information om winrt::hresult_error och hur du arbetar med det finns i Felhantering med C++/WinRT.
Ett exempel på att testa om ett C#-objekt är null eller inte är if (storageItems != null). I C++/WinRT kan vi förlita oss på en konverteringsoperator till bool, vilket gör testet mot nullptr internt.
Här är en något förenklad version av ett kodfragment från den portade C++/WinRT-versionen av exemplet.
std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());
Att konstruera en std::wstring_view från en winrt::hstring som det illustrerar ett alternativ till att anropa funktionen hstring::c_str (för att omvandla winrt::hstring till en C-sträng). Det här alternativet fungerar tack vare hstringskonverteringsoperator till std::wstring_view.
Tänk på det här fragmentet av C#.
var file = storageItem as StorageFile;
if (file != null)
...
För att överföra C#-nyckelordet as till C++/WinRT har vi hittills sett funktionen as användas ett par gånger. Den funktionen utlöser ett undantag om typkonverteringen misslyckas. Men om vi vill att konverteringen ska returneras nullptr om den misslyckas (så att vi kan hantera det villkoret i koden) använder vi i stället funktionen try_as .
auto file{ storageItem.try_as<StorageFile>() };
if (file)
...
Kopiera XAML som krävs för att slutföra porteringen av CopyFiles
Nu kan du välja hela innehållet i CopyFiles.xaml filen från shared mappen för den ursprungliga källkodshämtningen och klistra in det i CopyFiles.xaml filen i C++/WinRT-projektet (ersätta det befintliga innehållet i filen i C++/WinRT-projektet).
Slutligen redigera CopyFiles.h och .cpp och ta bort dummy-funktionen ClickHandler, eftersom vi nyss skrev över motsvarande XAML-kod.
Vi har nu slutfört porteringen av CopyFiles, och om du har följt stegen kommer ditt C++/WinRT-projekt nu att byggas och köras, och CopyFiles-scenariot kommer att fungera.
CopyImage
Om du vill portera XAML-sidtypen CopyImage följer du samma process som för CopyFiles. När du porterar CopyImage kommer du att stöta på C#-satsen using, som säkerställer att objekt som implementerar gränssnittet IDisposable frigörs korrekt.
if (imageReceived != null)
{
using (var imageStream = await imageReceived.OpenReadAsync())
{
... // Pass imageStream to other APIs, and do other work.
}
}
Motsvarande gränssnitt i C++/WinRT är IClosable med sin enda Close-metod . Här är C++/WinRT-motsvarigheten till C#-koden ovan.
if (imageReceived)
{
auto imageStream{ co_await imageReceived.OpenReadAsync() };
... // Pass imageStream to other APIs, and do other work.
imageStream.Close();
}
C++/WinRT-objekt implementerar IClosable främst till förmån för språk som saknar deterministisk slutförande. C++/WinRT har deterministisk slutförande, och därför behöver vi ofta inte anropa IClosable::Close när vi skriver C++/WinRT. Men det finns tillfällen då det är bra att kalla det, och det här är en av de gångerna. Här är imageStream-identifieraren en referensberäkningsomslutning runt ett underliggande Windows Runtime -objekt (i det här fallet ett objekt som implementerar IRandomAccessStreamWithContentType). Även om vi kan avgöra att slutföraren för imageStream (dess destruktor) körs i slutet av det omslutande blocket (klammerparenteserna), kan vi inte vara säkra på att den slutföraren kommer att anropa Close. Det beror på att vi skickade imageStream till andra API:er, och de kanske fortfarande bidrar till referensantalet för det underliggande Windows Runtime-objektet. Så det här är ett fall där det är en bra idé att uttryckligen anropa Close . Mer information finns i Behöver jag anropa IClosable::Close för körningsklasser som jag använder?.
Tänk sedan på C#-uttrycket (uint)(imageDecoder.OrientedPixelWidth * 0.5), som du hittar i händelsehanteraren OnDeferredImageRequestedHandler . Det uttrycket multiplicerar en uint med en double, vilket resulterar i en double. Den typomvandlar sedan detta till en uint. I C++/WinRT skulle vi kunna använda en liknande C-liknande rollbesättning ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)), men det är bättre att klargöra exakt vilken typ av rollbesättning vi avser, och i det här fallet skulle vi göra det med static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).
C#-versionen av CopyImage.OnDeferredImageRequestedHandler har en finally -sats, men inte en catch -sats. Vi gick bara lite längre i C++/WinRT-versionen och implementerade en catch -sats så att vi kan rapportera om den fördröjda renderingen lyckades eller inte.
Att portera resten av den här XAML-sidan ger inget nytt att diskutera. Kom ihåg att ta bort funktionen dummy ClickHandler . Precis som med CopyFiles är det sista steget i porten att välja hela innehållet i CopyImage.xamloch klistra in det i samma fil i C++/WinRT-projektet.
CopyText
Du kan porta CopyText.xaml och CopyText.xaml.cs använda tekniker som vi redan har gått igenom.
HistoryAndRoaming
Det finns några intressanta punkter som uppstår när du porterar XAML-sidtypen HistoryAndRoaming .
Ta först en titt på C#-källkoden och följ kontrollflödet från OnNavigatedTo via händelsehanteraren OnHistoryEnabledChanged och slutligen till den asynkrona funktionen CheckHistoryAndRoaming (som inte inväntas, så det är i princip eld och glöm). Eftersom CheckHistoryAndRoaming är asynkront måste vi vara försiktiga i C++/WinRT om pekarens this livslängd. Du kan se resultatet om du tittar på implementeringen i källkodsfilen HistoryAndRoaming.cpp . För det första, när vi kopplar delegater till händelserna Clipboard::HistoryEnabledChanged och Clipboard::RoamingEnabledChanged, tar vi bara en svag referens till sidobjektet för HistoryAndRoaming. Det gör vi genom att skapa delegaten med beroende av värdet som returneras från winrt::get_weak, i stället för beroende av pekaren this. Vilket innebär att delegaten själv, som så småningom anropar asynkron kod, inte håller sidan HistoryAndRoaming vid liv om vi navigerar bort från den.
Och för det andra, när vi slutligen når vår fire-and-forget-CheckHistoryAndRoaming-korutin, är det första vi gör att ta en stark referens till this för att garantera att sidan HistoryAndRoaming förblir i minnet åtminstone tills korutinen slutligen har slutförts. Mer information om båda de aspekter som just beskrivits finns i Starka och svaga referenser i C++/WinRT.
Vi hittar ytterligare en intressant detalj när vi porterar CheckHistoryAndRoaming. Den innehåller kod för att uppdatera användargränssnittet. så vi måste vara säkra på att vi gör det i huvudgränssnittstråden. Den tråd som ursprungligen anropar till en händelsehanterare är huvudtråden för användargränssnittet. Men vanligtvis kan en asynkron metod köra och/eller återuppta på valfri godtycklig tråd. I C# är lösningen att skicka arbete till användargränssnittstråden. I C++/WinRT kan vi använda funktionen winrt::resume_foreground tillsammans med pekarens thisDispatcherQueue för att pausa korutinen och omedelbart återuppta den på huvudtråden för användargränssnittet.
Det relevanta uttrycket är co_await winrt::resume_foreground(DispatcherQueue());. Den kortare versionen uppnås tack vare en konverteringsoperator som tillhandahålls av C++/WinRT.
Att portera resten av den här XAML-sidan ger inget nytt att diskutera. Kom ihåg att ta bort funktionen dummy ClickHandler och kopiera över XAML-markering.
OtherScenarios
Du kan porta OtherScenarios.xaml och OtherScenarios.xaml.cs använda tekniker som vi redan har gått igenom.
Conclusion
Förhoppningsvis har den här genomgången beväpnat dig med tillräckligt med portinformation och tekniker som du nu kan gå vidare med och portera dina egna C#-program till C++/WinRT. Som uppdatering kan du fortsätta att referera tillbaka till tidigare versioner (C#) och efter (C++/WinRT) av källkoden i Urklippsexemplet och jämföra dem sida vid sida för att se korrespondensen.
Relaterade ämnen
Windows developer