次の方法で共有


Writing modern C++ code in Hilo (Windows Store apps using C++ and XAML)

From: Developing an end-to-end Windows Store app using C++ and XAML: Hilo

Previous page | Next page

Here are some tips and coding guidelines for creating Windows Store apps using C++ and XAML, including how to adapt to asynchronous programming using the Parallel Patterns Library (PPL). They arose from questions we ourselves asked as we started developing Hilo C++.

Windows Store apps using C++ combine the best features of C++11, the modern C++ coding style, and C++/CX. If you're new to C++11, consider reading C++11 Features (Modern C++) and Welcome Back to C++ (Modern C++) first.

If you're new to C++/CX, read Visual C++ Language Reference (C++/CX).

Download

After you download the code, see Getting started with Hilo for instructions.

You will learn

  • When to use standard C++ types and libraries in Windows Store apps.
  • Best practices for using C++/CX.
  • How to port existing C++ libraries for use by Windows Store apps.
  • Recommended debugging techniques using Visual Studio.

Applies to

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

Understanding the app's environment

Windows Store apps, such as Hilo, include a distinctive visual design and often feature touch-based interaction. In addition, they run in an environment that gives the user more control over what the app is allowed to do compared to desktop and console apps. The extra control helps make the app secure and easier for the user to manage.

These features have implications for the way you implement the app. If you’re using C++, here’s what establishes your app’s static and run-time environment.

  • The package manifest
  • C++ standard libraries
  • Windows Runtime libraries
  • Microsoft Win32 and Component Object Model (COM) API
  • Parallel Patterns Library (PPL)
  • XAML
  • Visual Studio project templates
  • C++ language extensions for interop
  • The C++ compiler and linker
  • Certification process of the Windows Store
  • Deployment

These components and tools determine what platform capabilities are available to your app and how you access these capabilities. If you're new to Windows Store apps, review the overviews of these components and tools to learn how they contribute to your app. You'll see how each component and tool is used in Hilo as you read through this guide.

Note  

Another useful general starting point for C++ programmers is Roadmap for Windows Store apps using C++.

 

The package manifest

Visual Studio creates a project file named Package.appxmanifest to record settings that affect how the app is deployed and how it runs. The file is known as the package manifest. Visual Studio lets you edit the package manifest using a visual tool that is called the Manifest Designer.

Unlike desktop and console apps, Windows Store apps must declare in advance the environment capabilities that they intend to use, such as accessing protected system resources or user data.

You must declare all capabilities in the package manifest before your app can use them. You can use the Capabilities tab in the Manifest Designer to do this. For example, the Hilo manifest includes the ability to access the user’s picture library.

Here's the source code of Hilo's package manifest.

Package.appxmanifest

<Package xmlns="https://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 run within a special operating system environment. This environment allows the app to only use features of the Windows Runtime that the package manifest has declared as requested capabilities. To achieve this, the system restricts the functions, data types, and devices that the app can use.

Note  The package manifest only restricts calls to the Windows Runtime API. If you use Win32 or the COM API, you'll need to run tools that make sure you're using the API subset that is allowed for apps on the Windows Store. Visual Studio helps you adhere to the correct API subset by filtering functions in the Object Browser.

 

For a walkthrough of Hilo's use of the manifest, see Testing and deploying the app in this guide. See App capability declarations (Windows Store apps) for more about setting capabilities and Manifest Designer to learn how to edit the package manifest in Visual Studio. For complete documentation of the manifest's XML schema, see Package Manifest.

C++ standard libraries

The Visual C++ compiler supports the C++11 standard that lets you use a modern coding style. Your Windows Store app can use the C run-time library (CRT) and the Standard Template Library (STL). There are a few restrictions, such as no access to console I/O. (See Porting existing C++ code on this page.)

Windows Runtime libraries

The Windows Runtime is a programming interface that you can use to create Windows Store apps. Windows Runtime supports the distinctive visual style and touch-based interaction model of Windows Store apps as well as access to network, disks, devices, and printing. The data types and functions in these libraries use the Windows and Platform namespaces. The Object Browser in Visual Studio lets you examine what types are available.

At the lowest level, the Windows Runtime consists of an application binary interface (ABI). The ABI is a binary contract that makes Windows Runtime APIs accessible to multiple programming languages such as JavaScript, the .NET languages, and Microsoft Visual C++. For more info about the Windows Runtime API, see Windows API reference for Windows Store apps.

The structure of a Windows Store app differs from that of a traditional desktop app using the Windows API (Win32). Instead of working with handle types such as HWND and functions such as CreateWindow, the Windows Runtime provides interfaces such as Windows::UI::Core::ICoreWindow so that you can develop Windows Store apps in a more modern, object-oriented way.

Note  Part of the richness of the Windows Runtime programming model comes from an extended type system and additional language capabilities. The environment provides properties, delegates, events, interfaces and attributes. See Type system (C++/CX) for an overview.

 

Win32 and COM API

Windows Store apps can use a subset of the Win32 and COM API. Although the Windows Runtime provides a rich set of functionality, you can continue to use a subset of Win32 and COM to support key scenarios for Windows Store apps that aren't already covered by the Windows Runtime. For example, IXMLHTTPRequest2, a COM interface, is the recommended way to connect to HTTP servers in a Windows Store app using C++. See Win32 and COM API for more info about using Win32 and COM in your Windows Store app. See Porting existing C++ code on this page for an overview of what's included.

Hilo doesn't call the Win32 API directly. However, it does use COM to work with image pixel data.

Parallel Patterns Library (PPL)

Your app should use the Parallel Pattern Library (PPL) for asynchronous and parallel programming. You use PPL tasks and agents of the Asynchronous Agents Library in places where you might otherwise have used a thread. See Adapting to async programming and Using parallel programming and background tasks on this page to see how Hilo uses PPL.

XAML

Windows Store apps using Extensible Application Markup Language, or XAML, use a declarative approach for defining the visual presentation of the app. The design and structure of the UX are encoded in XAML, an XML-based markup language. You can use a visual design tool, such as the Visual Studio designer or Microsoft Expression Blend, to define UI elements, or you can write XAML expressions yourself, or both. You use XAML data binding expressions to connect UI elements to data sources in your app.

XAML is the design language for frameworks such as Microsoft Silverlight and Windows Presentation Foundation (WPF). If you're familiar with using XAML with these frameworks or with Expression Blend, you'll be able to continue using your skills when you create Windows Store apps. For more info about XAML as it relates to Windows Store apps, see XAML overview.

Data binding is a convenient way for text boxes, buttons, data grids, and other UI elements to connect to app logic. The Windows Runtime invokes methods and properties of the data sources as needed to supply the UI with information as the app runs. Data binding is also used in cases where the data being passed is a command, such as a request to perform an operation like opening a file. In this case, data binding decouples UI elements that invoke commands from the code that executes the command action. Nearly every object and property of the framework can be bound in XAML.

