Migrazione dell'esempio degli Appunti da C# a C++/WinRT: un caso di studio

Questo argomento presenta un case study sulla conversione di uno degli esempi di app Piattaforma UWP (Universal Windows Platform) da C# a C++/WinRT. Puoi fare pratica e acquisire esperienza nel porting seguendo la procedura dettagliata ed eseguendo personalmente il porting dell'esempio man mano che procedi.

Note

Il codice sorgente da convertire è un'app C# UWP. Il codice C++/WinRT di destinazione in questo articolo è scritto per WinUI 3 (SDK per app di Windows). Ovunque il codice sorgente UWP utilizzi API che differiscono in WinUI 3 (ad esempio, Windows.UI.Core.CoreDispatcher anziché Microsoft.UI.Dispatching.DispatcherQueue), questo articolo mostra esplicitamente l'equivalente corretto di WinUI 3 nell'output C++/WinRT. Puoi usare i modelli di codice della colonna C++/WinRT direttamente nella tua app WinUI 3.

Per un catalogo completo dei dettagli tecnici relativi alla conversione in C++/WinRT da C#, vedi l'argomento complementare Passare a C++/WinRT da C#.

Breve prefazione sui file di codice sorgente C# e C++

In un progetto C# i file di codice sorgente sono principalmente .cs file. Quando si passa a C++, si noterà che esistono più tipi di file di codice sorgente da usare. Il motivo è la differenza tra i compilatori, il modo in cui viene riutilizzato il codice sorgente C++ e le nozioni di dichiarazione e definizione di un tipo e delle relative funzioni (relativi metodi).

Una dichiarazione di funzione descrive solo la firma della funzione ,ovvero il tipo restituito, il nome e i relativi tipi e nomi di parametro. Una definizione di funzione include il corpo della funzione (la relativa implementazione).

È un po' diverso quando si tratta di tipi. Si definisce un tipo fornendone il nome e, come minimo, semplicemente dichiarando tutte le sue funzioni membro (e gli altri membri). In questo modo, è possibile definire un tipo anche se non si definiscono le funzioni membro.

  • I file di codice sorgente C++ comuni sono .h (dot aitch) e .cpp file. Un .h file è un file di intestazione e definisce uno o più tipi. Sebbene sia possibile definire funzioni membro in un header, in genere è a questo che serve un file .cpp. Pertanto, per un ipotetico tipo C++ MyClass, MyClass verrebbe definito in MyClass.h, e le relative funzioni membro verrebbero definite in MyClass.cpp. Per consentire ad altri sviluppatori di riutilizzare le classi, è possibile condividere solo i file e il .h codice oggetto. Terresti segreti i tuoi file .cpp, perché l'implementazione costituisce la tua proprietà intellettuale.
  • Intestazione precompilata (pch.h). In genere è presente un set di file di intestazione inclusi nell'applicazione e questi file non vengono modificati molto spesso. Invece di elaborare il contenuto di tale set di intestazioni ogni volta che si compila, è possibile aggregare tali intestazioni in un unico file, compilarli una sola volta e quindi usare l'output di tale passaggio di precompilazione ogni volta che si compila. A tale scopo, è possibile usare un file di intestazione precompilato (in genere denominato pch.h).
  • File .idl. Questi file contengono IDL (Interface Definition Language). Si può considerare IDL come i file header per i tipi di Windows Runtime. Nella sezione IDL per il tipo MainPage verranno illustrate altre informazioni su IDL.

Scaricare e testare l'esempio degli Appunti

Visitate la pagina web di esempio degli appunti, quindi fate clic su Scarica ZIP. Decomprimere il file scaricato ed esaminare la struttura di cartelle.

  • La versione C# del codice sorgente di esempio è contenuta nella cartella denominata cs.
  • La versione C++/WinRT del codice sorgente di esempio è contenuta nella cartella denominata cppwinrt.
  • Altri file, usati sia dalla versione C# che dalla versione C++/WinRT, sono disponibili nelle shared cartelle e SharedContent .

La procedura dettagliata in questo argomento illustra come ricreare la versione C++/WinRT dell'esempio degli Appunti trasferendola dal codice sorgente C#. In questo modo, è possibile vedere come convertire i propri progetti C# in C++/WinRT.

Per ottenere informazioni sulle operazioni eseguite dall'esempio, aprire la soluzione C# (\Clipboard_sample\cs\Clipboard.sln), modificare la configurazione in base alle esigenze (ad esempio x64), compilare ed eseguire. L'interfaccia utente (UI) dell'esempio illustra le varie funzionalità, passo passo.

Suggerimento

La cartella radice dell'esempio scaricato potrebbe essere denominata Clipboard anziché Clipboard_sample. Ma continueremo a riferirci a quella cartella come Clipboard_sample per distinguerla dalla versione C++/WinRT che creerai in un passaggio successivo.

Crea un'app vuota denominata Appunti

Note

Per informazioni sull'installazione e l'uso di C++/WinRT Visual Studio Extension (VSIX) e del pacchetto NuGet (che insieme forniscono il modello di progetto e il supporto per la compilazione), vedi Visual Studio supporto per C++/WinRT.

Iniziare il processo di conversione creando un nuovo progetto C++/WinRT in Microsoft Visual Studio. Crea un nuovo progetto con il modello di progetto per C++ App vuota con pacchetto (WinUI 3 per desktop). Impostare il nome su Appunti e, affinché la struttura delle cartelle corrisponda alla guida dettagliata, assicurarsi che Inserisci soluzione e progetto nella stessa directory sia deselezionata.

Giusto per stabilire un punto di partenza, assicurati che questo nuovo progetto vuoto venga compilato ed eseguito.

Package.appxmanifest e file di risorse

Se le versioni C# e C++/WinRT dell'esempio non devono essere installate side-by-side nello stesso computer, i file di origine del manifesto del pacchetto dell'app dei due progetti (Package.appxmanifest) possono essere identici. In questo caso, è sufficiente copiare Package.appxmanifest dal progetto C# al progetto C++/WinRT e si è fatto.

Per coesistere le due versioni dell'esempio, hanno bisogno di identificatori diversi. In tal caso, nel progetto C++/WinRT aprire il Package.appxmanifest file in un editor XML e prendere nota di questi tre valori.

  • All'interno dell'elemento /Package/Identity prendere nota del valore dell'attributo Name . Questo è il nome del pacchetto. Per un progetto appena creato, il progetto assegna un valore iniziale di un GUID univoco.
  • All'interno dell'elemento /Package/Applications/Application prendere nota del valore dell'attributo Id . Si tratta dell'ID applicazione.
  • All'interno dell'elemento /Package/mp:PhoneIdentity prendere nota del valore dell'attributo PhoneProductId . Anche in questo caso, per un progetto appena creato, questo verrà impostato sullo stesso GUID a cui è impostato il nome del pacchetto.

Copiare Package.appxmanifest quindi dal progetto C# al progetto C++/WinRT. Infine, è possibile ripristinare i tre valori annotati. In alternativa, è possibile modificare i valori copiati per renderli univoci e/o appropriati per l'applicazione e per l'organizzazione (come si farebbe normalmente per un nuovo progetto). In questo caso, ad esempio, invece di ripristinare il valore del nome del pacchetto, è sufficiente modificare il valore copiato da Microsoft. SDKSamples.Clipboard.CS a Microsoft. SDKSamples.Clipboard.CppWinRT. E possiamo lasciare l'ID applicazione impostato su App. Se il nome del pacchetto o l'ID dell'applicazione sono diversi tra loro, le due applicazioni avranno ID modello utente dell'applicazione (AUMID) diversi. E questo è ciò che è necessario per l'installazione affiancata di due app nello stesso computer.

Ai fini di questa procedura dettagliata, è opportuno apportare alcune altre modifiche in Package.appxmanifest. Sono presenti tre occorrenze della stringa Clipboard C# Sample. Passare a Clipboard C++/WinRT Sample (Esempio C++/WinRT).

Nel progetto C++/WinRT il Package.appxmanifest file e il progetto non sono sincronizzati rispetto ai file di asset a cui fanno riferimento. Per risolvere questo errore, rimuovere prima gli asset dal progetto C++/WinRT selezionando tutti i file nella Assets cartella (in Esplora soluzioni in Visual Studio) e rimuovendoli (scegliere Elimina nella finestra di dialogo).

Il progetto C# fa riferimento ai file di asset da una cartella condivisa. Puoi eseguire la stessa operazione nel progetto C++/WinRT oppure puoi copiare i file come faremo in questa procedura dettagliata.

Passa alla cartella \Clipboard_sample\SharedContent\media. Selezionare i sette file inclusi dal progetto C# (microsoft-sdk.png, smalltile-sdk.png, splash-sdk.png, squaretile-sdk.pngstorelogo-sdk.png, tile-sdk.png, e windows-sdk.png), copiarli e incollarli nella \Clipboard\Clipboard\Assets cartella nel nuovo progetto.

Fare clic con il pulsante destro del mouse sulla Assets cartella (in Esplora soluzioni nel progetto C++/WinRT) >Aggiungi>elemento esistente e passare a \Clipboard\Clipboard\Assets. Nella selezione file selezionare i sette file e fare clic su Aggiungi.

Package.appxmanifest è ora sincronizzato con i file di asset del progetto.

MainPage, inclusa la funzionalità che configura l'esempio

L'esempio Clipboard, al pari di tutti gli esempi di app Piattaforma UWP (Universal Windows Platform), consiste in una raccolta di scenari che l'utente può seguire, uno alla volta. La raccolta di scenari in un determinato esempio è configurata nel codice sorgente dell'esempio. Ogni scenario nella raccolta è un elemento di dati che archivia un titolo, nonché il tipo della classe nel progetto che implementa lo scenario.

Nella versione C# dell'esempio, se si esamina il file di SampleConfiguration.cs codice sorgente, verranno visualizzate due classi. La maggior parte della logica di configurazione si trova nella classe MainPage , che è una classe parziale (forma una classe completa quando viene combinata con il markup in MainPage.xaml e il codice imperativo in MainPage.xaml.cs). L'altra classe in questo file di codice sorgente è Scenario, con le relative proprietà Title e ClassType .

