Windows Dev Center

Schreiben von modernem C++-Code in Hilo (Windows Store-Apps mit C++ und XAML)

Aus: Umfassende Entwicklung einer Windows Store-App mit C++ und XAML: Hilo

Leitfaden-Logo

Vorherige Seite | Nächste Seite

Hier sind einige Tipps und Codierungsrichtlinien zum Erstellen von Windows Store-Apps mit C++ und XAML und zum Anpassen an die asynchrone Programmierung mithilfe der Parallel Patterns Library (PPL). Sie sind das Ergebnis von Fragen, die wir selbst lösen mussten, als wir mit der Entwicklung von Hilo C++ begannen.

In Windows Store-Apps mit C++ sind die besten Features von C++11, des modernen C++-Codierungsstils, und von C++/CX vereint. Wenn Sie noch nicht mit C++11 vertraut sind, sollten Sie zunächst C++11-Features (modernes C++) und Willkommen zurück bei C++ (modernes C++) lesen.

Wenn Sie noch nicht mit C++/CX vertraut sind, lesen Sie die Sprachreferenz zu Visual C++ (C++/CX).

Download

Herunterladen des Hilo-Beispiels
Buch herunterladen (PDF)

Anweisungen zu dem heruntergeladenen Code finden Sie unter Erste Schritte mit Hilo.

Sie erfahren Folgendes:

  • Wann die Verwendung von C++-Typen und -Bibliotheken in Windows Store-Apps sinnvoll ist.
  • Bewährte Methoden für die Verwendung von C++/CX.
  • Portieren von vorhandenen C++-Bibliotheken für die Verwendung durch Windows Store-Apps.
  • Empfohlene Verfahren zum Debuggen mit Visual Studio.

Betrifft

  • Windows-Runtime für Windows 8
  • C++/CX

Hintergrundinformationen zur Umgebung der App

Windows Store-Apps, z. B. Hilo, verfügen über ein charakteristisches visuelles Design und ermöglichen häufig Interaktion per Fingereingabe. Zudem werden sie in einer Umgebung ausgeführt, die dem Benutzer eine im Vergleich zu Desktop- und Konsolen-Apps genauere Kontrolle der für die App zulässigen Aktionen ermöglicht. Die zusätzliche Kontrolle erleichtert es, Sicherheit und Benutzerfreundlichkeit der App zu erhöhen.

Die Struktur einer Windows Store-App mit C++ und XAML

Diese Features wirken sich darauf aus, wie Sie die App implementieren. Wen Sie C++ verwenden, legen die folgenden Elemente die statische Umgebung und Laufzeitumgebung der App fest.

Diese Komponenten und Tools bestimmen, welche Plattformfunktionen für die App verfügbar sind und wie der Zugriff auf diese Funktionen erfolgt. Wenn Sie noch nicht mit Windows Store-Apps vertraut sind, lesen Sie die Übersichten über diese Komponenten und Tools, um sich über ihre Funktion für die App zu informieren. In diesem Handbuch erfahren Sie, wie die einzelnen Komponenten und Tools in Hilo verwendet werden.

Hinweis  

Die Roadmap für Windows Store-Apps mit C++ bietet ebenfalls eine hilfreiche allgemeine Einführung für C++-Programmierer.

Das Paketmanifest

Visual Studio erstellt eine Projektdatei mit dem Namen "Package.appxmanifest", um Einstellungen aufzuzeichnen, die sich auf die Bereitstellung und Ausführung der App auswirken. Die Datei wird als Paketmanifest bezeichnet. Sie können in Visual Studio das Paketmanifest mit einem visuellen Tool bearbeiten, dem Manifest- Designer.

Der Manifest-Designer von Visual Studio

Windows Store-Apps müssen im Gegensatz zu Desktop- und Konsolen-Apps die Umgebungsfunktionen, z. B. den Zugriff auf geschützte Systemressourcen oder Benutzerdaten, im Voraus deklarieren.

Sie müssen sämtliche Funktionen im Paketmanifest deklarieren, bevor sie von der App verwendet werden können. Zu diesem Zweck können Sie im Manifest-Designer die Registerkarte Funktionen verwenden. Beispielsweise ist im Hilo-Manifest die Fähigkeit zum Zugriff auf die Bildbibliothek des Benutzers festgelegt.

Die Registerkarte "Funktionen" des Manifest-Designers

Dies ist der Quellcode für das Paketmanifest von 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>


Windows Store-Apps werden in einer besonderen Betriebssystemumgebung ausgeführt. Diese Umgebung lässt nur die Verwendung der Windows-Runtime-Funktionen durch die App zu, die im Paketmanifest als angeforderte Funktionen deklariert sind. Zu diesem Zweck schränkt das System die Funktionen, Datentypen und Geräte ein, die von der App verwendet werden dürfen.

Hinweis  Das Paketmanifest schränkt nur Aufrufe der Windows-Runtime-API ein. Wenn Sie die Win32- oder die COM-API verwenden, müssen Sie Tools ausführen, die sicherstellen, dass Sie die APIs verwenden, die für Apps im Windows-Store zulässig sind. Visual Studio erleichtert Ihnen mit Filterfunktionen im Objektkatalog die Auswahl der richtigen Teilmenge von APIs.

In Testen und Bereitstellen der App in diesem Handbuch finden Sie eine exemplarische Vorgehensweise zur Verwendung des Manifests für Hilo. Weitere Informationen zum Festlegen von Funktionen finden Sie unter Deklarationen von App- Funktionen (Windows Store-Apps), und Informationen zum Bearbeiten des Paketmanifests in Visual Studio finden Sie unter Manifest- Designer. Eine umfassende Dokumentation zum XML-Schema des Manifests finden Sie unter Paketmanifest.

C++-Standardbibliotheken

Der Visual C++-Compiler unterstützt den C++11-Standard, der Ihnen einen modernen Codierungsstil ermöglicht. Die Windows Store-App kann die C++-Laufzeitbibliothek (C++ Run-Time, CRT) und die Standardvorlagenbibliothek (Standard Template Library, STL) verwenden. Es gelten einige Einschränkungen, z. B. ist der Zugriff auf die Konsolen-E/A nicht möglich. (Siehe Portieren von vorhandenem C++-Code auf dieser Seite.)

Windows-Runtime-Bibliotheken

Windows-Runtime-Dienste

Die Windows-Runtime ist eine Programmierschnittstelle, die Sie zum Erstellen von Windows Store-Apps verwenden können. Die Windows-Runtime unterstützt den charakteristischen visuellen Stil und das auf Fingereingabe basierende Interaktionsmodell von Windows Store-Apps sowie den Zugriff auf Netzwerke, Datenträger, Geräte und Druckfunktionen. Für die Datentypen und Funktionen in diesen Bibliotheken werden der Windows-Namespace und der Platform-Namespace verwendet. Mit dem Objektkatalog in Visual Studio können Sie überprüfen, welche Typen verfügbar sind.

Der Objektkatalog von Visual Studio

Die Windows-Runtime besteht auf der untersten Ebene aus einer binären Anwendungsschnittstelle (Application Binary Interface, ABI). Die ABI ist ein binärer Vertrag, der Windows-Runtime-APIs für mehrere Programmiersprachen, z. B. JavaScript, die .NET-Sprachen und Microsoft Visual C++, verfügbar macht. Weitere Informationen zu den Windows-Runtime-APIs finden Sie unter Windows-API-Referenz für Windows-Store-Apps.

Die Struktur einer Windows Store-App weicht von der Struktur einer herkömmlichen Desktop-App ab, die die Windows-API (Win32) verwendet. Die Windows-Runtime verwendet keine Handletypen wie z. B. HWND und keine Funktionen wie z. B. CreateWindow, sondern stellt Schnittstellen, z. B. Windows::UI::Core::ICoreWindow, bereit, sodass Sie Windows Store-Apps auf modernere Weise und mit stärkerer Objektorientierung entwickeln können.

Hinweis  Dieses umfassende Windows-Runtime-Programmiermodell wird u. a. durch ein erweitertes Typsystem und zusätzliche Sprachfunktionen ermöglicht. Die Umgebung stellt Eigenschaften, Delegaten, Ereignisse, Schnittstellen und Attribute bereit. Eine Übersicht finden Sie unter Typsystem (C++/CX).

Win32- und COM-API

Windows Store-Apps können eine Teilmenge der Win32- und COM-API verwenden. Die Windows-Runtime stellt zwar ein umfassendes Sortiment von Funktionen bereit, Sie können jedoch weiterhin eine Teilmenge von Win32 und COM verwenden, um Kernszenarien für Windows Store-Apps zu unterstützen, die von der Windows-Runtime noch nicht unterstützt werden. Beispielsweise wird zum Herstellen der Verbindung mit HTTP-Servern in Windows Store-Apps mit C++ die COM-Schnittstelle IXMLHTTPRequest2 empfohlen. Weitere Informationen über die Verwendung von Win32 und COM in einer Windows Store-App finden Sie unter Win32- und COM-API. Eine Übersicht über den Inhalt finden Sie unter Portieren von vorhandenem C++-Code auf dieser Seite.

Hilo ruft die Win32-API nicht direkt auf. Jedoch verwendet Hilo COM für das Arbeiten mit Bildpixeldaten.

Parallel Patterns Library (PPL)

Sie sollten in Ihrer App die Parallel Pattern Library (PPL) für die asynchrone und parallele Programmierung verwenden. Sie verwenden PPL-Aufgaben und Agents der Asynchronous Agents Library, wo Sie bisher einen Thread verwendet haben. Informationen zur Verwendung der PPL durch Hilo finden Sie unter Anpassen an die asynchrone Programmierung und unter Verwenden von paralleler Programmierung und Hintergrundaufgaben auf dieser Seite.

XAML

Windows Store-Apps, die XAML (Extensible Application Markup Language) verwenden, definieren die visuelle Darstellung der App deklarativ. Das Design und die Struktur der UX werden in XAML, einer XML-basierten Markupsprache, codiert. Sie können UI-Elemente mit einem visuellen Entwurfstool, z. B. Microsoft Expression Blend oder einem der Designer von Visual Studio, definieren, selbst XML-Ausdrücke schreiben oder beide Herangehensweisen verwenden. Sie verwenden XAML-Datenbindungsausdrücke, um UI-Elemente mit Datenquellen in der App zu verbinden.

XAML ist die Designsprache für Frameworks wie z. B. Microsoft Silverlight und Windows Presentation Foundation (WPF). Wenn Sie mit der Verwendung von XAML für diese Frameworks oder mit Expression Blend vertraut sind, können Sie diese Fähigkeiten auch beim Erstellen von Windows Store-Apps nutzen. Weitere Informationen über XAML für Windows Store-Apps finden Sie in der Übersicht über XAML.

Datenbindung eignet sich gut, um Textfelder, Schaltflächen, Datenraster und andere UI-Elemente mit App-Logik zu verknüpfen. Die Windows-Runtime ruft Methoden und Eigenschaften der Datenquellen nach Bedarf auf, um während der Ausführung der App Informationen für die UI bereitzustellen. Datenbindung wird auch verwendet, wenn es sich bei den übergebenen Daten um einen Befehl handelt, z. B. die Anforderung zum Ausführen eines Befehls, etwa das Öffnen einer Datei. In diesem Fall werden durch Datenbindung UI-Elemente, die Befehle aufrufen, vom Code entkoppelt, der den Befehl ausführt. In XAML können fast jedes Objekt und fast jede Eigenschaft des Frameworks gebunden werden.

Visual Studio-Projektvorlagen