Visual Studio project templates

We recommend that you use the built-in Visual Studio project templates because they define the compiler settings needed to run a Windows Store app. The project templates also provide code that implements basic functionality, saving you time. Visual Studio puts some files in a solution folder named Common, which you shouldn’t modify. Visual Studio also creates an App.xaml.cpp file that you can customize with app-specific logic. This file includes the main entry point to the app, the App::InitializeComponent method. Although the Visual Studio templates save time, they aren’t required. You can also modify existing static and dynamic libraries for use in your app. For more info, see Porting existing C++ code on this page and Building apps and libraries (C++/CX).

For more about Visual Studio’s project templates, see Templates to speed up your app development (Windows Store apps using C#/VB/C++ and XAML).

C++ language extensions for interop

The new user interface framework for Windows Store apps uses XAML to interact with native C++. The interop between C++ and XAML, or other languages such as JavaScript and C#, is direct, without calls to an intermediary translation layer. Interop uses a convention for the binary format of objects and function calls that is similar to COM-based programming. The programming environment for Windows Store apps includes C++/CX, which provides C++ language extensions that make interop easy to code. The extensions are only intended to be used with code that deals with interop. The rest of your app should use standards-compliant C++. For more info see Tips for using C++/CX as an interop layer on this page.

Note  You don't have to use the C++/CX for interop. You can also achieve interop with WRL.

 

The C++ compiler and linker

The /ZW compiler option enables C++ source files to use features of the Windows Runtime. It also enables the __cplusplus_winrt preprocessor directive that you'll see in some system header files. Your code gets declarations of Windows Runtime types from metadata (.winmd) files instead of from header files. You reference .winmd files with the #using directive, or the /FU compiler option. Visual Studio configures these options for you if you use one of the C++ project templates for Windows Store apps.

When you link the app, you need to provide two linker options: /WINMD and /APPCONTAINER. Visual Studio configures these options for you if you use one of the C++ project templates for Windows Store apps.

Certification process of the Windows Store

Publishing to the Windows Store makes your app available for purchase or free download. Publishing to the store is optional. Apps in the store must undergo a certification process that includes an automated structural check. For a description of Hilo's certification experience, see Testing and deploying the app in this guide.

Deployment

Windows Store apps use a simpler installation model than desktop apps. In a Windows Store app, all libraries and resources, other than those provided by the system, are located in the app’s installation directory or subdirectories. The installation process for a Windows Store app cannot write to the system registry or define environment variables. In addition, the user can limit the capabilities of the app they're installing. The package manifest lists all the files that are deployed. For more information, see The package manifest.

[Top]

Using C++11, standard C++ libraries, and a modern coding style

When possible, use C++11 and the standard C++ libraries (for example, the STL and CRT) for your core app logic and the C++/CX syntax only at the boundary where you interact with the Windows Runtime. For example, it's best if you use these techniques:

  • Lambda expressions
  • Stack semantics, smart pointers, and RAII
  • Automatic type deduction
  • Range-based for loops
  • Standard algorithms and containers
  • Free-form iterators
  • The pimpl idiom
  • Exception handling

Lambda expressions

Use lambda expressions to define the work that's performed by PPL tasks and by STL algorithms. For more info, see Async programming patterns for Windows Store apps using C++ and XAML in this guide.

Stack semantics, smart pointers, and RAII

Use stack semantics, smart pointers, and Resource Acquisition is Initialization (RAII) to automatically control object lifetime and ensure that resources are freed when the current function returns or throws an exception. For more info, see Tips for managing memory on this page.

Automatic type deduction

Use automatic type deduction to make code easier to read and faster to write. The auto and decltype keywords direct the compiler to deduce the type of a declared variable from the type of the specified expression. For example, you can use auto when you work with STL iterators, whose names can be tedious to type and don't add clarity to your code. Hilo makes extensive use of auto when it uses the concurrency::create_task and std::make_shared functions to reduce the need to declare the template type parameter.

ImageBase.cpp

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

ThumbnailGenerator.cpp

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

Note  Use the auto keyword when readers of your code can understand the type from the context, or when you want to abstract the type. The motivation is readability.

 

Range-based for loops

Use range-based for loops to work with collections of data. Range-based for loops have a more succinct syntax than for loops and the std::for_each algorithm because they don't require you to use iterators or capture clauses. Here's an example:

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

For more info, see Algorithms (Modern C++).

Standard algorithms and containers

Use standard algorithms and containers, such as std::vector, std::for_each, std::find_if, and std::transform to take advantage of C++ features. Because Windows RT types are language-neutral, Hilo uses types such as std::wstring and std::wstringstream to work with strings internally and Platform::String only when it interacts with the Windows Runtime. In this example, the returned Platform::String is passed to the Windows Runtime.

CalendarExtensions.cpp

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

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

dateRange << "..";

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

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

By using standard functionality, you can write code that's more portable and takes advantage of modern C++ features such as move semantics. (For more info about move semantics, see Rvalue Reference Declarator: &&.)

The same pattern applies to standard containers, such as std::vector. Using standard containers enables you to take advantage of C++ features such as move semantics and the ability to work with memory more directly. For example, you might perform internal processing on a std::vector object and then need to pass a Windows::Foundation::Collections::IVector object to the Windows Runtime. The Platform::Collections::Vector class, which is the C++ implementation of IVector, has an overloaded constructor that takes an rvalue reference to a std::vector. To call the overloaded constructor, use the std::move function or directly pass the result of a function that returns std::vector. For an example that shows this pattern, see Collections (C++/CX).

Note  You don't have to use move semantics. The Platform::Collections::Vector class includes a standard copy constructor that takes a std::vector object as its argument. In other words, you can keep your old vector data and create a new Platform::Collections::Vector instance with a copy of the original data.

 

As another example, Hilo uses std::iota and std::random_shuffle to choose random photos (more precisely, indices to an array of photos). This example uses std::iota to create a sequence of array indices and std::random_shuffle to randomly rearrange the sequence.

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

For more info about standard algorithms, see Algorithms (Modern C++)

Free-form iterators

Use the std::begin and std::end functions to work with ranges. Use these functions, instead of member functions such as .begin() and .end(), to write more flexible code. For example, you can write a generic algorithm that works with both STL types such as std::vector, std::array, and std::list, which provide begin and ends member functions, and also Windows Runtime collection types such as IVector, which use a different technique to iterate over values. The std::begin and std::end functions can be overloaded to accommodate different programming styles and also enable you to add iteration to data structures that you cannot alter.

The pimpl idiom

Use the pimpl idiom to hide implementation, minimize coupling, and separate interfaces. Pimpl (short for "pointer to implementation") involves adding implementation details to a .cpp file and a smart pointer to that implementation in the private section of the class declaration in the .h file. Doing so can also shorten compile times. Pimpl, when used together with copy construction and move semantics, also enables you to pass objects by value, which helps eliminate the need to worry about object lifetimes.

You can also take advantage of the pimpl idiom when you use predefined libraries. Pimpl is often related to rvalue references. For example, when you create task-based continuations, you pass concurrency::task objects by value and not by reference.

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

Passing by value guarantees that the object is valid and eliminates the need to worry about object lifetime. Additionally, passing by value isn't expensive in this case because the task class defines a pointer to the actual implementation as its only data member and copies or transfers that pointer in the copy and move constructors.

For more info, see Pimpl For Compile-Time Encapsulation (Modern C++).

Exception handling

Use exception handling to deal with errors. Exception handling has two main advantages over logging or error codes (such as HRESULT values):

  • It can make code easier to read and maintain.
  • It's a more efficient way to propagate an error to a function that can handle that error. The use of error codes typically requires each function to explicitly propagate errors.
  • It can make your app more robust because you can't ignore an exception as you might an HRESULT or GetLastError status.

You can also configure the Visual Studio debugger to break when an exception occurs so that you can stop immediately at the location and context of the error. The Windows Runtime also uses exception handling extensively. Therefore, by using exception handling in your code, you can combine all error handling into one model.

Important  Catch only the exceptions that you can safely handle and recover from. Otherwise, don't catch the exception and allow the app to terminate. Don't use catch(...) {...} unless you rethrow the exception from the catch block.

 

Here's an example from Hilo that shows the modern C++ coding style C++11 and standard libraries that copy StorageFile objects from a std::vector object to a Vector object so that the collection can be passed to the Windows Runtime. This example uses a lambda expression, automatic type deduction, std::begin and std::end iterators, and a range-based for loop.

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

For more info about modern C++ programming, see Welcome Back to C++ (Modern C++).

[Top]

Adapting to async programming

With async programming you call a function that starts a long-running operation but returns immediately without waiting for the work to be finished. Async operations help improve the responsiveness of the user experience. You'll find many more asynchronous operations in the Windows Runtime than in earlier frameworks.

You can tell if a Windows Runtime function is asynchronous from its name. The names of asynchronous functions end in "Async" such as ReadTextAsync. Hilo also follows this naming convention.

The Windows Runtime provides its own data types for interacting with asynchronous operations. These data types are C++/CX interfaces that derive from the Windows::Foundation::IAsyncInfointerface.

Each programming language provides native support for asynchronous programming and uses IAsyncInfo-derived interfaces for interop. In the case of C++, PPL provides the native support for async programming. In general, you should wrap the Windows Runtime's async interface types using PPL tasks when you get them from a call to a Windows Runtime async method. The only time you should expose these interfaces from your own classes is to create a Windows Runtime component for cross-language interop. For example, you use IAsyncInfo-derived types in public methods and properties of public ref classes that you define.

After wrapping an async operation with a PPL task, you can create additional tasks that the system schedules for execution after the prior task completes. The successor tasks are called continuation tasks (or continuations). You create continuations with the task::then method. Here's an example from Hilo that uses async operations to read an image from a file.

Note  The declarations of PPL tasks are located in the ppltasks.h header file.

 

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

The expression m_photo->File returns a Windows::Storage::StorageFile^ reference. This object's OpenReadAsync method starts an asynchronous operation function to open the file that contains the requested image. The call to OpenReadAsync returns an object of type IAsyncOperation<IRandomAccessStreamWithContentType^>^.

The async invocation starts the operation. The call itself returns very quickly, without waiting for the file open operation to complete. The return value of the call to OpenReadAsync represents the running operation that was started. The return type is parameterized by the asynchronous operation’s result type, which in this case is IRandomAccessStreamWithContentType^.

Note   The ^ (hat) syntax indicates a Windows Runtime reference. If you're unfamiliar with it, see Classes and structures (C++/CX).

 

The Photo::QueryPhotoImageAsync method then calls the concurrency::create_task function to produce a new PPL task that becomes the value of the local variable imageStreamTask. The type of the imageStreamTask variable is task<IRandomAccessStreamWithContentType^>. The effect of passing a Windows Runtime asynchronous object to the concurrency::create_task function is to wrap the asynchronous operation with a PPL task. The newly created PPL task finishes when the OpenReadAsync operation completes its work and a random access stream is available.

Async operations of the Windows Runtime do not always require their own threads to run. Windows often manages them as overlapped I/O operations using internal data structures that have less overhead than threads. This detail is internal to the operating system.

After a Windows Runtime operation is wrapped with a PPL task, you can use the task::then method to create continuations that run after the previous, or antecedent, task completes. Continuations are themselves PPL tasks. They make asynchronous programs easier to read and debug.

The task::then method is asynchronous. It returns a new task very quickly. Unlike other async tasks, the task returned by the task::then method doesn't begin to run immediately. Instead, PPL delays the start of the task's execution until the result of the antecedent task becomes available. This means that although the task::get method is synchronous when applied to the antecedent task, the value is immediately available. Continuation tasks that aren't ready to run don't block any threads. Instead, they are managed by the PPL internally until the data they need becomes available.

In the QueryPhotoImageAsync method shown above, the argument to the then method is a lambda expression that's the work function of the newly created task. (A task’s work function is the code that gets invoked when the task runs.) The type of the lambda expression’s input parameter matches the type of the imageStreamTask. The types match because the imageStreamTask is passed as an argument to the continuation’s work function when the continuation begins to run. You can think of this as a kind of data flow programming. Continuation tasks begin to run when their inputs become available. When they finish running, they pass their results to the next continuation task in the chain.

Note  If you're unfamiliar with lambda expressions in C++, see Lambda Expressions in C++.

 

In Windows Store apps, continuations of tasks that wrap IAsyncInfo objects run by default in the thread context that created the continuation. In most cases, the default context will be the app’s main thread. This is appropriate for querying or modifying XAML controls, and you can override the default to handle other cases.

Note  In Hilo, we found it useful to clarify our understanding of the thread context for our subroutines by using assert(IsMainThread() and assert(IsBackgroundThread()) statements. In the Debug version of Hilo, these statements break into the debugger if the thread being used is other than the one declared in the assertion. The IsMainThread and IsBackgroundThread functions are in the Hilo source.

 

This example didn’t need any special synchronization code, such as a lock or a critical section, for the update to the m_image member variable. This is because all interactions with view model objects occur on the main thread. Using a single thread automatically serializes any potentially conflicting updates. In UI programming, it’s a good idea to use continuations that run in a known thread context instead of other kinds of synchronization.

This code is an example of a continuation chain or a .then ladder (pronounced dot-then ladder). This pattern appears frequently in apps that use async APIs from the Windows Runtime. See Asynchronous programming in C++ (Windows Store apps) for more info about continuation chains. For tips and guidance about how to use them, along with more examples, see Async programming patterns for Windows Store apps using C++ and XAML in this guide.

[Top]

Using parallel programming and background tasks

The goal of asynchronous programming is interactive responsiveness. For example, in Hilo we use asynchronous techniques to make sure that the app’s main thread remains unblocked and ready to respond to new requests without delay. However, depending on your app’s functional requirements, you might need to ensure the responsiveness of the user experience and the overall throughput of the compute-intensive parts of your app.

C++ is particularly well-suited for apps that need to have both a responsive user interface and high performance for compute-intensive tasks. The concurrency features of Visual C++ can meet the needs of both sets of requirements.

You can increase the throughput of the compute-intensive areas of your app by breaking some tasks into smaller pieces that are performed concurrently by multiple cores of your CPU or by the specialized data-parallel hardware of your computer’s graphics processor unit (GPU). These techniques and others are the focus of parallel programming. We use parallel programming techniques in several areas of Hilo.

For example, one of Hilo's features is a cartoon effect that you can use to stylize an image using simpler colors and shape outlines. Here's what an image looks like before and after applying this effect.

The cartoon effect is computationally intensive. Hilo's implementation uses C++ AMP to calculate the result quickly on the GPU. On systems that don't have a compute-class GPU available, Hilo uses the PPL, which is part of the Concurrency Runtime. Here's how we use the accelerator::is_emulated property to determine whether to use the C++ AMP or the PPL algorithm to perform the cartoon effect:

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

Tip  We chose to run the algorithm that uses the PPL when the required hardware is not available because we already had the code available that leverages all CPU cores. However, if you do not have fallback code available, you can still use the WARP or reference device to run your C++ AMP code. Profile your C++ AMP code on multiple configurations to help you determine if you need to consider a similar fallback method.

 

See the CartoonEffect project in the Hilo source files for details on how we implemented these algorithms.

For more info about C++ AMP, see C++ AMP (C++ Accelerated Massive Parallelism).

When you apply parallel programming techniques you need to think in terms of foreground processing (on the main thread) and background processing (on worker threads). PPL tasks help you control which parts of the app run in the main thread and which parts run in the background. Operations that read or modify XAML controls are always invoked on the app’s main thread. However, for compute-intensive or I/O-intensive operations that don’t modify the user interface, you can take advantage of the computer’s parallel processing hardware by using PPL tasks that run on threads from the system’s thread pool. You can see the foreground/background pattern in the Crop action. Here's the 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));
}

The code shows Hilo's image cropping operation. The UI for the crop operation shows crop handles for the user to manipulate. After specifying the crop region, the user taps the image to generate a preview of the cropped image. The cropping operation's Grid control fires a Tapped event whose code-behind handler invokes the CropImageAsync method in the example.

Because an event handler invokes it, the CropImageAsync method runs on the main thread. Internally, it divides its work between the main thread and a background thread in the thread pool. To do this, it uses the concurrency::create_task function to schedule the DoCrop method in a background thread.

Note  While the DoCrop method is running in the background, the main thread is free to do other work in parallel. For example, while the DoCrop method runs, the main thread continues to animate the progress ring and respond to navigation requests from the user.

 

After the DoCrop method completes, a continuation task begins to run on the main thread to update the XAML controls.

Here's a diagram of the crop operation's use of threads.

The DoCrop method runs in a background thread, but it also uses PPL's parallel_for function to perform a compute-intensive operation using the computer's multicore hardware. Here's the 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
        }
    });        
}