Nelle prossime sottosezioni si esaminerà come convertire MainPage e Scenario.

IDL per il tipo MainPage

Iniziamo questa sezione parlando brevemente del linguaggio IDL (Interface Definition Language) e di come ci aiuta durante la programmazione con C++/WinRT. IDL è un tipo di codice sorgente che descrive la superficie chiamabile di un tipo Windows Runtime. La superficie chiamabile (o pubblica) di un tipo viene proiettata nel mondo, in modo che il tipo possa essere utilizzato. Tale parte proiettata del tipo è in contrasto con l'implementazione interna effettiva del tipo, che naturalmente non è chiamabile e non pubblica. È solo la porzione proiettata che definiamo in IDL.

Dopo aver creato il codice sorgente IDL (all'interno di un .idl file), è quindi possibile compilare il file IDL in file di metadati leggibili dal computer (noto anche come metadati Windows). Questi file di metadati hanno l'estensione .winmde di seguito sono riportati alcuni dei relativi usi.

  • Un .winmd oggetto può descrivere i tipi di Windows Runtime in un componente. Quando si fa riferimento a un componente Windows Runtime (WRC) da un progetto di applicazione, il progetto dell'applicazione legge i metadati Windows appartenenti al WRC (che i metadati possono trovarsi in un file separato o possono essere inseriti nello stesso file del WRC stesso) in modo da poter utilizzare i tipi WRC dall'interno dell'applicazione.
  • Un .winmd oggetto può descrivere i tipi di Windows Runtime in una parte dell'applicazione in modo che possano essere utilizzati da una parte diversa della stessa applicazione. Ad esempio, un tipo di Windows Runtime utilizzato da una pagina XAML nella stessa app.
  • Per semplificare l'utilizzo dei tipi di Windows Runtime (incorporati o di terze parti), il sistema di compilazione C++/WinRT usa i .winmd file per generare tipi wrapper per rappresentare le parti proiettate di tali tipi di Windows Runtime.
  • Per semplificare l'implementazione dei propri tipi di Windows Runtime, il sistema di compilazione C++/WinRT trasforma il file IDL in un .winmd file e quindi lo usa per generare wrapper per la proiezione, nonché gli stub su cui basare l'implementazione (più avanti in questo argomento verranno descritti più avanti questi stub).

La versione specifica di IDL usata con C++/WinRT è Microsoft Interface Definition Language 3.0. Nella parte restante di questa sezione dell'argomento verrà esaminato in dettaglio il tipo MainPage C#. Decideremo quali parti di esso devono trovarsi nella proiezione del tipo MainPage C++/WinRT (ovvero, nella sua superficie chiamabile, o pubblica) e quali possono essere solo parte dell'implementazione. Questa distinzione è importante perché, quando ci troveremo a scrivere la nostra IDL (cosa che faremo nella sezione successiva), definiremo al suo interno solo le parti richiamabili.

I file di codice sorgente C# che insieme implementano il tipo MainPage sono: MainPage.xaml (che verrà convertito a breve, copiandolo), MainPage.xaml.cse SampleConfiguration.cs.

Nella versione C++/WinRT il tipo MainPage verrà inserito in file di codice sorgente in modo analogo. Prenderemo la logica in MainPage.xaml.cs e la trasferiremo in gran parte in MainPage.h e MainPage.cpp. E per la logica in SampleConfiguration.cs, lo traslaremo in SampleConfiguration.h e SampleConfiguration.cpp.

Le classi in un'applicazione C# di Piattaforma UWP (Universal Windows Platform) sono naturalmente tipi di Windows Runtime. Tuttavia, quando si crea un tipo in un'applicazione C++/WinRT, è possibile scegliere se tale tipo è un tipo Windows Runtime o una normale classe/struct/struct/enumerazione C++.

Qualsiasi pagina XAML nel progetto deve essere un tipo Windows Runtime, quindi MainPage deve essere un tipo Windows Runtime. Nel progetto C++/WinRT MainPage è già un tipo di Windows Runtime, quindi non è necessario modificarlo. In particolare, si tratta di una classe di runtime.

  • Per altre informazioni sul fatto che tu debba creare o meno una classe di runtime per un determinato tipo, vedi l'argomento Creare API con C++/WinRT.
  • In C++/WinRT, l'implementazione interna di una classe di runtime e le parti proiettate (pubbliche) di esso esistono sotto forma di due classi diverse. Questi tipi sono noti come tipo di implementazione e tipo proiettato. Puoi saperne di più nell'argomento menzionato nella voce precedente dell'elenco puntato e anche in Utilizzare API con C++/WinRT.
  • Per altre info sulla connessione tra classi di runtime e IDL (.idl file), è possibile leggere e seguire l'argomento Controlli XAML, associare a una proprietà C++/WinRT. In questo argomento viene illustrato il processo di creazione di una nuova classe di runtime, il primo passaggio del quale aggiungere un nuovo elemento Midl File (con estensione idl) al progetto.

Per MainPage, abbiamo effettivamente il file necessario MainPage.idl già nel progetto C++/WinRT. Questo perché il modello di progetto lo ha creato per noi. Più avanti in questa esercitazione guidata aggiungeremo altri file .idl al progetto.

Tra poco vedremo un elenco esatto dell'IDL che dobbiamo aggiungere al file esistente MainPage.idl. Prima di allora, dobbiamo ragionare su cosa debba essere incluso nell'IDL e cosa no.

Per determinare quali membri di MainPage dobbiamo dichiarare in MainPage.idl (in modo che entrino a far parte della classe di runtime MainPage) e quali possono invece essere semplicemente membri del tipo di implementazione MainPage, facciamo un elenco dei membri della classe C# MainPage. Si trovano i membri cercando in MainPage.xaml.cs e in SampleConfiguration.cs.

Troviamo un totale di dodici protectedprivate campi e metodi. E troviamo i membri seguenti public .

  • Il costruttore predefinito MainPage().
  • Campi statici Corrente e FEATURE_NAME.
  • Le proprietà IsClipboardContentChangedEnabled e Scenarios.
  • I metodi BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications e NotifyUser.

Si tratta dei public membri candidati per la dichiarazione in MainPage.idl. Esaminiamo quindi ognuno di essi e vediamo se devono far parte della classe di runtime MainPage o se devono far parte solo dell'implementazione.

  • Il costruttore predefinito MainPage(). Per una pagina XAML, è normale dichiarare un costruttore predefinito nel codice IDL. In questo modo, il framework dell'interfaccia utente XAML può attivare il tipo.
  • Il campo statico Current viene usato dall'interno delle singole pagine XAML dello scenario per accedere all'istanza dell'applicazione di MainPage. Poiché Current non viene usato per interagire con il framework XAML (né viene usato in unità di compilazione), è possibile riservarlo esclusivamente come membro del tipo di implementazione. Con i propri progetti in casi come questo, è possibile scegliere di farlo. Tuttavia, poiché il campo è un'istanza del tipo proiettato, sembra logico dichiararlo nel file IDL. Questo è ciò che faremo qui (e questo rende il codice leggermente più pulito).
  • Si tratta di un caso simile per il campo FEATURE_NAME statico, a cui si accede all'interno del tipo MainPage . Anche in questo caso, la scelta di dichiararla nel file IDL rende il codice leggermente più pulito.
  • La proprietà IsClipboardContentChangedEnabled viene utilizzata solo nella classe OtherScenarios . Quindi, durante il porting, semplificheremo leggermente le cose e lo renderemo un campo privato della classe di runtime OtherScenarios. Quindi quello non andrà nel file IDL.
  • La proprietà Scenarios è una raccolta di oggetti di tipo Scenario (un tipo menzionato in precedenza). Parleremo di Scenario nella prossima sottosezione, quindi per il momento lasciamo da parte anche la proprietà Scenarios.
  • I metodi BuildClipboardFormatsOutputString, DisplayToast e EnableClipboardContentChangedNotifications sono funzioni di utilità che si sentono più a che fare con lo stato generale dell'esempio rispetto alla pagina principale. Quindi, durante il porting, sposteremo il refactoring di questi tre metodi in un nuovo tipo di utilità denominato SampleState (che non deve necessariamente essere un tipo di Windows Runtime). Per questo motivo, questi tre metodi non verranno inseriti nel file IDL.
  • Il metodo NotifyUser viene chiamato dall'interno delle singole pagine XAML dello scenario nell'istanza di MainPage restituita dal campo corrente statico. Poiché (come già indicato) Current è un'istanza del tipo proiettato, è necessario dichiarare NotifyUser nel file IDL. NotifyUser accetta un parametro di tipo NotifyType. Questo argomento verrà illustrato nella sottosezione successiva.

Qualsiasi membro a cui si vuole eseguire il databind deve essere dichiarato anche in IDL (se si usa {x:Bind} o {Binding}). Per altre informazioni, vedi Data binding.

Stiamo procedendo: stiamo sviluppando un elenco dei membri da aggiungere e quali non aggiungere al MainPage.idl file. Tuttavia, è comunque necessario discutere della proprietà Scenarios e del tipo NotifyType . Quindi facciamolo ora.

IDL per i tipi Scenario e NotifyType

La classe Scenario è definita in SampleConfiguration.cs. Abbiamo una decisione su come convertire la classe in C++/WinRT. Per impostazione predefinita, probabilmente lo renderemmo un normale C++ struct. Tuttavia, se scenario viene usato tra i file binari o per interagire con il framework XAML, deve essere dichiarato in IDL come tipo di Windows Runtime.

Studiare il codice sorgente C#, si scopre che scenario viene usato in questo contesto.

<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;

Una raccolta di oggetti Scenario viene assegnata alla proprietà ItemsSource di un controllo ListBox , ovvero un controllo elementi. Poiché scenariodeve interagire con XAML, deve essere un tipo di Windows Runtime. Quindi deve essere definita in IDL. La definizione del tipo Scenario in IDL fa sì che il sistema di compilazione C++/WinRT generi automaticamente per te una definizione nel codice sorgente di Scenario in un file di intestazione interno (il cui nome e percorso non sono importanti per questa guida).

Si ricorderà inoltre che MainPage.Scenarios è una raccolta di oggetti Scenario , che abbiamo appena detto di dover essere in IDL. Per questo motivo, Anche MainPage.Scenarios deve essere dichiarato nel file IDL.

NotifyType è un enum oggetto dichiarato in C#.MainPage.xaml.cs Poiché si passa NotifyType a un metodo appartenente alla classe di runtime MainPage, NotifyType deve essere un tipo Windows Runtime e deve essere definito in MainPage.idl.

Ora aggiungiamo al file MainPage.idl i nuovi tipi e il nuovo membro di Mainpage che abbiamo deciso di dichiarare in IDL. Allo stesso tempo, rimuoveremo dal file IDL i membri segnaposto di Mainpage che il modello di progetto Visual Studio ci ha fornito.

Quindi, nel progetto C++/WinRT aprire MainPage.idle modificarlo in modo che abbia un aspetto simile al listato seguente. Si noti che una delle modifiche consiste nel cambiare il nome dello spazio dei nomi da Clipboard a SDKTemplate. Se si preferisce, è sufficiente sostituire l'intero contenuto di MainPage.idl con il codice seguente. Un'altra modifica alla nota consiste nel modificare il nome di Scenario::ClassType in 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

Per altre info sul contenuto di un .idl file in un progetto C++/WinRT, vedi Microsoft Interface Definition Language 3.0.

Nel tuo lavoro di porting, potresti non voler o non aver bisogno di modificare il nome dello spazio dei nomi come abbiamo fatto sopra. Questa operazione viene eseguita solo perché lo spazio dei nomi predefinito del progetto C# da convertire è SDKTemplate; mentre il nome del progetto e dell'assembly è Clipboard.

Tuttavia, mentre procediamo con il porting in questa procedura dettagliata, modificheremo nel codice sorgente ogni occorrenza del nome del namespace Clipboard in SDKTemplate. C'è anche un punto nelle proprietà del progetto C++/WinRT in cui compare il nome dello spazio dei nomi Clipboard, quindi cogliamo l'occasione per cambiarlo ora.

In Visual Studio, per il progetto C++/WinRT, impostare la proprietà del progetto Proprietà> comuniC++/WinRT>Root Namespace sul valore SDKTemplate.

Salvare il file IDL e generare nuovamente i file stub

L'argomento Controlli XAML; eseguire il binding a una proprietà C++/WinRT introduce la nozione di file stub, e fornisce una procedura dettagliata che ne mostra il funzionamento. Abbiamo anche menzionato gli stub in precedenza in questo argomento quando abbiamo accennato che il sistema di compilazione C++/WinRT trasforma il contenuto dei file .idl in metadati Windows, e quindi da tale strumento di metadati denominato cppwinrt.exe genera stub su cui puoi basare l'implementazione.

Ogni volta che aggiungi, rimuovi o modifichi qualcosa nel file IDL e compila, il sistema di compilazione aggiorna le implementazioni stub in tali file di stub. Pertanto, ogni volta che si modifica il file IDL e la compilazione, è consigliabile visualizzare i file di stub, copiare le firme modificate e incollarle nel progetto. Verranno forniti più specifici ed esempi di come farlo in un momento. Tuttavia, il vantaggio di eseguire questa operazione consiste nel fornire un modo senza errori di sapere in ogni momento quale forma del tipo di implementazione deve essere e quale firma dei relativi metodi dovrebbe essere.

A questo punto della procedura dettagliata, per il momento abbiamo finito di modificare il file MainPage.idl, quindi dovresti salvarlo ora. Il progetto non verrà compilato per il completamento al momento, ma l'esecuzione di una compilazione è utile perché rigenera i file stub per MainPage. Compilare quindi il progetto ora e ignorare eventuali errori di compilazione.

Per questo progetto C++/WinRT, i file stub vengono generati nella \Clipboard\Clipboard\Generated Files\sources cartella . Si troveranno lì dopo che la compilazione parziale è arrivata a un termine (anche in questo caso, come previsto, la compilazione non riuscirà completamente. Ma il passaggio a cui ci interessa, generando stub, avrà esito positivo. I file a cui si è interessati sono MainPage.h e MainPage.cpp.

In questi due file stub verranno visualizzate nuove implementazioni di stub dei membri di MainPage aggiunti al file IDL (Current e FEATURE_NAME, ad esempio). Dovrai copiare quelle implementazioni stub nei file MainPage.h e MainPage.cpp già presenti nel progetto. Allo stesso tempo, proprio come abbiamo fatto con il file IDL, verranno rimossi dai file esistenti i membri segnaposto di Mainpage che il modello di progetto Visual Studio ci ha dato (la proprietà fittizia denominata MyProperty e il gestore eventi denominato ClickHandler).

In effetti, l'unico membro della versione corrente di MainPage che vogliamo mantenere è il costruttore.

Dopo aver copiato i nuovi membri dai file stub, eliminati i membri non desiderati e aggiornato lo spazio dei nomi, i MainPage.h file e MainPage.cpp nel progetto dovrebbero essere simili agli elenchi di codice riportati di seguito. Si noti che esistono due tipi MainPage . Uno si trova nello spazio dei nomi implementation e un secondo nello spazio dei nomi factory_implementation. L'unica modifica apportata a factory_implementation consiste nell'aggiungere SDKTemplate al suo spazio dei nomi.

// 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();
    }
}

