Dela via


Portera Urklippsexemplet till C++/WinRT från C#– en fallstudie

Det här avsnittet visar en fallstudie av portning av ett av UWP-appexemplen (Universal Windows Platform) från C# till C++/WinRT. Du kan få portningspraxis och erfarenhet genom att följa med i genomgången och portera exemplet själv under tiden.

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 funktions definition innehåller funktionens kropp (dess implementering).

Det är lite annorlunda när det gäller typer. Du definiera 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 .cpp filer. En .h fil är en rubrikfil och definierar en eller flera typer. Även om du kan definiera medlemsfunktioner i en headerfil, så är det vanligtvis vad en .cpp-fil är avsedd för. För en hypotetisk C++-typ av MyClass definierar du myclass i MyClass.hoch definierar dess medlemsfunktioner i MyClass.cpp. För att andra utvecklare ska kunna återanvända dina klasser delar du bara .h ut filer och objektkod. Du skulle hålla dina .cpp filer 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 namnet pch.h).
  • .idl filer. Dessa filer innehåller Gränssnittsdefinitionsspråk (IDL). Du kan se IDL som huvudfiler för Windows Runtime-typer. Vi pratar mer om IDL i avsnittet IDL för MainPage typ.

Ladda ned och testa Urklippsexemplet

Besök Exempel på Urklipp webbsida 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 shared och SharedContent .

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.

Tips/Råd

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 (C++/WinRT), med namnet Urklipp

Anmärkning

Information om hur du installerar och använder C++/WinRT Visual Studio-tillägget (VSIX) och NuGet-paketet (som tillsammans tillhandahåller projektmall och byggstöd) finns i Visual Studio-stöd 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 tom app (C++/WinRT) projektmall. Ange dess namn till Urklipp och (så att mappstrukturen matchar genomgången) kontrollera att Placera lösning och projekt i samma katalog är avmarkerat.

Bara för att få en baslinje, se till att det här nya, tomma projektet kompileras och körs.

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 kommer det att få ett initialt värde i form av ett unikt GUID.
  • I elementet /Package/Applications/Application noterar du värdet för ID-attributet . Det här är det 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). I det här fallet i stället för att återställa värdet för paketnamnet kan vi till exempel bara ä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 strängen Urklipp C# Exempel. Ändra det till Urklipp C++/WinRT-exempel.

