Windows Dev Center

Scrittura di codice C++ moderno in Hilo (app di Windows Store in C++ e XAML)

Da: Sviluppo di un'app di Windows Store end-to end in C++ e XAML: Hilo

Logo Patterns & Practices

Pagina precedente | Pagina successiva

Questo articolo contiene qualche suggerimento e alcune linee guida per scrivere codice e creare app di Windows Store in C++ e XAML adattandole alla programmazione asincrona mediante la libreria PPL (Parallel Patterns Library). Le linee guida e i suggerimenti sono nati dalle domande che noi stessi ci siamo posti quando abbiamo iniziato a sviluppare Hilo in C++.

Le app di Windows Store sviluppate in C++ combinano le migliori funzionalità di C++11, lo stile moderno di scrittura del codice di C++ e le estensioni C++/CX. Se non sei esperto nell'uso di C++11, ti sarà utile leggere prima di tutto C++11 Features (Modern C++) e Welcome Back to C++ (Modern C++) (in lingua inglese).

Se non sei esperto di C++/CX, leggi invece Riferimenti al linguaggio Visual C++ (C++/CX).

Download

Download dell'esempio da Hilo
Download guida (PDF)

Dopo aver scaricato il codice, per le istruzioni vedi Introduzione a Hilo.

Contenuto della sezione

  • Quando usare tipi e librerie C++ standard nelle app di Windows Store.
  • Procedure consigliate per l'uso di C++/CX.
  • Come convertire librerie C++ esistenti per usarle con app di Windows Store.
  • Tecniche di debug consigliate con Visual Studio.

Si applica a

  • Windows Runtime per Windows 8
  • C++/CX

Informazioni sull'ambiente dell'app

Le app di Windows Store, come Hilo, sono caratterizzate da una veste grafica particolare e spesso consentono all'utente di interagire toccando lo schermo. Inoltre, queste app vengono eseguite in un ambiente che, rispetto alle app desktop e console, offre all'utente maggior controllo sulle operazioni che l'app stessa può eseguire, rendendo quest'ultima più sicura e più facile da gestire.

Struttura di un'app di Windows Store in C++ e XAML

Queste funzionalità influiscono sulla modalità di implementazione di un'app. Se si usa il C++, ecco gli elementi che determinano l'ambiente statico e di runtime dell'app.

Questi componenti e questi strumenti determinano le funzionalità di cui l'app può disporre e la modalità di accesso ad esse. Se non sei esperto di app di Windows Store, ti consigliamo di rivedere la panoramica di questi componenti e di questi strumenti, per comprendere il contributo che possono offrire alle tue app. Man mano che procederai nella lettura di questa guida, potrai vedere in che modo i componenti e gli strumenti vengono usati in Hilo.

Nota  

Un altro utile punto di inizio generale per programmatori C++ è Roadmap per app di Windows Store con C++.

Manifesto del pacchetto

Visual Studio crea un file di progetto con il nome Package.appxmanifest per registrare le impostazioni che influiscono sulla distribuzione e sull'esecuzione dell'app. Il file è noto come manifesto del pacchetto. Visual Studio ti consente di modificare il manifesto del pacchetto con lo strumento visuale Progettazione manifesto.

Progettazione manifesto di Visual Studio

A differenza delle app desktop e console, le app di Windows Store devono dichiarare in anticipo le funzionalità di ambiente che intendono usare. Ad esempio devono dichiarare se devono accedere a risorse di sistema protette o a dati utente.

Perché la tua app possa usare le funzionalità che le servono devi dichiararle tutte nel manifesto. A tale scopo puoi usare la scheda Funzionalità in Progettazione manifesto. Il manifesto di Hilo, ad esempio, include la possibilità di accedere alla raccolta immagini dell'utente.

Scheda Funzionalità di progettazione manifesto

Ecco il codice sorgente del manifesto del pacchetto di Hilo.

Package.appxmanifest


<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest">
  <Identity Name="Microsoft.Hilo.Sample.CPP" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="1.0.0.0" />
  <Properties>
    <DisplayName>ms-resource:DisplayNameProperty</DisplayName>
    <PublisherDisplayName>ms-resource:PublisherDisplayNameProperty</PublisherDisplayName>
    <Logo>Assets\HiloStoreLogo.png</Logo>
  </Properties>
  <Prerequisites>
    <OSMinVersion>6.2.1</OSMinVersion>
    <OSMaxVersionTested>6.2.1</OSMaxVersionTested>
  </Prerequisites>
  <Resources>
    <Resource Language="x-generate" />
  </Resources>
  <Applications>
    <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="Hilo.App">
      <VisualElements DisplayName="ms-resource:DisplayName" Logo="Assets\HiloLogo.png" SmallLogo="Assets\HiloSmallLogo.png" Description="ms-resource:Description" ForegroundText="light" BackgroundColor="#292929">
        <DefaultTile ShowName="allLogos" WideLogo="Assets\HiloWideLogo.png" ShortName="ms-resource:ShortName" />
        <SplashScreen Image="Assets\HiloSplash.png" BackgroundColor="#292929" />
      </VisualElements>
    </Application>
  </Applications>
  <Capabilities>
    <Capability Name="picturesLibrary" />
  </Capabilities>
</Package>


L'esecuzione delle app di Windows Store avviene all'interno di un ambiente speciale del sistema operativo. Questo ambiente consente alle app di usare solo le funzionalità Windows Runtime dichiarate come necessarie nel manifesto del pacchetto. A tale scopo, il sistema limita le funzioni, i tipi di dati e i dispositivi che un'app può usare.

Nota  Il limite imposto dal manifesto del pacchetto riguarda solo le chiamate all'API Windows Runtime. Se usi l'API Win32 o COM, dovrai eseguire alcuni strumenti per verificare di usare il sottoinsieme dell'API consentito in Windows Store per le app. Per semplificare l'uso del sottoinsieme corretto, Visual Studio filtra le funzioni all'interno del Visualizzatore oggetti.

Per un tour dell'uso del manifesto in Hilo, vedi Test e distribuzione dell'app in questa guida. Vedi Dichiarazioni di funzionalità delle app (app di Windows Store) per ulteriori informazioni sull'impostazione delle funzionalità e Progettazione manifesto per informazioni su come modificare il manifesto del pacchetto in Visual Studio. Per la documentazione completa dello schema XML del manifesto, vedi Manifesto del pacchetto.

Librerie C++ standard

Il compilatore Visual C++ supporta lo standard C++11 che consente di usare lo stile moderno di scrittura del codice. Le tue app di Windows Store possono usare la libreria run-time del C (CRT, C Run-Time) e la libreria SLT (Standard Template Library). Ci sono alcune restrizioni, ad esempio non è possibile accedere alle funzioni I/O della console. Vedi Conversione di codice C++ esistente in questa pagina.

Librerie Windows Runtime

Servizi Windows Runtime

Windows Runtime è un'interfaccia di programmazione che puoi usare per creare app di Windows Store. Windows Runtime supporta il tipico stile grafico e il modello di interazione basato sul tocco delle app di Windows Store, oltre all'accesso alla rete, ai dischi, ai dispositivi e alla stampa. Per i tipi di dati e le funzioni di queste librerie vengono usati gli spazi dei nomi Windows e Platform. Il Visualizzatore oggetti in Visual Studio ti consente di vedere quali tipi sono disponibili.

Visualizzatore oggetti di Visual Studio

Al livello più basso, Windows Runtime è un'interfaccia ABI (Application Binary Interface). Un'interfaccia ABI è un contratto binario che rende le API Windows Runtime accessibili a linguaggi di programmazione diversi, ad esempio JavaScript, i linguaggi .NET e Microsoft Visual C++. Per ulteriori informazioni sull'API Windows Runtime, vedi Informazioni di riferimento sulle API Windows per le app di Windows Store.

La struttura di un'app di Windows Store è diversa da quella di un'app desktop tradizionale che usa l'API Windows (Win32). Invece di usare tipi di handle come HWND e funzioni come CreateWindow, con Windows Runtime puoi usare interfacce come Windows::UI::Core::ICoreWindow per sviluppare app di Windows Store in modo più moderno e orientato agli oggetti.

Nota  La ricchezza del modello di programmazione di Windows Runtime deriva in parte da un sistema di tipi esteso e dall'ampliamento delle funzionalità proprie del linguaggio. L'ambiente fornisce proprietà, delegati, eventi, interfacce e attributi. Per una panoramica, vedi Sistema di tipi (C++/CX).

API Win32 e COM

Le app di Windows Store possono usare un sottoinsieme di API Win32 e COM. Windows Runtime dispone di un'ampia gamma di funzionalità. Per supportare gli scenari chiave delle app di Windows Store non ancora coperti da Windows Runtime puoi tuttavia continuare a usare un sottoinsieme di Win32 e COM. Ad esempio, IXMLHTTPRequest2, un'interfaccia COM, è consigliata per la connessione a server HTTP in un'app di Windows Store in C++. Vedi API Win32 e COM per ulteriori informazioni sull'uso di Win32 e COM nelle tue app di Windows Store. Vedi Conversione di codice C++ esistente in questa pagina per una panoramica delle funzionalità disponibili.

Hilo non effettua chiamate dirette all'API Win32 ma usa COM per elaborare i dati relativi ai pixel delle immagini.

Libreria PPL (Parallel Patterns Library)

Per la programmazione asincrona in parallelo all'interno della tua app è consigliabile usare la libreria PPL (Parallel Pattern Library). Puoi usare attività e agenti PPL della libreria di agenti asincroni nei punti in cui avresti altrimenti potuto usare un thread. Per vedere come viene usata la libreria PPL in Hilo, vedi Adattamento alla programmazione asincrona e Uso di programmazione in parallelo e di attività in background in questa pagina.

XAML

Per la definizione della presentazione visuale delle app di Windows Store che usano il linguaggio XAML (Extensible Application Markup Language), un linguaggio di markup basato su XML, viene usato un approccio dichiarativo. La progettazione e la struttura dell'esperienza utente sono scritte in XAML, un linguaggio markup basato su XML. Per definire gli elementi dell'interfaccia utente, puoi usare uno strumento di progettazione visuale, ad esempio lo strumento di progettazione di Visual Studio o Microsoft Expression Blend, puoi scrivere espressioni XAML manualmente o adottare entrambi i metodi. Per connettere gli elementi dell'interfaccia utente alle origini dati dell'app devi utilizzare espressioni XAML di associazione dati.

XAML è il linguaggio di progettazione per framework quali Microsoft Silverlight e Windows Presentation Foundation (WPF). Se hai familiarità con XAML in associazione a questi framework o Expression Blend, potrai continuare a sfruttare le tue capacità per la creazione di app di Windows Store. Per ulteriori informazioni su XAML in relazione alle app di Windows Store, vedi Panoramica di XAML.

L'associazione dati è un metodo comodo per la connessione di caselle di testo, pulsanti, griglie dati e altri elementi dell'interfaccia utente alla logica dell'app. Windows Runtime richiama i metodi e le proprietà delle origini dati quando necessario per fornire le informazioni all'interfaccia utente durante l'esecuzione dell'app. L'associazione dati viene usata anche nei casi in cui i dati passati corrispondono a un comando, ad esempio a una richiesta di esecuzione di un'operazione come l'apertura di un file. In questo caso, l'associazione dati separa gli elementi dell'interfaccia utente che richiedono comandi e il codice che esegue l'azione del comando. In XAML è possibile associare quasi tutti gli oggetti e quasi tutte le proprietà del framework.

Modelli di progetto di Visual Studio