Per le stringhe, C# usa System.String. Per un esempio, vedere il metodo MainPage.NotifyUser . Nell'IDL dichiariamo una stringa con String e quando lo cppwinrt.exe strumento genera il codice C++/WinRT per noi, usa il tipo winrt::hstring . Ogni volta che si incontra una stringa nel codice C#, verrà convertito in winrt::hstring. Per altre info, vedi Gestione delle stringhe in C++/WinRT.

Per una spiegazione dei parametri const& nelle firme dei metodi, vedere Passaggio dei parametri.

Aggiornare tutte le restanti dichiarazioni e i riferimenti dello spazio dei nomi, quindi compilare

Prima di compilare il progetto C++/WinRT, individua eventuali dichiarazioni del namespace Clipboard e i relativi riferimenti, quindi sostituiscili con SDKTemplate.

  • MainPage.xaml e App.xaml. Lo spazio dei nomi viene visualizzato nei valori degli attributi x:Class e xmlns:local.
  • App.idl.
  • App.h.
  • App.cpp. Esistono due using namespace direttive (cercare la sottostringa using namespace Clipboard) e due qualifiche del tipo MainPage (cercare Clipboard::MainPage). Quelli vanno cambiati.

Poiché il gestore eventi è stato rimosso da MainPage, accedere MainPage.xaml ed eliminare anche l'elemento Button dal markup.

Salvare tutti i file. Pulire la soluzione (Compila>soluzione pulita) e quindi compilarla. Dopo aver apportato tutte le modifiche fin qui, esattamente come indicato, la compilazione dovrebbe andare a buon fine.

Implementare i membri MainPage dichiarati in IDL

Il costruttore, Current e FEATURE_NAME

Ecco il codice pertinente (dal progetto C#) che è necessario convertire.

<!-- 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";
...
}
...

A breve, riutilizzeremo MainPage.xaml nella sua interezza (copiandolo). Per il momento (di seguito), aggiungeremo temporaneamente un elemento TextBlock , con il nome appropriato, nel MainPage.xaml del progetto C++/WinRT.

FEATURE_NAME è un campo statico di MainPage (un campo C# const è essenzialmente statico nel suo comportamento), definito in SampleConfiguration.cs. Per C++/WinRT, invece di un campo (statico), lo renderemo l'espressione C++/WinRT di una proprietà di sola lettura (statica). Il metodo C++/WinRT per esprimere un getter di proprietà è una funzione che restituisce il valore della proprietà e non accetta parametri (una funzione di accesso). Di conseguenza, il campo statico C# FEATURE_NAME diventa la funzione di accesso statica C++/WinRT FEATURE_NAME (in questo caso, restituendo il valore letterale stringa).

In caso di conversione di una proprietà di sola lettura in C#, eseguiremo la stessa operazione. Per una proprietà scrivibile in C#, il modo C++/WinRT di esprimere un setter di proprietà è una void funzione che accetta il valore della proprietà come parametro (mutatore). In entrambi i casi, se il campo o la proprietà C# è statico/statica, allora anche l’accessor e/o il mutator C++/WinRT sono statici.

Current è un campo statico (non costante) di MainPage. Anche in questo caso, ne faremo una proprietà di sola lettura (nell’espressione C++/WinRT) e la renderemo nuovamente statica. Dove FEATURE_NAME è costante, Current non è . Quindi, in C++/WinRT sarà necessario un campo di supporto e il metodo di accesso restituirà tale campo. Quindi, nel progetto C++/WinRT dichiareremo in MainPage.h un campo statico privato denominato current, definiamo/inizializzeremo current in MainPage.cpp (perché ha una durata di archiviazione statica) e lo si accederà tramite una funzione di accesso statica pubblica denominata Current.

Il costruttore stesso esegue un paio di assegnazioni, che sono semplici da convertire.

Nel progetto C++/WinRT aggiungere un nuovo elemento Visual C++>Code>C++ File (.cpp) con il nome SampleConfiguration.cpp.

Modificare MainPage.xaml, MainPage.hMainPage.cpp, e SampleConfiguration.cpp in modo che corrispondano alle voci seguenti.

<!-- 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";
}

Assicurarsi inoltre di eliminare i corpi di funzione esistenti da MainPage.cpp per MainPage::Current() e MainPage::FEATURE_NAME(), perché questi metodi vengono ora definiti altrove.

Come si può notare, MainPage::current viene dichiarato come di tipo SDKTemplate::MainPage, ovvero il tipo proiettato. Non è di tipo SDKTemplate::implementation::MainPage, ovvero il tipo di implementazione. Il tipo proiettato è quello progettato per essere utilizzato all'interno del progetto per l'interoperabilità XAML o tra file binari. Il tipo di implementazione è quello usato per implementare le funzionalità esposte nel tipo proiettato. Poiché la dichiarazione di MainPage::current (in MainPage.h) viene visualizzata all'interno dello spazio dei nomi di implementazione (winrt::SDKTemplate::implementation), un MainPage non qualificato avrebbe fatto riferimento al tipo di implementazione. Quindi, usiamo il qualificatore SDKTemplate:: per chiarire che vogliamo che MainPage::current sia un'istanza del tipo proiettato winrt::SDKTemplate::MainPage.