I C++/WinRT-projektet Package.appxmanifest är filen och projektet nu osynkroniserade med avseende på de tillgångsfiler som de refererar till. Du kan åtgärda det genom att först ta bort tillgångarna från C++/WinRT-projektet genom att välja alla filer i Assets mappen (i Solution Explorer 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 Solution Explorer 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/, inkluderar den funktion som konfigurerar exemplet

Urklippsexemplet , som alla UWP-appexempel (Universal Windows Platform) 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 kommer vi att titta på hur man portar MainPage- och Scenario.

IDL för MainPage typ

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 anropbara (eller offentliga) ytan av en typ projiceras ut i världen, så att typen kan användas. Det projekterade 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 (även kallade Windows-metadata). Dessa metadatafiler har tillägget .winmd, och här är några av deras användningsområden.

  • Ett .winmd kan 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 .winmd kan beskriva 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 typ av Windows Runtime som används av en XAML-sida i samma app.
  • För att göra det enklare för dig att använda Windows Runtime-typer (inbyggda eller från tredje part) använder C++/WinRT-byggsystemet .winmd-filer för att generera wrapper-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 .winmd fil och använder den sedan för att generera omslutningar för din projektion, 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 kommer att avgöra vilka delar av den som behöver finnas i projektion av C++/WinRT-MainPage- typen (det vill säga, i dess anropsbara eller offentliga yta), och vilka som bara kan utgöra en del av dess implementation. 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.cs, och 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 UWP-program (C# Universal Windows Platform) ä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. Mer specifikt är det en runtime-klass.

  • Mer information om huruvida du bör skapa en runtime-klass för en viss typ finns i avsnittet Author APIs 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ämns i föregående punkt, och även i Konsumera API:er med C++/WinRT.
  • Om du vill ha mer information om anslutningen mellan körningsklasser och IDL (.idl filer) kan du läsa och följa med i avsnittet XAML-kontroller; Binda till en C++/WinRT-egenskap. Det här avsnittet går igenom processen för att skapa en ny körningsklass, där det första steget är att lägga till en ny Midl-fil (.idl) objektet 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 har vi några resonemang att göra kring vad som behöver och vad som inte behöver inkluderat i IDL.

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.
  • Följande metoder används: BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotificationsoch 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 runtime-klass, 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 Aktuell används från de enskilda scenariernas XAML-sidor för att komma åt applikationens instans av MainPage. Eftersom Current inte används för att interagera med XAML-ramverket (och inte heller används i kompileringsenheter), kan vi reservera det för att endast vara en 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 typen MainPage. 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 saker och ting lite och gör det till ett privat fält i körningsklassen OtherScenarios. Så att man inte hamnar i IDL.
  • Egenskapen Scenarios är en samling objekt av typen Scenario (en typ som vi nämnde tidigare). Vi diskuterar Scenario i nästa underavsnitt, så låt oss lämna attributet 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 individuella scenario-XAML-sidorna på instansen av MainPage som returneras från det statiska fältet Current. Eftersom (som redan nämnts) Aktuell är en instans av den projicerade 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 databindas till måste även deklareras i IDL, vare sig 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 kollektion av Scenario-objekt tilldelas egenskapen ItemsSource för en ListBox (vilket är en kontroll för objekt). 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 typ Scenario i IDL gör att C++/WinRT-byggsystemet genererar en källkodsdefinition av Scenario åt dig i en headerfil bakom kulisserna, och vars namn och plats inte är viktiga för denna genomgång.

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 Även NotifyType vara en Windows Runtime-typ. och den måste definieras i MainPage.idl.

Nu ska vi lägga till i filen MainPage.idl de nya typerna och den nya medlemmen i Mainpage som vi har beslutat att deklarera i IDL. Samtidigt tar vi bort från IDL platshållarmedlemmarna i Mainpage- som Visual Studio-projektmallen gav oss.

I ditt C++/WinRT-projekt öppnar du MainPage.idl och redigerar det så det följer strukturen 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;
        Windows.UI.Xaml.Interop.TypeName ClassName;
    };

    enum NotifyType
    {
        StatusMessage,
        ErrorMessage
    };

    [default_interface]
    runtimeclass MainPage : Windows.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);
    };
}

Anmärkning

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 när vi fortsätter med porten i den här genomgången ändrar vi varje förekomst i källkoden för Urklipp namnområdesnamn till SDKTemplate. Det finns också en plats i C++/WinRT-projektegenskaper där Urklipp namnområde visas, så vi passar på att ändra det nu.