Es wird empfohlen, die integrierten Visual Studio-Projektvorlagen zu verwenden, da diese Compilereinstellungen definieren, die zum Ausführen einer Windows Store-App benötigt werden. Die Projektvorlagen stellen außerdem Code zum Implementieren von Grundfunktionen bereit, sodass Sie Zeit sparen. In Visual Studio werden manche Dateien im Projektordner Common abgelegt, den Sie nicht ändern sollten. Visual Studio erstellt außerdem die Datei "App.xaml.cpp", die Sie mit App-spezifischer Logik anpassen können. Diese Datei enthält den Haupteinstiegspunkt in die App, die App::InitializeComponent-Methode. Zwar sparen Visual Studio-Vorlagen Zeit, jedoch sind sie nicht erforderlich. Sie können auch vorhandene statische und dynamische Bibliotheken für die Verwendung in der App anpassen. Weitere Informationen finden Sie unter Portieren von vorhandenem C++-Code auf dieser Seite und unter Erstellen von Apps und Bibliotheken (C++/CX).

Weitere Informationen über die Projektvorlagen von Visual Studio finden Sie unter Vorlagen für eine schnellere App-Entwicklung (Windows Store-Apps mit C#/VB/C++ und XAML).

C++-Spracherweiterungen für Interoperabilität

Im neuen Benutzeroberflächenframework für Windows Store-Apps wird XAML für die Interaktion mit systemeigenem C++ verwendet. Die Interaktion zwischen C++ und XAML oder anderen Sprachen, z. B. JavaScript und C#, erfolgt direkt, d. h. ohne Aufruf einer Übersetzungszwischenebene. Hierzu wird eine Konvention für das Binärformat von Objekten und Funktionsaufrufen verwendet, die der COM-basierten Programmierung ähnelt. Die Programmierumgebung für Windows Store-Apps enthält C++/CX. Dabei handelt es sich um C++-Spracherweiterungen, die das Codieren von Code erleichtern. Die Erweiterungen sind nur für Code vorgesehen, der Interoperabilität betrifft. Für den Rest der App sollte mit dem Standard konformes C++ verwendet werden. Weitere Informationen finden Sie unter Tipps für die Verwendung von C++/CX als Interoperabilitätsebene auf dieser Seite.

Hinweis  Sie müssen für die Interoperabilität nicht C++/CX verwenden. Sie können Interoperabilität auch mit WRL erzielen.

Der C++-Compiler und -Linker

Die /ZW-Compileroption ermöglicht C++-Quelldateien die Verwendung von Features der Windows-Runtime. Sie aktiviert außerdem die __cplusplus_winrt-Präprozessordirektive, die in manchen Systemheaderdateien enthalten ist. Der Code ruft Deklarationen von Windows-Runtime-Typen nicht aus Headerdateien, sondern aus Metadatendateien (".winmd") ab. Sie verweisen auf WINMD-Dateien mit der #using-Direktive oder der/FU-Compileroption. Diese Optionen werden in Visual Studio automatisch konfiguriert, wenn Sie eine der C++-Projektvorlagen für Windows Store-Apps verwenden.

Wenn Sie die App verknüpfen, müssen Sie zwei Linkeroptionen bereitstellen: /WINMD und /APPCONTAINER. Diese Optionen werden in Visual Studio automatisch konfiguriert, wenn Sie eine der C++-Projektvorlagen für Windows Store-Apps verwenden.

Zertifizierungsprozess des Windows Store

Wenn Sie die App im Windows Store veröffentlichen, kann sie erworben oder kostenlos heruntergeladen werden. Das Veröffentlichen im Store ist optional. Apps im Store müssen einem Zertifizierungsprozess unterzogen werden, bei dem eine automatische Strukturüberprüfung erfolgt. Eine Beschreibung der Zertifizierung von Hilo finden Sie unter Testen und Bereitstellen der App in diesem Handbuch.

Bereitstellung

Für Windows Store-Apps wird ein einfacheres Installationsmodell als für Desktop-Apps verwendet. In einer Windows Store-App befinden sich alle Bibliotheken und Ressourcen, die nicht vom System bereitgestellt werden, im Installationsverzeichnis oder den Installationsunterverzeichnissen der App. Bei der Installation einer Windows Store-App kann nicht in die Systemregistrierung geschrieben werden, und es können keine Umgebungsvariablen definiert werden. Zudem kann der Benutzer die Funktionen der zu installierenden App beschränken. Im Paketmanifest sind alle bereitgestellten Dateien aufgeführt. Weitere Informationen finden Sie unter Das Paketmanifest.

[Oben]

Verwenden von C++11, C++-Standardbibliotheken und eines modernen Codierungsstils

Verwenden Sie nach Möglichkeit C++11 und die C++-Standardbibliotheken (z. B. die STL und die CRT-Bibliothek) für die App-Kernlogik, und verwenden Sie die C++/CX-Syntax nur an der Grenzlinie, an der Interaktion mit der Windows-Runtime erfolgt. Beispielsweise werden die folgenden Verfahren empfohlen:

Lambda-Ausdrücke

Verwenden Sie Lambda-Ausdrücke zum Definieren der Arbeit, die von PPL-Aufgaben und STL-Algorithmen ausgeführt wird.. Weitere Informationen finden Sie unter Muster und Tipps für die asynchrone Programmierung in Hilo (Windows Store-Apps mit C++ und XAML) in diesem Handbuch.

Stapelsemantik, intelligente Zeiger und RAII

Verwenden Sie Stapelsemantik, intelligente Zeiger und RAII (Resource Acquisition is Initialization), um die Objektlebensdauer automatisch zu steuern und um sicherzustellen, dass Ressourcen freigegeben werden, wenn die aktuelle Funktion beendet wird oder eine Ausnahme auslöst. Weitere Informationen finden Sie unter Tipps zum Verwalten des Arbeitsspeichers auf dieser Seite.

Automatische Typherleitung

Verwenden Sie die automatische Typherleitung, damit sich Code einfacher lesen und schneller schreiben lässt. Mit dem auto-Schlüsselwort und dem decltype-Schlüsselwort wird der Compiler angewiesen, den Typ einer deklarierten Variablen vom Typ des angegebenen Ausdrucks herzuleiten Beispielsweise können Sie auto verwenden, wenn Sie mit STL-Iteratoren arbeiten, deren Namen mühsam einzugeben sind und die Verständlichkeit des Codes nicht verbessern. Hilo macht bei Verwendung der concurrency::create_task-Funktion und der std::make_shared-Funktion häufigen Gebrauch von auto, damit der Vorlagentypparameter weniger häufig deklariert werden muss.

ImageBase.cpp


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


ThumbnailGenerator.cpp


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


Hinweis  Verwenden Sie das auto-Schlüsselwort, wenn für Leser des Codes der Typ anhand des Kontexts ersichtlich ist oder wenn Sie den Typ abstrahieren möchten. Das Schlüsselwort soll die Lesbarkeit erhöhen.

Bereichsbasierte for-Schleifen

Verwenden Sie for-Schleifen für die Arbeit mit Auflistungen von Daten. Bereichsbasierte for-Schleifen weisen eine prägnantere Syntax als for-Schleifen und der std::for_each-Algorithmus auf, da Sie keine Iteratoren oder capture-Klauseln verwenden müssen. Hier ist ein Beispiel:

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


Weitere Informationen finden Sie unter Algorithmen (modernes C++).

Standardalgorithmen und -container

Verwenden Sie Standardalgorithmen und -Container, z. B. std::vector, std::for_each, std::find_if und std::transform, um die Vorteile von C++-Features zu nutzen. Da Windows RT-Typen sprachneutral sind, werden in Hilo Typen wie z. B. std::wstring und std::wstringstream verwendet, um mit Zeichenfolgen intern zu arbeiten, und Platform::String wird nur bei Interaktion mit der Windows-Runtime verwendet. In diesem Beispiel wird der zurückgegebene Platform::String an die Windows-Runtime übergeben.

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


Durch die Verwendung von Standardfunktionen können Sie Code schreiben, der besser portierbar ist und moderne C++-Features, z. B. Verschiebesemantik, nutzt. (Weitere Informationen über Verschiebesemantik finden Sie unter rvalue-Verweisdeklarator: &&.)

Das gleiche Muster gilt für Standardcontainer, z. B. std::vector. Durch die Verwendung von Standardcontainern können Sie C++-Features, z. B. Verschiebesemantik und direkteres Arbeiten mit dem Arbeitsspeicher, nutzen. Beispielsweise müssen Sie eventuell interne Verarbeitung in einem std::vector-Objekt ausführen und dann ein Windows::Foundation::Collections::IVector-Objekt an die Windows-Runtime übergeben. Die Platform::Collections::Vector-Klasse, bei der es sich um die C++-Implementierung von IVector handelt, verfügt über einen überladenen Konstruktor, der einen rvalue-Verweis auf einen std::vector akzeptiert. Rufen Sie mit der std::move-Funktion den überladenen Konstruktor auf, oder übergeben Sie direkt das Ergebnis einer Funktion, die std::vector zurückgibt. Ein Beispiel, in dem dieses Muster gezeigt wird, finden Sie unter Auflistungen (C++/CX).

Hinweis  Sie müssen keine Verschiebesemantik verwenden. Die Platform::Collections::Vector-Klasse enthält einen Standardkopierkonstruktor, der ein std::vector-Objekt als Argument akzeptiert. Dies bedeutet, dass Sie die vorhandenen Vektordaten beibehalten und eine neue Platform::Collections::Vector-Instanz mit einer Kopie der ursprünglichen Daten erstellen können.

Als weiteres Beispiel verwendet Hilo std::iota und std::random_shuffle für die Zufallsauswahl von Fotos (genauer gesagt, Indizes für ein Array von Fotos). In diesem Beispiel wird mit std::iota eine Sequenz von Arrayindizes erstellt und mit std::random_shuffle die Reihenfolge in der Sequenz nach dem Zufallsprinzip geändert.

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


Weitere Informationen zu Standardalgorithmen finden Sie unter Algorithmen (modernes C++).

Freiformiteratoren

Verwenden Sie für die Arbeit mit Bereichen die std::begin-Funktion und die std::end-Funktion. Verwenden Sie anstelle von Memberfunktionen wie .begin() und .end() diese Funktionen, um flexibleren Code zu schreiben. Sie können beispielsweise einen allgemeinen Algorithmus schreiben, der sowohl mit STL-Typen, z. B. std::vector, std::array und std::list, die begin- und ends-Memberfunktionen bereitstellen, als auch mit Windows-Runtime-Auflistungstypen wie z. B. IVector verwendet werden kann, die ein anderes Verfahren zum Durchlaufen von Werten nutzen. Die std::begin-Funktion und die std::end-Funktion können entsprechend verschiedenen Programmierstilen überladen werden. Zudem können Sie mit ihnen Datenstrukturen, die Sie nicht ändern können, Iteration hinzufügen.

Das PIMPL-Idiom

Verwenden Sie das PIMPL-Idiom, um die Implementierung zu verbergen, Kopplung zu minimieren und Schnittstellen zu trennen. PIMPL (Pointer to IMPLementation, Zeiger auf Implementierung) beinhaltet das Hinzufügen von Implementierungsdetails zu einer CPP-Datei und einen intelligenten Zeiger auf diese Implementierung im private-Abschnitt der Klassendeklaration in der H-Datei. Auf diese Weise lässt sich zudem die Implementierungszeit verringern. Wenn PIMPL gemeinsam mit dem Kopierkonstruktor und Verschiebesemantik verwendet wird, können Sie außerdem Objekte nach Wert übergeben. So müssen Sie sich keine Gedanken über die Lebensdauer von Objekten machen.

Sie können das PIMPL-Idiom auch nutzen, wenn Sie vordefinierte Bibliotheken verwenden. PIMPL wird häufig mit rvalue-Verweisen verwendet. Wenn Sie beispielsweise aufgabenbasierte Fortsetzungen erstellen, übergeben Sie concurrency::task-Objekte nach Wert und nicht per Verweis.

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


Die Übergabe nach Wert stellt sicher, dass das Objekt gültig ist, und die Objektlebensdauer muss nicht berücksichtigt werden. Außerdem ist in diesem Fall die Übergabe nach Wert nicht aufwendig, da die task-Klasse als einzigen Datenmember einen Zeiger auf die tatsächliche Implementierung definiert und diesen Zeiger im Kopier- und Verschiebekonstruktor kopiert bzw. überträgt.

Weitere Informationen finden Sie unter Pimpl für die Kapselung zur Kompilierzeit (modernes C++).

Ausnahmebehandlung

Verwenden Sie zum Behandeln von Fehlern Ausnahmebehandlung. Die Ausnahmebehandlung bietet gegenüber Protokollierung oder Fehlercodes (z. B. HRESULT-Werte) zwei wesentliche Vorteile:

  • Sie kann das Lesen und Verwalten von Code erleichtern.
  • Sie ist ein effizienteres Verfahren, um einen Fehler an eine Funktion weiterzugeben, die den Fehler behandeln kann. Bei der Verwendung von Fehlercodes muss i. d. R. jede Funktion explizit Fehler weitergeben.
  • Mit Ausnahmebehandlung können Sie die Robustheit der App erhöhen, da im Gegensatz zu einem HRESULT oder einem GetLastError-Status eine Ausnahme nicht ignoriert werden kann.

Sie können zudem den Visual Studio-Debugger so konfigurieren, dass er beim Auftreten einer Ausnahme unterbrochen wird, damit die Ausführung unmittelbar an der Position und im Kontext des Fehlers angehalten wird. Die Windows-Runtime macht ebenfalls extensiven Gebrauch von der Ausnahmebehandlung. Deshalb können Sie durch die Verwendung von Ausnahmebehandlung im Code die gesamte Fehlerbehandlung in einem einzigen Modell zusammenfassen.

Wichtig  Fangen Sie nur die Ausnahmen ab, die Sie auf jeden Fall behandeln und beheben können. Fangen Sie andernfalls die Ausnahme nicht ab, und ermöglichen Sie das Beenden der App. Verwenden Sie catch(...) {...} nur, wenn Sie die Ausnahme im catch-Block erneut auslösen.

Im folgenden Beispiel aus Hilo werden der moderne C++-Codierungsstil C++11 und Standardbibliotheken gezeigt, die StorageFile-Objekte aus einem std::vector-Objekt in ein Vector-Objekt kopieren, sodass die Auflistung an die Windows-Runtime übergeben werden kann. In diesem Beispiel werden ein Lambda-Ausdruck, automatische Typherleitung, der std::begin-Iterator und der std::end-Iterator sowie eine bereichsbasierte for-Schleife verwendet.

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


Weitere Informationen über moderne C++-Programmierung finden Sie unter Willkommen zurück bei C++ (modernes C++).

[Oben]

Anpassen an die asynchrone Programmierung

Bei der asynchronen Programmierung rufen Sie eine Funktion auf, die einen Vorgang langer Dauer startet, jedoch sofort beendet wird, ohne auf den Abschluss des Vorgangs zu warten. Mit asynchronen Vorgängen lässt sich die Reaktionsfähigkeit für die Benutzererfahrung erhöhen. Die Windows-Runtime enthält weitaus mehr asynchrone Vorgänge als frühere Frameworks.

Sie erkennen am Namen einer Windows-Runtime-Funktion, ob sie asynchron ist. Die Namen von asynchronen Funktionen enden mit "Async", z. B. ReadTextAsync. Diese Benennungskonvention wird auch in Hilo befolgt.

Die Windows-Runtime stellt eigene Datentypen für die Interaktion mit asynchronen Vorgängen bereit. Diese Datentypen sind C++/CX-Schnittstellen, die von der Windows::Foundation::IAsyncInfo -Schnittstelle abgeleitet werden.

Jede Programmiersprache bietet systemeigene Unterstützung für die asynchrone Programmierung und verwendet für die Interoperabilität von IAsyncInfo abgeleitete Schnittstellen. In C++ stellt die PPL die systemeigene Unterstützung für die asynchrone Programmierung bereit. Im Allgemeinen sollten Sie die asynchronen Schnittstellentypen der Windows-Runtime mit PPL-Aufgaben umschließen, wenn sie von einem Aufruf einer asynchronen Windows-Runtime-Methode zurückgegeben werden. Sie sollten diese Schnittstellen nur aus eigenen Klassen verfügbar machen, wenn Sie eine Windows-Runtime-Komponente für programmiersprachenübergreifende Interoperabilität erstellen. Beispielsweise verwenden Sie die von IAsyncInfo abgeleiteten Typen in public-Methoden und -Eigenschaften von public ref-Klassen, die Sie definieren.

Nachdem Sie einen asynchronen Vorgang mit einer PPL-Aufgabe umschlossen haben, können Sie weitere Aufgaben erstellen, deren Ausführung vom System nach Abschluss der vorherigen Aufgabe geplant wird. Die Folgeaufgaben werden als Fortsetzungsaufgaben (oder Fortsetzungen) bezeichnet. Fortsetzungen werden mit der task::then-Methode erstellt. Hier ist ein Beispiel aus Hilo, in dem asynchrone Vorgänge zum Lesen eines Bilds aus einer Datei verwendet werden.

Hinweis  Die Deklarationen der PPL-Aufgaben befinden sich in der Headerdatei "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));
}