Nel costruttore ci sono alcuni punti correlati a MainPage::current = *this; che meritano una spiegazione.

  • Quando si utilizza il puntatore this all'interno di un membro del tipo di implementazione, il puntatore this è ovviamente un puntatore al tipo di implementazione.
  • Per convertire il puntatore this nel tipo proiettato corrispondente, dereferenziarlo. Se si genera il tipo di implementazione da IDL (come illustrato qui), il tipo di implementazione ha un operatore di conversione che esegue la conversione nel tipo proiettato. È per questo che l'assegnazione qui funziona.

Per ulteriori informazioni su questi dettagli, consulta Creazione di istanze e restituzione di tipi e interfacce di implementazione.

Nel costruttore è presente anche SampleTitle().Text(FEATURE_NAME());. La parte SampleTitle() è una chiamata a una semplice funzione di accesso denominata SampleTitle, che restituisce il TextBlock che abbiamo aggiunto al codice XAML. Ogni volta che si x:Name un elemento XAML, il compilatore XAML genera per l'utente un metodo di accesso con il nome dell'elemento. La parte .Text(...) chiama la funzione mutatrice Text sull'oggetto TextBlock restituito dal metodo di accesso SampleTitle. E FEATURE_NAME() chiama la funzione di accesso statica MainPage::FEATURE_NAME per restituire la stringa letterale. Nel complesso, tale riga di codice imposta la proprietà Text di TextBlock denominata SampleTitle.

Si noti che, poiché in Windows Runtime le stringhe sono wide, per portare una stringa letterale la prefissiamo con il prefisso di codifica wide-char L. Di conseguenza, si cambia (ad esempio) "un valore letterale stringa" in L"un valore letterale stringa". Vedere anche Letterali di stringa estesi.

Scenari

Ecco il codice C# pertinente che è necessario convertire.

// 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) }
    };
...
}
...

Dalla nostra precedente analisi, sappiamo che questa raccolta di oggetti Scenario viene visualizzata in un ListBox. In C++/WinRT esistono limiti al tipo di raccolta che è possibile assegnare alla proprietà ItemsSource di un controllo elementi. La raccolta deve essere un vettore o un vettore osservabile e i relativi elementi devono essere uno dei seguenti:

Nel caso di IInspectable, se gli elementi non sono essi stessi classi di runtime, tali elementi devono essere di un tipo che possa essere sottoposto a boxing e unboxing in e da IInspectable. Ciò significa che devono essere tipi di Windows Runtime (vedi Boxing e unboxing dei valori in IInspectable).

Per questo case study non è stata creata una classe di runtime di Scenario . Questa è comunque un'opzione ragionevole. E ci saranno casi, nel vostro stesso lavoro di porting, in cui una classe runtime sarà senz'altro la scelta giusta. Ad esempio, se è necessario rendere osservabile il tipo di elemento (vedi controlli XAML; associazione a una proprietà C++/WinRT), oppure se l'elemento deve avere metodi per qualsiasi altro motivo ed è più di un semplice insieme di membri dati.

Poiché, in questa procedura dettagliata, non useremo una classe di runtime per il tipo Scenario, dobbiamo quindi considerare il boxing. Se avessimo reso Scenario un normale oggetto C++ struct, allora non saremmo stati in grado di inscatolarlo. Ma abbiamo dichiarato Scenario come struct in IDL, e quindi lo possiamo boxare.

Ci resta la scelta se eseguire il boxing di Scenario in anticipo oppure aspettare finché non stiamo per assegnarli a ItemsSource ed effettuarne il boxing secondo una logica just-in-time. Ecco alcune considerazioni relative a queste due opzioni.

  • Confezionamento anticipato. Per questa opzione, il nostro membro dati è una raccolta di IInspectable pronta per essere assegnata all'interfaccia utente. Durante l'inizializzazione, gli oggetti Scenario vengono inseriti in tale membro dati. Ci serve una sola copia di quella raccolta, ma dobbiamo eseguire l'unboxing di un elemento ogni volta che dovevamo leggerne i campi.
  • Inscatolamento just-in-time. Per questa opzione, il nostro elemento dati è una raccolta di Scenario. Quando arriva il momento di assegnare all'interfaccia utente, gli oggetti Scenario del membro dati vengono inseriti in una nuova raccolta di IInspectable. Possiamo leggere i campi degli elementi nel membro di dati senza effettuare l'unboxing, ma servono due copie della raccolta.

Come si può notare, per una piccola collezione come questa, i pro e i contro si equivalgono. Quindi, per questo caso di studio, sceglieremo l'opzione just-in-time.

Il membro degli scenari è un campo di MainPage, definito e inizializzato in SampleConfiguration.cs. E Scenarios è una proprietà di sola lettura di MainPage, definita in MainPage.xaml.cs (e implementata per restituire semplicemente il campo degli scenari ). Faremo qualcosa di simile nel progetto C++/WinRT; ma i due membri saranno statici (poiché è necessaria una sola istanza nell'applicazione e in modo che sia possibile accedervi senza bisogno di un'istanza di classe). E li chiameremo scenariosInner e scenarios, rispettivamente. Dichiareremo scenariosInner in MainPage.h. Inoltre, poiché ha una durata di archiviazione statica, verrà definita/inizializzata in un .cpp file (SampleConfiguration.cppin questo caso).

Modificare MainPage.h e SampleConfiguration.cpp in modo che corrispondano alle liste riportate di seguito.

// 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>() },
});

Inoltre, assicurati di eliminare il corpo esistente della funzione in MainPage.cpp per MainPage::scenarios(), perché ora definiamo quel metodo nel file header.

Come si può notare, in SampleConfiguration.cpp, inizializziamo il membro dati statico scenariosInner chiamando una funzione di supporto C++/WinRT denominata winrt::single_threaded_observable_vector. Tale funzione crea automaticamente un nuovo oggetto raccolta Windows Runtime e lo restituisce come interfaccia IObservableVector. Poiché, in questo esempio, la raccolta non è osservabile (non è necessario, perché non aggiunge né rimuove elementi dopo l'inizializzazione), è possibile che sia stato scelto di chiamare winrt::single_threaded_vector. Tale funzione restituisce la raccolta come interfaccia IVector .

Per altre informazioni sulle raccolte e sul relativo binding, vedi Controlli elenco XAML; binding a una raccolta C++/WinRT, e Raccolte con C++/WinRT.

Il codice di inizializzazione appena aggiunto fa riferimento a tipi non ancora presenti nel progetto( ad esempio winrt::SDKTemplate::CopyText. Per risolvere questo errore, andiamo avanti e aggiungiamo cinque nuove pagine XAML vuote al progetto.

Aggiungere cinque nuove pagine XAML vuote

Aggiungere al progetto un nuovo elemento Visual C++>Blank Page (C++/WinRT) (assicurarsi che si tratti del modello di elemento Blank Page (C++/WinRT) e non di Blank Page). Chiamalo CopyText. La nuova pagina XAML viene definita all'interno dello spazio dei nomi SDKTemplate , che è ciò che vogliamo.

Ripetere il processo precedente altre quattro volte e assegnare alle pagine XAML i nomi CopyImage, CopyFiles, HistoryAndRoaming e OtherScenarios.

A questo punto sarà possibile eseguire di nuovo la compilazione, se lo si desidera.

NotifyUser

Nel progetto C# è disponibile l'implementazione del metodo MainPage.NotifyUser in MainPage.xaml.cs. MainPage.NotifyUser ha una dipendenza da MainPage.UpdateStatus e tale metodo a sua volta ha dipendenze da elementi XAML non ancora trasferiti. Quindi, per il momento, ci limiteremo a creare uno stub di un metodo UpdateStatus nel progetto C++/WinRT, e ne faremo il porting più avanti.

Ecco il codice C# pertinente che è necessario convertire.

// 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 invia gli aggiornamenti dell'interfaccia utente al thread principale. In WinUI 3 questo usa Microsoft. UI. Dispatching.DispatcherQueue anziché l'oggetto precedenteCoreDispatcher. In C++/WinRT, ogni volta che vuoi usare un tipo da uno spazio dei nomi Windows o Microsoft, devi includere il file di intestazione dello spazio dei nomi C++/WinRT corrispondente (per altre info su questo, vedi Introduzione a C++/WinRT). In questo caso, come si vedrà nell'elenco di codice riportato di seguito, l'intestazione è winrt/Microsoft.UI.Dispatching.he verrà inclusa in pch.h.

UpdateStatus è privato. Si creerà quindi un metodo privato nel tipo di implementazione MainPage . UpdateStatus non deve essere chiamato nella classe di runtime, quindi non verrà dichiarato in IDL.

Dopo aver portato MainPage.NotifyUser e aver creato uno stub per MainPage.UpdateStatus, ecco cosa abbiamo nel progetto C++/WinRT. Dopo questo listato di codice, verranno esaminati alcuni dettagli.

// 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();
}
...

In C# è possibile usare la notazione punto per aggiungere punti alle proprietà annidate. Il tipo MainPage C# può quindi accedere alla propria proprietà Dispatcher con la sintassi Dispatcher. E C# può aggiungere altri punti a tale valore con la sintassi, Dispatcher.HasThreadAccessad esempio . In C++/WinRT le proprietà vengono implementate come funzioni di accesso, quindi la sintassi è diversa solo in quanto si aggiungono parentesi per ogni chiamata di funzione.

C# C++/WinRT
Dispatcher.HasThreadAccess DispatcherQueue().HasThreadAccess()

Quando la versione C# di NotifyUser chiama Dispatcher.RunAsync, l'equivalente di WinUI 3 usa DispatcherQueue.TryEnqueue. La versione C++/WinRT implementa il delegato di callback come funzione lambda. In C++/WinRT vengono acquisiti i due parametri che verranno usati, nonché il this puntatore (poiché chiameremo una funzione membro). Sono disponibili altre informazioni sull'implementazione di delegati come espressioni lambda ed esempi di codice nell'argomento Gestire gli eventi usando delegati in C++/WinRT.

Implementare i membri MainPage rimanenti

Verrà ora creato un elenco completo dei membri di MainPage (implementati in MainPage.xaml.cs e SampleConfiguration.cs) in modo da poter vedere quali sono stati trasferiti finora e quali sono ancora da fare.

Membro Access Status
Costruttore MainPage public Portato
Proprietà attuale public Portato
proprietà FEATURE_NAME public Portato
Proprietà IsClipboardContentChangedEnabled public Non avviato
Proprietà Scenari public Portato
Metodo BuildClipboardFormatsOutputString public Non avviato
metodo DisplayToast public Non avviato
metodo EnableClipboardContentChangedNotifications public Non avviato
NotifyUser metodo public Portato
Metodo OnNavigatedTo protected Non avviato
campo isApplicationWindowActive private Non avviato
needToPrintClipboardFormat campo private Non avviato
campo scenari private Portato
metodo Button_Click private Non avviato
metodo DisplayChangedFormats private Non avviato
metodo Footer_Click private Non avviato
metodo HandleClipboardChanged private Non avviato
Metodo OnClipboardChanged private Non avviato
Metodo OnWindowActivated private Non avviato
metodo ScenarioControl_SelectionChanged private Non avviato
UpdateStatus, metodo private Abbozzato

Parleremo quindi dei membri non ancora portati nelle prossime sottosezioni.

Note

Di tanto in tanto, verranno visualizzati i riferimenti nel codice sorgente agli elementi dell'interfaccia utente nel markup XAML (in MainPage.xaml). Man mano che arriviamo a questi riferimenti, le risolveremo temporaneamente aggiungendo semplici elementi segnaposto al codice XAML. In questo modo, il progetto continuerà a eseguire la compilazione al termine di ogni sottosezione. L'alternativa consiste nel risolvere i riferimenti copiando ora l'intero contenuto del MainPage.xaml progetto C# nel progetto C++/WinRT. Ma se lo facciamo allora sarà molto tempo prima di poter tornare a un pit stop e costruire di nuovo (quindi potenzialmente nascondendo eventuali errori di digitazione o altri errori che facciamo lungo la strada).

Una volta completato il porting del codice imperativo per la classe MainPage, poi copieremo i contenuti del file XAML e avremo la certezza che il progetto verrà ancora compilato.

IsClipboardContentChangedEnabled

Si tratta di una proprietà C# get-set che per impostazione predefinita è false. È un membro di MainPage ed è definito in SampleConfiguration.cs.

Per C++/WinRT, serviranno una funzione di accesso, una funzione di modifica e un membro dati di supporto come campo. Poiché IsClipboardContentChangedEnabled rappresenta lo stato di uno degli scenari nell'esempio, anziché lo stato di MainPage stesso, verranno creati i nuovi membri in un nuovo tipo di utilità denominato SampleState. Verrà inoltre implementato nel SampleConfiguration.cpp file di codice sorgente e verranno apportati i membri static (poiché è necessaria una sola istanza nell'applicazione e in modo che sia possibile accedervi senza bisogno di un'istanza di classe).

Per accompagnare il nostro SampleConfiguration.cpp nel progetto C++/WinRT, aggiungere un nuovo elemento Visual C++>Codice>File di intestazione (.h) con il nome SampleConfiguration.h. Modificare SampleConfiguration.h e SampleConfiguration.cpp in modo che corrispondano alle liste riportate di seguito.

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

Ancora una volta, un campo con archiviazione static (come SampleState::isClipboardContentChangedEnabled) deve essere definito una sola volta nell'applicazione e un file .cpp è un buon posto per farlo (SampleConfiguration.cpp in questo caso).

BuildClipboardFormatsOutputString

Questo metodo è un membro pubblico di MainPage ed è definito in 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();
}
...