Ti consigliamo di usare i modelli di progetto di Visual Studio predefiniti, poiché questi modelli contengono le impostazioni del compilatore necessarie per eseguire le app di Windows Store. I modelli di progetto, inoltre, forniscono il codice per l'implementazione di funzionalità di base, consentendo di risparmiare tempo. Con Visual Studio sono disponibili alcuni file in una cartella di soluzioni denominata Common, che non deve essere modificata. Visual Studio, inoltre, crea un file App.xaml.cpp che puoi personalizzare con la logica specifica dell'app. Questo file include il punto di ingresso principale dell'app, il metodo App::InitializeComponent. I modelli di Visual Studio consentono di risparmiare tempo, ma non sono obbligatori. Puoi inoltre modificare le librerie statiche e dinamiche esistenti per usarle nella tua app. Per ulteriori informazioni, vedi Conversione di codice C++ esistente in questa pagina e Compilazione di app e librerie (C++/CX).

Per ulteriori informazioni sui modelli di progetto di Visual Studio, vedi Modelli per velocizzare lo sviluppo di app (app di Windows Store in C#/VB/C++ e XAML).

Estensioni del linguaggio C++ per l'interoperabilità

Il nuovo framework di interfacce utente per le app di Windows Store interagisce con il C++ nativo attraverso il linguaggio XAML L'interoperabilità tra C++ e XAML, o altri linguaggi quali JavaScript e C#, è diretta, senza chiamate ad alcun livello di conversione intermedio. Per il formato binario degli oggetti e delle chiamate di funzione l'interoperabilità usa una convenzione simile alla programmazione basata su COM. L'ambiente di programmazione delle app di Windows Store comprende C++/CX, che fornisce le estensioni per il linguaggio C++ che semplificano la scrittura del codice per l'interoperabilità. Le estensioni devono essere usate solo per il codice relativo all'interoperabilità. Per il resto dell'app deve essere usato codice C++ conforme agli standard. Per ulteriori informazioni, vedi Suggerimenti per l'uso di C++/CX come livello di interoperabilità in questa pagina.

Nota  L'uso di C++/CX per l'interoperabilità non è obbligatorio. Puoi ottenere l'interoperabilità anche con la libreria Windows Runtime.

Compilatore e linker C++

L'opzione /ZW del compilatore consente ai file sorgente in C++ di usare le funzionalità di Windows Runtime, oltre ad abilitare la direttiva __cplusplus_winrt del preprocessore visibile in alcuni file di intestazione di sistema. Il tuo codice recupera le dichiarazioni dei tipi di Windows Runtime dai file di metadati (.winmd) anziché dai tuoi file di intestazione. Puoi fare riferimento ai file .winmd mediante la direttiva #using o con l'opzione /FU del compilatore. Se usi uno dei modelli di progetto C++ per app di Windows Store, in Visual Studio queste opzioni vengono configurate automaticamente.

Quando esegui il collegamento dell'app, devi specificare due opzioni del linker: /WINMD e /APPCONTAINER. Se usi uno dei modelli di progetto C++ per app di Windows Store, in Visual Studio queste opzioni vengono configurate automaticamente.

Processo di certificazione di Windows Store

La pubblicazione in Windows Store rende le app disponibili per l'acquisto o il download gratuito. La pubblicazione nello store è facoltativa. Le app presenti nello store devono essere sottoposte a un processo di certificazione che prevede un controllo strutturale automatizzato. Per la descrizione del processo di certificazione di Hilo, vedi Test e distribuzione dell'app in questa Guida.

Distribuzione

Le app di Windows Store usano un modello di installazione più semplice rispetto alle app desktop. Per un'app di Windows Store tutte le librerie e le risorse non fornite dal sistema si trovano nella directory o nelle sottodirectory di installazione dell'app. Il processo di installazione di un'app di Windows Store non può scrivere nel Registro di sistema né definire variabili di ambiente. Inoltre, l'utente può limitare le funzionalità dell'app in corso di installazione. Nel manifesto del pacchetto sono elencati tutti i file distribuiti. Per ulteriori informazioni, vedi Manifesto del pacchetto.

[Torna all'inizio]

Uso di C++11, librerie C++ standard e di uno stile moderno di scrittura di codice

Quando possibile, usa C++11 e le librerie C++ standard, ad esempio STL e CRT, per la logica di base dell'app e la sintassi C++/CX solo per l'interazione con Windows Runtime. Ad esempio, le tecniche migliori da usare sono le seguenti:

Espressioni lambda

Le espressioni lambda servono per definire le operazioni eseguite da attività PPL e algoritmi STL. Per ulteriori informazioni, vedi Schemi di programmazione asincrona per app di Windows Store in C++ e XAML in questa Guida.

Semantica dello stack, puntatori intelligenti e RAII

La semantica dello stack, i puntatori intelligenti e RAII (Resource Acquisition Is Initialization) consentono di controllare in modo automatico la durata degli oggetti e verificano che le risorse vengano liberate quando la funzione corrente termina o genera un'eccezione. Per ulteriori informazioni, vedi Suggerimenti per la gestione della memoria in questa pagina.

Deduzione automatica del tipo

La deduzione automatica del tipo rende più semplice la lettura e più rapida la scrittura del codice. Le parole chiave auto e decltype consentono al compilatore di dedurre il tipo di una variabile dichiarata dal tipo dell'espressione specificata. Ad esempio, puoi usare auto quando usi operatori di iterazione STL, i cui nomi sono antipatici da scrivere e non contribuiscono a rendere più chiaro il codice. In Hilo la parola chiave auto viene ampiamente usata insieme alle funzioni concurrency::create_task e std::make_shared per ridurre la necessità di dichiarare il parametro relativo al tipo di modello.

ImageBase.cpp


auto filePickerTask = create_task(savePicker->PickSaveFileAsync());


ThumbnailGenerator.cpp


auto decoder = make_shared<BitmapDecoder^>(nullptr);
auto pixelProvider = make_shared<PixelDataProvider^>(nullptr);


Nota  Usa la parola chiave auto se chi dovrà leggere il tuo codice può dedurre il tipo dal contesto o se vuoi rendere astratto il tipo. Il motivo è la leggibilità.

Cicli for basati sull'intervallo

Usa cicli for basati sull'intervallo per elaborare raccolte di dati. La sintassi dei cicli for basati sull'intervallo è più sintetica rispetto ai cicli for e all'algoritmo std::for_each perché non richiedono l'uso di operatori di iterazione o di clausole di acquisizione. Ecco un esempio:

FileAllPhotosQuery.cpp


auto photos = ref new Vector<IPhoto^>();
for (auto file : files)
{
    auto photo = ref new Photo(file, ref new NullPhotoGroup(), policy);
    photos->Append(photo);
}


Per ulteriori informazioni, vedi Algoritmi (C++ moderno).

Algoritmi e contenitori standard

Usa algoritmi e contenitori standard, ad esempio std::vector, std::for_each, std::find_if e std::transform per sfruttare le funzionalità del C++. Poiché i tipi di Windows Runtime sono indipendenti dal linguaggio, Hilo usa tipi quali std::wstring e std::wstringstream per elaborare le stringhe internamente e Platform::String solo quando interagisce con Windows Runtime. In questo esempio, il tipo Platform::String restituito viene passato a Windows Runtime.

CalendarExtensions.cpp


wstringstream dateRange;
dateRange << L"System.ItemDate:" ;

cal->Day = cal->FirstDayInThisMonth;
cal->Period = cal->FirstPeriodInThisDay;
cal->Hour = cal->FirstHourInThisPeriod;
cal->Minute = cal->FirstMinuteInThisHour;
cal->Second = cal->FirstSecondInThisMinute;
cal->Nanosecond = 0;
dateRange << GetAqsFormattedDate(cal->GetDateTime()); 

dateRange << "..";

cal->Day = cal->LastDayInThisMonth;
cal->Period = cal->LastPeriodInThisDay;
cal->Hour = cal->LastHourInThisPeriod;
cal->Minute = cal->LastMinuteInThisHour;
cal->Second = cal->LastSecondInThisMinute;
cal->Nanosecond = 999999;
dateRange << GetAqsFormattedDate(cal->GetDateTime()); 

return ref new String(dateRange.str().c_str());


Tramite l'uso di funzionalità standard è possibile scrivere codice più portatile e in grado di sfruttare le funzionalità del C++ moderno, ad esempio la semantica move. Per ulteriori informazioni sulla semantica move, vedi Dichiarazione di riferimento Rvalue: &&.

Lo stesso modello vale per i contenitori standard, ad esempio std::vector. L'uso di contenitori standard consente di sfruttare funzionalità del C++ come la semantica move e la possibilità di accedere alla memoria in modo più diretto. Ad esempio, è possibile eseguire l'elaborazione interna di un oggetto std::vector e quindi passare un oggetto Windows::Foundation::Collections::IVector a Windows Runtime. La classe Platform::Collections::Vector, che corrisponde all'implementazione C++ di IVector, contiene un costruttore con overload che riceve un riferimento rvalue a uno std::vector. Per chiamare il costruttore con overload, puoi usare la funzione std::move o passare direttamente il risultato di una funzione che restituisce std::vector. Per un esempio di questo schema, vedi Raccolte (C++/CX).

Nota  Non è necessario usare semantica move. La classe Platform::Collections::Vector include un costruttore di copia standard che riceve un oggetto std::vector come argomento. In altre parole, è possibile mantenere i vecchi dati vector e creare una nuova istanza di Platform::Collections::Vector con una copia dei dati originari.

In un altro esempio Hilo usa std::iota e std::random_shuffle per scegliere casualmente delle foto o, più precisamente, gli indici di una matrice di foto. Questo esempio usa std::iota per creare una sequenza di indici di matrice e std::random_shuffle per ridisporre la sequenza in modo casuale.

RandomPhotoSelector.cpp


vector<unsigned int> RandomPhotoSelector::CreateRandomizedVector(unsigned int vectorSize, unsigned int sampleSize)
{
    // Seed the rand() function, which is used by random_shuffle.
    srand(static_cast<unsigned int>(time(nullptr)));

    // The resulting set of random numbers.
    vector<unsigned int> result(vectorSize);

    // Fill with [0..vectorSize).
    iota(begin(result), end(result), 0);

    // Shuffle the elements.
    random_shuffle(begin(result), end(result));

    // Trim the list to the first sampleSize elements if the collection size is greater than the sample size.
    if (vectorSize > sampleSize)
    {
        result.resize(sampleSize);
    }

    return result;
}


Per ulteriori informazioni sugli algoritmi standard, vedi Algoritmi (C++ moderno)

Operatori di iterazione in formato libero

Usa le funzioni std::begin e std::end per lavorare con gli intervalli. Usa queste funzioni, invece di funzioni membro come .begin() e .end(), per scrivere codice più flessibile. Ad esempio, puoi scrivere un algoritmo generico che funziona con tipi STL come std::vector, std::array, e std::list, che forniscono le funzioni membro begin e ends, e anche tipi di raccolte di Windows Runtime come IVector, che usano una tecnica diversa per eseguire l'iterazione di valori. Le funzioni std::begin e std::end possono essere rese funzioni con overload per adattarle a stili di programmazione diversi e consentire l'aggiunta dell'iterazione alle strutture dati non modificabili.

Idioma pimpl

Usa l'idioma pimpl per nascondere l'implementazione, ridurre al minimo l'accoppiamento e separare le interfacce. L'idioma pimpl (abbreviazione di "Pointer to Implementation") prevede l'aggiunta di dettagli di implementazione a un file .cpp e l'aggiunta di un puntatore intelligente a tale implementazione private nella dichiarazione della classe all'interno del file .h. Questo metodo riduce anche i tempi di compilazione. Se usato in combinazione con costruttori di copia e semantica move, l'idioma pimpl consente inoltre di passare oggetti per valore, evitando il problema della durata degli oggetti stessi.

Puoi inoltre sfruttare i vantaggi dell'idioma pimpl quando usi librerie predefinite. L'idioma pimpl è spesso correlato a riferimenti rvalue. Ad esempio, quando crei continuazioni basate su attività passi oggetti concurrency::task per valore, non per riferimento.

ImageBrowserViewModel.cpp


void ImageBrowserViewModel::StartMonthQuery(int queryId, cancellation_token token)
{
    m_runningMonthQuery = true;
    OnPropertyChanged("InProgress");
    m_photoCache->Clear();
    run_async_non_interactive([this, queryId, token]()
    {
        // if query is obsolete, don't run it.
        if (queryId != m_currentQueryId) return;

        m_repository->GetMonthGroupedPhotosWithCacheAsync(m_photoCache, token).then([this, queryId](task<IVectorView<IPhotoGroup^>^> priorTask)
        {
            assert(IsMainThread());
            if (queryId != m_currentQueryId)  
            {
                // Query is obsolete. Propagate exception and quit.
                priorTask.get();
                return;
            }

            m_runningMonthQuery = false;
            OnPropertyChanged("InProgress");
            if (!m_runningYearQuery)
            {
                FinishMonthAndYearQueries();
            }
            try
            {     
                // Update display with results.
                m_monthGroups->Clear();                   
                for (auto group : priorTask.get())
                {  
                    m_monthGroups->Append(group);
                }
                OnPropertyChanged("MonthGroups");
            }
            // On exception (including cancellation), remove any partially computed results and rethrow.
            catch (...)
            {
                m_monthGroups = ref new Vector<IPhotoGroup^>();
                throw;
            }
        }, task_continuation_context::use_current()).then(ObserveException<void>(m_exceptionPolicy));
    });
}


Il passaggio per valore garantisce la validità dell'oggetto ed evita il problema della durata dell'oggetto stesso. Il passaggio per valore, inoltre, in questo caso non è dispendioso, poiché la classe task definisce un puntatore all'implementazione effettiva come unico membro dati e copia o trasferisce questo puntatore nei costruttori di copia e spostamento.

Per ulteriori informazioni, vedi Pimpl per l'incapsulamento della fase di compilazione (C++ moderno).

Gestione delle eccezioni

Puoi usare la gestione delle eccezioni per gestire gli errori. La gestione degli errori offre principalmente due vantaggi rispetto ai codici di registrazione o di errore, ad esempio i valori HRESULT:

  • Semplifica la lettura e la gestione del codice.
  • È un modo più efficiente di propagare un errore a una funzione in grado di gestirlo. In genere, l'uso di codici di errore richiede che ogni funzione esegua esplicitamente la propagazione degli errori.
  • Rende l'app più solida, poiché non consente di ignorare un'eccezione come avviene con lo stato HRESULT o GetLastError.

Puoi inoltre configurare il debugger di Visual Studio in modo che interrompa l'esecuzione quando si verifica un'eccezione, per fermarti immediatamente nel punto e nel contesto dell'errore. Anche Windows Runtime usa la gestione delle eccezioni in modo intensivo. Pertanto, con l'uso della gestione delle eccezioni nel tuo codice puoi combinare tutta la gestione degli errori in un unico modello.

Importante  Rileva solo le eccezioni che sei in grado di gestire in modo sicuro e da cui puoi eseguire il ripristino senza rischi. In caso contrario, non rilevare l'eccezione e lascia che l'app si interrompa. Usa catch(...) {...} solo se puoi generare di nuovo l'eccezione dal blocco catch.

Ecco un esempio tratto da Hilo che illustra lo stile di scrittura di codice C++ moderno, C++11, e le librerie standard che copiano oggetti StorageFile da un oggetto std::vector a un oggetto Vector perché la raccolta possa essere passata a Windows Runtime. Questo esempio usa un'espressione lambda, la deduzione automatica del tipo, gli operatori di iterazione std::begin e std::end e un ciclo for basato sull'intervallo.

ThumbnailGenerator.cpp


return when_all(begin(thumbnailTasks), end(thumbnailTasks)).then(
    [](vector<StorageFile^> files)
{
    auto result = ref new Vector<StorageFile^>();
    for (auto file : files)
    {
        if (file != nullptr)
        {
            result->Append(file);
        }
    }

    return result;
});


Per ulteriori informazioni sulla programmazione in C++ moderno, vedi Digitare di nuovo a C++ (C++ moderno).

[Torna all'inizio]

Adattamento alla programmazione asincrona

Con la programmazione asincrona è possibile chiamare una funzione che avvia un'operazione di lunga durata ma che ritorna immediatamente, senza aspettare che l'operazione sia conclusa. Le operazioni asincrone consentono una maggiore velocità di risposta da parte delle azioni dell'utente. In Windows Runtime sono disponibili molte più operazioni asincrone rispetto ai framework precedenti.

Puoi capire se una funzione di Windows Runtime è asincrona dal nome. Il nome delle funzioni asincrone termina in "Async", ad esempio ReadTextAsync. Anche Hilo segue questa convenzione.

Per l'interazione con le operazioni asincrone, sono disponibili tipi di dati specifici di Windows Runtime. Questi tipi di dati sono interfacce C++/CX che derivano dall'interfaccia Windows::Foundation::IAsyncInfo .

Ogni linguaggio di programmazione fornisce supporto nativo per la programmazione asincrona e usa interfacce derivate da IAsyncInfoper l'interoperabilità. Nel caso del C++, il supporto nativo per la programmazione asincrona è fornito dalla libreria PPL. In genere, quando si ricevono tipi di interfaccia asincrona di Windows Runtime da una chiamata a un metodo asincrono di Windows Runtime, è consigliabile eseguire il wrapping di questi tipi di interfaccia con attività PPL. L'unica occasione in cui devi esporre queste interfacce dalle tue classi è la creazione di un componente Windows Runtime per l'interoperabilità tra linguaggi. Ad esempio, puoi usare tipi derivati da IAsyncInfo in metodi public e proprietà di classi public ref definite da te.

Dopo il wrapping di un'operazione asincrona con un'attività PPL, puoi creare attività aggiuntive, la cui esecuzione viene programmata dal sistema dopo il completamento dell'attività precedente. Le attività successive sono dette attività di continuazione o continuazioni. Per creare continuazioni si usa il metodo task::then. Ecco un esempio tratto da Hilo in cui sono usate operazioni asincrone per leggere un'immagine da un file.

Nota  Le dichiarazioni di attività PPL si trovano nel file di intestazione ppltasks.h.

PhotoImage.cpp


task<void> PhotoImage::InitializeImageAsync()
{
    assert(IsMainThread());
    auto imageStreamTask = create_task(m_photo->File->OpenReadAsync());
    return imageStreamTask.then([this](task<IRandomAccessStreamWithContentType^> priorTask) -> task<void>
    {
        assert(IsMainThread());
        assert(m_image == nullptr);
        IRandomAccessStreamWithContentType^ imageData = priorTask.get();

        m_image = ref new BitmapImage();
        m_imageFailedEventToken = m_image->ImageFailed::add(ref new ExceptionRoutedEventHandler(this, &PhotoImage::OnImageFailedToOpen));
        OnPropertyChanged("Image");
        return create_task(m_image->SetSourceAsync(imageData));
    }).then([this](task<void> priorTask) {
        assert(IsMainThread());
        try
        {
            priorTask.get();
        }
        catch (Exception^)
        {
           OnImageFailedToOpen(nullptr, nullptr);
        }
    }).then(ObserveException<void>(m_exceptionPolicy));
}


L'espressione m_photo->File restituisce un riferimento a Windows::Storage::StorageFile^. Il metodo OpenReadAsync dell'oggetto avvia una funzione di operazione asincrona per aprire e leggere il file che contiene l'immagine richiesta. La chiamata a OpenReadAsync restituisce un oggetto di tipo IAsyncOperation<IRandomAccessStreamWithContentType^>^.

La chiamata asincrona avvia l'operazione. La chiamata vera e propria ritorna molto in fretta, senza aspettare il completamento dell'operazione di apertura del file. Il valore restituito dalla chiamata a OpenReadAsync rappresenta l'operazione in esecuzione avviata. Il tipo restituito è parametrizzato dal tipo del risultato dell'operazione asincrona, che in questo caso è IRandomAccessStreamWithContentType^.

Nota   La sintassi ^ (hat) indica un riferimento a Windows Runtime. Se non hai familiarità con questa sintassi, vedi Classi e struct di riferimento (C++/CX).

Il metodo Photo::QueryPhotoImageAsync chiama quindi la funzione concurrency::create_task per generare una nuova attività PPL, che diventa il valore della variabile locale imageStreamTask. Il tipo della variabile imageStreamTask è task<IRandomAccessStreamWithContentType^>. La conseguenza del passaggio di un oggetto asincrono Windows Runtime alla funzione concurrency::create_task è il wrapping dell'operazione asincrona con un'attività PPL. L'attività PPL appena creata termina quando l'operazione OpenReadAsync è completata ed è disponibile un flusso ad accesso casuale.

L'esecuzione di operazioni asincrone di Windows Runtime non richiede sempre un thread riservato. Queste operazioni vengono spesso gestite da Windows come operazioni I/O sovrapposte che usano strutture dati interne con un overhead minore rispetto ai thread. Questo dettaglio è interno al sistema operativo.

Dopo il wrapping di un'operazione Windows Runtime con un'attività PPL, puoi usare il metodo task::then per creare continuazioni che vengono eseguite dopo il completamento dell'attività precedente, detta anche antecedente. Le continuazioni, che sono esse stesse attività PPL, semplificano la lettura e il debug dei programmi asincroni.

Il metodo task::then è asincrono e restituisce una nuova attività molto rapidamente. A differenza di altre attività asincrone, l'attività restituita dal metodo task::then non viene avviata immediatamente. La libreria PPL ne ritarda l'avvio finché non è disponibile il risultato dell'attività antecedente. Questo significa che, nonostante il metodo task::get sia sincrono quando viene applicato all'attività antecedente, il valore è immediatamente disponibile. Le attività di continuazione che non sono pronte per l'esecuzione non bloccano alcun thread, ma vengono gestite internamente dalle PPL finché i dati necessari non diventano disponibili.

Nel metodo QueryPhotoImageAsync illustrato in precedenza l'argomento passato al metodo then è un'espressione lambda che corrisponde alla funzione di lavoro dell'attività appena creata. La funzione di lavoro di un'attività è il codice che viene chiamato quando l'attività viene eseguita. Il tipo del parametro di input dell'espressione lambda corrisponde al tipo della variabile imageStreamTask. I tipi corrispondono perché la variabile imageStreamTask viene passata come argomento alla funzione di lavoro della continuazione quando la continuazione viene avviata. Puoi immaginare questa modalità di esecuzione come se fosse una specie di programmazione flusso di dati. L'esecuzione delle attività di continuazione inizia quando è disponibile l'input corrispondente. Al termine dell'esecuzione, le attività di continuazione passano il risultato all'attività di continuazione successiva nella catena.

Nota  Se non hai familiarità con le espressioni lambda in C++, vedi Espressioni lambda in C++.

Nelle app di Windows Store le continuazioni di attività di wrapping di oggetti IAsyncInfo vengono eseguite per impostazione predefinita nel contesto di thread che ha creato la continuazione. Nella maggior parte dei casi, il contesto predefinito corrisponde al thread principale dell'app. Questa impostazione è appropriata per l'esecuzione di query o di modifiche di controlli XAML. Per gestire gli altri casi, puoi sostituire il valore predefinito.

Nota  In Hilo ci è sembrato utile chiarire il contesto di thread per le nostre subroutine tramite le istruzioni assert(IsMainThread() e assert(IsBackgroundThread()). Nella versione di debug di Hilo, queste istruzioni intervengono nel debugger se il thread in uso è diverso da quello dichiarato nell'asserzione. Le funzioni IsMainThread e IsBackgroundThread sono nel codice sorgente di Hilo.

Questo esempio non richiede codice particolare di sincronizzazione, come un blocco o una sezione critica, per l'aggiornamento della variabile membro m_image, perché tutte le interazioni con oggetti modello di visualizzazione avvengono nel thread principale. L'uso di un unico thread ordina automaticamente in serie gli aggiornamenti potenzialmente in conflitto. Nella programmazione dell'interfaccia utente è una buona idea usare continuazioni che vengono eseguite in un contesto di thread noto, anziché usare altri tipi di sincronizzazione.

Questo codice è un esempio di catena di continuazione o .then ladder (che si legge "dot-then ladder"). Questo schema si presenta spesso nelle app che usano API Windows Runtime asincrone. Vedi Programmazione asincrona in C++ (app di Windows Store) per ulteriori informazioni sulle catene di continuazione. Per suggerimenti e linee guida sull'uso di queste e per ulteriori esempi, vedi Schemi di programmazione asincrona per app di Windows Store in C++ e XAML in questa Guida.

[Torna all'inizio]

Uso di programmazione in parallelo e di attività in background

L'obiettivo della programmazione asincrona è la realizzazione di una risposta interattiva. Ad esempio, in Hilo vengono usate tecniche asincrone per garantire che il thread principale dell'app non sia mai bloccato e sia pronto a rispondere senza alcun ritardo alle nuove richieste. Tuttavia, a seconda dei requisiti funzionali dell'app, potrebbe essere necessario assicurare una certa velocità di risposta delle azioni dell'utente e una determinata velocità effettiva complessiva delle parti dell'app destinate all'esecuzione di calcoli complessi.

Il linguaggio C++ è particolarmente adatto alle app che richiedono sia un'interfaccia utente reattiva che elevate prestazioni per le attività con calcoli di particolare complessità. Le funzionalità di concorrenza di Visual C++ sono in grado di soddisfare le esigenze di entrambi i gruppi di requisiti.

Per aumentare la velocità effettiva delle aree di calcolo complesso della tua app puoi suddividere alcune attività in parti più piccole da far eseguire contemporaneamente da più core della CPU o dall'hardware specializzato nel parallelismo dei dati della GPU (Graphics Processor Unit, unità di elaborazione grafica) del computer. Queste e altre tecniche sono al centro della programmazione in parallelo. Le tecniche di programmazione in parallelo vengono impiegate all'interno di diverse aree di Hilo.

Ad esempio, una delle funzionalità di Hilo è un effetto cartoon che puoi usare per caratterizzare un'immagine con colori più semplici e profili delle forme. Ecco come appare un'immagine prima e dopo l'applicazione di questo effetto.

Effetto cartoon applicato a una foto

L'effetto cartoon è impegnativo dal punto di vista del calcolo. Per calcolare rapidamente il risultato sulla GPU, l'implementazione di Hilo usa C++ AMP. Per i sistemi che non dispongono di una GPU ad elevate prestazioni di calcolo, Hilo usa la libreria PPL, che fa parte del Runtime di concorrenza. Ecco come la proprietà accelerator::is_emulated contribuisce a determinare se per ottenere l'effetto cartoon è opportuno usare C++ AMP o l'algoritmo PPL:

CartoonizeImageViewModel.cpp


void CartoonizeImageViewModel::CartoonizeImage(Object^ parameter)
{
    assert(IsMainThread());
    m_cts = cancellation_token_source();
    auto token = m_cts.get_token();

    // Check for hardware acceleration if we haven't already.
    if (!m_checkedForHardwareAcceleration)
    {
        m_checkedForHardwareAcceleration = true;
        accelerator acc;
        m_useHardwareAcceleration = !acc.is_emulated;
    }

    ChangeInProgress(true);
    EvaluateCommands();
    m_initializationTask = m_initializationTask.then([this, token]() -> task<void> 
    {
        // Use the C++ AMP algorithim if the default accelerator is not an emulator (WARP or reference device).
        if (m_useHardwareAcceleration)
        {
            return CartoonizeImageAmpAsync(token);
        }
        // Otherwise, use the PPL to leverage all available CPU cores.
        else
        {
            return CartoonizeImagePPLAsync(token);
        }
    }, task_continuation_context::use_current()).then([this](task<void> priorTask)
    {
        m_initializationTask = create_empty_task();
        ChangeInProgress(false);
        EvaluateCommands();
        priorTask.get();
    }, task_continuation_context::use_current()).then(ObserveException<void>(m_exceptionPolicy));
}


Suggerimento  Abbiamo scelto di eseguire l'algoritmo che usa la libreria PPL quando l'hardware necessario non è disponibile, perché avevamo già a disposizione il codice per l'uso di tutti i core della CPU. Se tuttavia non disponi di codice di fallback, per eseguire il codice C++ AMP puoi usare il dispositivo WARP o di riferimento. Esegui il profiling del codice C++ AMP in base a più configurazioni per determinare con maggiore facilità se è necessario prendere in considerazione un metodo di fallback simile.

Per i dettagli sull'implementazione di questi algoritmi, vedi il progetto CartoonEffect nei file sorgente di Hilo.

Per ulteriori informazioni su C++ AMP, vedi C++ AMP (C++ Accelerated Massive Parallelism).

Per l'applicazione delle tecniche di programmazione in parallelo devi pensare in termini di elaborazione in primo piano (nel thread principale) ed elaborazione in background (nei thread di lavoro). Le attività PPL consentono di controllare quali parti dell'app vengono eseguite nel thread principale e quali in background. Le operazioni di lettura o modifica di controlli XAML vengono sempre richiamate nel thread principale dell'app. Tuttavia, per operazioni complesse di calcolo o I/O che non modificano l'interfaccia utente, puoi sfruttare l'hardware di elaborazione in parallelo del computer tramite attività PPL eseguite all'interno di thread del pool di thread del sistema. Puoi vedere lo schema primo piano/background nell'azione Crop. Ecco il codice:

CropImageViewModel.cpp


task<void> CropImageViewModel::CropImageAsync(float64 actualWidth)
{
    assert(IsMainThread());
    ChangeInProgress(true);

    // Calculate crop values
    float64 scaleFactor = m_image->PixelWidth / actualWidth;
    unsigned int xOffset = safe_cast<unsigned int>((m_cropOverlayLeft - m_left) * scaleFactor);
    unsigned int yOffset = safe_cast<unsigned int>((m_cropOverlayTop - m_top) * scaleFactor);
    unsigned int newWidth = safe_cast<unsigned int>(m_cropOverlayWidth * scaleFactor); 
    unsigned int newHeight = safe_cast<unsigned int>(m_cropOverlayHeight * scaleFactor);

    if (newHeight < MINIMUMBMPSIZE || newWidth < MINIMUMBMPSIZE)
    {
        ChangeInProgress(false);
        m_isCropOverlayVisible = false;
        OnPropertyChanged("IsCropOverlayVisible");
        return create_empty_task();
    }

    m_cropX += xOffset;
    m_cropY += yOffset;

    // Create destination bitmap
    WriteableBitmap^ destImage = ref new WriteableBitmap(newWidth, newHeight);

    // Get pointers to the source and destination pixel data
    byte* pSrcPixels = GetPointerToPixelData(m_image->PixelBuffer, nullptr);
    byte* pDestPixels = GetPointerToPixelData(destImage->PixelBuffer, nullptr);
    auto oldWidth = m_image->PixelWidth;

    return create_task([this, xOffset, yOffset, newHeight, newWidth, oldWidth, pSrcPixels, pDestPixels] () {
        assert(IsBackgroundThread());
        DoCrop(xOffset, yOffset, newHeight, newWidth, oldWidth, pSrcPixels, pDestPixels);
    }).then([this, destImage](){
        assert(IsMainThread());

        // Update image on screen
        m_image = destImage;
        OnPropertyChanged("Image");
        ChangeInProgress(false);
    }, task_continuation_context::use_current()).then(ObserveException<void>(m_exceptionPolicy));
}


Il codice mostra l'operazione di ritaglio dell'immagine eseguita da Hilo. Nell'interfaccia utente per l'operazione di ritaglio sono visualizzati punti di ritaglio che l'utente può modificare. Dopo aver specificato l'area di ritaglio, l'utente tocca l'immagine per generare un'anteprima dell'immagine ritagliata. Il controllo Grid dell'operazione di ritaglio genera un evento Tapped il cui handler code-behind richiama il metodo CropImageAsync dell'esempio.

Poiché viene richiamato da un handler di evento, il metodo CropImageAsync viene eseguito nel thread principale. Il lavoro del metodo viene diviso internamente tra il thread principale e un thread in background del pool di thread. A tale scopo, il metodo usa la funzione concurrency::create_task per pianificare il metodo DoCrop in un thread in background.

Nota  Durante l'esecuzione del metodo DoCrop in background, il thread principale è libero di eseguire altro lavoro in parallelo. Ad esempio, durante l'esecuzione del metodo DoCrop il thread principale continua ad animare l'anello di stato e a rispondere alle richieste di spostamento dell'utente.
Al termine del metodo DoCrop, nel thread principale inizia l'esecuzione di un'attività di continuazione per l'aggiornamento dei controlli XAML.

Ecco il diagramma dell'uso dei thread da parte dell'operazione di ritaglio.

Diagramma che illustra l'uso dei thread da parte di un'operazione di ritaglio

Il metodo DoCrop viene eseguito in un thread in background, e inoltre usa la funzione parallel_for della libreria PPL per eseguire un'operazione di calcolo complesso tramite l'hardware multicore del computer. Ecco il codice.

CropImageViewModel.cpp


void CropImageViewModel::DoCrop(uint32_t xOffset, uint32_t yOffset, uint32_t newHeight, uint32_t newWidth, uint32_t oldWidth, byte* pSrcPixels, byte* pDestPixels)
{    
    assert(IsBackgroundThread());
    parallel_for (0u, newHeight, [xOffset, yOffset, newHeight, newWidth, oldWidth, pDestPixels, pSrcPixels](unsigned int y)
    {
        for (unsigned int x = 0; x < newWidth; x++)
        {
            pDestPixels[(x + y * newWidth) * 4] = 
                pSrcPixels[(x +  xOffset + (y + yOffset) * oldWidth) * 4];     // B
            pDestPixels[(x + y * newWidth) * 4 + 1] = 
                pSrcPixels[(x +  xOffset + (y + yOffset) * oldWidth) * 4 + 1]; // G
            pDestPixels[(x + y * newWidth) * 4 + 2] = 
                pSrcPixels[(x +  xOffset + (y + yOffset) * oldWidth) * 4 + 2]; // R
            pDestPixels[(x + y * newWidth) * 4 + 3] =
                pSrcPixels[(x +  xOffset + (y + yOffset) * oldWidth) * 4 + 3]; // A
        }
    });        
}


Il codice è un esempio di integrazione di tecniche di programmazione in parallelo all'interno dell'app. Per ottenere i massimi vantaggi dalla concorrenza, è opportuno eseguire in parallelo solo il ciclo esterno. Se si esegue in parallelo il ciclo interno, non si ottiene un miglioramento delle prestazioni, perché la piccola quantità di lavoro eseguita dal ciclo interno non compensa l'overhead dell'elaborazione in parallelo.

Per applicare uno stile di programmazione moderno e ottenere le prestazioni migliori, ti consigliamo di usare gli algoritmi in parallelo e i tipi di dati della libreria PPL per il nuovo codice.

Per ulteriori informazioni, vedi l'articolo sulla programmazione in parallelo in C++.

[Torna all'inizio]

Suggerimenti per l'uso di C++/CX come livello di interoperabilità

Ecco alcuni suggerimenti per l'interoperabilità tra linguaggi che abbiamo sviluppato durante la creazione di Hilo. Per la documentazione completa delle estensioni dei linguaggi disponibili per un'applicazione di Windows Store in C++ per l'interoperabilità tra linguaggi, vedi Informazioni di riferimento su Visual C++ (C++/CX).

Tieni presente l'overhead per la conversione di tipi

Per interagire con le funzionalità Windows Runtime, talvolta è necessario creare tipi di dati dagli spazi dei nomi Platform e Windows. Questi tipi devono essere creati nel modo più efficiente possibile.

Ad esempio, se crei un riferimento Windows::Foundation::Collections::Vector^ da un oggetto std::vector, il costruttore Vector potrebbe creare una copia. Se allochi un oggetto std::vector e sai che non esistono altri riferimenti a questo oggetto, puoi creare un oggetto Vector senza eseguire una copia, chiamando la funzione std::move in std::vector prima di passare l'oggetto al costruttore Vector. Questa operazione funziona perché la classe Vector fornisce un costruttore di spostamento che richiede un argomento std::vector<T>&&.

Esistono costruttori di spostamento per le classi Platform::Collections Vector, VectorView, Map e MapView. Queste classi hanno costruttori che richiedono riferimenti rvalue ai tipi std::vector std::map. Ecco un esempio.

YearGroup.cpp


vector<IMonthBlock^> monthBlocks;
monthBlocks.reserve(nMonths);
for (int month = 1; month <= nMonths; month++)
{
    auto monthBlock = ref new MonthBlock(this, month, m_folderQuery, m_repository, m_exceptionPolicy);
    monthBlocks.push_back(monthBlock);
}
m_months = ref new Vector<IMonthBlock^>(std::move(monthBlocks));


Il codice crea l'oggetto m_months senza copiare il vettore monthBlock.

Per le classi Platform::Array e Platform::String non ci sono costruttori di spostamento. In alternativa, puoi usare la classe Platform::ArrayReference e la classe Platform::StringReference.

In Hilo abbiamo preferito usare le classi Vector e Map anziché la classe Platform::Array per la loro compatibilità con std::vector e std::map. Non usiamo std::array perché ne vengono richieste le dimensioni al momento della compilazione.

Chiama i metodi delle classi ref dal thread richiesto

Per alcune classi ref è necessario che l'accesso ai metodi, agli eventi e alle proprietà di tali classi avvenga da un thread specifico. Ad esempio, l'interazione con le classi XAML deve avvenire dal thread principale. Se crei una nuova classe che deriva da una classe Windows Runtime esistente, erediti i requisiti di contesto della classe di base.

Fai attenzione a rispettare il modello di threading degli oggetti che usi.

Un effetto collaterale delle operazioni eseguite in un thread è la diminuzione dei conteggi dei riferimenti. Per questo motivo devi assicurarti che sia possibile chiamare da qualsiasi thread i distruttori delle classi ref che implementi. Non richiamare metodi o proprietà del distruttore che richiedono un contesto di thread specifico.

Ad esempio, per alcune classi Windows Runtime è necessario annullare la registrazione di handler di evento nel thread principale. Ecco un esempio di codice di un distruttore thread-safe che esegue questa operazione.

ImageView.cpp


ImageView::~ImageView()
{
    if (nullptr != PhotosFilmStripGridView)
    {
        // Remove the event handler on the UI thread because GridView methods
        // must be called on the UI thread.
        auto photosFilmStripGridView = PhotosFilmStripGridView;
        auto filmStripLoadedToken = m_filmStripLoadedToken;
        run_async_non_interactive([photosFilmStripGridView, filmStripLoadedToken]()
        {
            photosFilmStripGridView->Loaded::remove(filmStripLoadedToken);
        });
    }
}


La funzione run_async_non_interactive è una funzione di utilità definita in Hilo che invia un oggetto funzione al thread principale, concedendo una priorità più alta all'interazione dell'utente tramite interfaccia utente.

Contrassegna i distruttori di classi ref pubbliche come virtuali

I distruttori di classi ref pubbliche devono essere dichiarati virtual.

Usa le classi ref solo per l'interoperabilità

Quando crei oggetti o componenti di Windows Runtime, devi usare solo ^ e ref new. Puoi usare la sintassi C++ standard quando scrivi codice di applicazioni di base che non usano Windows Runtime.

Hilo usa ^ e std::shared_ptr per gestire gli oggetti allocati a heap e ridurre al minimo le perdite di memoria. Ti consigliamo di usare ^ per gestire la durata delle variabili Windows Runtime, ComPtr per gestire la durata delle variabili COM (ad esempio quando usi DirectX) e std::shared_ptr o std::unique_ptr per gestire la durata di tutti gli altri oggetti C++ allocati a heap.

Ti consigliamo inoltre di dichiarare pubbliche tutte le classi ref, perché sono destinate esclusivamente all'interoperabilità. Se hai classi private, protected o internal ref, significa che stai provando a usare classi ref a scopo di implementazione e non per l'interoperabilità attraverso l'interfaccia ABI (Abstract Binary Interface).

Usa tecniche che riducono al minimo i costi di marshalling

C++/CX è progettato per l'interoperabilità dei linguaggi. Quando chiami le funzioni attraverso l'interfaccia ABI, talvolta si verifica un overhead a causa del costo del marshalling (copia) dei dati. Poiché il framework dell'interfaccia utente di XAML è scritto in C++, questo overhead non si verifica durante l'interoperabilità con XAML da un'app in C++. Se implementi un componente in C++ che viene chiamato da un linguaggio diverso da C++ o XAML, l'app subisce un certo costo di marshalling.

Vedi Threading e marshaling (C++/CX) per i modi in cui specificare il comportamento relativo a threading e marshalling dei componenti che crei. Per ulteriori informazioni sull'overhead di marshalling con altri linguaggi, vedi Mantenere invariata la velocità dell'app quando si fa uso dell'interoperabilità (app di Windows Store apps in C#/VB/C++ e XAML).

Usa il Visualizzatore oggetti per comprendere l'output .winmd dell'app

Quando generi l'app, il compilatore crea un file .winmd che contiene i metadati per tutti i tipi ref pubblici definiti nell'app stessa. Componenti quali XAML usano il file .winmd per richiamare metodi dei tipi di dati dell'app attraverso l'interfaccia ABI.

È spesso utile esaminare il contenuto del file .winmd generato. A tale scopo, puoi usare il Visualizzatore oggetti di Visual Studio. Dal Visualizzatore oggetti passa al file .winmd nella directory Debug del progetto e aprilo. Potrai vedere tutti i tipi che l'app espone all'XAML.

Nota  Osserva quali tipi ref pubblici sono inclusi nel file winmd dell'app.

Se C++/CX non soddisfa le tue esigenze, considera la libreria Windows Runtime per l'interoperabilità di basso livello

Le estensioni di linguaggio di C++/CX consentono di risparmiare tempo, ma non è obbligatorio usarle. Puoi ottenere un accesso di basso livello all'interoperabilità tra linguaggi dal C++ standard se usi la libreria di modelli C++ di Windows Runtime (libreria Windows Runtime). La libreria Windows Runtime usa convenzioni familiari ai programmatori COM.

La libreria Windows Runtime è un modo indipendente dal compilatore per la creazione e il consumo di API Windows Runtime. Puoi usare la libreria Windows Runtime al posto della sintassi C++/CX. Questa libreria ti consente di creare codice ottimizzato per le migliori prestazioni o per scenari specifici. La libreria inoltre supporta metodologie di sviluppo di app in cui non è previsto l'uso di eccezioni. Per ulteriori informazioni, vedi Libreria modelli C++ per Windows Runtime.

Per Hilo, C++/CX disponeva delle funzionalità necessarie e garantiva le prestazioni richieste. Abbiamo usato la libreria Windows Runtime solo per accedere a un'interfaccia COM per la lettura dei dati relativi ai pixel di un'immagine. In genere, la libreria Windows Runtime è una buona candidata nei casi in cui si vuole convertire un oggetto COM in oggetto di Windows Runtime, dato che le radici della libreria Windows Runtime affondano in ATL.

Non confondere le estensioni del linguaggio C++/CX con C++/CLI

La sintassi delle estensioni del linguaggio C++/CX e di C++/CLI sono simili, ma i modelli di esecuzione sono molto diversi. Approfondiamo questo aspetto.

Per chiamare le API Windows Runtime da JavaScript e .NET, questi linguaggi richiedono proiezioni specifiche per ciascun ambiente di linguaggio. Quando chiami un'API Windows Runtime da JavaScript o .NET, stai richiamando la proiezione, che a sua volta chiama la funzione ABI sottostante. Nonostante sia possibile chiamare le funzioni ABI direttamente dal C++ standard, Microsoft fornisce anche le proiezioni per C++, perché queste rendono molto più semplice il consumo delle API Windows Runtime, mantenendo tuttavia alte le prestazioni.

Microsoft fornisce inoltre le estensioni di linguaggio per Visual C++ che supportano specificamente le proiezioni Windows Runtime. La sintassi di molte di queste estensioni è simile a quella del linguaggio C++/CLI. Tuttavia, invece di avere come obiettivo CLR (Common Language Runtime), le app C++ usano questa sintassi per generare codice nativo compatibile con i requisiti del formato binario dell'interfaccia ABI.

Poiché non esiste un runtime per la gestione della memoria, il sistema elimina gli oggetti C++/CX basati sul conteggio dei riferimenti in modo simile a std::shared_ptr. L'operatore handle-to-object, o hat (^), è una parte importante della nuova sintassi, poiché consente il conteggio dei riferimenti. Anziché chiamare direttamente metodi quali AddRef e Release per gestire la durata di un oggetto Windows Runtime, puoi lasciare che questa operazione venga eseguita automaticamente dal runtime. Questo elimina l'oggetto se nessun altro componente vi fa riferimento, ad esempio se l'oggetto esce dall'ambito o se tu imposti tutti i riferimenti su nullptr.

Un'altra parte importante relativa all'uso di Visual C++ per la creazione di app di Windows Store è la parola chiave ref new. Usa ref new invece di new per creare oggetti Windows Runtime con conteggio dei riferimenti. Per ulteriori informazioni, vedi Sistemi di tipi (C++/CX).

Non provare a esporre tipi interni in classi ref pubbliche

Le classi ref pubbliche generano metadati che vengono esposti nel file .winmd dell'app. I metadati descrivono ciascuno dei tipi e i membri di ogni tipo. Perché i metadati siano completi, ogni membro di un tipo pubblico deve a sua volta essere un tipo visibile pubblicamente. Di conseguenza, esiste il requisito secondo il quale non si possono esporre tipi interni come membri public di una classe ref pubblica.

Il requisito dei metadati può influire sulla progettazione dell'app. In genere, prova a suddividere i tuoi tipi in modo che i tipi ref pubblici siano usati nell'app esclusivamente per l'interoperabilità e non per altri scopi. Se non presti sufficiente attenzione, potresti assistere a una proliferazione involontaria di classi ref pubbliche nella tua app.

[Torna all'inizio]

Suggerimenti per la gestione della memoria

Poiché di solito le app di Windows Store non vengono chiuse e vengono eseguite su tablet con caratteristiche hardware diverse, è importante avere sempre consapevolezza della quantità di memoria usata dall'app. Per evitare perdite di memoria, è particolarmente importante che gli oggetti non accessibili dal codice non rimangano in memoria. È inoltre necessario tenere conto della durata degli oggetti, per evitare che rimangano in memoria più a lungo del necessario. Per un'efficace gestione della memoria, ti consigliamo di:

Usare puntatori intelligenti

Usa puntatori intelligenti per garantire l'assenza di perdite di memoria e di risorse nei programmi, oltre all'assenza di eccezioni. Nelle app di Windows Store è consigliabile usare handle-to-object, ^ (ovvero "hat"), per gestire la durata delle variabili Windows Runtime, Microsoft::WRL::ComPtr per gestire la durata delle variabili COM (ad esempio quando usi DirectX) e std::shared_ptr o std::unique_ptr per gestire la durata di tutti gli altri oggetti C++ allocati a heap.

È facile ricordarsi di usare ^, perché quando si chiama ref new il compilatore genera codice per l'allocazione di un oggetto di Windows Runtime e quindi restituisce un handle a questo oggetto. Anche i metodi di Windows Runtime che non restituiscono tipi valore restituiscono handle. Ciò su cui è importante concentrare l'attenzione è l'uso di tipi di puntatore standard e di oggetti COM per eliminare le perdite di memoria e di risorse.

In Hilo, std::shared_ptr è usato tra l'altro per consentire a un'attività di una catena di continuazione di scrivere in una variabile e a un'altra variabile di leggere il valore scritto. Per ulteriori informazioni, vedi Assemblaggio dell'output di più continuazioni in questa Guida.

In Hilo si è reso necessario accedere direttamente ai dati relativi ai pixel per ritagliare alcune immagini e applicare l'effetto cartoon ad altre. A tale scopo, abbiamo dovuto convertire un oggetto IBuffer nell'interfaccia COM sottostante, IBufferByteAccess. Abbiamo eseguito il cast dell'oggetto IBuffer sull'interfaccia IInspectable sottostante e abbiamo chiamato QueryInterface per recuperare un puntatore a un'interfaccia supportata nell'oggetto. Per gestire i puntatori, abbiamo anche usato la classe ComPtr, per cui non sono state necessarie chiamate a AddRef e a Release.

ImageUtilities.cpp


// Retrieves the raw pixel data from the provided IBuffer object.
// Warning, the lifetime of the returned buffer is controlled by the lifetime of the
// buffer object passed to this method, once the buffer has been released 
// pointer will be invalid and must not be used.
byte* GetPointerToPixelData(IBuffer^ buffer, unsigned int *length)
{
    if (length != nullptr)
    {
        *length = buffer->Length;
    }
    // Query the IBufferByteAccess interface.
    ComPtr<IBufferByteAccess> bufferByteAccess;
    ThrowIfFailed(reinterpret_cast<IInspectable*>(buffer)->QueryInterface(IID_PPV_ARGS(&bufferByteAccess)));

    // Retrieve the buffer data.
    byte* pixels = nullptr;
    ThrowIfFailed(bufferByteAccess->Buffer(&pixels));
    return pixels;
}

Dato che stavamo usando COM, abbiamo definito la funzione ThrowIfFailed per gestire con maggiore facilità i valori HRESULT. Puoi usare questa funzione di utilità nel tuo codice quando devi convertire un codice di errore in un'eccezione Windows Runtime.

ImageUtilities.cpp


inline void ThrowIfFailed(HRESULT hr)
{
    if (FAILED(hr))
    {
        throw Exception::CreateException(hr);
    }
}


Per ulteriori informazioni sui puntatori intelligenti, vedi Puntatori intelligenti (C++ moderno).

Usare la semantica dello stack e lo schema RAII

La semantica dello stack e lo schema RAII sono strettamente correlati.

Puoi usare la semantica dello stack per controllare automaticamente la durata degli oggetti e ridurre al minimo le allocazioni di heap non necessarie. Puoi usare la semantica dello stack anche per definire le variabili membro all'interno delle tue classi e altre strutture dati in modo che liberino automaticamente le risorse quanto l'oggetto padre viene liberato.

Puoi usare lo schema RAII (Resource Acquisition Is Initialization) per assicurarti che tutte le risorse vengano liberate quando la funzione corrente termina o genera un'eccezione. Sotto lo schema RAII, sullo stack viene allocata una struttura dati. Questa struttura dati inizializza o acquisisce una risorsa quando viene creata e la distrugge o la rilascia quando la struttura dati stessa viene distrutta. Lo schema RAII garantisce che il distruttore venga chiamato prima che l'ambito contenitore termini. Questo schema è utile se una funzione contiene più istruzioni return e semplifica la scrittura di codice privo di eccezioni. Quando un'istruzione throw causa l'unwind dello stack, viene chiamato il distruttore per l'oggetto RAII, pertanto la risorsa viene sempre eliminata o rilasciata correttamente. L'uso di una classe di modelli std::shared_ptr per le variabili allocate nello stack è un esempio di schema RAII.

Per ulteriori informazioni sulla gestione della durata degli oggetti, vedi Durata degli oggetti e delle risorse (C++ moderno). Per ulteriori informazioni su RAII, vedi Oggetti e le proprie risorse (RAII).

Non mantenere gli oggetti più a lungo del necessario

Puoi usare la semantica dello stack o impostare riferimenti a nullptr per assicurarti che gli oggetti vengano liberati quando non esistono più riferimenti ad essi.

In Hilo abbiamo preferito usare la semantica dello stack piuttosto che le variabili membro per due motivi. Prima di tutto, la semantica dello stack contribuisce a garantire che la memoria venga usata solo nel contesto in cui è necessaria. Poiché Hilo è un'app di gestione di foto, ci è sembrato particolarmente importante liberare i dati delle immagini il prima possibile, per limitare al massimo il consumo di memoria. Lo stesso principio vale per altri tipi di app. In un lettore di blog, ad esempio, è necessario rilasciare le connessioni di rete quando non sono più necessarie, per consentire ad altre applicazioni di usare tali risorse.

In secondo luogo, poiché buona parte dell'app dipende da azioni asincrone, abbiamo voluto consentire l'accesso alle variabili solo al codice che ne ha bisogno, per consentire l'esecuzione dell'app in concorrenza. Nella programmazione multithreading tradizionale che non usa espressioni lambda è spesso necessario memorizzare lo stato sotto forma di variabili membro. Ecco un esempio in cui, per creare in modo asincrono un'anteprima da un'immagine sul disco, abbiamo usato variabili locali e abbiamo acquisito la semantica invece di usare variabili membro.

ThumbnailGenerator.cpp


task<InMemoryRandomAccessStream^> ThumbnailGenerator::CreateThumbnailFromPictureFileAsync(
    StorageFile^ sourceFile, 
    unsigned int thumbSize)
{
    (void)thumbSize; // Unused parameter
    auto decoder = make_shared<BitmapDecoder^>(nullptr);
    auto pixelProvider = make_shared<PixelDataProvider^>(nullptr);
    auto resizedImageStream = ref new InMemoryRandomAccessStream();
    auto createThumbnail = create_task(
        sourceFile->GetThumbnailAsync(
        ThumbnailMode::PicturesView, 
        ThumbnailSize));

    return createThumbnail.then([](StorageItemThumbnail^ thumbnail)
    {
        IRandomAccessStream^ imageFileStream = 
            static_cast<IRandomAccessStream^>(thumbnail);

        return BitmapDecoder::CreateAsync(imageFileStream);

    }).then([decoder](BitmapDecoder^ createdDecoder)
    {
        (*decoder) = createdDecoder;
        return createdDecoder->GetPixelDataAsync( 
            BitmapPixelFormat::Rgba8,
            BitmapAlphaMode::Straight,
            ref new BitmapTransform(),
            ExifOrientationMode::IgnoreExifOrientation,
            ColorManagementMode::ColorManageToSRgb);

    }).then([pixelProvider, resizedImageStream](PixelDataProvider^ provider)
    {
        (*pixelProvider) = provider;
        return BitmapEncoder::CreateAsync(
            BitmapEncoder::JpegEncoderId, 
            resizedImageStream);

    }).then([pixelProvider, decoder](BitmapEncoder^ createdEncoder)
    {
        createdEncoder->SetPixelData(BitmapPixelFormat::Rgba8,
            BitmapAlphaMode::Straight,
            (*decoder)->PixelWidth,
            (*decoder)->PixelHeight,
            (*decoder)->DpiX,
            (*decoder)->DpiY,
            (*pixelProvider)->DetachPixelData());
        return createdEncoder->FlushAsync();

    }).then([resizedImageStream]
    {
        resizedImageStream->Seek(0);
        return resizedImageStream;
    });
}


Se la tua app richiede l'uso di una variabile membro, dovresti impostare la variabile su nullptr quando non è più necessaria.

Evitare riferimenti circolari

Gli oggetti interessati da un riferimento circolare non possono mai raggiungere un conteggio dei riferimenti uguale a zero e non vengono mai distrutti. Per evitare riferimenti circolari nel tuo codice, sostituisci uno dei riferimenti con un riferimento debole.

Ad esempio, in Hilo sono definite le interfacce IPhoto e IPhotoGroup. L'interfaccia IPhoto incapsula le informazioni relative a una foto (percorso, tipo di immagine, data di ripresa e così via). L'interfaccia IPhotoGroup gestisce le raccolte di foto (ad esempio, tutte le foto di un mese specifico). Tra queste interfacce esiste una relazione padre-figlio— è possibile recuperare una singola foto da un gruppo di foto o il gruppo di foto padre da una foto. Se avessimo usato riferimenti standard, o forti, esisterebbe sempre un riferimento tra una foto e il gruppo corrispondente e la memoria non verrebbe mai liberata né per l'una né per l'altro, anche dopo che tutti gli altri riferimenti fossero stati rilasciati.

Per evitare riferimenti circolari come questo, Hilo usa riferimenti deboli. Un riferimento debole consente a un oggetto di fare riferimento a un altro senza influire sul conteggio dei riferimenti di quest'ultimo.

Windows Runtime fornisce la classe WeakReference il cui metodo Resolve restituisce l'oggetto se è valido, in caso contrario restituisce nullptr. Ecco come la classe Photo, che deriva da IPhoto, dichiara un riferimento debole al suo gruppo padre:

Photo.h


Platform::WeakReference m_weakPhotoGroup;


Ecco come la classe Photo risolve il riferimento debole nella proprietà Group:

Photo.cpp


IPhotoGroup^ Photo::Group::get()
{
    return m_weakPhotoGroup.Resolve<IPhotoGroup>();
}


Per interrompere il riferimento circolare, Hilo usa anche std::weak_ptr. Usa WeakReference quando devi esporre un riferimento debole attraverso il limite dell'interfaccia ABI. Usa std::weak_ptr nel codice interno che non interagisce con Windows Runtime.

Un riferimento circolare può essere causato anche dall'acquisizione dell'handle this di un oggetto in un'espressione lambda, se l'espressione lambda viene usata come gestore evento o viene passata a un'attività a cui l'oggetto fa riferimento.

Nota  All'interno delle funzioni membro di una classe ref il tipo del simbolo this è un handle di costante (^), non un puntatore.

I gestori evento di un oggetto vengono scollegati dall'origine quando l'oggetto viene distrutto. Tuttavia, un'espressione lambda che acquisisce this incrementa il conteggio dei riferimenti dell'oggetto. Se un oggetto crea un'espressione lambda che acquisisce this e la usa come gestore di un evento fornito da se stesso o da un oggetto a cui fa riferimento direttamente o indirettamente, il conteggio dei riferimenti dell'oggetto non può mai raggiungere lo zero. In questo caso, l'oggetto non viene mai distrutto.

Hilo evita il riferimento circolare mediante l'uso di funzioni membro con gli eventi al posto di espressioni lambda. Ecco un esempio tratto dalla classe HiloPage.

HiloPage.cpp


m_navigateBackEventToken = viewModel->NavigateBack::add(ref new NavigateEventHandler(this, &HiloPage::NavigateBack));
m_navigateHomeEventToken = viewModel->NavigateHome::add(ref new NavigateEventHandler(this, &HiloPage::NavigateHome));


In questo esempio, NavigateEventHandler è un tipo di delegato definito da Hilo. Un delegato è una classe ref in Windows Runtime equivalente a un oggetto funzione in C++ standard. Quando si crea un'istanza di un delegato usando un handle all'oggetto e un puntatore a una funzione membro, il conteggio dei riferimenti dell'oggetto non viene incrementato. Se si richiama l'istanza del delegato dopo che l'oggetto di destinazione è stato distrutto, viene generata un'eccezione Platform::DisconnectedException.

Oltre al costruttore a due argomenti illustrato in questo esempio, i tipi di delegato hanno un costruttore con overload che accetta un unico argomento. L'argomento è un oggetto funzione, ad esempio un'espressione lambda. Se questo esempio acquisisse l'handle this dell'oggetto in un'espressione lambda e passasse quest'ultima come argomento del costruttore del delegato, il conteggio dei riferimento dell'oggetto verrebbe incrementato. Il conteggio dei riferimenti verrebbe decrementato solo quando non ci fossero più riferimenti all'espressione lambda.

Se si usano espressioni lambda come gestori evento, è possibile gestire la memoria in due modi: acquisendo riferimenti deboli invece di handle all'oggetto oppure facendo attenzione ad annullare la sottoscrizione agli eventi al momento giusto, interrompendo i riferimenti circolari prima che questi causino problemi di gestione della memoria.

In Hilo abbiamo scelto funzioni membro per i callback di evento, perché ci è sembrato che fosse la soluzione più facile da trasformare correttamente in codice.

Per ulteriori informazioni sui riferimenti deboli, vedi Riferimenti deboli e cicli di interruzione (C++/CX) e Procedura: Creare e utilizzare le istanze di weak_ptr.

[Torna all'inizio]

Suggerimenti e tecniche di debug

Per le app di Windows Store puoi usare molti degli strumenti e delle tecniche tradizionali che usi per il debug di app desktop. Ecco alcuni strumenti e tecniche che abbiamo usato per Hilo:

Punti di interruzione e punti di analisi

Un punto di interruzione istruisce il debugger perché interrompa, o sospenda, l'esecuzione di un'applicazione in un punto specifico. Un punto di analisi è un punto di interruzione a cui è associata un'azione. Per Hilo abbiamo usato intensamente i punti di interruzione per esaminare lo stato dell'app durante l'esecuzione di attività di continuazioni PPL.

È possibile configurare punti di interruzione che si attivino quando alcune condizioni sono vere. Per Hilo è stato necessario eseguire il debug di un'interazione tra XAML e il code-behind in C++. Questa interazione presentava un problema solo se il codice veniva eseguito almeno 20 volte. Abbiamo quindi usato l'impostazione Conteggio visite per interrompere l'esecuzione dopo che il punto di interruzione è stato raggiunto per almeno 20 volte.

Con Hilo i punti di analisi sono stati particolarmente utili per diagnosticare da quale thread venivano eseguite determinate attività PPL. Il messaggio predefinito del punto di analisi conteneva tutte le informazioni necessarie.


Function: $FUNCTION, Thread: $TID $TNAME

Per ulteriori informazioni, vedi Utilizzo di punti di interruzione e punti di analisi.

Suggerimento  Usa __debugbreak per impostare un punto di interruzione a livello di programmazione dal codice.

Usa OutputDebugString per debug stile "printf"

Puoi usare OutputDebugString quando non devi creare interruzioni all'interno del debugger. OutputDebugString funziona anche con strumenti come WinDbg e può essere usata con la modalità di rilascio.

Attenzione  Per eseguire debug stile "printf" puoi usare anche TextBlock e altri controlli XAML. Tuttavia abbiamo preferito OutputDebugString perché la rimozione dei controlli dopo l'uso può influire sul layout delle pagine in modo imprevisto. Se per il debug usi uno o più controlli, assicurati di rimuoverli e quindi di sottoporre a test il codice prima di distribuire la tua app.

Interruzione quando viene generata un'eccezione

Al momento di un'interruzione con l'istruzione catch non puoi sapere quale parte del codice ha generato l'eccezione. In Visual Studio scegli Debug, Eccezioni e quindi scegli Generata per interrompere quando viene generata un'eccezione. Questa tecnica è utile soprattutto durante l'uso di attività PPL, perché ti consente di esaminare il contesto di threading specifico in cui si è verificato l'errore.

Importante  È importante rilevare tutte le eccezioni generate dalle attività PPL. Se non osservi un'eccezione generata da un'attività PPL, il runtime termina l'app. Se l'app termina in modo imprevisto, puoi abilitare questa funzionalità per scoprire con maggiore facilità dove si è verificata l'eccezione.

Finestre di debug in parallelo

Puoi usare le finestre Attività in parallelo, Stack in parallelo ed Espressioni di controllo in parallelo per comprendere e verificare il comportamento in fase di esecuzione di codice che usa la libreria PPL e altre funzionalità del Runtime di concorrenza. Per Hilo abbiamo usato queste finestre per comprendere lo stato di tutte le attività in esecuzione in corrispondenza di punti diversi del programma. Per ulteriori informazioni, vedi Procedura dettagliata: debug di un'applicazione in parallelo e Procedura: utilizzare la finestra Espressione di controllo in parallelo.

Simulatore e debug remoto per il debug di configurazioni hardware specifiche

Se, ad esempio, il tuo monitor non è dotato di funzionalità di tocco, puoi usare il simulatore per simulare l'avvicinamento delle dita, lo zoom, la rotazione e altri gesti. Il simulatore ti consente inoltre di simulare la georilevazione e di usare risoluzioni diverse dello schermo. Per ulteriori informazioni, vedi Esecuzione di app di Windows Store nel simulatore e Test di dispositivi con il simulatore e il debugger remoto in questa Guida.

Esecuzione di Hilo nel simulatore

Puoi usare il debug remoto per le app presenti in un computer in cui non è installato Visual Studio. Questa funzione è utile se un computer ha una configurazione hardware diversa da quello che stai usando, in cui è in esecuzione Visual Studio. Per ulteriori informazioni, vedi Esecuzione di app di Windows Store di Windows in un computer remoto.

Per ulteriori informazioni sul debug, vedi Debug e test di app di Windows Store di Windows e Debug del codice nativo.

[Torna all'inizio]

Conversione di codice C++ esistente

Se vuoi convertire codice in C++ esistente nella tua app di Windows Store, l'impegno necessario dipende dal codice. Se il codice usa framework di interfaccia utente precedenti deve essere riscritto, mentre in altri casi, come logica di business e routine numeriche, il codice può essere convertito in genere senza difficoltà. Se usi codice scritto da un altro programmatore, puoi inoltre controllare se esiste già una versione per app di Windows Store. In Hilo abbiamo adattato il codice esistente dell'effetto cartoon e l'abbiamo convertito per l'uso all'interno di una libreria statica. Vedi il progetto CartoonEffect nei file sorgente di Hilo. Nella libreria statica non abbiamo inserito dipendenze a Windows Runtime, in modo da poter considerare come destinazioni anche le versioni precedenti di app desktop per Windows e Windows 8.

Le app di Windows Store in C++ possono fare riferimento a librerie statiche esistenti, DLL e componenti COM, a condizione che tutto il codice esistente chiamato dall'app soddisfi i requisiti per le app di Windows Store. Nelle app di Windows Store tutti i componenti, comprese le librerie statiche e le DLL, devono essere locali rispetto all'app e comprese nel pacchetto del manifesto. Tutti i componenti del pacchetto dell'app devono rispettare i requisiti delle app di Windows Store.

Panoramica del processo di conversione

Ecco uno schema delle operazioni per la conversione di codice C++ esistente per renderlo utilizzabile in un'app di Windows Store. Abbiamo eseguito questo processo per la conversione del codice esistente da importare in Hilo.

Compilazione e test del codice in Windows 8

Il primo passo per la conversione è la compilazione e il test del codice esistente in Windows 8. Il codice che può essere compilato ed eseguito in Windows 7 dovrebbe richiedere pochissime modifiche per Windows 8, ma prima di convertire il codice per un'app di Windows Store è buona norma trovare eventuali dipendenze da altre piattaforme.

Per scrivere Hilo abbiamo riciclato da un progetto precedente il codice del filtro di immagini per l'effetto cartoon. Per la compilazione e l'esecuzione di questo codice nella piattaforma Windows 8 non è stata necessaria alcuna modifica.

Nota  Dato che la conversione può modificare il codice originario, è buona norma creare unit test per quest'ultimo prima di iniziare la conversione. Per eseguire questa operazione puoi usare il framework di test nativo di Microsoft Visual Studio.

Individuazione di funzioni di libreria non supportate

Durante l'esecuzione del codice, devi verificare che questo usi solo le funzioni disponibili per le app di Windows Store. Puoi trovare l'elenco delle funzioni supportate in Informazioni di riferimento sulle API per le app di Windows Store. Vedi Panoramica delle funzioni supportate in questa pagina per una descrizione rapida delle funzioni disponibili.

Puoi inoltre usare la direttiva del preprocessore del compilatore WINAPI_FAMILY=WINAPI_PARTITION_APP per trovare eventuali incompatibilità in modo più semplice. Con questa direttiva il compilatore C++ genera errori ogni volta che rileva chiamate a funzioni non supportate.

Suggerimento  Se si imposta la direttiva WINAPI_FAMILY su WINAPI_PARTITION_APP durante la compilazione, le dichiarazioni di alcune funzioni e di alcuni tipi di dati Win32 vengono rimosse, ma questa operazione non influisce sulla fase di collegamento. Puoi reintrodurre alcune delle dichiarazioni mancanti in un file di intestazione temporaneo, toremove.h, e quindi eliminare i riferimenti a queste funzioni uno alla volta. Un file di intestazione temporaneo è un metodo valido per aggirare il problema dell'arresto del compilatore dopo che questo ha rilevato circa 100 errori.

Uso di funzioni dell'API Window Runtime

Nella maggior parte dei casi è disponibile una funzione Windows Runtime che esegue l'operazione che desideri. Ad esempio, la funzione Win32 InitializeCriticalSectionAndSpinCount non è disponibile per le app di Windows Store, ma in Windows Runtime è presente InitializeCriticalSectionEx. Man mano che procedi attraverso le aree problematiche, genera il codice con le nuove funzioni ed esegui test incrementali dell'app. Vedi l'articolo relativo alle alternative alle API Windows nelle app di Windows Store per alcuni suggerimenti sulle funzioni sostitutive da usare.

In alcuni casi, puoi risolvere le incompatibilità usando funzioni della libreria runtime C++ e di STL. In genere, usa C++ standard ogni volta che è possibile. Effettua il refactoring della logica di base per usare librerie C++ su contenitori, buffer e così via. Le librerie C++ 11 sono ricche di nuove funzionalità, ad esempio thread, mutex e I/O. Quando cerchi una sostituzione per un'API Win32 non dimenticare di esaminare le librerie C++.

Suggerimento  Per facilitare la conversione, isola il codice specifico di piattaforma all'interno di astrazioni.

Il codice rimanente, che costituiva la base del filtro di immagini per l'effetto cartoon, non eseguiva chiamate all'API Win32 ma era costituito solo da funzioni numeriche che gestivano i pixel dell'immagine. Gli unici problemi effettivi della conversione per Hilo si sono verificati nella riconciliazione dei diversi formati di dati per le bitmap. Ad esempio, al momento della codifica dell'immagine prima del salvataggio di questa, abbiamo dovuto riordinare i canali di colore per usare il layout BGRA (Blue/Green/Red/Alpha, blu/verde/rosso/alfa) richiesto dalle funzioni Windows Runtime.

Sostituzione delle funzioni di libreria sincrone con la corrispondente versione asincrona

Dopo che sei riuscito a compilare ed eseguire il codice usando solo tipi di dati e funzioni disponibili per le app di Windows Store, esamina il codice per trovare funzioni di libreria sincrone per le quali è disponibile una versione asincrona. Effettua il refactoring del codice in modo che possa consumare le operazioni asincrone, se il codice non esegue già questa operazione. L'app deve applicare l'approccio asincrono ovunque sia possibile.

Suggerimento  Effettua il refactoring del codice in modo che possa gestire dati parziali. Nei casi in cui sono disponibili risultati parziali in modo incrementale, devi rendere disponibili all'utente questi dati senza alcun ritardo.
Suggerimento  Mantieni attiva l'interfaccia utente durante le operazioni asincrone.

In Hilo abbiamo dovuto convertire in una versione asincrona le funzioni del filtro di immagini originario per l'effetto cartoon per il caricamento dell'immagine da elaborare.

Conversione delle operazioni di lunga durata del codice nella corrispondente versione asincrona

Se il codice contiene operazioni di lunga durata, è consigliabile rendere asincrone queste operazioni. Un modo semplice per eseguire ciò è di pianificare le operazioni di lunga durata nel pool di thread, ad esempio mediante un'attività PPL. Puoi riscrivere parte del codice o scrivere un wrapper che esegua il codice esistente nel pool di thread.

In Hilo abbiamo usato un'attività di continuazione PPL eseguita nel pool di thread per il filtro di immagine per l'effetto cartoon convertito.

Convalida del pacchetto con il Kit di certificazione app Windows

Dopo aver modificato il tuo codice in modo che usi solo le funzioni documentate nelle informazioni di riferimento sulle API per le app di Windows Store, il passaggio finale della conversione è la creazione di un pacchetto mediante binari dell'app e quindi l'uso del Kit di certificazione app Windows per controllare che il pacchetto sia conforme a tutti i requisiti di Windows Store. Il pacchetto contiene tutti i componenti dell'app, comprese librerie statiche e DLL.

Per la descrizione della certificazione di Hilo, vedi Test e distribuzione dell'app in questa Guida.

Panoramica delle funzioni supportate

Ecco un riepilogo delle funzioni e dei tipi di dati che puoi usare nelle app di Windows Store. Per l'elenco completo, vedi Informazioni di riferimento sulle API di Windows Store.

Conversione da un'interfaccia utente basata su Win32

Eventuale codice esistente che usa funzioni e dati di interfaccia utente, come HWND, derivati da User, GDI o MFC, deve essere reimplementato in XAML.

Conversione di DirectX

DirectX è disponibile nelle app di Windows Store. Se usi DirectX 11, la conversione della maggior parte del codice è facile. Come punto di partenza puoi usare un modello di Visual Studio per un progetto DirectX. Al momento dell'inizializzazione di un'app di Windows Store in fase di esecuzione è necessario eseguire qualche operazione di configurazione di DirectX. Il codice di avvio è disponibile già pronto nel modello di Visual Studio. Sposta il tuo codice esistente nel nuovo progetto.

Conversione di MFC

MFC non è disponibile nelle app di Windows Store. Per sostituire le classi di interfaccia utente di MFC, puoi usare XAML o DirectX. Per le app di dialogo puoi usare controlli dati XAML con associazione dati. Per sostituire le classi utilità MFC, ad esempio i contenitori, puoi usare i tipi di dati SLT e CRT.

Uso della libreria CRT (C++ Run-Time)

Un ampio sottoinsieme della libreria CRT è disponibile per le app di Windows Store. Un numero limitato di funzioni non è invece disponibile.

  • Funzioni stringa multibyte. Le funzioni mb* e _ismb* non sono disponibili. Al loro posto usa stringhe Unicode.
  • Funzioni di controllo di processo. Le funzioni exec* non sono disponibili. Non è in alcun modo possibile creare un'applicazione dall'interno di un'app di Windows Store.
  • Funzioni di creazione di thread. Le funzioni beginthread* e endthread* non sono disponibili. Il threading è disponibile nelle app di Windows Store, ma si basa su un pool di thread. Puoi usare anche std::thread.
  • Funzioni relative a heap e stack. Le funzioni heapwalk, heapmin, resetstkoflw e altre non sono disponibili. Nelle app di Windows Store non è possibile creare un heap personalizzato.
  • Funzioni relative a variabili di ambiente. Le funzioni putenv, getenv, _enviorn e funzioni correlate non sono disponibili. Nelle app di Windows Store non ci sono blocchi di ambiente.
  • Funzioni relative alla console. Le funzioni cprintf, cscanf e funzioni correlate non sono disponibili. Nelle app di Windows Store non c'è alcuna console.
  • Funzioni relative alle porte. Le funzioni outp, inp e altre funzioni relative alle porte non sono disponibili.
  • Funzioni relative alle pipe. Le funzioni popen, pclose e altre funzioni relative alle pipe non sono disponibili.
Nota  Usa la direttiva del preprocessore del compilatore WINAPI_FAMILY=WINAPI_PARTITION_APP per trovare eventuali incompatibilità in modo più semplice.

Alcune funzioni CRT disponibili per le app di Windows Store bloccano le funzioni di I/O. Ti consigliamo di sostituire le funzioni di I/O sincrone, come open, read e write, con le funzioni asincrone equivalenti di Windows Runtime.

Le funzioni stringa ANSI della libreria CRT sono disponibili nelle app di Windows Store, ma ti consigliamo di usare stringhe Unicode.

Uso della libreria standard C++

Nelle app di Windows Store sono disponibili quasi tutte le funzioni e i tipi di dati della libreria standard C++. Tuttavia, la console I/O non è disponibile. In Hilo la libreria standard C++ è usata intensamente.

Uso di ATL

Nelle app di Windows Store è disponibile un sottoinsieme della libreria ATL. Ecco i tipi di dati disponibili.

  • DLL server.
  • Oggetti COM. Puoi creare oggetti COM personalizzati. Tuttavia, IDispatch non è supportato.
  • CStringW. Sono supportate solo stringhe di tipo wide string.
  • Classi contenitore ATL. I contenitori MFC, ad esempio le mappe convertite alla libreria ATL, sono ancora disponibili.
  • CCriticalSection, CEvent, CMutex, CSemaphore, CMutexLock. Gli oggetti sincronizzazione ATL sono disponibili.
  • CComVariant.
  • CComSafeArray.
  • CComBSTR.

In genere, è possibile convertire componenti COM correlati alla logica di business e non all'interfaccia utente.

Linee guida per la conversione

Ecco alcuni suggerimenti per la conversione di codice C++ esistente in un'app di Windows Store.

Converti tutto il codice esistente, comprese le librerie

Il pacchetto dell'app deve comprendere tutti i componenti binari necessari perl'app stessa, ovvero librerie statiche e DLL.

Collega librerie statiche o importa librerie come di consueto

Nell'app di Windows Store puoi usare librerie scritte in C++ standard. Le app di Windows Store usano lo stesso tipo di collegamento delle app desktop e console. In caso di dipendenze binarie, è importante convalidare il pacchetto dell'applicazione nelle prime fasi del processo di sviluppo, per avere la certezza che tutte le funzionalità di libreria soddisfino i requisiti.

Nota  Nonostante la libreria CartoonEffect non usi direttamente alcuna funzionalità Windows Runtime, è stato comunque necessario specificare l'opzione del compilatore /ZW (consumo dell'estensione Windows Runtime), perché la versione PPL dell'algoritmo dell'effetto cartoon nelle app di Windows Store usa una semantica speciale. Ad esempio, in un'app di Windows Store è possibile configurare il contesto in cui vengono eseguite le continuazioni di attività PPL.

Usa C++/CX o WRL se la libreria deve richiamare funzioni Windows Runtime

Se la tua libreria deve richiamare funzioni Windows Runtime, puoi usare C++/CX o WRL. In genere, C++/CX è più facile da usare, mentre WRL consente un controllo più preciso.

Nota  L''esposizione di classi di riferimento pubbliche dall'interno di una libreria scritta dall'utente presenta alcuni problemi tecnici, ma questo punto non è trattato in questa Guida.

Per l'attivazione usa COM senza registrazione

L'app di Windows Store può usare componenti COM provenienti da librerie. La libreria non registra classi COM. È l'app a richiamare l'attivazione COM mediante la nuova funzione CoCreateInstanceFromApp.

Se il costo di marshalling è un problema, esegui la conversione ai tipi Windows Runtime

Usa i tipi di Windows Runtime per gli oggetti che attraversano spesso il limite dell'interfaccia ABI e la cui conversione ha un costo molto alto.

Puoi usare String e Array, che possono essere convertiti in modo efficiente, senza copia, come parametri di input nell'API Windows Runtime. StringReference e ArrayReference aggiungono un livello Windows Runtime mediante l'uso di semantica borrow.

Per i contenitori e i tipi di raccolta, la conversione da std::* a Platform::* richiede la copia. In genere, i contenitori e le raccolte dello spazio dei nomi std sono più efficienti rispetto ai contenitori e alle raccolte dello spazio dei nomi Platform. La scelta di usare gli uni o gli altri dipende dalla frequenza con cui il contenuto delle raccolte cambia rispetto alla frequenza con cui attraversano il limite dell'interfaccia ABI.

In Hilo, la decisione relativa a quando usare classi ref pubbliche ha richiesto molto tempo. Per la nostra applicazione, ci siamo resi conto che l'overhead della conversione dei tipi aveva una rilevanza maggiore rispetto agli altri problemi.

Decidi se usare codice wrapper o convertire codice esistente

Se hai codice esistente che deve essere chiamato attraverso l'interfaccia ABI, devi decidere se convertire questo codice in C++/CX o lasciarlo in C++ standard, aggiungendo il wrapping con un livello in C++/CX. Nella maggior parte delle situazioni consigliamo di usare un livello wrapper.

La scelta di convertire codice esistente e di usare all'interno di esso tipi e concetti di Windows Runtime in genere viene fatta solo per evitare l'overhead della conversione dei dati, ad esempio se il componente deve essere chiamato molto spesso attraverso l'interfaccia ABI. Queste situazioni sono rare.

Se decidi di creare un wrapper in C++/CX per le tue classi, il modo standard di procedere è di definire le interfacce e nella loro implementazione delegare al codice C++ esistente, dopo le conversioni di tipo necessarie. Questo uso delle interfacce crea un livello Windows Runtime sul codice esistente, e questo nella maggior parte dei casi è sufficiente.

Se disponi di componenti COM esistenti, è consigliabile esporli come tipi Windows Runtime. In questo modo sarà facile per le app di Windows Store consumare i tuoi oggetti COM. Le tecniche necessarie per questo tipo di implementazione non sono trattate nell'ambito di questa Guida.

Ulteriori informazioni sulla conversione

Un'utile fonte di informazioni sulla conversione di codice C++ esistente per l'uso in un'app di Windows Store è costituita da un video di Channel 9. Per ulteriori informazioni, vedi il video sulla conversione di un'app desktop in un'app di Windows Store.

Vedi l'articolo relativo a Win32 e COM per app di Windows Store per il sottoinsieme di funzioni API supportate. Se non riesci a trovare una funzione API Win32 adeguata, vedi l'articolo sulle alternative alle API Windows nelle apps di Windows Store per suggerimenti sulle funzioni Windows Runtime utilizzabili al posto delle funzioni API Win32.

[Torna all'inizio]

 

 

Mostra:
© 2015 Microsoft