Windows Runtime via C# Chapter 1

Notes

  • There are some WinRT APIs that callable only from Desktop apps, not Windows Store apps. (page 3)
  • The system automatically terminates an app if the user upgrates the app to a new version or uninstall the app. To help make this experience seamless for the user, you (the app developer) must do some additional work in your code. (See chapter 3, "Process model.") (page 5)
  • Approve list of Win32 and COM APIs that a Windows Store app is allowed to use. (See http://msdn.microsoft.com/en-us/library/windows/apps/br205757 also see https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-and-com-for-uwp-apps) (page 7)
  • I find it very useful to call some unapproved Win32 and COM APIs during app developement. For example, i sometimes call the Win32 MessageBeep API during developement so that i can hear when a particular location in my code executes. Then, before i sumbit my app to the Windows Store for certification, I remove the calls to these APIs. (page 7)
  • Note that your cannot create WinRT components using JavaScript because there is no compiler capable of producing a WinMD file (discussed later in this chapter). (page 10)
C++/CX = C++ Component eXtensions

It is possible to invoke WinRT APIs without using C++/CX using the Windows Runtime C++ Template Library (WRL). For more information, see https://msdn.microsoft.com/en-us/library/hh438466.aspx.

Windows Runtime type-system

WinRT components do not share a common base class. When using a WinRT component from C#, the Common Language Runtime (CLR) makes the component look like it is derived from System.Object; therefore, you can pass it around throughout your code. In addition, all WinRT components inherit System.Object's public methods like ToString, GetHashCode, Equals, and GetType; so all these methods are callable on WinRT objects. Because WinRT components are implemented as extended COM object, internally the CLR uses Runtime Callbale Wrappers (RCWs) to access them. Invoking an RCW's members cause a managed-to-native code transition, which incurs some performance overhead.

Windows Runtime type-system projections
  • CLR projections
  • Framework projections are required when the impedance mismatch between the WinRT type system and the CLR's type system is too greate for the CLR to do it implicity. Framework projections are used for asynchronous programing (discussed later in this chapter) and when working with steams and data buffers (discussed in Chapter 6, "Stream input and output").
Calling asynchronous WinRT APIs from .NET code

    public static void WinRTAsyncIntro()
    {
        IAsyncOperation asyncOperation = KnownFolders.MusicLibrary.GetFileAsync("demo.mp3");
        asyncOperation.Completed = OnOperationCompleted;
    }

    // NOTE: Callback method executes via GUI or thread pool thread:
    private static void OnOperationCompleted(IAsyncOperation asyncInfo, AsyncStatus asyncStatus)
    {
        if (asyncStatus == AsyncStatus.Canceled) 
        {
            // Process cancellation
        }
        else
        {
            try
            {
                // Throws if operation failed
                StorageFile file = asyncInfo.GetResults(); 
                // Process result (do something with file)...
            }
            catch (Exception ex)
            {
                // Process exception
            }
        }

        asyncInfo.Close();
    }

1 The IAsyncInfo interface offer a Status property that contains the same value that is passed into the callback method's status parameter. Because the parameter is passed by value, your application's performance is better if you access the parameter rather than querying IAsyncInfo's Status property. This is because querying the property invokes a WinRT API via an RCW.

Simplifying the calling of asynchronous methods

    public static async void WinRTAsyncIntro()
    {
        try
        {
            StorageFile file = await KnownFolders.MusicLibrary.GetFileAsync("demo.mp3");
            // TODO: Completed code
        }
        catch (Exception ex)
        {
            // Error code
        }
    }

What's happening here is that the use of C#'s await operator causes the complier to look for a GetAwaiter method on the IAsyncOperation interface returned from the GetFileAsync method. This interface doesn't provide a GetAwaiter method, so the complier looks for an extensions method. Fortunately, the .NET Framework team has provided a bunch of extension methods that are callable when you have one of WinRT's IAsyncXxx interfaces:


namespace System
{
    //
    // Summary:
    //     Provides extension methods for converting between tasks and Windows Runtime asynchronous
    //     actions and operations.
    public static class WindowsRuntimeSystemExtensions
    {
        public static TaskAwaiter GetAwaiter(this IAsyncOperation source);

        public static TaskAwaiter GetAwaiter(this IAsyncAction source);

        public static TaskAwaiter GetAwaiter(this IAsyncActionWithProgress source);

        public static TaskAwaiter GetAwaiter(this IAsyncOperationWithProgress source);
    }
}
Cancallation and progress

What I've just shown is the common scenario of calling an asynchronous WinRT API and discovering its outcome. However, the preceding code ignored cancellation and progress updates. To properly handle cancallation and progress updates, instead of having the complier implicity call one of the GetAwaiter extension methods show earlier, you instead explicitly call one of the AsTask extension method that the WindowsRuntimeSystemExtensions class also defines:


namespace System
{
    //
    // Summary:
    //     Provides extension methods for converting between tasks and Windows Runtime asynchronous
    //     actions and operations.
    public static class WindowsRuntimeSystemExtensions
    {
        public static Task AsTask(this IAsyncOperationWithProgress source, CancellationToken cancellationToken, IProgress progress);

        public static Task AsTask(this IAsyncActionWithProgress source, CancellationToken cancellationToken, IProgress progress);

        // Simpler overloads not shown here
    }
}

Here is an example of the proper way to call a WinRT asynchronous method and then block the thread until the operation completes:


StorageFile file = KnownFolders.MusicLibrary.GetFileAsync("demo.mp3").AsTask().GetAwaiter().GetResult();

Although you could write the code like this, you should not:


StorageFile file = KnownFolders.MusicLibrary.GetFileAsync("demo.mp3").AsTask().Result;

The reason is that querying a Task's Result property throwns an AggregateException if the operation fails instead of throwing the correct exception.

Be aware that blocking a GUI thread by calling GetAwaiter().GetResult() could potentially deadlock the thead, forcing the user or operating system to terminate your app. So you should really avoid blocking a thread issuing asynchronous opertaions where possbile.

WinRT deferrals

    private async void OnSuspending(object sender, SuspendingEventArgs e)
    {
        var deferral = e.SuspendingOperation.GetDeferral();
        //TODO: Save application state and stop any background activity

        // Thread return but app is NOT suspended
        var result = await XxxAsync();

        deferral.Complete();
    }

Remember that the deferral variable refers to a Runtime Callable Wrapper that internally refers to the WinRT component. So, if your code does not call the Complete method, the garbage collector will eventually run and clean up the object, which effectively calls Complete for you. Although, when suspending, the garbage collector (GC) cannot if your app is terminated.


    private void OnSuspending(object sender, SuspendingEventArgs e)
    {
        // Thread return but app is NOT suspended
        var result = XxxAsync().GetAwaiter().GetResult();
    } // App is suspended

This code is simpler, and you have to ask yourself, what is the harm? This code does block the GUI thread, which means the UI could become unresponsive to the user. This would be bad in general; but, in this case. the user is not interacting with the app, which is why it is being suspended in the first place. In addition, the first version of this method used an async method, which makes the code bigger and can decrease app performance. This version does not use an async method and, in the case of suspending, your app has just a few seconds to complete its operation or Windows forcibly terminates your app. Therefore, making your code faster here could make a big difference. Background tasks have a similar time limit when they execute too, so you want to make background task code fast as well.

In addition, many defferal classes are used with operations that do not execute on GUI threads; therefore, app UI responsivness is not an issus. For example, background tasks never execute on GUI threads, so there is partically no reason to use the BackgroundTaskDeferral class 2

2 In fact, WinRT has a design flaw with background tasks, if you use Background task code throw an exception after an await, your app will not be able to determine that the background task failed.

ZhuiMeng

The dark night gave me black eyes I use them to look for light

Subscribe to ZhuiMeng's Corner

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!