In C++/WinRT creeremo BuildClipboardFormatsOutputString un metodo statico pubblico di SampleState. Possiamo renderlo static perché non accede ad alcun membro di istanza.

Per usare i tipi Clipboard e DataPackageView in C++/WinRT, è necessario includere il file winrt/Windows.ApplicationModel.DataTransfer.hdi intestazione dello spazio dei nomi C++/WinRT Windows .

In C#, la proprietà DataPackageView.AvailableFormats è un oggetto IReadOnlyList, in modo da poter accedere alla proprietà Count di tale proprietà. In C++/WinRT la funzione di accesso DataPackageView::AvailableFormats restituisce un oggetto IVectorView, che dispone di una funzione di accesso Size che è possibile chiamare.

Per convertire l'uso del tipo C# System.Text.StringBuilder , verrà usato il tipo C++ standard std::wostringstream. Questo tipo è un flusso di output per stringhe wide (e per usarlo dovremo includere il file di intestazione sstream). Anziché usare un metodo Append come si fa con stringBuilder, si usa l'operatore di inserimento (<<) con un flusso di output, ad esempio wostringstream. Per altre info, vedi Programmazione iostream e Formattazione di stringhe C++/WinRT.

Il codice C# costruisce stringBuilder con la new parola chiave . In C# gli oggetti sono tipi di riferimento per impostazione predefinita, dichiarati nell'heap con new. Nel moderno C++ standard, gli oggetti sono tipi di valore per impostazione predefinita, allocati sullo stack (senza usare new). Quindi portiamo StringBuilder output = new StringBuilder(); in C++/WinRT come semplicemente std::wostringstream output;.

La parola chiave C# var chiede al compilatore di dedurre un tipo. Esegui il porting di var a auto con C++/WinRT. Ma in C++/WinRT, esistono casi in cui (per evitare copie) si vuole un riferimento a un tipo dedotto (o dedotto) e si esprime un riferimento lvalue a un tipo dedotto con auto&. Esistono anche casi in cui si desidera un tipo speciale di riferimento che si associa correttamente se viene inizializzato con un lvalue o con un rvalue. E lo esprimete con auto&&. È la forma che vedi usata nel ciclo for nel codice convertito riportato di seguito. Per un'introduzione a lvalue e rvalue, vedere Categorie di valori e riferimenti a tali valori.

Modificare pch.h, SampleConfiguration.he SampleConfiguration.cpp in modo che corrispondano alle voci seguenti.

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

La sintassi nella riga di codice DataPackageView clipboardContent{ Clipboard::GetContent() }; usa una funzionalità di C++ standard moderna denominata inizializzazione uniforme, con la sua caratteristica di usare parentesi graffe anziché un = segno. Tale sintassi rende chiaro che l'inizializzazione, anziché l'assegnazione, avviene. Se si preferisce la forma di sintassi simile all'assegnazione (ma in realtà non è), è possibile sostituire la sintassi precedente con l'equivalente DataPackageView clipboardContent = Clipboard::GetContent();. È consigliabile acquisire familiarità con entrambi i modi per esprimere l'inizializzazione, tuttavia, perché è probabile che entrambi vengano usati di frequente nel codice riscontrato.

DisplayToast

DisplayToast è un metodo statico pubblico della classe MainPage C# e lo si troverà definito in SampleConfiguration.cs. In C++/WinRT lo renderemo un metodo statico pubblico di SampleState.

Sono già stati rilevati la maggior parte dei dettagli e delle tecniche rilevanti per la conversione di questo metodo. Una novità da notare è che si porta una stringa letterale verbatim di C# (@) a una stringa letterale raw standard di C++ (LR).

Inoltre, quando fai riferimento ai tipi ToastNotification e XmlDocument in C++/WinRT, puoi qualificarli in base al nome dello spazio dei nomi oppure puoi modificare SampleConfiguration.cpp e aggiungere using namespace direttive come l'esempio seguente.

using namespace Windows::UI::Notifications;

Si ha la stessa scelta quando si fa riferimento al tipo XmlDocument e ogni volta che si fa riferimento a qualsiasi altro tipo di Windows Runtime.

Oltre a questi elementi, seguire le stesse indicazioni eseguite in precedenza per eseguire i passaggi seguenti.

  • Dichiarare il metodo in SampleConfiguration.he definirlo in SampleConfiguration.cpp.
  • Modifica pch.h per includere i file di intestazione necessari dello spazio dei nomi Windows C++/WinRT.
  • Costruisci oggetti C++/WinRT nello stack, non nell'heap.
  • Sostituire le chiamate agli accessor get di proprietà con la sintassi di chiamata di funzione (()).

Una causa molto comune degli errori del compilatore/linker è dimenticare di includere i file header del namespace Windows di C++/WinRT necessari. Per altre informazioni su un possibile errore, vedere C3779: Perché il compilatore segnala l'errore «consume_Something: una funzione che restituisce 'auto' non può essere usata prima di essere definita»?.

Se vuoi seguire la guida dettagliata ed eseguire tu stesso il porting di DisplayToast, puoi confrontare i tuoi risultati con il codice della versione C++/WinRT nel file ZIP del codice sorgente dell'esempio Clipboard sample che hai scaricato.

EnableClipboardContentChangedNotifications

EnableClipboardContentChangedNotifications è un metodo statico pubblico della classe MainPage C# ed è definito in 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) { ... }
...

In C++/WinRT lo renderemo un metodo statico pubblico di SampleState.

In C# si usa la sintassi dell'operatore += e -= per registrare e revocare i delegati di gestione degli eventi. In C++/WinRT sono disponibili diverse opzioni sintattiche per registrare/revocare un delegato, come descritto in Gestire gli eventi usando delegati in C++/WinRT. Tuttavia, lo schema generale è che la registrazione e la revoca avvengono tramite chiamate a una coppia di funzioni i cui nomi richiamano l'evento. Per eseguire la registrazione, si passa il delegato alla funzione di registrazione e si ottiene in cambio un token di revoca (winrt::event_token). Per revocarlo, è necessario passare quel token alla funzione di revoca. In questo caso, il gestore è statico e, come si può vedere nel seguente listato di codice, la sintassi della chiamata di funzione è lineare.

I token simili vengono effettivamente usati, dietro le quinte, in C#. Ma il linguaggio rende implicito questo dettaglio. C++/WinRT lo rende esplicito.