The code is an example of how you can integrate parallel programming techniques into your app. Parallelizing only the outer loop maximizes the benefits of concurrency. If you parallelize the inner loop, you will not receive a gain in performance because the small amount of work that the inner loop performs does not overcome the overhead for parallel processing.

For a modern programming style and best performance, we recommend that you use PPL's parallel algorithms and data types for new code.

For more info, see Parallel Programming in C++.

[Top]

Tips for using C++/CX as an interop layer

Here are some tips for cross-language interop that we developed during the creation of Hilo. (For complete documentation of the language extensions that are available to a C++ Windows Store app for cross-language interop, see Visual C++ language reference (C++/CX).)

  • Be aware of overhead for type conversion.
  • Call methods of ref classes from the required thread.
  • Mark destructors of public ref classes as virtual.
  • Use ref classes only for interop.
  • Use techniques that minimize marshaling costs.
  • Use the Object Browser to understand your app's .winmd output.
  • If C++/CX doesn't meet your needs, consider WRL for low-level interop.
  • Don't confuse C++/CX language extensions with C++/CLI.
  • Don't try to expose internal types in public ref classes.

Be aware of overhead for type conversion

In order to interact with Windows Runtime features, you sometimes need to create data types from the Platform and Windows namespaces. You should create these types in the most efficient way.