I Visual Studio för C++/WinRT-projektet anger du projektegenskapen Common Properties>C++/WinRT>Root Namespace till värdet SDKTemplate.

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 också 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. För tillfället kan projektet inte byggas till slut, men att bygga nu är användbart eftersom det återskapar stub-filerna 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 versionen har upphört (återigen, som förväntat, kommer bygget inte att lyckas helt. Men det steg som vi är intresserade av – att generera stubbar –kommer att ha lyckats). 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 som Visual Studio-projektmallen gav oss (egenskapen dummy med namnet MyPropertyoch 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 implementation namnrymd och ett annat 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.xaml och App.xaml. Namnområdet visas i värdena för attributen x:Class och xmlns:local .
  • App.idl.
  • App.h.
  • App.cpp. Det finns två using namespace direktiv (sök efter delsträngen using namespace Clipboard) och två kvalifikationer av MainPage-typen (sök efter ). 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 elementet Button från markup.

Spara alla filer. Rensa lösningen (Build>Clean Solution) och kompilera 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 Nuvarandeoch 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 egenskapsavläsare är som en funktion som returnerar egenskapsvärdet och inte tar några parametrar (en accessor). Så blir C# FEATURE_NAME ett statiskt fält som i C++/WinRT blir en FEATURE_NAME statisk åtkomstfunktion (som i detta fall returnerar en strängliteral).

För övrigt skulle vi göra samma sak om vi portade en skrivskyddad C#-egenskap. För en C#-skrivbar egenskap är C++/WinRT-sättet att uttrycka en egenskapsuppsättning som en void funktion som tar egenskapsvärdet som en parameter (en mutator). I båda fallen, om C#-fältet eller egenskapen är statisk, så är även C++/WinRT-åtkomstorn och/eller -mutatorn.

Current är ett statiskt fält (inte en konstant) i MainPage. Återigen ska vi göra C++/WinRT-uttrycket till en skrivskyddad egenskap och göra den statisk igen. Om FEATURE_NAME är konstant är aktuell 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 beräknade typen är den som är utformad för att förbrukas antingen i projektet för XAML-interoperation eller mellan binärfiler. Implementeringstypen är den du använder för att implementera de funktioner som du har exponerat för din projekterade 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 this pekaren inuti en medlem av implementeringstypen this pekaren naturligtvis en pekare till implementeringstypen.
  • För att konvertera this-pekaren till motsvarande projekterade typ, avreferera 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 informationen finns i Instansiering och returnering av implementeringstyper och gränssnitt.

I konstruktorn finns också SampleTitle().Text(FEATURE_NAME());. Del SampleTitle() är ett anrop till en enkel accessorfunktion med namnet SampleTitle, som returnerar TextBlock som vi lade till i XAML. När du x:Name använder ett XAML-element genererar XAML-kompilatorn en accessor åt dig som får samma namn som elementet. I .Text(...)-delen anropas mutatorfunktionen TextTextBlock-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ängarna är breda i Windows Runtime, måste vi för att porta en strängliteral prefixa den med det "wide-char"-kodningsprefixet L. Därför ändrar vi (till exempel) "en strängliteral" till L"a string literal". Se även Breda strängkonstanter.

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 denna samling av Scenario-objekt 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:

För IInspectable-fallet, om elementen inte själva är körningsklasser, måste dessa element vara av ett slag som kan boxas och packas upp till och från IInspectable. Och det innebär att de måste vara Windows Runtime-typer (se Boxning och avboxningsvärden till 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 tillfällen i ditt eget portningsarbete där en körningsklass verkligen är rätt väg att gå. 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 gå med en körningsklass för Scenario typ, måste vi tänka på boxning. Om vi hade gjort scenariot en vanlig C++ structskulle vi inte kunna boxa den. Men vi deklarerade Scenario som en struct i IDL, så vi kan kapsla in det.

Vi har valet att boxa scenariot i förväg, eller vänta tills vi är på väg att tilldela till ItemsSourceoch därefter boxa dem just-in-time. 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 datamedlemsvariabel en samling IInspectable redo att tilldela i användargränssnittet. Vid initieringen boxas scenariot objekt till den datamedlemmen. Vi behöver bara en kopia av samlingen, men vi måste packa upp ett element varje gång vi behöver läsa dess fält.
  • Boxning precis i tid. För det här alternativet är vår datakomponent en samling av Scenario. När det är dags att tilldela användargränssnittet så boxas Scenario-objekt från datamedlemsvariabeln till 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 man kan se, för en liten samling som denna, gör för- och nackdelarna det till något som kvittar. Så för den här fallstudien går vi med alternativet just-in-time.

De scenariernas medlemsvariabel är ett fält i MainPage, som är definierat och initierat i SampleConfiguration.cs. Och Scenarier är en skrivskyddad egenskap för MainPage, definierad i MainPage.xaml.cs (och implementerad för att helt enkelt returnera scenarier fältet). 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 namnger dem scenarieroch scenarier, respektive. Vi kommer att deklarera scenarier 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.cppden statiska datamedlemmen scenarierInner 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 tom sida (C++/WinRT) objektmall och inte tom sida en). 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.

InformeraAnvändare

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. För tillfället kommer vi att skapa en stub av en UpdateStatus-metod i C++/WinRT-projektet, och vi kommer att porta 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 använder Windows.UI.Core.CoreDispatcherPriority uppräkning. När du vill använda en typ från en Windows-namnrymd i C++/WinRT måste du inkludera motsvarande C++/WinRT Windows-namnområdeshuvudfil (mer information om detta finns i Kom igång med C++/WinRT). I det här fallet, som du ser i kodlistan nedan, är winrt/Windows.UI.Core.hrubriken, 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 avsedd att anropas i runtime-klassen, så vi kommer inte att deklarera den 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/Windows.UI.Core.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 (Dispatcher().HasThreadAccess())
    {
        UpdateStatus(strMessage, type);
    }
    else
    {
        Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [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 punkt notation för att punkt i kapslade egenskaper. Därför kan typen C# MainPage komma åt sin egen Dispatcher-egenskap med syntaxen Dispatcher. Och C# kan ytterligare använda punktnotation på 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 Dispatcher().HasThreadAccess()

När C#-versionen av NotifyUser anroparCoreDispatcher.RunAsync implementerar den asynkrona återanropsdelegaten som en lambda-funktion. C++/WinRT-versionen gör samma sak, men syntaxen är lite annorlunda. I C++/WinRT vi samla in de två parametrar som vi ska använda samt this pekaren (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. Vi kan också bortse från delen var task = i det här specifika fallet. Vi väntar inte på det returnerade asynkrona objektet, så du behöver inte lagra det.

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 Åtkomst Läge
konstruktor för MainPage public Portats
aktuell egenskap för public Portats
FEATURE_NAME egenskap public Portats
IsClipboardContentChangedEnabled-egenskapen property public Ej påbörjad
Scenarios egenskap public Portats
BuildClipboardFormatsOutputString-metod public Ej påbörjad
DisplayToast-metod public Ej påbörjad
EnableClipboardContentChangedNotifications-metod public Ej påbörjad
NotifyUser-metod public Portats
OnNavigatedTo-metod protected Ej påbörjad
ärApplikationsFönsterAktivt fält private Ej påbörjad
needToPrintClipboardFormat fält private Ej påbörjad
scenarier fält private Portats
Button_Click metod 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 metod private Ej påbörjad
UpdateStatus-metod private Utstubbad

Vi kommer att prata om de ännu ej porterade medlemmarna i de kommande underavsnitten.

Anmärkning

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 ändå gör det så kommer det att ta lång tid innan vi kan komma till ett depåstopp och bygga igen (vilket möjligen kan dölja stavfel eller andra fel som vi gör längs vägen).

När vi är klara med att portera den imperativa koden för klassen MainPage, sedan kopierar vi innehållet i XAML-filen så att vi kan vara säkra på att projektet fortfarande kommer att kompileras.

IsClipboardContentChangedEnabled

Det här är en get-set C#-egenskap som har standardvärdet false. Den är medlem i MainPage och definieras i SampleConfiguration.cs.

För C++/WinRT behöver vi en åtkomstfunktion, en mutatorfunktion och en stöddatamedlem som ett fält. Eftersom IsClipboardContentChangedEnabled representerar tillståndet för ett av scenarierna i exemplet snarare än tillståndet för MainPage, skapar vi de nya medlemmarna med en ny verktygsklass 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).

För att komplettera vårt SampleConfiguration.cpp i C++/WinRT-projektet, lägg till en ny Visual C++>Kod>huvudfil (.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 ställa in det som static eftersom det inte använder några instansmedlemmar.

Om vi vill använda typerna Urklipp och DataPackageView i C++/WinRT måste vi inkludera Windows-namnområdets huvudfil winrt/Windows.ApplicationModel.DataTransfer.h.

I C# är egenskapen DataPackageView.AvailableFormats en IReadOnlyList-, så att vi kan komma åt egenskapen Count. I C++/WinRT returnerar DataPackageView::AvailableFormats accessor-funktionen en IVectorView, som har en Size accessor-funktion 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 Lägg till-metod som du gör med en StringBuilderanvänder du operatorn insertion (<<) med en utdataström som wostringstream. Mer information finns i iostream-programmering och formatering av C++/WinRT-strängar.

C#-koden konstruerar en StringBuilder med nyckelordet new . I språket C# är objekt som standard referenstyper och deklarerade på heap med new. I modern standard-C++ är objekt värdetyper som standard, deklarerade i stacken (utan att använda new). Så vi portar StringBuilder output = new StringBuilder(); till C++/WinRT till bara 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 du, för att undvika kopior, vill ha en -referens till en infererad (eller härledd) typ. 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() };
}

Anmärkning

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å.

Visa Toast-meddelande

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 stött på de flesta detaljer och tekniker som är relevanta för att överföra 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 refererar till XmlDocument-typen och när du refererar 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 i SampleConfiguration.cpp.
  • Redigera pch.h för att inkludera nödvändiga C++/WinRT Windows-namnområdeshuvudfiler.
  • Skapa C++/WinRT-objekt på stacken, inte på heapen.
  • Ersätt anrop till egenskapen hämta accessorer med funktionsanropssyntax (()).

En mycket vanlig orsak till kompilerings-/länkfel är att glömma att inkludera de 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 med i genomgången och överföra DisplayToast själv kan du jämföra dina resultat med koden i C++/WinRT-versionen i ZIP-filen för Urklippsexempel källkod som du har laddat ner.

Aktivera aviseringar för ändring av 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 den allmänna formen är att du registrerar och återkallar med anrop till ett par funktioner som är namngivna efter händelsen. För att registrera överför du din delegat till registreringsfunktionen, och som belöning får du en återkallningstoken (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 i bakgrunden i C#. Men språket gör den informationen implicit. C++/WinRT gör det explicit.

-objektet av typen visas i C#-händelsehanterarens signaturer. På C#-språket är objekt 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, Windows::UI::Core::WindowActivatedEventArgs const& e);
...

// SampleConfiguration.cpp
...
using namespace Windows::Foundation;
using namespace Windows::UI::Core;
using namespace Windows::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 händelsehanteringsdelegaterna själva (OnClipboardChanged och OnWindowActivated) lämnas som stubbar 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 av Scenario-objekt och tilldelar den till egenskapen ItemsSource för en ListBox (som är en kontroll för objekt). 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 ska vi göra OnNavigatedTo till en publik metod för MainPage. Och vi lägger till ett ListBox--stubbelement i XAML så att en kompilering lyckas. Efter kodlistan undersöker vi några av detaljerna.

<!-- MainPage.xaml -->
...
<StackPanel ...>
    ...
    <ListBox x:Name="ScenarioControl" />
</StackPanel>
...
// MainPage.h
...
void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
...

// MainPage.cpp
...
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::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 av IInspectable. Det var en del av det beslut vi tog att utföra boxningen av vårt scenario objekt just-in-time.

I stället för att använda C#:s stränginterpolation här, använder vi en kombination av funktionen to_hstring och sammanfogningsoperatorn i winrt::hstring.

ärProgramfönstretAktivt

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 genomgång, titta tillbaka på vad vi gjorde med fältet isClipboardContentChangedEnabled och gör samma sak med isApplicationWindowActive.

needToPrintClipboardFormat

Samma mönster som isApplicationWindowActive (se rubriken direkt före den här).

Klicka_på_Knappen

Button_Click är en privat metod (händelsehantering) i klassen C# MainPage och definieras i MainPage.xaml.cs. Här är det, tillsammans med XAML-SplitView- som den refererar till, och ToggleButton- som registrerar den.

<!-- 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 händelsehanteraren i C++/WinRT-versionen är public (som du kan se, deklarerar du den innan du gör-deklarationerna av 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, Windows::UI::Xaml::RoutedEventArgs const& e);
private:
...

// MainPage.cpp
void MainPage::Button_Click(Windows::Foundation::IInspectable const& /* sender */, Windows::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). Denna metod skapar sin egen std::wostringstream. Och den förvandlar sin ström till en winrt::hstring och returnerar den. 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 mer effektivt att skapa bara en std::wostringstreamoch sedan passera (en referens till) den vidare, 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 ä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 delat upp den från en rad till fyra, för att göra det lättare att se vad det gör, för att visa hur vi ska portera det.

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 den behöver inte co_awaiteller 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 klickhändelsen av en Hyperlänkknappär objektet med namnet avsändare faktiskt 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, Windows::UI::Xaml::RoutedEventArgs const& e);
private:
...

// MainPage.cpp
...
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml::Controls;
...
void MainPage::Footer_Click(Windows::Foundation::IInspectable const& sender, Windows::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 som på avsändarobjektet för att konvertera det till HyperlinkButton. I C++/WinRT är egenskapen Tag en IInspectable (motsvarigheten till Object). Men det finns ingen ToStringIInspectable. I stället måste vi ta ut IInspectable - till ett skalärvärde, i detta fall en sträng. Mer information om boxning och avboxning finns i Boxning 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 porta dem är enkelt, och det väcker inga nya frågor 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::navigating, som är ett privat booleskt fält som initierats till false. Och du behöver en Frame i MainPage.xamloch heter ScenarioFrame. Men förutom dessa detaljer avslöjar portning av den här metoden inga nya tekniker.

Om du istället för att porta manuellt kopierar kod från C++/WinRT-versionen i ZIP-filen för källkoden till urklippsexemplet som du laddade ner, 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.

UppdateraStatus

Vi har bara en stub hittills för MainPage.UpdateStatus. Att återigen överföra implementeringen omfattar till stor del bekant mark. 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 utföra resten av porteringen med 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.

  • Om du vill MainPage.xamllägger du till en border med namnet StatusBorder.
  • Till MainPage.xaml, lägg till en TextBlock som heter StatusBlock.
  • För att MainPage.xaml, lägg till en StackPanel med namnet StatusPanel.
  • Lägg till pch.htill #include "winrt/Windows.UI.Xaml.Media.h".
  • Lägg till pch.htill #include "winrt/Windows.UI.Xaml.Automation.Peers.h".
  • Lägg till MainPage.cppi using namespace winrt::Windows::UI::Xaml::Media;.
  • Lägg till MainPage.cppi using namespace winrt::Windows::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 Urklippsexemplet är ett av dessa fall.

I sin Styles.xaml-fil har Urklippsexemplet en XAML-ResourceDictionary- med stilar som tillämpas på knappar, menyer och andra gränssnittselement i applikationens gränssnitt. Sidan Styles.xaml sammanfogas till App.xaml. Och sedan har vi standard-MainPage.xaml-startpunkten för användargränssnittet, som vi redan har sett kortfattat. 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 Solution Explorer 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 Solution Explorer 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.

Samla dina .idl-filer

Förutom den standardiserade startpunkten MainPage.xaml för användargränssnittet har Urklippsexemplet fem andra scenariobaserade XAML-sidor, tillsammans med deras motsvarande code-behind-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 kommer att titta på hur du portar den bakomliggande koden i de kommande huvudavsnitten. Men innan det ska vi prata om IDL.

Det finns ett värde i att konsolidera IDL för dina körningsklasser i en enda IDL-fil. Mer information om det värdet finns i Factoring-körningsklasser 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 : Windows.UI.Xaml.Controls.Page
    {
        CopyFiles();
    }

    [default_interface]
    runtimeclass CopyImage : Windows.UI.Xaml.Controls.Page
    {
        CopyImage();
    }

    [default_interface]
    runtimeclass CopyText : Windows.UI.Xaml.Controls.Page
    {
        CopyText();
    }

    [default_interface]
    runtimeclass HistoryAndRoaming : Windows.UI.Xaml.Controls.Page
    {
        HistoryAndRoaming();
    }

    [default_interface]
    runtimeclass OtherScenarios : Windows.UI.Xaml.Controls.Page
    {
        OtherScenarios();
    }
}

Som du ser är det bara en kopia av innehållet i de enskilda .idl-filerna, allt i ett namnområde, och med MyProperty borttaget från varje körningsklass.

I Solution Explorer i Visual Studio väljer du alla ursprungliga IDL-filer (CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idloch ) och OtherScenarios.idlRedigera>Ta bort dem (välj Ta bort i dialogrutan).

Slutligen, för att slutföra borttagningen av MyProperty, ska du i filerna .h och .cpp för var och en av de fem XAML-sidtyperna ta bort deklarationerna och definitionerna för int32_t MyProperty()-åtkomstfunktionerna och void MyProperty(int32_t)-mutatfunktionerna.

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.

KopieraFiler

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 när en coroutine har slutförts. I sådana fall returnerar koden en asynkron objekttyp som kan avvaktas och som eventuellt rapporterar förlopp. 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 coroutine som returnerar winrt::fire_and_forget. Mer information finns i Fire och glöm.

Även om idén med en "fire-and-forget"-korutin är att du inte bryr dig om när den är klar, pågår arbetet fortfarande (eller är pausad i väntan på att återupptas) i bakgrunden. 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 CopyButton_Click korutin. 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 sidan CopyFiles förstörs (genom att navigera bort från den) medan CopyButton_Click fortfarande är i flykt på en bakgrundstråd, är det inte säkert att komma åt rootPage. För att göra coroutine korrekt måste den erhålla en stark referens till this-pekaren och behålla den referensen under hela coroutinens varaktighet. För mer information, se 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 på den IVectorkallar vi IVector<T>. ReplaceAll(T[])-metod. Den intressanta aspekten är det värde som vi skickar till den metoden, där en matris förväntas. Här är kodraden.

filePicker.FileTypeFilter().ReplaceAll({ L"*" });

Det värde som vi skickar ({ L"*" }) är en standardinitialiseringslista i C++ . 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 omvandla IReadOnlyList<StorageFile>, som representeras av storageItems, till IEnumerable<IStorageItem>, vilket 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å som funktion i aktion.

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 lita på att en konverteringsoperator till boolsom utfö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 hstringkonverteringsoperator till std::wstring_view.

Tänk på det här fragmentet av C#.

var file = storageItem as StorageFile;
if (file != null)
...

För att porta nyckelordet C# as till C++/WinRT har vi hittills sett som funktion 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 ska du redigera CopyFiles.h och .cpp samt ta bort ClickHandler-funktionen, eftersom vi just skrev över den motsvarande XAML-markeringen.

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 porta XAML-sidtypen CopyImage följer du samma process som för CopyFiles. När du porterar CopyImage kommer du att stöta på användningen av C# using-satsen, vilket säkerställer att objekt som implementerar IDisposable-gränssnittet blir korrekt disponerade.

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 för att gynna språk som saknar deterministisk finalisering. 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 identifierare en referensberäkningsomslutning runt ett underliggande Windows Runtime-objekt (i det här fallet ett objekt som implementerar IRandomAccessStreamWithContentType). Även om vi kan fastställa att slutgiltiga delen av imageStream (dess destruktor) kommer att köras vid slutet av det omgivande omfånget (klammerparenteserna), kan vi inte vara säkra på att den delen kommer att anropa Stäng. 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 anropa Close explicit. Mer information finns i Behöver jag anropa IClosable::Close på runtime-klasser 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. Det kastar sedan det till en uint. I C++/WinRT kan vi använda en liknande C-stil ((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.

KopieraText

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 OnHistoryEnabledChanged, händelsehanteraren och slutligen till den asynkrona funktionen CheckHistoryAndRoaming (som inte inväntas, så det är i huvudsak "skjut 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örst kopplar vi delegeringar till händelserna Urklipp::HistoryEnabledChanged och Urklipp::RoamingEnabledChanged, och vi tar bara en svag referens till HistoryAndRoaming sidobjekt. Det gör vi genom att skapa delegaten med ett beroende på värdet som returneras från winrt::get_weaki stället för ett beroende på pekaren this. Vilket innebär att själva ombudet, 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 äntligen når vår "fire-and-forget" CheckHistoryAndRoaming coroutine, är det första vi gör att ta en stark referens till this för att garantera att HistoryAndRoaming-sidan lever åtminstone tills coroutine verkligen har slutförts. Mer information om båda de aspekter som just beskrivits finns i Starka och svaga referenser i C++/WinRT.

Vi hittar ett annat intresse vid portering av 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 anropa CoreDispatcher.RunAsync och uppdatera användargränssnittet inifrån lambda-funktionen. I C++/WinRT kan vi använda funktionen winrt::resume_foreground tillsammans med markörens thisDispatcher för att pausa koroutinen och omedelbart återuppta den på huvudtråden för användargränssnittet.

Det relevanta uttrycket är co_await winrt::resume_foreground(Dispatcher());. Alternativt, även om vi med mindre klarhet kan uttrycka det helt enkelt som co_await Dispatcher();. 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.

Slutsats

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.