Il tipo di oggetto viene visualizzato nelle firme del gestore eventi C#. Nel linguaggio C# l'oggetto è un alias per il .NET tipo System.Object. L'equivalente in C++/WinRT è winrt::Windows::Foundation::IInspectable. Quindi, vedrai IInspectable nei gestori eventi C++/WinRT.

Modificare SampleConfiguration.h e SampleConfiguration.cpp in modo che corrispondano alle liste riportate di seguito.

// 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){}

Lasciare i delegati di gestione degli eventi stessi (OnClipboardChanged e OnWindowActivated) come stub per il momento. Sono già presenti nell'elenco dei membri da convertire, quindi verranno visualizzati nelle sottosezioni successive.

OnNavigatedTo

OnNavigatedTo è un metodo protetto della classe MainPage C# ed è definito in MainPage.xaml.cs. Eccolo, insieme a XAML ListBox a cui fa riferimento.

<!-- 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;
    }
}

È un metodo importante e interessante, perché ecco dove viene assegnata la raccolta di oggetti Scenario all'interfaccia utente. Il codice C# compila un oggetto System.Collections.Generic.List di oggetti Scenario e lo assegna alla proprietà ItemsSource di un controllo ListBox , ovvero un controllo elementi. Inoltre, in C#, viene usata l'interpolazione di stringhe per compilare il titolo per ogni oggetto Scenario (si noti l'uso del $ carattere speciale).

In C++/WinRT si creerà OnNavigatedTo un metodo pubblico di MainPage. Si aggiungerà un elemento ListBox stub al codice XAML in modo che una compilazione abbia esito positivo. Dopo l'elenco di codice, verranno esaminati alcuni dettagli.

<!-- 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);
    }
}
...

Ancora una volta viene chiamata la funzione winrt::single_threaded_observable_vector , ma questa volta per creare una raccolta di IInspectable. Questo faceva parte della decisione che abbiamo preso di effettuare il boxing degli oggetti Scenario in modalità just-in-time.

Al posto dell'uso dell'interpolazione di stringhe in C#, viene usata una combinazione della funzione to_hstring e dell'operatore di concatenazione di winrt::hstring.

isApplicationWindowActive

In C# isApplicationWindowActive è un campo privato bool semplice appartenente alla classe MainPage ed è definito in SampleConfiguration.cs. Il valore predefinito è false. In C++/WinRT lo renderemo un campo statico privato di SampleState (per i motivi già descritti) nei SampleConfiguration.h file e SampleConfiguration.cpp , con lo stesso valore predefinito.

Si è già visto come dichiarare, definire e inizializzare un campo statico. Per ripassare, ripensa a ciò che abbiamo fatto con il campo isClipboardContentChangedEnabled e fai lo stesso con isApplicationWindowActive.

needToPrintClipboardFormat

Stesso modello di isApplicationWindowActive (vedere l'intestazione immediatamente prima di questa).

Button_Click

Button_Click è un metodo privato (gestione degli eventi) della classe MainPage C# ed è definito in MainPage.xaml.cs. In questo caso, insieme al controllo SplitView XAML a cui fa riferimento e al controllo ToggleButton che lo registra.

<!-- MainPage.xaml -->
...
<SplitView x:Name="Splitter" ... />
...
<ToggleButton Click="Button_Click" .../>
...
private void Button_Click(object sender, RoutedEventArgs e)
{
    Splitter.IsPaneOpen = !Splitter.IsPaneOpen;
}

Ed ecco l'equivalente, convertito in C++/WinRT. Tieni presente che nella versione C++/WinRT il gestore eventi è public (come puoi vedere, lo dichiari prima delle private:dichiarazioni). Ciò è dovuto al fatto che un gestore eventi registrato nel markup XAML, come questo, deve trovarsi public in C++/WinRT per consentire al markup XAML di accedervi. D'altra parte, se si registra un gestore eventi nel codice imperativo (come in MainPage::EnableClipboardContentChangedNotifications in precedenza), il gestore eventi non deve essere 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

In C# DisplayChangedFormats è un metodo privato appartenente alla classe MainPage ed è definito in SampleConfiguration.cs.

private void DisplayChangedFormats()
{
    string output = "Clipboard content has changed!" + Environment.NewLine;
    output += BuildClipboardFormatsOutputString();
    NotifyUser(output, NotifyType.StatusMessage);
}

In C++/WinRT lo renderemo un campo statico privato di SampleState (non accede ad alcun membro dell'istanza), nei SampleConfiguration.h file e SampleConfiguration.cpp . Il codice C# per questo metodo non usa System.Text.StringBuilder; ma fa abbastanza formattazione di stringa che per la versione C++/WinRT questo è un altro buon posto per usare std::wostringstream.

Anziché la proprietà static System.Environment.NewLine , usata nel codice C#, verrà inserito il C++ std::endl standard (un carattere di nuova riga) nel flusso di output.

// 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);
}

C'è una piccola inefficienza nella progettazione della versione C++/WinRT precedente. Prima di tutto, creiamo un oggetto std::wostringstream. Ma chiamiamo anche il metodo BuildClipboardFormatsOutputString (che abbiamo convertito in precedenza). Questo metodo crea il proprio std::wostringstream. E trasforma il flusso in un winrt::hstring e lo restituisce . Chiamiamo la funzione hstring::c_str per trasformare la funzione hstring restituita in una stringa di tipo C e quindi la inserisciamo nel flusso. Sarebbe più efficiente creare un solo std::wostringstream e passarne un riferimento ai vari metodi, in modo che possano inserirvi direttamente le stringhe.

È quello che facciamo nella versione C++/WinRT del codice sorgente del codice di esempio Clipboard (nel file ZIP che hai scaricato). Nel codice sorgente è disponibile un nuovo metodo statico privato denominato SampleState::AddClipboardFormatsOutputString, che accetta e opera su un riferimento a un flusso di output. E poi i metodi SampleState::DisplayChangedFormats e SampleState::BuildClipboardFormatsOutputString vengono quindi rifattorizzati per chiamare quel nuovo metodo. È funzionalmente equivalente agli elenchi di codice in questo argomento, ma è più efficiente.

Footer_Click è un gestore eventi asincrono appartenente alla classe MainPage C# ed è definito in MainPage.xaml.cs. Il listato di codice seguente è funzionalmente equivalente al metodo nel codice sorgente scaricato. Ma qui l'ho decompressa da una riga a quattro, per rendere più semplice vedere cosa sta facendo, e di conseguenza come dovremmo convertirlo.

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);
}

Anche se, tecnicamente, il metodo è asincrono, non esegue alcuna operazione dopo , awaitquindi non ha bisogno di await (né la async parola chiave ). Probabilmente li usa per evitare il messaggio IntelliSense in Visual Studio.

Il metodo C++/WinRT equivalente sarà anche asincrono (perché chiama Launcher.LaunchUriAsync). Ma non è necessario né co_await né restituire un oggetto asincrono. Per informazioni su co_await e oggetti asincroni, vedi Concorrenza e operazioni asincrone con C++/WinRT.

A questo punto si parlerà di cosa sta facendo il metodo. Poiché si tratta di un gestore eventi per l'evento Click di un oggetto HyperlinkButton, l'oggetto denominato sender è effettivamente HyperlinkButton. Pertanto, la conversione del tipo è sicura (in alternativa è possibile che questa conversione sia espressa come sender as HyperlinkButton). Si recupera quindi il valore della proprietà Tag (se si esamina il markup XAML nel progetto C#, si noterà che questa proprietà è impostata su una stringa che rappresenta un URL Web). Anche se la proprietà FrameworkElement.Tag (HyperlinkButton è un FrameworkElement) è di tipo object, in C# è possibile eseguire una stringa che con Object.ToString. Dalla stringa risultante si costruisce un oggetto Uri . Infine (con l'aiuto della shell) avvieremo un browser e passiamo all'URL.

Ecco il metodo convertito in C++/WinRT (ancora una volta espanso per maggiore chiarezza), dopo di che è una descrizione dei dettagli.

// 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);
}

Come sempre, viene creato il gestore eventi public. Usiamo la funzione as sull'oggetto sender per convertirlo in HyperlinkButton. In C++/WinRT, la proprietà Tag è un IInspectable (l'equivalente di Object). Ma non c'è Tostring su IInspectable. Dobbiamo invece estrarre il contenuto di IInspectable in un valore scalare (in questo caso, una stringa). Anche in questo caso, per ulteriori informazioni sul boxing e sull'unboxing, vedere Conversione boxing e unboxing dei valori in IInspectable.

Le ultime due righe ripetono i modelli di conversione illustrati in precedenza e riecheggiano praticamente la versione C#.

HandleClipboardChanged

Non c'è niente di nuovo coinvolto nella conversione di questo metodo. È possibile confrontare le versioni C# e C++/WinRT nello ZIP del codice sorgente dell’esempio Clipboard che hai scaricato.

OnClipboardChanged e OnWindowActivated

Finora abbiamo solo stub vuoti per questi due gestori di eventi. Ma la conversione è semplice e non genera nulla di nuovo da discutere.

ScenarioControl_SelectionChanged

Si tratta di un altro gestore eventi privato appartenente alla classe MainPage C# e definito in MainPage.xaml.cs. In C++/WinRT lo renderemo pubblico e lo implementeremo in MainPage.h e MainPage.cpp.

Per questo metodo, sarà necessario MainPage::navigating, ovvero un campo booleano privato, inizializzato in false. E sarà necessario un frame in MainPage.xaml, denominato ScenarioFrame. Tuttavia, a parte questi dettagli, la conversione di questo metodo non rivela nuove tecniche.

Se, invece di effettuare manualmente il porting, stai copiando il codice dalla versione C++/WinRT nello ZIP del codice sorgente del sample Appunti che hai scaricato, vedrai che lì viene usato MainPage::NavigateTo. Per il momento, è sufficiente eseguire il refactoring del contenuto di NavigateTo in ScenarioControl_SelectionChanged.

UpdateStatus

Finora è disponibile solo uno stub per MainPage.UpdateStatus. Il porting della sua implementazione, ancora una volta, riprende in gran parte concetti già noti. Un nuovo punto da notare è che mentre in C# è possibile confrontare una stringa con String.Empty, In C++/WinRT viene invece chiamata la funzione winrt::hstring::empty . Un altro è che nullptr è l'equivalente standard in C++ di null di C#.