For example, if you create a Windows::Foundation::Collections::Vector^ reference from a std::vector object, the Vector constructor may perform a copy. If you allocate a std::vector object and know that there are no other references to it, you can create a Vector object without copying by calling the std::move function on the std::vector before passing it to the Vector constructor. This works because the Vector class provides a move constructor that takes a std::vector<T>&& argument.

There are move constructors for the Platform::Collections classes Vector, VectorView, Map, and MapView. These classes have constructors that take rvalue references to std::vector and std::map types. Here is an example.

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

The code creates the m_months object without copying the monthBlock vector.

There are no move constructors for the Platform::Array and Platform::String class. Instead, you use the Platform::ArrayReference class and Platform::StringReference class.

In Hilo, we preferred to use the Vector and Map classes instead of the Platform::Array class because of their compatibility with std::vector and std::map. (We don't use std::array because you have to give its size at compile time.)

Call methods of ref classes from the required thread

Some ref classes require their methods, events and properties to be accessed from a specific thread. For example, you must interact with XAML classes from the main thread. If you create a new class that derives from an existing Windows Runtime class, you inherit the context requirements of the base class.

Be careful to respect the threading model of the objects you use.

Reference counts can be decremented as a side effect of operations that occur in any thread. For this reason, you should make sure that destructors for ref classes that you implement can be called from any thread. You must not invoke methods or properties in your destructor that require a specific thread context.

For example, some Windows Runtime classes require you to unregister event handlers in the main thread. Here is a code example of a thread-safe destructor that does this.

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

The run_async_non_interactive function is a utility function that is defined in Hilo. It dispatches a function object to the main thread, allowing UI interactions by the user to have higher priority.

Mark destructors of public ref classes as virtual

Destructors of public ref classes must be declared virtual.

Use ref classes only for interop

You only have to use ^ and ref new when you create Windows Runtime objects or create Windows Runtime components. You can use the standard C++ syntax when you write core application code that doesn't use the Windows Runtime.

Hilo uses ^ and std::shared_ptr to manage heap-allocated objects and minimize memory leaks. We recommend that you use ^ to manage the lifetime of Windows Runtime variables, ComPtr to manage the lifetime of COM variables (such as when you use DirectX), and std::shared_ptr or std::unique_ptr to manage the lifetime of all other heap-allocated C++ objects.

We recommend that you declare all ref classes as public because they're only intended for interop. If you have private, protected, or internal ref classes, it's an indication that you're attempting to use ref classes for implementation purposes and not interop across the Abstract Binary Interface (ABI).

Use techniques that minimize marshaling costs

C++/CX is designed for language interop. When you call functions across the ABI, you sometimes incur overhead due to the cost of marshaling (copying) data. Because the XAML UI framework is written in C++, you don't incur marshaling overhead when you interoperate with XAML from a C++ app. If you implement a component in C++ that is called from a language other than C++ or XAML, your app would incur some cost for marshaling.

See Threading and Marshaling (C++/CX) for ways to specify the threading and marshaling behavior of components that you create. For more info about marshaling overhead with other languages, see Keep your app fast when you use interop (Windows Store apps using C#/VB/C++ and XAML).

Use the Object Browser to understand your app's .winmd output

When you build your app, the compiler creates a .winmd file that contains metadata for all the public ref types that your app defines. Components such as XAML use the .winmd file to invoke methods of your app's data types across the ABI.

It's often helpful to examine what's in the generated .winmd file. To see this, you can use the Visual Studio Object Browser. From the Object Browser, navigate to the .winmd file in your project's Debug directory and open it. You'll be able to see all the types that your app exposes to XAML.

Note  Be aware of what public ref types you're including in your app's .winmd file.

 

If C++/CX doesn't meet your needs, consider WRL for low-level interop

The language extensions of C++/CX save time, but you don’t have to use them. You can get lower-level access to cross-language interop from standard C++ if you use the Windows Runtime C++ Template Library (WRL). WRL uses conventions that will be familiar to COM programmers.

WRL is a compiler-agnostic way to create and consume Windows Runtime APIs. You can use the WRL instead of the C++/CX syntax. It enables you to optimize your code for performance or for specific scenarios. It also supports app development methodologies that don't use exceptions. For more info, see Windows Runtime C++ Template Library.

In Hilo, we found that C++/CX had the features and performance we needed. We only used the WRL to access a COM interface that enabled us to read pixel data from an image. In general, WRL is a good candidate when you have a COM object that you want to port to be a Windows Runtime object, since WRL roots are in ATL.

Don't confuse C++/CX language extensions with C++/CLI

The syntax of C++/CX language extensions and C++/CLI are similar, but there are two very different execution models. Here's how to think about this.

In order to call Windows Runtime APIs from JavaScript and .NET, those languages require projections that are specific to each language environment. When you call a Windows Runtime API from JavaScript or .NET, you're invoking the projection, which in turn calls the underlying ABI function. Although you can call the ABI functions directly from standard C++, Microsoft provides projections for C++ as well, because they make it much simpler to consume the Windows Runtime APIs, while still maintaining high performance.

Microsoft also provides language extensions to Visual C++ that specifically support the Windows Runtime projections. Many of these language extensions resemble the syntax for the C++/CLI language. However, instead of targeting the common language runtime (CLR), C++ apps use this syntax to generate native code that's compatible with the binary format requirements of the ABI.

Because there's no runtime to manage memory, the system deletes C++/CX objects based on reference counting, in a manner that's similar to std::shared_ptr. The handle-to-object operator, or hat (^), is an important part of the new syntax because it enables reference counting. Instead of directly calling methods such as AddRef and Release to manage the lifetime of a Windows Runtime object, the runtime does this for you. It deletes the object when no other component references it, for example, when it leaves scope or you set all references to nullptr.

Another important part of using Visual C++ to create Windows Store apps is the ref new keyword. Use ref new instead of new to create reference-counted Windows Runtime objects. For more info, see Type System (C++/CX).

Don't try to expose internal types in public ref classes

Public ref classes produce metadata that is exposed in the app's .winmd file. The metadata describes each of the types and the members of each type. In order for the metadata to be complete, every member of a public type must itself be a publicly visible type. As a result, there's a requirement that you can't expose internal types as public members of a public ref class.

The requirement of metadata can affect the design of your app. In general, try to partition your types so that your public ref types are used exclusively for interop and not for other purposes in your app. If you're not careful, it's possible that you'll see an unintended proliferation of public ref classes in your app.

[Top]

Tips for managing memory

Because you don't typically close Windows Store apps and because Windows Store apps run on tablets of varying hardware capabilities, it's important to be aware of the amount of memory your app uses. It's especially important to prevent memory leaks by not allowing objects to remain in memory that are not accessible by code. Also consider the lifetime of every object to ensure you don't keep it in memory longer than it needs to be. For efficient memory management, we recommend that you:

  • Use smart pointers.
  • Use stack semantics and the RAII pattern.
  • Don't keep objects around longer than you need.
  • Avoid circular references.

Use smart pointers

Use smart pointers to help ensure that programs are free of memory and resource leaks and are exception-safe. In Windows Store apps, use handle-to-object, ^ (pronounced "hat"), to manage the lifetime of Windows Runtime variables, Microsoft::WRL::ComPtr to manage the lifetime of COM variables, (such as when you use DirectX), and std::shared_ptr or std::unique_ptr to manage the lifetime of all other heap-allocated C++ objects.

It's easy to remember to use ^ because when you call ref new, the compiler generates code to allocate a Windows Runtime object and then returns a handle to that object. Windows Runtime methods that don't return value types also return handles. What's important to focus on is the use of standard pointer types and COM objects to eliminate memory and resource leaks.

One way that Hilo uses std::shared_ptr is to allow for one task in a continuation chain to write to a variable and another task to read from it. See Assembling the outputs of multiple continuations in this guide for more info.

In Hilo, we needed to directly access pixel data to crop images and to apply the cartoon effect to images. To do so, we needed to convert an IBuffer object to its underlying COM interface, IBufferByteAccess. We cast the IBuffer object to the underlying IInspectable interface and called QueryInterface to retrieve a pointer to a supported interface on the object. We also used the ComPtr class to manage the pointers so that calls to AddRef and Release were not necessary.

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

Because we're working with COM, we defined the ThrowIfFailed function to more easily deal with HRESULT values. You can use this utility function in your code when you need to convert a failure code to a Windows Runtime exception.

ImageUtilities.cpp

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

For more about smart pointers, see Smart Pointers (Modern C++).

Use stack semantics and the RAII pattern

Stack semantics and the RAII pattern are closely related.

Use stack semantics to automatically control object lifetime and help minimize unnecessary heap allocations. Also use stack semantics to define member variables in your classes and other data structures to automatically free resources when the parent object is freed.

Use the resource acquisition is initialization (RAII) pattern to ensure that resources are freed when the current function returns or throws an exception. Under the RAII pattern, a data structure is allocated on the stack. That data structure initializes or acquires a resource when it's created and destroys or releases that resource when the data structure is destroyed. The RAII pattern guarantees that the destructor is called before the enclosing scope exits. This pattern is useful when a function contains multiple return statements. This pattern also helps you write exception-safe code. When a throw statement causes the stack to unwind, the destructor for the RAII object is called; therefore, the resource is always correctly deleted or released. Using the std::shared_ptr template class for stack-allocated variables is an example of the RAII pattern.

For more info about managing object lifetime, see Object Lifetime And Resource Management (Modern C++). For more info about RAII, see Objects Own Resources (RAII).

Don't keep objects around longer than you need

Use stack semantics or set references to nullptr to help ensure that objects are freed when they have no more references.

In Hilo, we preferred the use of stack semantics over member variables for two reasons. First, stack semantics helps ensure that memory is only used in the context of where it's needed. Because Hilo is a photo app, we found it particularly important to free image data as soon as possible to keep memory consumption to a minimum. The same applies to other kinds of apps. For example, in a blog reader, you might want to release network connections when they're no longer needed to allow other apps to use those resources.

Second, because much of the app depends on asynchronous actions, we wanted to restrict variable access to the code that needs it to help make the app concurrency-safe. In traditional multithreading programming that doesn't use lambda expressions, you often need to store state as member variables. Here's an example where we used local variables and capture semantics instead of member variables to asynchronously create a thumbnail from an image on disk.

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

If your app requires the use of a member variable, you should set that variable to nullptr when you no longer need it.

Avoid circular references

Objects involved in a circular reference can never reach a reference count of zero and are never destroyed. Avoid circular references in your code by replacing one of the references with a weak reference.

For example, Hilo defines the IPhoto and IPhotoGroup interfaces. IPhoto encapsulates info about a photo (its file path, image type, date taken, and so on). IPhotoGroup manages collections of photos (for example, all the photos in a given month). These interfaces have a parent-child relationship— you can get an individual photo from a photo group or the parent photo group from a photo. If we were to use standard, or strong, references, a photo and its group will always hold one reference to the other, and the memory for neither is ever freed, even after all other references are released.

Instead, Hilo uses weak references to avoid circular references like this. A weak reference enables one object to reference another without affecting its reference count.

The Windows Runtime provides the WeakReference class. Its Resolve method returns the object if it is valid; otherwise it returns nullptr. Here is how the Photo class, which derives from IPhoto, declares a weak reference to its parent group:

Photo.h

Platform::WeakReference m_weakPhotoGroup;

Here's how the Photo class resolves the weak reference in the Group property:

Photo.cpp

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

Hilo also uses std::weak_ptr to break circular references. Use WeakReference when you must expose a weak reference across the ABI boundary. Use std::weak_ptr in internal code that doesn't interact with the Windows Runtime.

Capturing an object's this handle in a lambda expression can also cause a circular reference if you use the lambda as an event handler or pass it to a task that the object references.

Note  Within member functions of a ref class, the type of the symbol this is a const handle (^), not a pointer.

 

An object's event handlers are detached from the source when that object is destroyed. However, a lambda expression that captures this increments that object's reference count. If an object creates a lambda expression that captures this and uses that lambda as the handler of an event that is provided by itself or an object it directly or indirectly references, the object's reference count can never reach zero. In this case, the object is never destroyed.

Hilo prevents the circular reference by using member functions instead of lambda expressions to work with events. Here's an example from the HiloPage class.

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 this example, NavigateEventHandler is a delegate type defined by Hilo. A delegate is a ref class that's the Windows Runtime equivalent of a function object in standard C++. When you instantiate a delegate using an object handle and a pointer to a member function, the object's reference count is not incremented. If you invoke the delegate instance after the target object has been destroyed, a Platform::DisconnectedException will be raised.

In addition to the two-argument constructor that is shown in this example, delegate types also have an overloaded constructor that accepts a single argument. The argument is a function object such as a lambda expression. If this example captured the object's this handle in a lambda expression and passed the lambda as the delegate's constructor argument, then the object's reference count would be incremented. The reference count would be decremented only when there were no more references to the lambda expression.

If you use lambdas as event handlers, you have two ways to manage memory. You can capture weak references instead of object handles. Or, you can be careful to unsubscribe from events at the right time and break circular references before they cause problems with memory management.

In Hilo, we chose member functions for event callbacks because we felt that this was the easiest to code correctly.

For more info about weak references, see Weak references and breaking cycles (C++/CX) and How to: Create and Use weak_ptr Instances.

[Top]

Debugging tips and techniques

You can use many of the traditional tools and techniques for Windows Store apps that you use to debug desktop apps. Here are some that we used for Hilo:

  • Use breakpoints and tracepoints.
  • Use OutputDebugString for "printf" style debugging.
  • Break when exceptions are thrown.
  • Use the parallel debugging windows.
  • Use the simulator and remote debugging to debug specific hardware configurations.

Use breakpoints and tracepoints

A breakpoint tells the debugger that an application should break, or pause, execution at a certain point. A tracepoint is a breakpoint with a custom action associated with it. For Hilo, we used breakpoints extensively to examine app state when running PPL continuation tasks.

You can configure breakpoints to trigger when certain conditions are true. For Hilo, we had an interaction between XAML and the C++ code-behind that we needed to debug. However, the issue occurred only after the code had run at least 20 times. So we used the Hit Count setting to break after the breakpoint was hit at least 20 times.

For Hilo, we found tracepoints to be especially useful to diagnose from which thread certain PPL tasks were running. The default tracepoint message contained all the info we needed.

Function: $FUNCTION, Thread: $TID $TNAME

For more info, see Using Breakpoints and Tracepoints.

Tip  Use __debugbreak to programmatically set a breakpoint from code.

 

Use OutputDebugString for "printf" style debugging

Use OutputDebugString when you don't require breaking into the debugger. OutputDebugString also works with tools such as WinDbg and can be used with Release mode.

Caution  You can also use TextBlock and other XAML controls to perform "printf" style debugging. However, we preferred the use of OutputDebugString because using a control can affect the layout of pages in ways you may not expect after you remove them. If you use controls to debug your code, be sure to remove them and then test your code before you deploy your app.

 

Break when exceptions are thrown

When you break in a catch statement, you don't know what code threw the exception. From Visual Studio, choose Debug, Exceptions, and then choose Thrown to break when the exception is thrown. This technique is especially useful when using PPL tasks because you can examine the specific threading context under which the error occurred.

Important  It's important to catch every exception that is thrown by PPL tasks. If you don't observe an exception that was thrown by a PPL task, the runtime terminates the app. If your app terminates unexpectedly, enable this feature to help diagnose where the exception occurred.

 

Use the parallel debugging windows

Use the Parallel Tasks, Parallel Stacks, and Parallel Watch windows to understand and verify the runtime behavior of code that uses the PPL and other Concurrency Runtime features. For Hilo, we used these windows to understand the state of all running tasks at various points in the program. For more info, see Walkthrough: Debugging a Parallel Application and How to: Use the Parallel Watch Window.

Use the simulator and remote debugging to debug specific hardware configurations

For example, if your monitor isn't touch-enabled, you can use the simulator to emulate pinch, zoom, rotation, and other gestures. The simulator also enables you to simulate geolocation and work with different screen resolutions. For more info, see Running Windows Store apps in the simulator and Using the simulator and remote debugger to test devices in this guide.

Use remote debugging to debug your app on a computer that doesn't have Visual Studio. This is useful when you have a computer with a different hardware configuration than your computer that is running Visual Studio. For more info, see Running Windows Windows Store apps on a remote machine.

For more info about debugging, see Debugging Windows Windows Store apps and Debugging Native Code.

[Top]

Porting existing C++ code

If you have existing code in C++ that you want to port to your Windows Store app, the amount of work that you need to do depends on the code. Code that uses previous UI frameworks needs to be rewritten, while other types of code, such as business logic and numerical routines, can generally be ported without difficulty. If you are using code written by someone else, you might also check to see whether a version already exists for Windows Store apps. In Hilo, we adapted existing code that performs the cartoon effect and ported it for use as a static library (see the CartoonEffect project in the Hilo source files.) We did not introduce any Windows Runtime dependencies in the static library, which allows us to target previous versions of Windows and Windows 8 desktop apps.

Windows Store apps using C++ can reference existing static libraries, DLLs, and COM components, as long as all the existing code that your app calls meets the requirements for Windows Store apps. In Windows Store apps, all components, including static libraries and DLLs, must be local to the app and packaged in the manifest. All the components in the app’s package must adhere to the requirements for Windows Store apps.

Overview of the porting process

Here’s an outline of how to port existing C++ to make it usable in a Windows Store app. We used this process when porting the existing code that we brought into Hilo.

  • Compile and test the code on Windows 8.
  • Identify unsupported library functions.
  • Use functions from the Window Runtime API reference.
  • Replace synchronous library functions with async versions.
  • Convert long running operations in your code to async versions.
  • Validate the package with the Windows App Certification Kit.

Compile and test the code on Windows 8

The first step for porting is to compile and test your existing code on Windows 8. Code that compiles and runs on Windows 7 should require very few changes on Windows 8, but it’s a good idea to identify any platform dependencies before you port your code for use in a Windows Store app.

When writing Hilo, we reused the code that performs the cartoon effect image filtering from an earlier project. This code compiled and ran without change on the Windows 8 platform.

Note  Because porting can change the original code, it's a good idea to create unit tests for the original code before starting the port. You can use the Microsoft Visual StudioNative Testing framework to do this.

 

Identify unsupported library functions

After your code is running, you must make sure that your code only uses functions that are available to Windows Store apps. You can find the list of supported functions in the API reference for Windows Store apps. See Overview of supported functions on this page for an outline of what’s available.

You can also use the compiler preprocessor directive WINAPI_FAMILY=WINAPI_PARTITION_APP to help find incompatibilities. This directive causes the C++ compiler to issue errors when it encounters calls to unsupported functions.

Tip  Setting the WINAPI_FAMILY directive to WINAPI_PARTITION_APP during compilation removes the declarations of some Win32 functions and data types, but it doesn't affect the linking step. You can reintroduce some of the missing declarations in a temporary toremove.h header file and then work to eliminate references to those functions one at a time. A temporary header file is also a good way to work around the fact that the compiler stops after encountering approximately 100 errors.

 

Use functions from the Window Runtime API reference

In most cases, you can find a Windows Runtime function that performs the operation you want to do. For example, the Win32 function InitializeCriticalSectionAndSpinCount isn't available to Windows Store apps, but the Windows Runtime provides InitializeCriticalSectionEx instead. As you work through the problem areas, build your code with the new functions and incrementally test your app. See Alternatives to Windows APIs in Windows Store apps for suggestions of replacement functions to use.

In some cases, you can resolve incompatibilities by using functions from the C++ run-time library and STL. In general, use standard C++ whenever possible. Refactor core logic to use C++ libraries to operate on containers, buffers, and so on. There are many new features in C++ 11 libraries, such as threads, mutexes, and I/O. Be sure to look in the C++ libraries when checking for a replacement for a Win32 API in the C++ library.

Tip  Isolate platform-specific code into abstractions to make porting easier.

 

The remaining code, which formed the core of the cartoon effect image filter, didn’t make calls to the Win32 API. It just consisted of numerical functions that manipulated the pixels in the image. The only real porting issues for Hilo were in reconciling the various data formats for bitmaps. For example, when encoding the image prior to saving it, we needed to reorder the color channels to use the BGRA (blue/green/red/alpha) layout that is required by Windows Runtime functions.

Replace synchronous library functions with async versions

After your code compiles and runs using only data types and functions that are available to Windows Store apps, review your code for synchronous library functions that have asynchronous versions available. Refactor the code to consume aysnc operations if it doesn't already do so. Your app should use the async approach whenever possible.

Tip  Refactor code to deal with partial data. In cases where partial results are available incrementally, make these results available to the user without delay.

 

Tip  Keep the UI responsive during async operations.

 

In Hilo, we needed to convert the functions of the original cartoon effect image filter code that loaded the image to be processed to async versions.

Convert long running operations in your code to async versions

If your code has long running operations, consider making these operations asynchronous. An easy way to do this is to schedule long running work on the thread pool, for example, by using a PPL task. You can rewrite code or write a wrapper that runs existing code in thread pool.

In Hilo, we used a PPL continuation task that runs in the thread pool for the ported cartoon effect image filter.

Validate the package with the Windows App Certification Kit

After you have changed your code to use only the functions that are documented in the API reference for Windows Store apps, the final porting step is to create a package using app binaries and then to use the Windows App Certification Kit tool to check that the package is compliant with all the requirements of the Windows Store. The package contains all the app’s components, including static libraries and DLLs.

For a description of Hilo’s experience with certification, see Testing and deploying the app in this guide.

Overview of supported functions

Here’s a summary of what functions and data types a Windows Store app can use. For a complete list, see the API reference for Windows Store apps.

  • Porting from Win32-based UI.
  • Porting DirectX.
  • Porting MFC.
  • Using the C++ run-time library (CRT).
  • Using the C++ Standard Library.
  • Using ATL.

Porting from Win32-based UI

If you have existing code that uses UI functions and data types, such as HWND, from User, GDI, or MFC, you’ll need to reimplement that code using XAML.

Porting DirectX

DirectX is available in Windows Store apps. If you use DirectX 11, most code ports easily. Use a Visual Studio template for a DirectX project as a starting point. When you initialize a Windows Store app at run time, there is some DirectX setup required. The Visual Studio template sets up this startup code for you. Move your existing code into the new project.

Porting MFC

MFC is not available in Windows Store apps. To replace MFC UI classes, use XAML or DirectX. For dialog apps, you can use XAML data controls with data binding. To replace MFC utility classes such as containers, use STL and C run-time (CRT) data types.

Using the C++ run-time library (CRT)

A large subset of the CRT is available for Windows Store apps. There are a few functions that aren't available.

  • Multi-byte string functions. The mb* and _ismb* functions aren't available. Use Unicode strings instead.
  • Process control functions. The exec* functions aren't available. There is no way for you to spawn an application from within a Windows Store app.
  • Thread creation functions. The beginthread* and endthread* functions aren't available. Threading is available in Windows Store apps, but it's based on a thread pool. You can also use std::thread.
  • Heap and stack functions. The functions heapwalk, heapmin, resetstkoflw, and others aren't available. You cannot create your own heap in Windows Store apps.
  • Environment variable functions. The functions putenv, getenv, _enviorn, and related functions aren't available. There are no environment blocks in Windows Store apps.
  • Console functions. The functions cprintf, cscanf, and related functions aren't available. There's no console in Windows Store apps.
  • Port functions. The functions outp, inp, and other port functions aren't available.
  • Pipe functions. The functions popen, pclose, and other pipe functions aren't available.

Note  Use the compiler preprocessor directive WINAPI_FAMILY=WINAPI_PARTITION_APP to help find incompatibilities.

 

Some CRT functions that are available to Windows Store apps are blocking I/O functions. We recommend that you replace synchronous I/O functions, such as open, read, and write, with async equivalents from the Windows Runtime.

ANSI string functions of the CRT are available in Windows Store apps, but we recommend that you use Unicode strings.

Using the C++ Standard Library

Nearly all functions and data types of the C++ Standard Library are available in Windows Store apps (console I/O is not available.) We use the C++ Standard Library extensively in Hilo.

Using ATL

A subset of the ATL library is available in Windows Store apps. Here are the available data types.

  • DLL server.
  • COM objects. You can create your own COM objects. However, IDispatch isn't supported.
  • CStringW. Only wide strings are supported.
  • ATL container classes. MFC containers such as map that were ported to ATL are still available.
  • CCriticalSection, CEvent, CMutex, CSemaphore, CMutexLock. ATL synchronization objects are available.
  • CComVariant.
  • CComSafeArray.
  • CComBSTR.

If you have COM components that are related to business logic and not UI, you'll generally be able to port them.

Porting guidance

Here are some tips for porting existing C++ into a Windows Store app.

  • Port all existing code, including libraries.
  • Link to static libraries or import libraries as usual.
  • Use C++/CX or WRL if your library needs to invoke Windows Runtime functions.
  • Use reg-free COM for activation.
  • Convert to Windows Runtime types when marshaling cost is an issue.
  • Decide between using wrapper code and converting existing code.

Port all existing code, including libraries

Your app’s package must include all the binary components that your app needs. This includes static libraries and DLLs.

You can use libraries that are written in standard C++ in your Windows Store app. Windows Store apps use the same linking as desktop and console apps. If you have binary dependencies, it is important to validate your application package early in the development process so that all library functionality meets the requirements.

Note  Although the CartoonEffect library does not directly use any Windows Runtime features, we did need to specify the /ZW (Consume Windows Runtime Extension) compiler option because the PPL version of the cartoon effect algorithm uses special semantics in Windows Store apps (for example, in a Windows Store app, you can configure the context on which PPL task continuations run.)

 

Use C++/CX or WRL if your library needs to invoke Windows Runtime functions

If your library needs to invoke Windows Runtime functions, you can use either C++/CX or WRL. In general terms, C++/CX is easier to use. WRL gives you more fine-grained control.

Note  There are some technical issues with exposing public reference classes from within a user-written library. This is outside of the scope of this guide.

 

Use reg-free COM for activation

Your Windows Store app can use COM components that come from libraries. The library doesn't register COM classes. Instead, your app invokes COM activation using the new function CoCreateInstanceFromApp.

Convert to Windows Runtime types when marshaling cost is an issue

Use Windows Runtime types for objects that frequently cross the ABI boundary and are costly to convert.

You can use String and Array, which can be efficiently converted without copying, as input parameters to the Windows Runtime API. StringReference and ArrayReference add a Windows Runtime veneer using borrow semantics.

For containers and collection types, the conversion from std::* to Platform::* requires copying. In general, the containers and collections of the std namespace are more efficient than the containers and collections of the Platform namespace. When to use each of these depends on how often collection contents change compared to how often they cross the ABI.

In Hilo, we spent a lot of time deciding when to use public ref classes. For our application, we found that the overhead of type conversion outweighed other concerns.

Decide between using wrapper code and converting existing code

If you have existing code that needs to be called across the ABI, you must decide whether to port that code to C++/CX or leave it in standard C++ and wrap it with a C++/CX layer. In most situations we recommend using a wrapper layer.

Converting existing code and making it use Windows Runtime types and concepts is normally only done to avoid data conversion overhead, for example, when your component must be called very frequently across the ABI. These situations are rare.

If you decide to create a C++/CX wrapper for your classes, the standard way to do this is to define interfaces and in their implementation delegate to your existing C++ code, after any necessary type conversions. Using interfaces in this way creates a Windows Runtime veneer over your existing code and is sufficient for most cases.

If you have existing COM components, consider exposing them as Windows Runtime types. Doing this makes it easy for Windows Store apps to consume your COM objects. The techniques required to do this are outside the scope of this guide.

For more info about porting

A Channel 9 video is a helpful source of information about porting existing C++ for use in a Windows Store app. See Porting a desktop app to a Windows Store app for more information.

See Win32 and COM for Windows Store apps for the API subset of supported functions. If you can't find a suitable Win32 API function, see Alternatives to Windows APIs in Windows Store apps for suggestions of Windows Runtime functions that can be used instead.

[Top]