Mit dem Ausdruck m_photo->File wird ein Windows::Storage::StorageFile^-Verweis zurückgegeben. Mit der OpenReadAsync-Methode dieses Objekts wird eine asynchrone Funktion gestartet, um die Datei zu öffnen, die das angeforderte Bild enthält. Der Aufruf von OpenReadAsync gibt ein Objekt vom Typ IAsyncOperation<IRandomAccessStreamWithContentType^>^ zurück.

Der asynchrone Aufruf startet den Vorgang. Der Aufruf selbst wird sehr schnell beendet, ohne zu warten, bis das Öffnen der Datei abgeschlossen ist. Der Rückgabewert des Aufrufs von OpenReadAsync stellt den gestarteten Vorgang dar, der noch ausgeführt wird. Der Rückgabetyp ist durch den Ergebnistyp, in diesem Fall IRandomAccessStreamWithContentType^, des asynchronen Vorgangs parametrisiert.

Hinweis   Die ^ (Hütchenzeichen)-Syntax gibt einen Windows-Runtime-Verweis an. Wenn Sie damit nicht vertraut sind, finden Sie entsprechende Informationen unter Referenzklassen und Strukturen (C++/CX).

Anschließend ruft die Photo::QueryPhotoImageAsync-Methode die concurrency::create_task-Funktion auf, um eine neue PPL-Aufgabe zu erstellen, die als Wert der lokalen Variablen imageStreamTask verwendet wird. Der Typ der imageStreamTask-Variablen lautet task<IRandomAccessStreamWithContentType^>. Durch das Übergeben eines asynchronen Windows-Runtime-Objekts an die concurrency::create_task-Funktion wird der asynchrone Vorgang mit einer PPL-Aufgabe umschlossen. Die neu erstellte PPL-Aufgabe wird beendet, wenn die Ausführung des OpenReadAsync-Vorgangs abgeschlossen und ein Datenstrom mit wahlfreiem Zugriff verfügbar ist.

Für asynchrone Vorgänge der Windows-Runtime müssen nicht immer eigene Datenströme ausgeführt werden. Sie werden von Windows häufig mithilfe von internen Datenstrukturen, die weniger Aufwand als Threads erfordern, als überlappende E/A-Vorgänge verwaltet. Dies ist ein internes Detail des Betriebssystems.

Nachdem ein Windows-Runtime-Vorgang mit einer PPL-Aufgabe umschlossen wurde, können Sie mit der task::then-Methode Fortsetzungen erstellen, die ausgeführt werden, nachdem die vorhergehende Aufgabe abgeschlossen wurde. Fortsetzungen sind ebenfalls PPL-Aufgaben. Sie erleichtern das Lesen und Debuggen von asynchronen Programmen.

Die task::then-Methode ist asynchron. Damit wird eine neue Aufgabe sehr schnell zurückgegeben. Die von der task::then-Methode zurückgegebene Aufgabe wird im Gegensatz zu anderen asynchronen Aufgaben nicht sofort gestartet. Stattdessen verzögert die PPL den Ausführungsbeginn der Aufgabe, bis das Ergebnis der vorhergehenden Aufgabe verfügbar ist. Das bedeutet, dass die task::get-Methode zwar asynchron ist, wenn sie auf die vorhergehende Aufgabe angewendet wird, der Wert jedoch sofort verfügbar ist. Mit Fortsetzungsaufgaben, die noch nicht bereit zur Ausführung sind, werden keine Threads blockiert. Stattdessen werden sie von der PPL intern verwaltet, bis die benötigten Daten verfügbar sind.

In der oben gezeigten QueryPhotoImageAsync-Methode ist das Argument für die then-Methode ein Lambda-Ausdruck, der die Arbeitsfunktion der neu erstellten Aufgabe ist. (Die Arbeitsfunktion einer Aufgabe ist der Code, der beim Ausführen der Aufgabe aufgerufen wird.) Der Typ des Eingabeparameters für den Lambda-Ausdruck stimmt mit dem Typ des imageStreamTask überein. Die Typen stimmen überein, da imageStreamTask als Argument an die Arbeitsfunktion der Fortsetzung übergeben wird, wenn die Ausführung der Fortsetzung beginnt. Sie können sich dies als eine Art von Datenflussprogrammierung vorstellen. Die Ausführung von Fortsetzungsaufgaben wird gestartet, wenn ihre Eingabe verfügbar wird. Wenn ihre Ausführung beendet wird, werden ihre Ergebnisse an die nächste Fortsetzungsaufgabe in der Kette übergeben.

Hinweis  Wenn Sie mit Lambda-Ausdrücken in C++ nicht vertraut sind, finden Sie entsprechende Informationen unter Lambda-Ausdrücke in C++.

In Windows-Store-Apps werden Fortsetzungen von Aufgaben, die IAsyncInfo-Objekte umschließen, standardmäßig in dem Threadkontext ausgeführt, in dem die Fortsetzung erstellt wurde. In den meisten Fällen ist der Standardkontext der Hauptthread der App. Dies ist zum Abfragen oder Ändern von XAML-Steuerelementen sinnvoll, und Sie können den Standardkontext überschreiben, um andere Fälle zu behandeln.