Puoi completare il resto del porting con tecniche che abbiamo già illustrato. Ecco un elenco delle attività da svolgere prima che la versione sottoposta a porting di questo metodo possa essere compilata.

  • Per aggiungere un Border denominato StatusBorder a MainPage.xaml.
  • Per MainPage.xamlaggiungere un controllo TextBlock denominato StatusBlock.
  • Per MainPage.xamlaggiungere uno StackPanel denominato StatusPanel.
  • Per pch.haggiungere #include "winrt/Microsoft.UI.Xaml.Media.h".
  • Per pch.haggiungere #include "winrt/Microsoft.UI.Xaml.Automation.Peers.h".
  • Per MainPage.cpp aggiungere using namespace winrt::Microsoft::UI::Xaml::Media;.
  • Per MainPage.cpp aggiungere using namespace winrt::Microsoft::UI::Xaml::Automation::Peers;.

Copiare il codice XAML e gli stili necessari per completare la conversione di MainPage

Per XAML, il caso ideale è che puoi usare lo stesso markup XAML in un progetto C# e C++/WinRT. E l'esempio degli Appunti è uno di questi casi.

Nel file Styles.xaml, l'esempio Clipboard contiene un ResourceDictionary di stili XAML, applicati ai pulsanti, ai menu e ad altri elementi dell'interfaccia utente in tutta l'interfaccia utente dell'applicazione. La Styles.xaml pagina viene unita a App.xaml. E poi c'è il punto di partenza standard MainPage.xaml per l'interfaccia utente, che abbiamo già visto brevemente. È ora possibile riutilizzare questi tre .xaml file, invariati, nella versione C++/WinRT del progetto.

Come per i file di asset, puoi scegliere di fare riferimento agli stessi file XAML condivisi da più versioni dell'applicazione. In questa procedura dettagliata, solo per semplicità, i file verranno copiati nel progetto C++/WinRT e aggiunti in questo modo.

Passare alla \Clipboard_sample\SharedContent\xaml cartella, selezionare e copiare App.xaml e MainPage.xamle quindi incollare i due file nella \Clipboard\Clipboard cartella nel progetto C++/WinRT, scegliendo di sostituire i file quando richiesto.

Nel progetto C++/WinRT in Visual Studio fare clic su Mostra tutti i file per attivarlo. Aggiungere ora una nuova cartella, immediatamente sotto il nodo del progetto e denominarla Styles. In Esplora file passare alla \Clipboard_sample\SharedContent\xaml cartella, selezionare e copiare Styles.xamle incollarlo nella \Clipboard\Clipboard\Styles cartella appena creata. Di nuovo in Esplora soluzioni nel progetto C++/WinRT, fare clic con il pulsante destro del mouse sulla cartella Styles>Aggiungi>Elemento esistente... e passare a \Clipboard\Clipboard\Styles. Nella selezione file selezionare Styles e fare clic su Aggiungi.

Aggiungere una nuova cartella al progetto C++/WinRT, immediatamente sotto il nodo del progetto e denominata Styles. Passare alla \Clipboard_sample\SharedContent\xaml cartella, selezionare e copiare Styles.xamle incollarlo nella \Clipboard\Clipboard\Styles cartella nel progetto C++/WinRT. Fare clic con il pulsante destro del mouse sulla Styles cartella (in Esplora soluzioni nel progetto C++/WinRT) >Aggiungi>elemento esistente e passare a \Clipboard\Clipboard\Styles. Nella selezione file selezionare Styles e fare clic su Aggiungi.

Fare di nuovo clic su Mostra tutti i file per disattivarlo.

La conversione di MainPage è stata completata e, se è stata eseguita la procedura, il progetto C++/WinRT verrà ora compilato ed eseguito.

Consolida i tuoi .idl file

Oltre al punto di partenza standard MainPage.xaml per l'interfaccia utente, l'esempio degli Appunti include cinque altre pagine XAML specifiche dello scenario, insieme ai file code-behind corrispondenti. Useremo nuovamente il markup XAML effettivo di tutte queste pagine, senza modifiche, nella versione C++/WinRT del progetto. E vedremo come portare il code-behind nelle prossime sezioni principali. Ma prima di tutto, parliamo di IDL.

È utile consolidare l'IDL delle classi di runtime in un unico file IDL. Per informazioni su tale valore, vedi Suddividere le classi di runtime in file MIDL (.idl). Successivamente consolideremo i contenuti di CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl e OtherScenarios.idl spostando quell'IDL in un unico file denominato Project.idl (e quindi eliminando i file originali).

Mentre lo facciamo, rimuoviamo anche la proprietà fittizia generata automaticamente (Int32 MyProperty;e la relativa implementazione) da ognuno di questi cinque tipi di pagina XAML.

Aggiungere prima di tutto un nuovo elemento Midl File (con estensione idl) al progetto C++/WinRT. Chiamalo Project.idl. Sostituire l'intero contenuto di Project.idl con il codice seguente.

// 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();
    }
}

Come si può notare, si tratta solo di una copia del contenuto dei singoli .idl file, tutti all'interno di uno spazio dei nomi e con MyProperty rimosso da ogni classe di runtime.

In Esplora soluzioni in Visual Studio, selezionare più tutti i file IDL originali (CopyFiles.idl, CopyImage.idl, CopyText.idlHistoryAndRoaming.idl, e OtherScenarios.idl) e Modifica>Rimuovi (scegliere Elimina nella finestra di dialogo).

Infine, e per completare la rimozione di MyProperty, nei file .h e .cpp per ciascuno di quegli stessi cinque tipi di pagina XAML, eliminate le dichiarazioni e le definizioni delle funzioni di accesso int32_t MyProperty() e di modifica void MyProperty(int32_t).

Per inciso, è sempre consigliabile avere il nome dei file XAML corrispondenti al nome della classe rappresentata. Ad esempio, se si ha x:Class="MyNamespace.MyPage" in un file di markup XAML, tale file deve essere denominato MyPage.xaml. Anche se questo non è un requisito tecnico, non dover gestire nomi diversi per lo stesso artefatto renderà il progetto più comprensibile, più manutenibile e più facile da gestire.

CopyFiles

Nel progetto C# il tipo di pagina XAML CopyFiles viene implementato nei file di CopyFiles.xaml codice sorgente e CopyFiles.xaml.cs . Esaminiamo a sua volta ognuno dei membri di CopyFiles .

rootPage

Si tratta di un campo privato.

// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
    MainPage rootPage = MainPage.Current;
    ...
}
...

In C++/WinRT è possibile definirlo e inizializzarlo in questo modo.

// CopyFiles.h
...
struct CopyFiles : CopyFilesT<CopyFiles>
{
    ...
private:
    SDKTemplate::MainPage rootPage{ MainPage::Current() };
};
...

Anche in questo caso (proprio come con MainPage::current), CopyFiles::rootPage viene dichiarato come di tipo SDKTemplate::MainPage, ovvero il tipo proiettato e non il tipo di implementazione.

CopyFiles (il costruttore)

Nel progetto C++/WinRT il tipo CopyFiles ha già un costruttore contenente il codice desiderato (chiama solo InitializeComponent).

CopyButton_Click

Il metodo CopyButton_Click C# è un gestore eventi e dalla async parola chiave nella firma è possibile indicare che il metodo funziona in modo asincrono. In C++/WinRT implementiamo un metodo asincrono come coroutine. Per un'introduzione alla concorrenza in C++/WinRT, insieme a una descrizione di ciò che è una coroutine , vedi Concorrenza e operazioni asincrone con C++/WinRT.

È comune pianificare ulteriori operazioni dopo il completamento di una coroutine e, in questi casi, la coroutine restituirà un tipo di oggetto asincrono che può essere atteso e che, facoltativamente, segnala lo stato di avanzamento. Tuttavia, queste considerazioni in genere non si applicano a un gestore eventi. Quindi, quando si dispone di un gestore di eventi che esegue operazioni asincrone, è possibile implementarlo come coroutine che restituisce winrt::fire_and_forget. Per ulteriori informazioni, vedi Fire and forget.

Sebbene l'idea di una coroutine di tipo fire-and-forget sia che non importa quando si completa, il lavoro continua comunque in esecuzione in background (oppure è sospeso, in attesa di essere ripreso). È possibile vedere dall'implementazione in C# che CopyButton_Click dipende dal puntatore this (accede al membro dati dell'istanza rootPage). È quindi necessario assicurarsi che il puntatore this (un puntatore a un oggetto CopyFiles) abbia una durata superiore a quella della coroutine CopyButton_Click. In una situazione come questa applicazione di esempio, in cui l'utente si sposta tra le pagine dell'interfaccia utente, non è possibile controllare direttamente la durata di tali pagine. Se la pagina CopyFiles viene distrutta (allontanandosi da essa) mentre CopyButton_Click è ancora in esecuzione in un thread in background, non sarà sicuro accedere a rootPage. Affinché la coroutine sia corretta, deve ottenere un riferimento forte al puntatore this e mantenerlo per tutta la durata della coroutine. Per altre info, vedi Riferimenti sicuri e deboli in C++/WinRT.

Se si esamina la versione C++/WinRT dell'esempio, in CopyFiles::CopyButton_Click si noterà che è stata eseguita con una semplice dichiarazione nello stack.

fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
    auto lifetime{ get_strong() };
    ...
}

Esaminiamo gli altri aspetti del codice convertito che sono degni di nota.

Nel codice viene creata un'istanza di un oggetto FileOpenPicker e due righe successivamente si accede alla proprietà FileTypeFilter dell'oggetto. Il tipo restituito di tale proprietà implementa un IVector di stringhe. E su quell'IVector, si chiama il metodo IVector<T>.ReplaceAll(T[]). L'aspetto interessante è il valore che stiamo passando a quel metodo, per il quale è previsto un array. Ecco la riga di codice.

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

Il valore passato ({ L"*" }) è un elenco di inizializzatori C++ standard. Contiene un singolo oggetto, in questo caso, ma un elenco di inizializzatori può contenere un numero qualsiasi di oggetti delimitati da virgole. Le parti di C++/WinRT che consentono di passare un elenco di inizializzatori a un metodo come questo sono spiegati negli elenchi inizializzatori standard.

Eseguiamo il porting della parola chiave C# await in co_await in C++/WinRT. Ecco l'esempio del codice.

auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };

Prendere quindi in considerazione questa riga di codice C#.

dataPackage.SetStorageItems(storageItems);

C# è in grado di convertire in modo implicito IReadOnlyList<StorageFile> rappresentato da storageItems in IEnumerable<IStorageItem> previsto da DataPackage.SetStorageItems. Ma in C++/WinRT è necessario convertire in modo esplicito da IVectorView<StorageFile> a IIterable<IStorageItem>. Quindi abbiamo un altro esempio della funzione as in azione.

dataPackage.SetStorageItems(storageItems.as<IVectorView<IStorageItem>>());

Dove si usa la null parola chiave in C# (ad esempio, Clipboard.SetContentWithOptions(dataPackage, null)), viene usata nullptr in C++/WinRT (ad esempio, Clipboard::SetContentWithOptions(dataPackage, nullptr)).

PasteButton_Click

Si tratta di un altro gestore di eventi nella forma di una coroutine di tipo fire-and-forget. Esaminiamo ora gli aspetti del codice convertito che sono importanti.

Nella versione C# dell'esempio vengono rilevate eccezioni con catch (Exception ex). Nel codice C++/WinRT convertito verrà visualizzata l'espressione catch (winrt::hresult_error const& ex). Per altre info su winrt::hresult_error e su come usarlo, vedi Gestione degli errori con C++/WinRT.

Esempio di test che indica se un oggetto C# è null o meno .if (storageItems != null) In C++/WinRT, possiamo affidarci a un operatore di conversione verso bool, che esegue internamente la verifica rispetto a nullptr.

Ecco una versione leggermente semplificata di un frammento di codice dalla versione C++/WinRT convertita dell'esempio.

std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());

Creazione di un std::wstring_view da un winrt::hstring simile a questo illustra un'alternativa alla chiamata alla funzione hstring::c_str (per trasformare winrt::hstring in una stringa in stile C). Questa alternativa funziona grazie all'operatore di conversione di hstringin std::wstring_view.

Si consideri questo frammento di C#.

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

Per convertire la parola chiave C# as in C++/WinRT, finora è stata usata la funzione come un paio di volte. Tale funzione genera un'eccezione se la conversione del tipo non riesce. Tuttavia, se vogliamo che la conversione restituisca nullptr in caso di errore (in modo da poter gestire tale condizione nel codice), allora usiamo invece la funzione try_as.

auto file{ storageItem.try_as<StorageFile>() };
if (file)
...

Copiare il codice XAML necessario per completare la conversione di CopyFiles

È ora possibile selezionare l'intero contenuto del CopyFiles.xaml file dalla shared cartella del download del codice sorgente di esempio originale e incollarlo nel CopyFiles.xaml file nel progetto C++/WinRT (sostituendo il contenuto esistente del file nel progetto C++/WinRT).

Infine, modificare CopyFiles.h ed .cpp eliminare la funzione ClickHandler fittizia, poiché abbiamo appena sovrascritto il markup XAML corrispondente.

A questo punto è stata completata la conversione di CopyFiles e, se sono stati eseguiti insieme ai passaggi, il progetto C++/WinRT verrà ora compilato ed eseguito e lo scenario CopyFiles sarà funzionale.

CopyImage

Per convertire il tipo di pagina XAML CopyImage , segui lo stesso processo di CopyFiles. Durante la conversione di CopyImage, si verifica l'uso dell'istruzione using C#, che garantisce che gli oggetti che implementano l'interfaccia IDisposable vengano eliminati correttamente.

if (imageReceived != null)
{
    using (var imageStream = await imageReceived.OpenReadAsync())
    {
        ... // Pass imageStream to other APIs, and do other work.
    }
}

L'interfaccia equivalente in C++/WinRT è IClosable, con il relativo singolo metodo Close . Ecco l'equivalente C++/WinRT del codice C# precedente.

if (imageReceived)
{
    auto imageStream{ co_await imageReceived.OpenReadAsync() };
    ... // Pass imageStream to other APIs, and do other work.
    imageStream.Close();
}

Gli oggetti C++/WinRT implementano principalmente IClosable per il vantaggio dei linguaggi che non dispongono di finalizzazione deterministica. C++/WinRT ha finalizzazione deterministica e quindi spesso non è necessario chiamare IClosable::Close durante la scrittura di C++/WinRT. Ma ci sono momenti in cui è bello chiamarlo, e questo è uno di questi tempi. In questo caso, l'identificatore imageStream è un wrapper con conteggio dei riferimenti intorno a un oggetto Windows Runtime sottostante(in questo caso, un oggetto che implementa IRandomAccessStreamWithContentType). Sebbene possiamo determinare che il finalizzatore di imageStream (ovvero il suo distruttore) verrà eseguito alla fine dell'ambito esterno (tra parentesi graffe), non possiamo essere certi che tale finalizzatore chiamerà Close. Ciò è dovuto al fatto che imageStream è stato passato ad altre API e potrebbero comunque contribuire al conteggio dei riferimenti dell'oggetto Windows Runtime sottostante. Quindi, questo è un caso in cui è consigliabile chiamare Close in modo esplicito. Per altre info, vedi È necessario chiamare IClosable::Close nelle classi di runtime usate?.

Si consideri quindi l'espressione (uint)(imageDecoder.OrientedPixelWidth * 0.5)C#, disponibile nel gestore eventi OnDeferredImageRequestedHandler . Tale espressione moltiplica un oggetto uint per un doubleoggetto , generando un oggetto double. Esegue quindi il cast di tale oggetto in un oggetto uint. In C++/WinRT è possibile usare un cast in stile C simile ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)), ma è preferibile rendere chiaro esattamente il tipo di cast che si intende, e in questo caso lo faremmo con static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).

La versione C# di CopyImage.OnDeferredImageRequestedHandler include una finally clausola, ma non una catch clausola . Siamo andati solo un po 'più avanti nella versione C++/WinRT e abbiamo implementato una catch clausola in modo da poter segnalare se il rendering ritardato è riuscito o meno.

Il porting del resto di questa pagina XAML non offre nulla di nuovo di cui discutere. Ricordarsi di eliminare la funzione ClickHandler fittizia. Analogamente a CopyFiles, l'ultimo passaggio nella porta consiste nel selezionare l'intero contenuto di CopyImage.xamle incollarlo nello stesso file nel progetto C++/WinRT.

CopyText

È possibile eseguire il porting di CopyText.xaml e CopyText.xaml.cs usando le tecniche che abbiamo già illustrato.

HistoryAndRoaming

Ci sono alcuni punti di interesse che si verificano durante la conversione del tipo di pagina XAML HistoryAndRoaming .

Per prima cosa, dai un'occhiata al codice sorgente C# e segui il flusso di controllo da OnNavigatedTo, attraverso il gestore dell'evento OnHistoryEnabledChanged, fino alla funzione asincrona CheckHistoryAndRoaming (di cui non viene eseguito l'await, quindi in pratica viene avviata e poi ignorata). Poiché CheckHistoryAndRoaming è asincrono, è necessario prestare attenzione in C++/WinRT sulla durata del this puntatore. È possibile visualizzare il risultato se si esamina l'implementazione nel file del HistoryAndRoaming.cpp codice sorgente. Innanzitutto, quando associamo i delegati agli eventi Clipboard::HistoryEnabledChanged e Clipboard::RoamingEnabledChanged, acquisiamo solo un riferimento debole all'oggetto pagina HistoryAndRoaming. Lo facciamo creando il delegato con una dipendenza dal valore restituito da winrt::get_weak, anziché dal puntatore this. Ciò significa che il delegato stesso, che alla fine richiama codice asincrono, non mantiene in vita la pagina HistoryAndRoaming nel caso in cui si passi a un'altra pagina.

In secondo luogo, quando raggiungiamo finalmente la coroutine fire-and-forget CheckHistoryAndRoaming, la prima cosa che facciamo è acquisire un riferimento forte a this per garantire che la pagina HistoryAndRoaming rimanga in vita almeno finché la coroutine non sia completata. Per altre info su entrambi gli aspetti appena descritti, vedi Riferimenti sicuri e deboli in C++/WinRT.

Troviamo un altro punto di interesse durante la conversione di CheckHistoryAndRoaming. Contiene il codice per aggiornare l'interfaccia utente; quindi dobbiamo essere certi che lo facciamo nel thread principale dell'interfaccia utente. Il thread che inizialmente chiama in un gestore eventi è il thread principale dell'interfaccia utente. In genere, tuttavia, un metodo asincrono può essere eseguito e/o ripreso in qualsiasi thread arbitrario. In C# la soluzione consiste nell'inviare il lavoro al thread dell'interfaccia utente. In C++/WinRT, è possibile usare la funzione winrt::resume_foreground insieme a DispatcherQueue del puntatore this per sospendere la coroutine e riprendere immediatamente l'esecuzione nel thread principale dell'interfaccia utente.

L'espressione pertinente è co_await winrt::resume_foreground(DispatcherQueue());. La versione più breve viene ottenuta per gentile concessione di un operatore di conversione fornito da C++/WinRT.

La conversione della parte restante di questa pagina XAML non produce nulla di nuovo da discutere. Ricordarsi di eliminare la funzione ClickHandler fittizia e di copiare il markup XAML.

OtherScenarios

È possibile portare OtherScenarios.xaml e OtherScenarios.xaml.cs utilizzando tecniche che abbiamo già illustrato.

Conclusione

Speriamo che questa procedura dettagliata ti abbia armato con informazioni e tecniche di portabilità sufficienti che ora puoi procedere e convertire le tue applicazioni C# in C++/WinRT. Come ripasso, puoi continuare a fare riferimento alle versioni prima (C#) e dopo (C++/WinRT) del codice sorgente nell'esempio Clipboard e confrontarle una accanto all'altra per vederne la corrispondenza.