Hinweis  In Hilo fanden wir es hilfreich, mit der assert(IsMainThread()-Anweisung und der assert(IsBackgroundThread())-Anweisung den Threadkontext für die Unterroutinen besser verständlich zu machen. In der Debugversion von Hilo wird die Ausführung des Debuggers bei diesen Anweisungen unterbrochen, wenn ein anderer Thread als der in der Assertion deklarierte Thread verwendet wird. Die IsMainThread-Funktion und die IsBackgroundThread-Funktion befinden sich in den Quelldateien für Hilo.

In diesem Beispiel war für die Aktualisierung der m_image-Membervariablen kein besonderer Synchronisierungscode, z. B. eine Sperre oder ein kritischer Abschnitt, benötigt. Der Grund hierfür ist, dass alle Aktionen mit Ansichtsmodellobjekten im Hauptthread erfolgen. Durch die Verwendung eines einzelnen Threads werden alle potenziell Konflikte verursachenden Aktualisierungen automatisch serialisiert. Bei der UI-Programmierung empfiehlt sich statt anderer Arten der Synchronisierung die Verwendung von Fortsetzungen, die in einem bekannten Threadkontext ausgeführt werden.

Dieser Code ist ein Beispiel für eine Fortsetzungskette oder eine .then-Leiter (ausgesprochen "dot-then-Leiter"). Dieses Muster ist häufig in Apps vorhanden, die asynchrone APIs aus der Windows-Runtime verwenden. Weitere Informationen über Fortsetzungsketten finden Sie unter Asynchrone Programmierung in C++ (Windows Store-Apps). Tipps und Richtlinien für ihre Verwendung sowie weitere Beispiele finden Sie unter Muster und Tipps für die asynchrone Programmierung in Hilo (Windows Store-Apps mit C++ und XAML) in diesem Handbuch.

[Oben]

Verwenden von paralleler Programmierung und Hintergrundaufgaben

Das Ziel der interaktiven Programmierung ist eine hohe interaktive Reaktionsfähigkeit. Beispielsweise verwenden wir in Hilo asynchrone Verfahren, um sicherzustellen, dass der Hauptthread der App nicht blockiert wird und ohne Verzögerung auf neue Anforderungen reagieren kann. Abhängig von den Funktionsanforderungen der App müssen Sie möglicherweise jedoch eine hohe Reaktionsfähigkeit für die Benutzererfahrung und einen hohen allgemeinen Durchsatz der rechenintensiven Teile der App sicherstellen.

C++ eignet sich besonders gut für Apps, die eine hohe Reaktionsfähigkeit für die Benutzererfahrung und eine hohe Leistung für rechenintensive Aufgaben erfordern. Die Parallelitätsfeatures von Visual C++ können beide Anforderungen erfüllen.

Sie können den Durchsatz rechenintensiver Bereiche der App steigern, indem Sie einige Aufgaben in kleinere Aufgaben unterteilen, die von mehreren Kernen der CPU oder von Hardware mit Datenparallelität des Grafikprozessors (Graphics Processing Unit, GPU) auf dem Computer gleichzeitig ausgeführt werden. Um diese und andere Verfahren geht es bei der parallelen Programmierung. Wir verwenden in mehreren Bereichen von Hilo Verfahren der parallelen Programmierung.

Beispielsweise ist eines der Features von Hilo der Cartooneffekt, den Sie verwenden können, um ein Bild mit einfacheren Farben und Konturen zu stilisieren. Hier wird gezeigt, wie ein Bild vor und nach dem Anwenden dieses Effekts aussieht.

Der auf ein Foto angewendete Comiceffekt

Der Cartooneffekt ist rechenintensiv. In der Implementierung von Hilo wird das Ergebnis mithilfe von C++ AMP schnell im Grafikprozessor berechnet. Auf Systemen, auf denen kein Grafikprozessor mit hoher Rechenleistung verfügbar ist, verwendet Hilo die PPL, die zur Konkurrenz-Runtime gehört. Hier wird gezeigt, wie wir mit der accelerator::is_emulated-Eigenschaft bestimmt haben, ob für den Cartooneffekt der C++ AMP- oder der PPL-Algorithmus verwendet wird:

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


Tipp  Wir wählten die Ausführung des PPL-Algorithmus, wenn die erforderliche Hardware nicht verfügbar ist, da wir bereits über den Code verfügten, der alle CPU-Kerne nutzt. Wenn jedoch kein Fallbackcode verfügbar ist, können Sie den C++ AMP-Code mit dem WARP- oder Referenzgerät ausführen. Erstellen Sie das Profil des C++ AMP-Codes für mehrere Konfigurationen, um leichter bestimmen zu können, ob Sie ein ähnliches Fallbackverfahren berücksichtigen müssen.

Informationen darüber, wie wir diese Algorithmen implementiert haben, finden Sie in den Hilo-Quelldateien im CartoonEffect-Projekt.

Weitere Informationen über C++ AMP finden Sie unter C++ AMP (C++ Accelerated Massive Parallelism).

Wenn Sie Verfahren der parallelen Programmierung anwenden, müssen Sie zwischen Vordergrundverarbeitung (im Hauptthread) und Hintergrundverarbeitung (in Workerthreads) unterscheiden. Mit der PPL können Sie leichter steuern, welche Teile der App im Hauptthread und welche Teile im Hintergrund ausgeführt werden. Vorgänge zum Lesen oder Ändern von XAML-Steuerelementen werden immer im Hauptthread der App aufgerufen. Für rechenintensive oder E/A-intensive Vorgänge ohne Änderung der Benutzeroberfläche können Sie jedoch die Hardware für parallele Verarbeitung des Computers nutzen, indem Sie PPL-Aufgaben verwenden, die in Threads aus dem Threadpool des Systems ausgeführt werden. Das Vordergrund/Hintergrund-Muster wird in der Crop-Aktion gezeigt. Hier ist der Code:

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


Der Code zeigt den Vorgang zum Zuschneiden von Bildern in Hilo. Die UI für den Zuschneidvorgang enthält Ziehpunkte zum Zuschneiden, die vom Benutzer geändert werden können. Nachdem der Benutzer den Zuschneidbereich angegeben hat, tippt er auf das Bild, um eine Vorschau des zugeschnittenen Bilds zu generieren. Im Beispiel löst das Grid-Steuerelement des Zuschneidvorgangs ein Tapped-Ereignis aus, dessen CodeBehind-Handler die CropImageAsync-Methode aufruft.

Da die CropImageAsync-Methode von einem Ereignishandler aufgerufen wird, wird sie im Hauptthread ausgeführt. Intern wird ihre Arbeit zwischen dem Hauptthread und einem Hintergrundthread im Threadpool aufgeteilt. Zu diesem Zweck plant die Methode mit der concurrency::create_task-Funktion die DoCrop-Methode in einem Hintergrundthread.

Hinweis  Während die DoCrop-Methode im Hintergrund ausgeführt wird, kann der Hauptthread gleichzeitig andere Aufgaben ausführen. Beispielsweise fährt der Hauptthread während der Ausführung der DoCrop-Methode mit dem Animieren des Statusrings und dem Reagieren auf Navigationsanforderungen des Benutzers fort.
Nach Abschluss der DoCrop-Methode wird im Hauptthread eine Fortsetzungsaufgabe gestartet, um die XAML-Steuerelemente zu aktualisieren.

In diesem Diagramm wird die Verwendung von Threads durch den Zuschneidvorgang gezeigt.

Ein Diagramm, das zeigt, wie Threads von einem Zuschneidvorgang genutzt werden

Die DoCrop-Methode wird in einem Hintergrundthread ausgeführt. Sie verwendet jedoch außerdem die parallel_for-Funktion der PPL, um rechenintensive Vorgänge mit dem Mehrkernprozessor des Computers auszuführen. Hier ist der Code.

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


Der Code zeigt beispielhaft, wie Sie Verfahren der parallelen Programmierung in die App integrieren können. Sie maximieren die Vorteile von Parallelität, wenn Sie nur die äußere Schleife parallelisieren. Das Parallelisieren der inneren Schleife bringt keine höhere Leistung, da der geringe Betrag der in der inneren Schleife ausgeführten Aufgaben den Aufwand der parallelen Verarbeitung nicht aufwiegt.

Für einen modernen Programmierstil und optimale Leistung wird empfohlen, für neuen Code die parallelen Algorithmen und Datentypen der PPL zu verwenden.

Weitere Informationen finden Sie unter Parallele Programmierung in C++.

[Oben]

Tipps für die Verwendung von C++/CX als Interoperabilitätsebene

Hier sind einige Tipps für die Interoperabilität zwischen Sprachen, die wir während der Erstellung von Hilo entwickelt haben. (Eine vollständige Dokumentation der für eine C++-Windows Store-App verfügbaren Spracherweiterungen für die Interoperabilität zwischen Sprachen finden Sie in der Sprachreferenz zu Visual C++ (C++/CX).)

Achten Sie auf den Aufwand für die Typkonvertierung

Für die Interaktion mit Windows-Runtime-Features müssen Sie zuweilen Datentypen aus dem Platform-Namespace und dem Windows-Namespace erstellen. Sie sollten diese Typen möglichst effizient erstellen.

Wenn Sie beispielsweise aus einem std::vector-Objekt einenWindows::Foundation::Collections::Vector^-Verweis erstellen, kann es vom Vector-Konstruktor kopiert werden. Wenn Sie ein std::vector-Objekt zuordnen und wissen, dass keine weiteren Verweise auf das Objekt vorhanden sind, können Sie ein Vector-Objekt erstellen, ohne es zu kopieren, indem Sie vor der Übergabe an den Vector-Konstruktor die std::move-Funktion für den std::vector aufrufen. Dies ist möglich, da die Vector Klasse einen Verschiebekonstruktor bereitstellt, der ein std::vector<T>&&-Argument akzeptiert.

Es gibt Verschiebekonstruktoren für die Platform::Collections-Klassen Vector, VectorView, Map und MapView. Konstruktoren dieser Klassen akzeptieren rvalue-Verweise auf den std::vector-Typ und den std::map-Typ. Hier ist ein Beispiel.

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


Der Code erstellt das m_months-Objekt, ohne den monthBlock-Vektor zu kopieren.

Für die Platform::Array-Klasse und die Platform::String-Klasse gibt es keine Verschiebekonstruktoren. Stattdessen verwenden Sie die Platform::ArrayReference-Klasse und die Platform::StringReference- Klasse.

In Hilo verwendeten wir statt der Platform::Array-Klasse die Vector-Klasse und die Map-Klasse, da sie mit std::vector und std::map kompatibel sind. (std::array wird von uns nicht verwendet, da zur Kompilierzeit seine Größe angegeben werden muss.)

Rufen Sie Methoden von Verweisklassen aus dem erforderlichen Thread auf

Einige Verweisklassen erfordern, dass ihre Methoden, Ereignisse und Eigenschaften aus einem bestimmten Thread aufgerufen werden. Beispielsweise muss die Interaktion mit XAML-Klassen aus dem Hauptthread erfolgen. Wenn Sie eine neue Klasse erstellen, die von einer vorhandenen Windows-Runtime-Klasse abgeleitet wird, erbt die neue Klasse die Kontextanforderungen der Basisklasse.

Befolgen Sie das Threadingmodell der von Ihnen verwendeten Objekte.

In jedem Thread können Vorgänge dazu führen, dass Verweiszähler verringert werden. Aus diesem Grund sollten Sie sicherstellen, dass Destruktoren für die von Ihnen implementierten Verweisklassen aus jedem Thread aufgerufen werden können. Sie dürfen im Destruktor keine Methoden oder Eigenschaften aufrufen, die einen bestimmten Threadkontext erfordern.

Beispielsweise erfordern manche Windows-Runtime-Klassen, dass die Registrierung von Ereignishandlern im Hauptthread aufgehoben wird. Hier ist ein Codebeispiel für einen threadsicheren Destruktor, der diese Anforderung erfüllt.

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


run_async_non_interactive ist eine in Hilo definierte Hilfsfunktion. Sie sendet ein Funktionsobjekt an den Hauptthread, damit UI-Interaktionen des Benutzers mit höherer Priorität ausgeführt werden können.

Markieren Sie Destruktoren öffentlicher Verweisklassen als virtuell

Destruktoren öffentlicher ref-Klassen müssen als virtual deklariert werden.

Verwenden Sie Verweisklassen nur für die Interoperabilität

Wenn Sie Windows-Runtime Objekte oder Windows-Runtime-Komponenten erstellen, müssen Sie nur ^ und ref new verwenden. Sie können die C++-Standardsyntax verwenden, wenn Sie Kernanwendungscode schreiben, in dem die Windows-Runtime nicht genutzt wird.

In Hilo werden mit ^ und std::shared_ptr vom Heap zugewiesene Objekte verwaltet und Arbeitsspeicherverluste minimiert. Es wird empfohlen, mit ^ die Lebensdauer von Windows-Runtime-Variablen zu verwalten, mit ComPtr die Lebensdauer von COM-Variables zu verwalten (z. B. bei Verwendung von DirectX) und mit std::shared_ptr oder std::unique_ptr die Lebensdauer sämtlicher vom Heap zugewiesenen C++-Objekte zu verwalten.

Es wird empfohlen, alle ref-Klassen als öffentliche Klassen zu deklarieren, da sie nur für die Interoperabilität vorgesehen sind. Wenn Sie über die Klasse private, protected oder internal ref verfügen, bedeutet dies, dass Sie versuchen, ref-Klassen für die Implementierung und nicht für die Interoperabilität über die abstrakte Binärschnittstelle (Abstract Binary Interface, ABI) hinweg zu verwenden.

Verwenden Sie Verfahren, die Marshallingkosten verringern

C++/CX ist für die Interoperabilität von Programmiersprachen vorgesehen. Wenn Sie über die ABI hinweg Funktionen aufrufen, verursachen Sie manchmal aufgrund der Kosten für das Marshallen (Kopieren) von Daten zusätzlichen Aufwand. Da das XAML-Benutzeroberflächenframework in C++ geschrieben ist, verursachen Sie keinen Marshallingaufwand, wenn Sie aus einer C++-App mit XMAL interoperieren. Wenn Sie in C++ eine Komponente implementieren, die aus einer anderen Sprache als C++ oder XAML aufgerufen wird, verursacht die App Kosten für das Marshalling.

Informationen zu Verfahren, um das Threading- und Marshallingverhalten der von Ihnen erstellten Komponenten anzugeben, finden Sie unter Threading und Marshaling (C++/CX). Weitere Informationen zum Marshallingverhalten bei anderen Sprachen finden Sie unter Aufrechterhalten der Schnelligkeit Ihrer App bei Verwendung der Interoperabilität (Windows Store-Apps mit C#/VB/C++ und XAML).

Verwenden Sie den Objektkatalog, um die WINMD-Ausgabe der App zu verstehen

Wenn Sie die App erstellen, erzeugt der Compiler eine WINMD-Datei, die Metadaten für alle öffentlichen ref-Typen enthält, die von der App definiert werden. Komponenten, z. B. XAML, verwenden die WINMD-Datei, um Methoden der Datentypen der App über die ABI hinweg aufzurufen.

Häufig ist es hilfreich, den Inhalt der generierten WINMD-Datei zu überprüfen. Sie können den Inhalt im Objektkatalog von Visual Studio anzeigen. Navigieren Sie im Objektkatalog zur WINMD-Datei im Verzeichnis "Debug" des Projekts, und öffnen Sie die Datei. Dort werden alle Typen angezeigt, die von der App für XAML verfügbar gemacht werden.

Hinweis  Achten Sie darauf, welche öffentlichen ref-Typen Sie in die WINMD-Datei der App einschließen.

Wenn C++/CX Ihre Anforderungen nicht erfüllt, ziehen Sie für Interoperabilität auf niedriger Ebene WRL in Betracht

Mit den Spracherweiterungen von C++/CX können Sie Zeit sparen, Sie müssen sie jedoch nicht verwenden. Mit der C++-Vorlagenbibliothek der Windows-Runtime (WRL) erhalten Sie über Standard-C++ Zugriff auf sprachübergreifende Interoperabilität von einer niedrigeren Ebene. COM-Programmierer sind mit den Konventionen der WRL vertraut.

Mit der WRL lassen sich Windows-Runtime-APIs compilerunabhängig erstellen und nutzen. Sie können statt der C++/CX-Syntax die WRL verwenden. Mit ihr können Sie den Code für die Leistung oder für bestimmte Szenarien optimieren. Sie unterstützt außerdem App-Entwicklungs-Methodiken, die keine Ausnahmen verwenden. Weitere Informationen finden Sie unter C++-Vorlagenbibliothek der Windows-Runtime.

Wir stellten fest, dass C++/CX die Features und Leistung bietet, die für Hilo benötigt werden. Wir verwendeten die WRL nur für den Zugriff auf eine COM-Schnittstelle, die das Lesen von Pixeldaten aus einem Bild ermöglicht. Da die WRL auf der ATL aufbaut, ist die WRL im Allgemeinen eine gute Wahl, wenn Sie über ein COM-Objekt verfügen, aus dem durch Portieren ein Windows-Runtime-Objekt gemacht werden soll.

Verwechseln Sie nicht die C++/CX-Spracherweiterungen mit C++/CLI

Die Syntax der C++/CX-Spracherweiterungen und die Syntax von C++/CLI sind ähnlich, jedoch gibt es zwei sehr unterschiedliche Ausführungsmodelle. Dies wird nachfolgend erläutert.

Diese Sprachen erfordern für den Aufruf von Windows-Runtime-APIs aus JavaScript und .NET Projektionen, die für die jeweilige Sprachumgebung spezifisch sind. Wenn Sie eine Windows-Runtime-API aus JavaScript oder .NET aufrufen, rufen Sie die Projektion auf, die wiederum die zugrunde liegende ABI-Funktion aufruft. Sie können zwar die ABI-Funktionen direkt aus Standard-C++ aufrufen, jedoch stellt Microsoft auch Projektionen für C++ bereit, da diese die Verwendung der Windows-Runtime-APIs stark vereinfachen und dennoch eine hohe Leistung aufrechterhalten.

Microsoft stellt außerdem Spracherweiterungen für Visual C++ bereit, die spezielle Unterstützung für die Windows-Runtime- Projektionen bieten. Viele dieser Spracherweiterungen ähneln der Syntax für C++/CLI. C++-Apps verwenden diese Syntax jedoch nicht für die Common Language Runtime (CLR), sondern zum Generieren von systemeigenem Code, der mit den Anforderungen für das Binärformat der ABI kompatibel ist.

Da keine Laufzeit zum Verwalten des Speichers vorhanden ist, löscht das System C++/CX-Objekte anhand des Verweiszählers, und zwar auf ähnliche Weise wie std::shared_ptr. Der Hütchen (^)-Operator (Handle für ein Objekt) ist ein wichtiger Bestandteil der neuen Syntax, da er Verweiszähler ermöglicht. Sie müssen keine Methoden wie z. B. AddRef und Release direkt aufrufen und die Lebensdauer eines Windows-Runtime-Objekts verwalten, da dies automatisch durch die Laufzeit erfolgt. Sie löscht das Objekt, wenn keine andere Komponente darauf verweist, z. B. wenn es den Bereich verlässt oder wenn Sie alle Verweise auf nullptr festlegen.

Ein weiterer wichtiger Aspekt beim Erstellen von Windows Store-Apps mit Visual C++ ist das ref new-Schlüsselwort. Verwenden Sie ref new anstelle von new, um Windows- Runtime-Objekte mit Verweiszählung zu erstellen. Weitere Informationen finden Sie unter Typsystem (C++/CX).

Versuchen Sie nicht, interne Typen in öffentlichen Verweisklassen verfügbar zu machen

Öffentliche ref-Klassen liefern Metadaten, die in der WINMD-Datei der App verfügbar gemacht werden. Die Metadaten beschreiben die einzelnen Typen und die Member jedes Typs. Damit die Metadaten vollständig sind, muss jeder Member eines öffentlichen Typs selbst ein öffentlich sichtbarer Typ sein. Daher dürfen Sie interne Typen nicht als public-Member einer öffentlichen ref-Klasse verfügbar machen.

Die Anforderung, Metadaten zu verwenden, kann sich auf den Entwurf der App auswirken. Im Allgemeinen sollten Sie versuchen, die Typen aufzuteilen, damit die öffentlichen ref-Typen in der App ausschließlich für die Interoperabilität und für keine anderen Zwecke verwendet werden. Wenn Sie nicht sorgfältig vorgehen, kann es vorkommen, dass die App eine unbeabsichtigte Verbreitung öffentlicher ref-Klassen enthält.

[Oben]

Tipps zum Verwalten des Arbeitsspeichers

Da Windows Store-Apps i. d. R. nicht geschlossen werden und da Windows Store-Apps auf Tablets mit unterschiedlichen Hardwarefunktionen ausgeführt werden, muss der Betrag an Arbeitsspeicher berücksichtigt werden, den die App nutzen kann. Insbesondere müssen Sie Speicherverluste verhindern, indem Sie nicht zulassen, dass Objekte, auf die der Code nicht zugreifen kann, im Arbeitsspeicher bleiben. Berücksichtigen Sie außerdem die Lebensdauer jedes Objekts, um sicherzustellen, dass Objekte nicht länger als erforderlich im Arbeitsspeicher bleiben. Für eine effiziente Speicherverwaltung wird Folgendes empfohlen:

Verwenden Sie intelligente Zeiger

Verwenden Sie intelligente Zeiger, um sicherzustellen, dass in Programmen keine Speicher- oder Ressourcenlecks auftreten und dass die Programme ausnahmesicher sind. Verwenden Sie in Windows Store-Apps das Handle für ein Objekt ^ (Hütchen) zum Verwalten der Lebensdauer von Windows-Runtime-Variablen, verwenden Sie Microsoft::WRL::ComPtr zum Verwalten der Lebensdauer von COM-Variablen (wenn Sie z. B. DirectX verwenden), und verwenden Sie std::shared_ptr oderstd::unique_ptr zum Verwalten der Lebensdauer aller anderen vom Heap zugewiesenen C++-Objekte.

Wenn Sie ref new aufrufen, generiert der Compiler Code zum Zuweisen eines Windows-Runtime-Objekts und gibt dann ein Handle auf dieses Objekt zurück. Daher ist es leicht zu beachten, dass ^ verwendet werden muss. Windows-Runtime-Methoden, die keine Werttypen zurückgeben, geben ebenfalls Handles zurück. Es ist wichtig, Standardzeigertypen und COM-Objekte zu verwenden, um Speicher- und Ressourcenverluste zu vermeiden.

In Hilo wird std::shared_ptr u. a. verwendet, damit eine Aufgabe in einer Fortsetzungskette in eine Variable schreiben und eine andere Aufgabe aus der Variablen lesen kann. Weitere Informationen finden Sie unter Zusammenstellen der Ausgaben von mehreren Fortsetzungen in diesem Handbuch.

In Hilo wurde direkter Zugriff auf Pixeldaten benötigt, um Bilder zuzuschneiden und den Cartooneffekt auf Bilder anzuwenden. Zu diesem Zweck mussten wir ein IBuffer-Objekt in die zugrunde liegende COM-Schnittstelle IBufferByteAccess konvertieren. Wir haben dasIBuffer-Objekt in die zugrundeliegende IInspectable-Schnittstelle umgewandelt und QueryInterface aufgerufen, um den Zeiger zu einer unterstützten Schnittstelle für das Objekt abzurufen. Wir haben zum Verwalten der Zeiger außerdem die ComPtr-Klasse verwendet, sodass keine Aufrufe von AddRef und Release erforderlich waren.

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

Da wir mit COM arbeiteten, definierten wir die ThrowIfFailed-Funktion, um die Behandlung von HRESULT-Werten zu erleichtern. Sie können im Code diese Hilfsfunktion verwenden, wenn Sie Fehlercode in eine Windows-Runtime-Ausnahme umwandeln müssen.

ImageUtilities.cpp


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


Weitere Informationen über intelligente Zeiger finden Sie unter Intelligente Zeiger (modernes C++).

Verwenden Sie Stapelsemantik und das RAII-Muster

Stapelsemantik und das RAII-Muster sind eng miteinander verwandt.

Verwenden Sie Stapelsemantik für die automatische Steuerung der Objektlebensdauer und zum Minimieren unnötiger Heapzuordnungen. Verwenden Sie außerdem Stapelsemantik zum Definieren von Membervariablen in den Klassen und anderen Datenstrukturen, damit Ressourcen automatisch freigegeben werden, wenn das übergeordnete Objekt freigegeben wird.

Verwenden Sie das RAII (Resource Acquisition Is Initialization)-Muster, um sicherzustellen, dass Ressourcen freigegeben werden, wenn die aktuelle Funktion eine Ausnahme auslöst oder zurückgibt. Bei Verwendung des RAII-Musters wird eine Datenstruktur auf dem Stapel zugeordnet. Diese Datenstruktur initialisiert eine Ressource oder ruft sie ab, wenn sie erstellt wird, und wenn die Datenstruktur zerstört wird, wird diese Ressource zerstört oder freigegeben. Das RAII-Muster stellt sicher, dass vor Verlassen des umschließenden Bereichs der Destruktor aufgerufen wird. Dieses Muster ist hilfreich, wenn eine Funktion mehrere return-Anweisungen enthält. Das Muster erleichtert Ihnen außerdem das Schreiben von ausnahmesicherem Code. Wenn eine throw-Anweisung das Entladen des Stapels verursacht, wird der Destruktor für das RAII-Objekt aufgerufen. Daher wird die Ressource immer ordnungsgemäß gelöscht oder freigegeben. Ein Beispiel für das RAII-Muster ist die Verwendung der std::shared_ptr-Vorlagenklasse für stapelzugeordnete Variablen.

Weitere Informationen über die Verwaltung der Objektlebensdauer finden Sie unter Verwaltung von Objektlebensdauer und Ressourcen (modernes C++). Weitere Informationen über RAII finden Sie unter Objekte sind Besitzer von Ressourcen (RAII).

Behalten Sie Objekte nicht länger als erforderlich bei

Verwenden Sie Stapelsemantik, oder legen Sie Verweise auf nullptr fest, um sicherzustellen, dass Objekte freigegeben werden, wenn nicht mehr auf sie verwiesen wird.

In Hilo verwendeten wir aus zweierlei Gründen Stapelsemantik statt Membervariablen. Erstens lässt sich mit Stapelsemantik leichter sicherstellen, dass Speicher nur in dem Kontext verwendet wird, in dem er benötigt wird. Da Hilo eine Foto-App ist, fanden wir es besonders wichtig, Bilddaten möglichst früh freizugeben, um den Speicherverbrauch minimal zu halten. Das gleiche Prinzip gilt für andere Arten von Apps. Beispielsweise sollten in einem Blogleser Netzwerkverbindungen freigegeben werden, wenn sie nicht mehr benötigt werden, damit diese Ressourcen von anderen Apps genutzt werden können.

Da ein Großteil der App asynchrone Aktionen erfordert, wollten wir zweitens den Variablenzugriff auf den Code beschränken, der ihn benötigt, um die App parallelitätssicher zu machen. Bei der herkömmlichen Multithread-Programmierung ohne Lambda-Ausdrücke müssen Sie häufig den Zustand als Membervariable speichern. Im folgenden Beispiel verwendeten wir anstelle von Membervariablen lokale Variablen und Erfassungssemantik, um asynchron eine Miniaturansicht von einem Bild auf dem Datenträger zu erstellen.

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


Wenn die App die Verwendung einer Membervariablen erfordert, sollte diese Variable auf nullptr festgelegt werden, wenn sie nicht mehr benötigt wird.

Vermeiden Sie Zirkelverweise

Objekte in einem Zirkelverweis erreichen niemals den Verweiszähler null und werden daher niemals zerstört. Vermeiden Sie Zirkelverweise im Code, indem Sie einen der Verweise durch einen schwachen Verweis ersetzen.

In Hilo sind beispielsweise die IPhoto-Schnittstelle und die IPhotoGroup-Schnittstelle definiert. IPhoto kapselt Informationen über ein Foto (Dateipfad, Bildtyp, Aufnahmedatum usw). IPhotoGroup kapselt Auflistungen von Fotos (z. B. alle Fotos in einem bestimmten Monat). Diese Schnittstellen weisen eine Beziehung zwischen übergeordneten und untergeordneten Elementen auf: Sie können ein einzelnes Foto aus einer Fotogruppe oder die übergeordnete Fotogruppe aus einem Foto abrufen. Wenn Standardverweise oder starke Verweise verwendet werden, verweisen ein Foto und die entsprechende Gruppe immer gegenseitig aufeinander. Daher wird der Speicher für keines dieser Elemente jemals freigegeben, auch wenn alle anderen Verweise freigegeben werden.

In Hilo werden stattdessen schwache Verweise verwendet, um derartige Zirkelverweise zu vermeiden. Mit einem schwachen Verweis kann ein Objekt auf ein anderes Objekt verweisen, ohne dass sich dies auf den Verweiszähler auswirkt.

Die Windows-Runtime stellt die WeakReference-Klasse bereit. Deren Resolve-Methode gibt das Objekt zurück, wenn es gültig ist. Andernfalls gibt sie nullptr zurück. So deklariert die von IPhoto abgeleitete Photo-Klasse einen schwachen Verweis auf die übergeordnete Gruppe:

Photo.h


Platform::WeakReference m_weakPhotoGroup;


So löst die Photo-Klasse den schwachen Verweis in der Group-Eigenschaft auf:

Photo.cpp


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


In Hilo wird außerdem std::weak_ptr verwendet, um Zirkelverweise aufzulösen. Verwenden Sie WeakReference, wenn Sie einen schwachen Verweis über die ABI-Grenze hinweg verfügbar machen müssen. Verwenden Sie std::weak_ptr in internem Code, der nicht mit der Windows-Runtime interagiert.

Das Erfassen des this-Handles eines Objekts in einem Lambda-Ausdruck kann ebenfalls einen Zirkelverweis verursachen, wenn Sie den Lambda-Ausdruck als Ereignishandler verwenden oder ihn an eine Aufgabe übergeben, auf die das Objekt verweist.

Hinweis  In Memberfunktionen der ref-Klasse ist der Typ des Symbols this ein konstantes Handle (^) und kein Zeiger.

Die Ereignishandler eines Objekts werden von der Quelle getrennt, wenn das Objekt zerstört wird. Ein Lambda-Ausdruck, der this erfasst, erhöht jedoch den Verweiszähler des Objekts. Wenn ein Objekt einen Lambda-Ausdruck erstellt, der this erfasst, und den Lambda-Ausdruck als Handler eines Ereignisses verwendet, das eigenständig oder über ein Objekt bereitgestellt wird, auf das es direkt oder indirekt verweist, erreicht der Verweiszähler des Objekts niemals null. In diesem Fall wird das Objekt niemals zerstört.

In Hilo werden Zirkelverweise verhindert, indem für Ereignisse Memberfunktionen anstelle von Lambda-Ausdrücken verwendet werden. Hier ist ein Beispiel aus der HiloPage-Klasse.

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 diesem Beispiel ist NavigateEventHandler ein von Hilo definierter Delegattyp. Ein Delegat ist eine Verweisklasse. Dies ist in der Windows-Runtime die Entsprechung für ein Funktionsobjekt in Standard-C++. Wenn Sie einen Delegaten mit einem Objekthandle und einem Zeiger auf eine Memberfunktion instanziieren, wird der Verweiszähler des Objekts nicht erhöht. Wenn Sie die Delegatinstanz aufrufen, nachdem das Zielobjekt zerstört wurde, wird eine Platform::DisconnectedException ausgelöst.

Delegattypen verfügen zusätzlich zu dem Konstruktor mit zwei Argumenten, der im Beispiel gezeigt wird, über einen überladenen Konstruktor, der ein einzelnes Argument akzeptiert. Das Argument ist ein Funktionsobjekt, z. B. ein Lambda-Ausdruck. Wenn in diesem Beispiel das this-Handle des Objekts in einem Lambda-Ausdruck erfasst und dieser als Konstruktorargument des Delegaten übergeben wird, wird der Verweiszähler des Objekts erhöht. Der Verweiszähler wird jedoch nur verringert, wenn keine weiteren Verweise auf den Lambda-Ausdruck vorhanden sind.

Wenn Sie Lambda-Ausdrücke als Ereignishandler verwenden, haben Sie zwei Möglichkeiten zum Verwalten des Speichers. Sie können anstelle von Objekthandles schwache Verweise erfassen. Oder Sie können sicherstellen, dass das Abonnement von Ereignissen zum richtigen Zeitpunkt gekündigt wird und Zirkelverweise korrigiert werden, bevor sie Probleme bei der Speicherverwaltung verursachen können.

In Hilo wählten wir für Ereignisrückrufe Memberfunktionen, da uns dies als die einfachste Möglichkeit zum Schreiben von ordnungsgemäßem Code erschien.

Weitere Informationen über schwache Verweise finden Sie unter Schwache Verweise und Korrigieren von Zirkelverweisen (C++/CX) und unter Gewusst wie: Erstellen und Verwenden von weak_ptr-Instanzen.

[Oben]

Tipps und Verfahren zum Debuggen

Sie können viele herkömmliche Tools und Verfahren zum Debuggen von Desktop-Apps auch für Windows Store-Apps verwenden. Dies sind einige der Tools und Verfahren, die wir für Hilo verwendet haben:

Verwenden Sie Haltepunkte und Ablaufverfolgungspunkte

Der Debugger erkennt anhand eines Haltepunkts, dass die Ausführung der Anwendung an diesem Punkt unterbrochen oder angehalten werden soll. Ein Ablaufverfolgungspunkt ist ein Haltepunkt, dem eine benutzerdefinierte Aktion zugeordnet ist. Für Hilo verwendeten wir Haltepunkte, um den App-Zustand beim Ausführen von PPL-Fortsetzungsaufgaben ausführlich zu testen.

Sie können festlegen, dass Haltepunkte ausgelöst werden, wenn bestimmte Bedingungen zutreffen. Für Hilo war das Debuggen einer Interaktion zwischen XAML und dem C++-CodeBehind erforderlich. Das Problem trat jedoch nur auf, nachdem der Code mindestens 20 Mal ausgeführt worden war. Daher verwendeten wir die Einstellung Trefferanzahl, um die Ausführung anzuhalten, nachdem der Haltepunkt mindestens 20 Mal erreicht wurde.

Für Hilo erwiesen sich Ablaufverfolgungspunkte als besonders hilfreich, um zu ermitteln, über welchen Thread bestimmte PPL-Aufgaben ausgeführt wurden. Die Standardmeldung für Ablaufverfolgungspunkte enthielt alle erforderlichen Informationen.


Function: $FUNCTION, Thread: $TID $TNAME

Weitere Informationen finden Sie unter Verwenden von Haltepunkten und Ablaufverfolgungspunkten.

Tipp  Verwenden Sie __debugbreak, um einen Haltepunkt programmgesteuert aus Code festzulegen.

Verwenden Sie OutputDebugString für das Debuggen im Format von "printf".

Verwenden Sie OutputDebugString, wenn die Ausführung im Debugger nicht angehalten werden muss. OutputDebugString eignet sich auch für Tools wie z. B. WinDbg und kann im Releasemodus verwendet werden.

Achtung  Sie können für das Debuggen im Format von "printf" auch TextBlock und andere XAML-Steuerelemente verwenden. Wir entschieden uns jedoch für OutputDebugString, da sich die Verwendung eines Steuerelements auf nicht vorhersehbare Weise auf das Layout von Seiten auswirken kann, nachdem Sie es entfernt haben. Wenn Sie den Code mithilfe von Steuerelementen debuggen, entfernen Sie die Steuerelemente, und testen Sie den Code, bevor Sie die App bereitstellen.

Unterbrechen Sie die Ausführung, wenn Ausnahmen ausgelöst werden

Wenn die Unterbrechung in einer catch-Anweisung erfolgt, wissen Sie nicht, von welchem Code die Ausnahme ausgelöst wurde. Wählen Sie in Visual Studio Debuggen, Ausnahmen und dann Ausgelöst aus, damit die Unterbrechung erfolgt, wenn die Ausnahme ausgelöst wird. Dieses Verfahren ist besonders hilfreich, wenn PPL-Aufgaben verwendet werden, da Sie den Threadingkontext untersuchen können, in dem der Fehler aufgetreten ist.

Wichtig  Es muss jede Ausnahme abgefangen werden, die von PPL-Aufgaben ausgelöst wird. Wenn Sie keine Ausnahme feststellen, die von einer PPL-Aufgabe ausgelöst wird, beendet die Laufzeit die App. Wenn die App unerwartet beendet wird, aktivieren Sie dieses Feature, um zu ermitteln, wo die Ausnahme aufgetreten ist.

Verwenden Sie die Parallelitätsdebugfenster

Verwenden Sie die Fenster Parallele Aufgaben, Parallele Stapel und Parallele Überwachung, um das Laufzeitverhalten von Code zu ermitteln und zu überprüfen, der die PPL und andere Features der Konkurrenz-Runtime verwendet. Für Hilo verwendeten wir diese Fenster, um den Zustand von allen ausgeführten Aufgaben an verschiedenen Punkten im Programm zu ermitteln. Weitere Informationen finden Sie unter Exemplarische Vorgehensweise: Debuggen einer parallelen Anwendung und Gewusst wie: Verwenden des Fensters "Parallele Überwachung".

Verwenden Sie zum Debuggen für bestimmte Hardwarekonfigurationen den Simulator und Remotedebuggen

Wenn beispielsweise Ihr Monitor die Fingereingabe nicht unterstützt, können Sie mit dem Simulator Zusammendrück-, Zoom-, Dreh- und andere Gesten emulieren. Mit dem Simulator können Sie auch Geolocation simulieren und mit verschiedenen Bildschirmauflösungen arbeiten. Weitere Informationen finden Sie unter Ausführen von Windows Store-Apps im Simulator und in diesem Handbuch unter Testen von Geräten mit dem Simulator und Remotedebugger.

Ausführen von Hilo im Simulator

Verwenden Sie Remotedebuggen, um die App auf einem Computer zu testen, auf dem Visual Studio nicht installiert ist. Dies ist hilfreich, wenn ein Computer eine andere Hardwarekonfiguration als der Computer aufweist, auf dem Visual Studio ausgeführt wird. Weitere Informationen finden Sie unter Ausführen von Windows Store-Apps auf einem Remotecomputer.

Weitere Informationen zum Debuggen finden Sie unter Debuggen von Windows Store-Apps und unter Debuggen von systemeigenem Code.

[Oben]

Portieren von vorhandenem C++-Code

Wenn Sie vorhandenen C++-Code in die Windows Store-App portieren möchten, hängt der Umfang der erforderlichen Aufgaben vom Code ab. Code, in dem frühere Benutzeroberflächenframeworks verwendet werden, muss neu geschrieben werden, während andere Typen von Code, z. B. Geschäftslogikroutinen und numerische Routinen, im Allgemeinen problemlos portiert werden können. Wenn Sie Code verwenden, den Sie nicht selbst geschrieben haben, sollten Sie überprüfen, ob bereits eine Version für Windows Store-Apps vorhanden ist. In Hilo passten wir vorhandenen Code an, der den Cartooneffekt ausführt, und portierten ihn, um ihn als statische Bibliothek zu verwenden (siehe das CartoonEffect-Projekt in den Hilo-Quelldateien). Wir führten in der statischen Bibliothek keine Windows-Runtime-Abhängigkeiten ein, damit wir die Bibliothek auch für frühere Versionen von Windows sowie für Windows 8-Desktop-Apps verwenden können.

Windows Store-Apps mit C++ können auf vorhandene statische Bibliotheken, DLLs und COM-Komponenten verweisen, solange sämtlicher vorhandener Code, der von der App aufgerufen wird, die Anforderungen für Windows Store-Apps erfüllt. In Windows Store-Apps müssen alle Komponenten, einschließlich statischer Bibliotheken und DLLs, für die App lokal sein und im Manifest gepackt sein. Alle Komponenten im Paket der App müssen die Anforderungen für Windows Store-Apps erfüllen.

Übersicht über den Portierungsprozess

Nachfolgend wird in Grundzügen beschrieben, wie vorhandenes C++ portiert wird, damit es in einer Windows Store-App verwendet werden kann. Wir portierten auf diese Weise vorhandenen Code, um ihn in Hilo zu nutzen.

Kompilieren und testen Sie den Code unter Windows 8

Vor dem Portieren müssen Sie zunächst den vorhandenen Code für Windows 8 kompilieren und testen. Code, der unter Windows 7 kompiliert und ausgeführt werden kann, erfordert i. d. R. äußerst geringe Änderungen für Windows 8. Es empfiehlt sich jedoch, Plattformabhängigkeiten zu ermitteln, bevor Sie den Code für die Verwendung in einer Windows Store-App portieren.

Beim Schreiben von Hilo verwendeten wir den Code aus einem früheren Projekt wieder, der die Bildfilterung für den Cartooneffekt ausführt. Dieser Code wurde ohne Änderung auf der Windows 8-Plattform kompiliert und ausgeführt.

Hinweis  Da das Portieren zu Änderungen des ursprünglichen Codes führen kann, sollten Sie vor dem Portieren Komponententests für den ursprünglichen Code erstellen. Sie können zu diesem Zweck das neue Framework für systemeigene Tests in Microsoft Visual Studio verwenden.

Identifizieren Sie nicht unterstützte Bibliotheksfunktionen

Nachdem Sie den Code zur Ausführung gebracht haben, müssen Sie sicherstellen, dass im Code nur Funktionen verwendet werden, die für Windows Store-Apps verfügbar sind. Sie finden die Liste unterstützter Funktionen in der API-Referenz für Windows Store-Apps. Eine Übersicht über die verfügbaren Funktionen finden Sie in der Übersicht über unterstützte Funktionen auf dieser Seite.

Sie können auch die Compilerpräprozessordirektive WINAPI_FAMILY=WINAPI_PARTITION_APP verwenden, um Inkompatibilitäten zu ermitteln. Mit dieser Direktive löst der C++-Compiler Fehler aus, wenn in ihm Aufrufe von nicht unterstützten Funktionen auftreten.

Tipp  Wenn die WINAPI_FAMILY-Direktive während der Kompilierung auf WINAPI_PARTITION_APP festgelegt ist, werden die Deklarationen einiger Win32-Funktionen und -Datentypen entfernt. Dies wirkt sich jedoch nicht auf die Verknüpfung aus. Sie können einige der fehlenden Deklarationen in die temporäre Headerdatei toremove.h einfügen und dann die Verweise auf diese Funktionen einzeln nacheinander entfernen. Eine temporäre Headerdatei bietet sich außerdem als Problemumgehung an, da die Ausführung des Compilers nach ca. 100 Fehlern beendet wird.

Verwenden Sie Funktionen aus der Windows-Runtime-API-Referenz

In den meisten Fällen finden Sie eine Windows Runtime-Funktion, die den gewünschten Vorgang ausführt. Beispielsweise ist die Win32-Funktion InitializeCriticalSectionAndSpinCount nicht für Windows Store-Apps verfügbar, jedoch stellt die Windows-Runtime stattdessen InitializeCriticalSectionEx bereit. Erstellen Sie beim Bearbeiten der Problembereiche den Code mit den neuen Funktionen, und testen Sie die App inkrementell. Empfehlungen für Ersatzfunktionen finden Sie unter Alternativen zu Windows-APIs in Windows Store-Apps.

In manchen Fällen können Sie Inkompatibilitäten korrigieren, indem Sie Funktionen aus der C++-Laufzeitbibliothek und der STL verwenden. Im Allgemeinen sollten Sie nach Möglichkeit immer Standard-C++ verwenden. Gestalten Sie Kernlogik um, damit C++-Bibliotheken auf Container, Puffer usw. angewendet werden. C++ 11-Bibliotheken enthalten viele neue Features, z. B. Threads, Mutexe und E/A. Überprüfen Sie die C++-Bibliotheken, wenn Sie in der C++-Bibliothek Ersatz für eine Win32-API suchen.

Tipp  Isolieren Sie plattfomspezifischen Code in Abstraktionen, um die Portierung zu erleichtern.

Der restliche Code, der den Kern des Bildfilters für den Cartooneffekt bildete, rief nicht die Win32-API auf. Er bestand lediglich aus numerischen Funktionen, mit denen die Pixel im Bild geändert wurden. Die einzigen echten Portierungsprobleme für Hilo bestanden im Abstimmen der verschiedenen Datenformate für Bitmaps. Beispielsweise mussten wir beim Codieren des Bilds vor dem Speichern die Farbkanäle neu anordnen, um das BGRA (Blue/Green/Red/Alpha)-Layout zu verwenden, das für Windows-Runtime-Funktionen erforderlich ist.

Ersetzen Sie synchrone Bibliotheksfunktionen durch asynchrone Versionen

Wenn der Code ausschließlich mit Datentypen und Funktionen, die für Windows Store-Apps verfügbar sind, kompiliert und ausgeführt wird, überprüfen Sie den Code auf synchrone Funktionen, für die asynchrone Versionen verfügbar sind. Gestalten Sie ggf. den Code um, damit asynchrone Funktionen genutzt werden. In der App sollte nach Möglichkeit immer die asynchrone Vorgehensweise verwendet werden.

Tipp  Gestalten Sie den Code für die Behandlung von partiellen Daten um. Wenn Teilergebnisse inkrementell verfügbar sind, machen Sie diese ohne Verzögerung für den Benutzer verfügbar.
Tipp  Sorgen Sie dafür, dass die UI während asynchronen Vorgängen reaktionsfähig bleibt.

Wir mussten in Hilo die Funktionen des ursprünglichen Codes für den Bildfilter des Cartooneffekts, der das Bild lädt, konvertieren, damit sie als asynchrone Versionen geladen werden.

Konvertieren Sie Vorgänge mit langer Ausführungsdauer im Code in asynchrone Versionen

Wenn der Code Vorgänge von langer Dauer enthält, sollten Sie diese in asynchrone Vorgänge ändern. Dies lässt sich einfach erreichen, indem Sie Aufgaben langer Dauer für die Ausführung im Threadpool planen, z. B. mithilfe einer PPL-Aufgabe. Sie können Code neu schreiben oder einen Wrapper schreiben, der vorhandenen Code im Threadpool ausführt.

Wir verwendeten in Hilo für den portierten Cartooneffekt-Bildfilter eine PPL-Fortsetzungsaufgabe, die im Threadpool ausgeführt wird.

Überprüfen Sie das Paket mit dem Zertifizierungskit für Windows-Apps

Nachdem Sie den Code geändert haben, damit nur die Funktionen verwendet werden, die in der API-Referenz für Windows Store-Apps dokumentiert sind, wird im letzten Schritt zum Portieren mit App-Binärdateien ein Paket erstellt, und dann wird mit dem Zertifizierungskit für Windows-Apps überprüft, ob das Paket alle Anforderungen des Windows Store erfüllt. Das Paket enthält alle Komponenten der App, einschließlich statischer Bibliotheken und DLLs.

Eine Beschreibung der Zertifizierung von Hilo finden Sie unter Testen und Bereitstellen der App in diesem Handbuch.

Übersicht über unterstützte Funktionen

Nachfolgend wird zusammengefasst, welche Funktionen und Datentypen von einer Windows Store-App verwendet werden können. Die vollständige Liste finden Sie in der API- Referenz für Windows Store-Apps.

Portieren aus Win32-basierter UI

Wenn Sie bereits über Code verfügen, in dem UI-Funktionen und Datentypen, z. B. HWND, aus User, GDI oder MFC verwendet werden, müssen Sie diesen Code mit XAML neu implementieren.

Portieren von DirectX

DirectX ist in Windows Store-Apps verfügbar. Wenn Sie DirectX 11 verwenden, lässt sich der meiste Code einfach portieren. Verwenden Sie als Ausgangspunkt eine Visual Studio-Vorlage für ein DirectX-Projekt. Wenn Sie eine Windows Store-App zur Laufzeit initialisieren, muss DirectX eingerichtet werden. Dieser Startcode wird von der Visual Studio-Vorlage automatisch eingerichtet. Verschieben Sie den vorhandenen Code in das neue Projekt.

Portieren von MFC

MFC ist in Windows Store-Apps verfügbar. Verwenden Sie zum Ersetzen von MFC-UI-Klassen XAML oder DirectX. Für Dialogfeld-Apps können Sie XAML-Datensteuerelemente mit Datenbindung verwenden. Verwenden Sie zum Ersetzen von Hilfsklassen, z. B. Containern, STL- und CRT (C Run-Time)- Datentypen.

Verwenden der C++-Laufzeitbibliothek (CRT)

Für Windows Store-Apps ist eine umfangreiche Teilmenge der CRT verfügbar. Einige wenige Funktionen sind nicht verfügbar.

  • Multibyte- Zeichenfolgenfunktionen. Die mb*-Funktion und die _ismb*-Funktion sind nicht verfügbar. Verwenden Sie stattdessen Unicode-Zeichenfolgen.
  • Prozesssteuerungs- funktionen. Die exec*-Funktionen sind nicht verfügbar. Sie können aus einer Windows Store-App keine Anwendung erzeugen.
  • Funktionen für die Threaderstellung. Die beginthread*-Funktion und die endthread*-Funktion sind nicht verfügbar. Threading ist in Windows Store-Apps verfügbar, basiert jedoch auf einem Threadpool. Sie können auch std::thread verwenden.
  • Heap- und Stapelfunktionen. Die Funktionen heapwalk, heapmin, resetstkoflw und weitere Funktionen sind nicht verfügbar. Sie können in Windows Store-Apps keinen eigenen Heap erstellen.
  • Funktionen für Umgebungsvariablen. Die Funktionen putenv, getenv, _enviorn und verwandte Funktionen sind nicht verfügbar. Windows Store-Apps enthalten keine Umgebungsblöcke.
  • Konsolen- funktionen. Die Funktionen cprintf, cscanf und verwandte Funktionen sind nicht verfügbar. Windows Store-Apps enthalten keine Konsole.
  • Port- funktionen. Die Funktionen outp, inp und weitere Portfunktionen sind nicht verfügbar.
  • Pipe- funktionen. Die Funktionen popen, pclose und weitere Pipefunktionen sind nicht verfügbar.
Hinweis  Verwenden Sie die Compilerpräprozessordirektive WINAPI_FAMILY=WINAPI_PARTITION_APP, um Inkompatibilitäten zu ermitteln.

Einige CRT-Funktionen, die für Windows Store-Apps verfügbar sind, blockieren E/A-Funktionen. Es wird empfohlen, synchrone E/A-Funktionen, z. B. open, read und write, durch die asynchronen Entsprechungen aus der Windows-Runtime zu ersetzen.

ANSI-Zeichenfolgen der CRT sind in Windows Store-Apps verfügbar, es wird jedoch empfohlen, Unicode-Zeichenfolgen zu verwenden.

Verwenden der C++-Standardbibliothek

Fast alle Funktionen und Datentypen der C++-Standardbibliothek sind in Windows Store-Apps verfügbar (Konsolen-E/A ist nicht verfügbar.) Die C++-Standardbibliothek wird in Hilo umfassend genutzt.

Verwenden der ATL

Für Windows Store-Apps ist eine Teilmenge der ATL verfügbar. Dies sind die verfügbaren Datentypen.

  • DLL-Server.
  • COM- Objekte. Sie können eigene COM-Objekte erstellen. IDispatch wird jedoch nicht unterstützt.
  • CStringW. Es werden nur breite Zeichenfolgen unterstützt.
  • ATL- Containerklassen. MFC-Container, z. B. Zuordnungen, die in die ATL portiert wurden, sind weiterhin verfügbar.
  • CCriticalSection, CEvent, CMutex, CSemaphore, CMutexLock. ATL- Synchronisierungsobjekte sind verfügbar.
  • CComVariant.
  • CComSafeArray.
  • CComBSTR.

Wenn Sie über COM-Komponenten verfügen, die Geschäftslogik und nicht UI betreffen, können Sie diese im Allgemeinen portieren.

Ratschläge zum Portieren

Hier sind einige Tipps zum Portieren von C++ in eine Windows Store-App.

Portieren Sie sämtlichen vorhandenen Code, einschließlich Bibliotheken

Das Paket der App muss sämtliche Binärkomponenten enthalten, die für die App benötigt werden. Dies schließt statische Bibliotheken und DLLs ein.

Erstellen Sie Verknüpfungen mit statischen Bibliotheken, oder importieren Sie Bibliotheken auf die übliche Weise

Sie können in der Windows Store-App Bibliotheken verwenden, die in Standard-C++ geschrieben sind. Windows Store-Apps verwenden die gleiche Verknüpfung wie Konsolen-Apps. Wenn binäre Abhängigkeiten vorhanden sind, muss das Anwendungspaket zu einem frühen Zeitpunkt der Entwicklung überprüft werden, damit alle Bibliotheksfunktionen die Anforderungen erfüllen.

Hinweis  Obwohl die CartoonEffect-Bibliothek keine Features der Windows-Runtime verwendet, mussten wir die Compileroption /ZW ("Windows-Runtime-Erweiterung verwenden") angeben, da die PPL-Version des Cartooneffektalgorithmus in Windows Store-Apps eine besondere Semantik verwendet (z. B. können Sie in einer Windows Store-App den Kontext konfigurieren, in dem PPL-Aufgabenfortsetzungen ausgeführt werden.)

Verwenden Sie C++/CX oder WRL, wenn die Bibliothek Windows-Runtime-Funktionen aufrufen muss

Wenn die Bibliothek Windows-Runtime-Funktionen aufrufen muss, können Sie C++/CX oder WRL verwenden. Im Allgemeinen ist C++/CX einfacher zu verwenden. WRL ermöglicht Ihnen eine differenziertere Steuerung.

Hinweis  Beim Verfügbarmachen von öffentlichen Verweisklassen aus einer vom Benutzer geschriebenen Bibliothek gibt es einige technische Probleme. Dieses Thema sprengt den Rahmen dieses Handbuchs.

Verwenden Sie für die Aktivierung COM-Klassen ohne Registrierung

Die Windows Store-App kann COM-Komponenten aus Bibliotheken nutzen. Die Bibliothek registriert keine COM-Klassen. Stattdessen ruft die App die COM-Aktivierung mit der neuen Funktion CoCreateInstanceFromApp auf.

Konvertieren Sie die Windows-Runtime-Typen, wenn die Kosten für Marshalling ein Problem darstellen

Verwenden Sie für Objekte, die häufig die ABI-Grenze überschreiten und aufwendig zu konvertieren sind, Windows-Runtime-Typen.

Sie können String und Array verwenden. Diese können effizient und ohne Kopieren in Eingabeparameter der Windows-Runtime-API konvertiert werden. StringReference und ArrayReference fügen mithilfe von Leihsemantik eine Windows-Runtime-Fassade hinzu.

Bei der Konvertierung von std::* in Platform::* ist für Container und Auflistungstypen Kopieren erforderlich. Im Allgemeinen sind die Container und Auflistungen des std-Namespaces effizienter als die Container und Auflistungen des Platform-Namespaces. Welcher dieser Namespaces verwendet werden soll, hängt davon ab, ob sich der Auflistungsinhalt häufiger ändert, als er die ABI-Grenze überschreitet.

Für Hilo haben wir sehr viel Zeit dafür aufgewendet zu entscheiden, wann öffentliche ref-Klassen verwendet werden sollen. Wir fanden, dass für unsere App der Aufwand für die Typkonvertierung schwerer wiegt als andere Bedenken.

Entscheiden Sie zwischen der Verwendung von Wrappercode und dem Konvertieren von vorhandenem Code

Wenn bereits vorhandener Code über die ABI hinweg aufgerufen werden muss, müssen Sie entscheiden, ob dieser Code in C++/CX portiert oder als Standard-C++ belassen und von einer C++/CX-Ebene umschlossen werden soll. Für die meisten Fälle empfehlen wir die Verwendung einer Wrapperebene.

Das Konvertierung von bereits vorhandenem Code, damit er Typen und Konzepte der Windows-Runtime verwendet, erfolgt i. d. R. nur, um den Aufwand für die Datenkonvertierung zu vermeiden. Dies ist z. B. der Fall, wenn die Komponente sehr häufig über die ABI hinweg aufgerufen werden muss. Diese Situationen treten selten ein.

Wenn Sie sich für die Erstellung eines C++/CX-Wrappers für die Klassen entscheiden, ist das Standardverfahren das Definieren von Schnittstellen und in ihrer Implementierung das Delegieren an den vorhandenen C++-Code, nachdem ggf. erforderliche Typkonvertierungen erfolgt sind. Durch eine solche Verwendung von Schnittstellen wird eine Windows-Runtime-Fassade um den vorhandenen Code erstellt, und in den meisten Fällen reicht dies aus.

Wenn Sie über bereits vorhandene COM-Komponenten verfügen, sollten Sie diese als Windows-Runtime-Typen verfügbar machen. Dann können Windows-Store-Apps die COM-Komponenten problemlos nutzen. Die hierfür erforderlichen Verfahren liegen außerhalb des Rahmens dieses Handbuchs.

Weitere Informationen zum Portieren

Ein Channel 9-Video bietet hilfreiche Informationen zum Portieren von vorhandenem C++-Code für die Verwendung in einer Windows Store-App. Weitere Informationen finden Sie unter Portieren einer Desktop-App in eine Windows Store-App.

Informationen zur Teilmenge der unterstützten API-Funktionen finden Sie unter Win32 und COM für Windows Store-Apps. Wenn Sie keine geeignete Win32-API-Funktion finden, bietet Alternativen zu Windows-APIs in Windows Store-Apps Vorschläge für Windows-Runtime-Funktionen, die stattdessen verwendet werden können.

[Oben]

 

 

Anzeigen:
© 2015 Microsoft