Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Framework Projections

When the CLR can’t implicitly project a WinRT type to the .NET Framework developer, the devel- oper must resort to explicitly using framework projections. There are three main technologies where framework projections are required: asynchronous programming, interoperating between WinRT streams and .NET Framework streams, and when passing blocks of data between the CLR and WinRT APIs. These three framework projections are discussed in the following three sections of this chapter. Because many applications require the use of these technologies, it is important that you understand them well and use them effectively.

 

Calling Asynchronous WinRT APIs from .NET Code

When a thread performs an I/O operation synchronously, the thread can block for an indefinite amount of time. When a GUI thread waits for a synchronous I/O operation to complete, the applica- tion’s user interface stops responding to user input, such as touch, mouse, and stylus events, caus- ing the user to get frustrated with the application. To prevent non-responsive applications, WinRT components that perform I/O operations expose the functionality via asynchronous APIs. In fact, WinRT components that perform compute operations also expose this functionality via asynchro- nous APIs if the CPU operation could take greater than 50 milliseconds. For more information about building responsive applications, see Part V, “Threading” of this book.

 

 
 

5 To learn even more, go to http://msdn.microsoft.com/en-us/library/windows/apps/hh995050.aspx and then download the CLRandtheWindowsRuntime.docx document.


Because so many WinRT APIs are asynchronous, being productive with them requires that you understand how to interoperate with them from C#. To understand it, examine the following code.

 

public void WinRTAsyncIntro() {

IAsyncOperation<StorageFile> asyncOp = KnownFolders.MusicLibrary.GetFileAsync("Song.mp3"); asyncOp.Completed = OpCompleted;

// Optional: call asyncOp.Cancel() sometime later

}

 

// NOTE: Callback method executes via GUI or thread pool thread:

private void OpCompleted(IAsyncOperation<StorageFile> asyncOp, AsyncStatus status) { switch (status) {

case AsyncStatus.Completed: // Process result

StorageFile file = asyncOp.GetResults(); /* Completed... */ break;

 

case AsyncStatus.Canceled: // Process cancellation

/* Canceled... */ break;

 

case AsyncStatus.Error: // Process exception

Exception exception = asyncOp.ErrorCode; /* Error... */ break;

}

asyncOp.Close();

}

 

The WinRTAsyncIntro method invokes the WinRT GetFileAsync method to find a file in the user’s music library. All WinRT APIs that perform asynchronous operations are named with the Async suffix and they all return an object whose type implements a WinRT IAsyncXxx interface; in this ex- ample, an IAsyncOperation<TResult> interface where TResult is the WinRT StorageFile type. This object, whose reference I put in an asyncOp variable, represents the pending asynchronous op- eration. Your code must somehow receive notification when the pending operation completes. To do this, you must implement a callback method (OpCompleted in my example), create a delegate to it, and assign the delegate to the asyncOp’s Completed property. Now, when the operation completes, the callback method is invoked via some thread (not necessarily the GUI thread). If the operation completed before assigning the delegate to the OnCompleted property, then the system will invoke the callback as soon as possible. In other words, there is a race condition here, but the object imple- menting the IAsyncXxx interface resolves the race for you, ensuring that your code works correctly.



As noted at the end of the WinRTAsyncIntro method, you can optionally call a Cancel method offered by all IAsyncXxx interfaces if you want to cancel the pending operation. All asynchronous operations complete for one of three possible reasons: the operation runs to completion success- fully, the operation is explicitly canceled, or the operation results in a failure. When the operation completes due to any of these reasons, the system invokes the callback method, passes it a refer- ence to the same object that the original XxxAsync method returned, and an AsyncStatus. In

my OnCompleted method, I examine the status parameter and either process the result due to the successful completion, handle the explicit cancellation, or handle the failure.6 Also, note that after

 

 
 

6 The IAsyncInfo interface offers 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 as opposed to querying IAsyncInfo’s Status property because querying the property invokes a WinRT API via an RCW.


processing the operation’s completion, the IAsyncXxx interface object should be cleaned up by calling its Close method.

Figure 25-2 shows the various WinRT IAsyncXxx interfaces. The four main interfaces all derive from the IAsyncInfo interface. The two IAsyncAction interfaces give you a way to know when the operation has completed, but their operations complete with no return value (their GetResults methods have a void return type). The two IAsyncOperation interfaces also give you a way to know when the operation has completed and allow you to get their return value (their GetResults methods have a generic TResult return type).

The two IAsyncXxxWithProgress interfaces allow your code to receive periodic progress up- dates as the asynchronous operation is progressing through its work. Most asynchronous operations do not offer progress updates but some (like background downloading and uploading) do. To receive periodic progress updates, you would define another callback method in your code, create a del- egate that refers to it, and assign the delegate to the IAsyncXxxWithProgress object’s Progress property. When your callback method is invoked, it is passed an argument whose type matches the generic TProgress type.

 

 

 
 

IAsyncOperation<TResult>

AsyncStatus Started Completed, Canceled, Error
Completed (delegate) GetResults (TResult)

 

  IAsyncOperationWithProgress<TResult, TProgress>
  Completed GetResults Progress (delegate) (TResult) (delegate)

FIGURE 25-2WinRT’s interfaces related to performing asynchronous I/O and compute operations.

 

In the .NET Framework, we use the types in the System.Threading.Tasks namespace to simplify performing asynchronous operations. I explain these types and how to use them to perform compute operations in Chapter 27, “Compute-Bound Asynchronous Operations,” and how to use them to per- form I/O operations in Chapter 28, “I/O-Bound Asynchronous Operations.” In addition, C# offers the async and await keywords, which allow you to perform asynchronous operations by using a sequen- tial programming model, thereby simplifying your code substantially.


The following code is a rewrite of the WinRTAsyncIntro method previously mentioned. However, this version is leveraging some extension methods supplied with the .NET Framework, which turn the WinRT asynchronous programming model into the more convenient C# programming model.

 

using System; // Required for extension methods in WindowsRuntimeSystemExtensions

.

.

.

public async void WinRTAsyncIntro() { try {

StorageFile file = await KnownFolders.MusicLibrary.GetFileAsync("Song.mp3");

/* Completed... */

}

catch (OperationCanceledException) { /* Canceled... */ } catch (SomeOtherException ex) { /* Error... */ }

}

 

What’s happening here is that the use of C#’s await operator causes the compiler to look for a GetAwaiter method on the IAsyncOperation<StorageFile> interface returned from the

GetFileAsync method. This interface doesn’t provide a GetAwaiter method, and so, the compiler looks for an extension method. Fortunately, the .NET Framework team has provided in System.Run­ time.WindowsRuntime.dll a bunch of extension methods callable when you have one of WinRT’s IAsyncXxx interfaces.

 

namespace System {

public static class WindowsRuntimeSystemExtensions {

public static TaskAwaiter GetAwaiter(this IAsyncAction source);

public static TaskAwaiter GetAwaiter<TProgress>(this IAsyncActionWithProgress<TProgress> source);

public static TaskAwaiter<TResult> GetAwaiter<TResult>(this IAsyncOperation<TResult> source);

public static TaskAwaiter<TResult> GetAwaiter<TResult, TProgress>( this IAsyncOperationWithProgress<TResult, TProgress> source);

}

}

 

Internally, all these methods construct a TaskCompletionSource and tell the IAsyncXxx object to invoke a callback that sets the TaskCompletionSource’s final state when the asynchronous operation completes. The TaskAwaiter object returned from these extension methods is ultimately what C# awaits. When the asynchronous operation completes, the TaskAwaiter object ensures that the code continues executing via the SynchronizationContext (discussed in Chapter 28) that is associated with the original thread. Then, the thread executes the C# compiler generated code, which queries the TaskCompletionSource’s Task’s Result property, which returns the result (a Storage­ File in my example), throws an OperationCanceledException in case of cancellation, or throws some other exception if a failure occurred. For an example of how these methods work internally, see the code at the end of this section.


What I have just shown is the common scenario of calling an asynchronous WinRT API and discov- ering its outcome. But, in the previous code, I showed how to find out if cancellation occurred, but I didn’t show how to actually cancel the operation. In addition, I didn’t show how to deal with progress updates. To properly handle cancellation and progress updates, instead of having the compiler auto- matically call one of the GetAwaiter extension methods shown earlier, you would explicitly call one of the AsTask extension methods that the WindowsRuntimeSystemExtensions class also defines.

 

namespace System {

public static class WindowsRuntimeSystemExtensions {

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

 

public static Task<TResult> AsTask<TResult, TProgress>(

this IAsyncOperationWithProgress<TResult, TProgress> source, CancellationToken cancellationToken, IProgress<TProgress> progress);

 

// Simpler overloads not shown here

}

}

 

So now, let me show you the complete picture. Here is how to call an asynchronous WinRT API and fully leverage cancellation and progress for those times when you need these enhancements.

 

using System; // For WindowsRuntimeSystemExtensions’s AsTask using System.Threading; // For CancellationTokenSource

 

internal sealed class MyClass {

private CancellationTokenSource m_cts = new CancellationTokenSource();

 

// NOTE: If invoked by GUI thread, all code executes via GUI thread: private async void MappingWinRTAsyncToDotNet(WinRTType someWinRTObj) {

try {

// Assume XxxAsync returns IAsyncOperationWithProgress<IBuffer, UInt32> IBuffer result = await someWinRTObj.XxxAsync(...)

.AsTask(m_cts.Token, new Progress<UInt32>(ProgressReport));

/* Completed... */

}

catch (OperationCanceledException) { /* Canceled... */ } catch (SomeOtherException) { /* Error... */ }

}

 

private void ProgressReport(UInt32 progress) { /* Update progress... */ }

 

public void Cancel() { m_cts.Cancel(); } // Called sometime later

}


I know that some readers would like to understand how these AsTask methods internally convert a WinRT IAsyncXxx into a .NET Framework Task that can ultimately be awaited. The following code shows how the most complicated AsTask method is effectively implemented internally. The simpler overloads are, of course, simpler than this.

 

public static Task<TResult> AsTask<TResult, TProgress>(

this IAsyncOperationWithProgress<TResult, TProgress> asyncOp, CancellationToken ct = default(CancellationToken), IProgress<TProgress> progress = null) {

 

// When CancellationTokenSource is canceled, cancel the async operation ct.Register(() => asyncOp.Cancel());

 

// When the async operation reports progress, report it to the progress callback asyncOp.Progress = (asyncInfo, p) => progress.Report(p);

 

// This TaskCompletionSource monitors the async operation's completion var tcs = new TaskCompletionSource<TResult>();

 

// When the async operation completes, notify the TaskCompletionSource

// Code awaiting the TaskCompletionSource regains control when this happens asyncOp.Completed = (asyncOp2, asyncStatus) => {

switch (asyncStatus) {

case AsyncStatus.Completed: tcs.SetResult(asyncOp2.GetResults()); break; case AsyncStatus.Canceled: tcs.SetCanceled(); break;

case AsyncStatus.Error: tcs.SetException(asyncOp2.ErrorCode); break;

}

};

 

// When calling code awaits this returned Task, it calls GetAwaiter, which

// wraps a SynchronizationContext around the Task ensuring that completion

// occurs on the SynchronizationContext object's context return tcs.Task;

}

 

 

Interoperating Between WinRT Streams and .NET Streams

There are many .NET Framework classes that operate on System.IO.Stream-derived types, such as serialization and LINQ to XML. To use a WinRT object that implements WinRT’s IStorageFile or IStorageFolder interfaces with a .NET Framework class that requires a Stream-derived type, we leverage extension methods defined in the System.IO.WindowsRuntimeStorageExtensions class.

 

namespace System.IO { // Defined in System.Runtime.WindowsRuntime.dll public static class WindowsRuntimeStorageExtensions {

public static Task<Stream> OpenStreamForReadAsync(this IStorageFile file); public static Task<Stream> OpenStreamForWriteAsync(this IStorageFile file);

 

public static Task<Stream> OpenStreamForReadAsync(this IStorageFolder rootDirectory, String relativePath);

public static Task<Stream> OpenStreamForWriteAsync(this IStorageFolder rootDirectory, String relativePath, CreationCollisionOption creationCollisionOption);

}

}


Here is an example that uses one of the extension methods to open a WinRT StorageFile and read its contents into a .NET Framework XElement object.

 

async Task<XElement> FromStorageFileToXElement(StorageFile file) { using (Stream stream = await file.OpenStreamForReadAsync()) {

return XElement.Load(stream);

}

}

 

Finally, the System.IO.WindowsRuntimeStreamExtensions class offers extension methods that “cast” WinRT stream interfaces (such as IRandomAccessStream, IInputStream or IOutputStream) to the .NET Framework's Stream type and vice versa.

 

namespace System.IO { // Defined in System.Runtime.WindowsRuntime.dll public static class WindowsRuntimeStreamExtensions {

public static Stream AsStream(this IRandomAccessStream winRTStream);

public static Stream AsStream(this IRandomAccessStream winRTStream, Int32 bufferSize);

 

 

public static Stream AsStreamForRead(this IInputStream winRTStream);

public static Stream AsStreamForRead(this IInputStream winRTStream, Int32 bufferSize);

 

 

public static Stream AsStreamForWrite(this IOutputStream winRTStream);

public static Stream AsStreamForWrite(this IOutputStream winRTStream, Int32 bufferSize);

 

public static IInputStream AsInputStream (this Stream clrStream); public static IOutputStream AsOutputStream(this Stream clrStream);

}

}

 

Here is an example that uses one of the extension methods to “cast” a WinRT IInputStream to a

.NET Framework Stream object.

 

XElement FromWinRTStreamToXElement(IInputStream winRTStream) { Stream netStream = winRTStream.AsStreamForRead();

return XElement.Load(netStream);

}

 

Note that the "casting" extension methods provided by the .NET Framework do a little more than just casting under the covers. Specifically, the methods that adapt a WinRT stream to a .NET Framework stream implicitly create a buffer for the WinRT stream in the managed heap. As a result, most operations write to this buffer and do not need to cross the interop boundary, thereby im- proving performance. This is especially significant in scenarios that involve many small I/O opera- tions, such as parsing an XML document.

One of the benefits of using the .NET Framework’s stream projections is that if you use an As­ StreamXxx method more than once on the same WinRT stream instance, you do not need to worry that different, disconnected buffers will be created, and data written to one buffer will not be visible in the other. The .NET Framework APIs ensure that every stream object has a unique adapter instance and all users share the same buffer.


Although in most cases the default buffering offers a good compromise between performance and memory usage, there are some cases where you may want to tweak the size of your buffer to be dif- ferent from the 16-KB default. The AsStreamXxx methods offer overloads for that. For instance, if you know that you will be working with a very large file for an extended period of time, and that not many other buffered streams will be used at the same time, you can gain some additional performance by requesting a very large buffer for your stream. Conversely, in some network scenarios with low-latency requirements, you may want to ensure that no more bytes are read from the network than were explic- itly requested by your application. In such cases, you can disable buffering altogether. If you specify a buffer size of zero bytes to the AsStreamXxx methods, no buffer object will be created.

 

Passing Blocks of Data Between the CLR and WinRT

When possible, you should use the stream framework projections as discussed in the previous section, because they have pretty good performance characteristics. However, there may be times when you need to pass raw blocks of data between the CLR and WinRT components. For example, WinRT’s file and socket stream components require you to write and read raw blocks of data. Also, WinRT’s cryp- tography components encrypt and decrypt blocks of data, and bitmap pixels are maintained in raw blocks of data too.

In the .NET Framework, the usual way to obtain a block of data is either with a byte array (Byte[]) or with a stream (such as when using the MemoryStream class). Of course, byte array and MemoryStream objects can’t be passed to WinRT components directly. So, WinRT defines an IBuffer interface and objects that implement this interface represent raw blocks of data that can be passed to WinRT APIs. The WinRT IBuffer interface is defined as follows.

 

namespace Windows.Storage.Streams { public interface IBuffer {

UInt32 Capacity { get; } // Maximum size of the buffer (in bytes)

UInt32 Length { get; set; } // Number of bytes currently in use by the buffer

}

}

 

As you can see, an IBuffer object has a maximum size and length; oddly enough, this interface gives you no way to actually read from or write to the data in the buffer. The main reason for this is because WinRT types cannot express pointers in their metadata, because pointers do not map well to some languages (like JavaScript or safe C# code). So, an IBuffer object is really just a way to pass a memory address between the CLR and WinRT APIs. To access the bytes at the memory address, an internal COM interface, known as IBufferByteAccess, is used. Note that this interface is a COM interface (because it returns a pointer) and it is not a WinRT interface. The .NET Framework team has defined an internal RCW for this COM interface, which looks like the following.

 

namespace System.Runtime.InteropServices.WindowsRuntime { [Guid("905a0fef­bc53­11df­8c49­001e4fc686da")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [ComImport]

internal interface IBufferByteAccess { unsafe Byte* Buffer { get; }

}

}


Internally, the CLR can take an IBuffer object, query for its IBufferByteAccess interface, and then query the Buffer property to get an unsafe pointer to the bytes contained within the buffer. With the pointer, the bytes can be accessed directly.

To avoid having developers write unsafe code that manipulates pointers, the Framework Class Li- brary includes a WindowsRuntimeBufferExtensions class that defines a bunch of extension meth- ods, which .NET Framework developers explicitly invoke to help pass blocks of data between CLR byte arrays and streams to WinRT IBuffer objects. To call these extension methods, make sure you add a using System.Runtime.InteropServices.WindowsRuntime; directive to your source code.

 

namespace System.Runtime.InteropServices.WindowsRuntime { public static class WindowsRuntimeBufferExtensions {

public static IBuffer AsBuffer(this Byte[] source);

public static IBuffer AsBuffer(this Byte[] source, Int32 offset, Int32 length); public static IBuffer AsBuffer(this Byte[] source, Int32 offset, Int32 length, Int32

capacity);

 

public static IBuffer GetWindowsRuntimeBuffer(this MemoryStream stream);

public static IBuffer GetWindowsRuntimeBuffer(this MemoryStream stream, Int32 position, Int32 length);

}

}

 

So, if you have a Byte[] and you want to pass it to a WinRT API requiring an IBuffer, you simply call AsBuffer on the Byte[] array. This effectively wraps the reference to the Byte[] inside an ob- ject that implements the IBuffer interface; the contents of the Byte[] array is not copied, so this is very efficient. Similarly, if you have a MemoryStream object wrapping a publicly visible Byte[] array buffer in it, you simply call GetWindowsRuntimeBuffer on it to wrap the reference to the Memory­ Stream’s buffer inside an object that implements the IBuffer interface. Again, the buffer’s content is not copied, so this is very efficient. The following method demonstrates both scenarios.

 

private async Task ByteArrayAndStreamToIBuffer(IRandomAccessStream winRTStream, Int32 count) { Byte[] bytes = new Byte[count];

await winRTStream.ReadAsync(bytes.AsBuffer(), (UInt32)bytes.Length, InputStreamOptions.None); Int32 sum = bytes.Sum(b => b); // Access the bytes read via the Byte[]

 

using (var ms = new MemoryStream()) using (var sw = new StreamWriter(ms)) {

sw.Write("This string represents data in a stream"); sw.Flush();

UInt32 bytesWritten = await winRTStream.WriteAsync(ms.GetWindowsRuntimeBuffer());

}

}

 

WinRT’s IRandomAccessStream interface implements WinRT’s IInputStream interface defined

as follows.

 

namespace Windows.Storage.Streams {

public interface IOutputStream : IDisposable { IAsyncOperationWithProgress<UInt32, UInt32> WriteAsync(IBuffer buffer);

}

}


When you call the AsBuffer or GetWindowsRuntimeBuffer extension methods in your code, these methods wrap the source object inside an object whose class implements the IBuffer inter- face. The CLR then creates a CCW for this object and passes it to the WinRT API. When the WinRT API queries the IBufferByteAccess interface’s Buffer property to obtain a pointer to the underlying byte array, the byte array is pinned, and the address is returned to the WinRT API so it can access

the data. The underlying byte array is unpinned when the WinRT API internally calls COM’s Release

method on the IBufferByteAccess interface.

 

If you call a WinRT API that returns an IBuffer back to you, then the data itself is probably in native memory, and you’ll need a way to access this data from managed code. For this, we’ll turn to some other extension methods defined by the WindowsRuntimeBufferExtensions class.

 

namespace System.Runtime.InteropServices.WindowsRuntime { public static class WindowsRuntimeBufferExtensions {

public static Stream AsStream(this IBuffer source); public static Byte[] ToArray(this IBuffer source);

public static Byte[] ToArray(this IBuffer source, UInt32 sourceIndex, Int32 count);

 

// Not shown: CopyTo method to transfer bytes between an IBuffer and a Byte[]

// Not shown: GetByte, IsSameData methods

}

}

 

The AsStream method creates a Stream-derived object that wraps the source IBuffer. With this Stream object, you can access the data in the IBuffer by calling Stream’s Read, Write, and similar methods. The ToArray method internally allocates a Byte[] and then copies all the bytes from the source IBuffer into the Byte[]; be aware that this extension method is potentially expensive in terms of memory consumption and CPU time.

The WindowsRuntimeBufferExtensions class also has several overloads of a CopyTo method that can copy bytes between an IBuffer and a Byte[]. It also has a GetByte method that retrieves a single byte at a time from an IBuffer and an IsSameData method that compares the contents of two IBuffer objects to see if their contents are identical. For most applications, it is unlikely that you will have a need to call any of these methods.

I’d also like to point out that the .NET Framework defines a System.Runtime.InteropServices.

WindowsRuntimeBuffer class that allows you to create an IBuffer object whose bytes are in the managed heap. Similarly, there is a WinRT component called Windows.Storage.Streams.Buffer that allows you to create an IBuffer object whose bytes are in the native heap. For most .NET Framework developers, there should be no need to use either of these classes explicitly in your code.

 

 

 
 

Defining WinRT Components in C#

So far in this chapter, I’ve been focusing on how to consume WinRT components from C#. However, you can also define WinRT components in C# and then these components can be used by native C/C++, C#/Visual Basic, JavaScript, and potentially other languages too. Although this is possible to do, we need to think about the scenarios where this actually makes sense. For example, it makes no


sense at all to define a WinRT component in C# if the only consumers of the component are other managed languages that run on top of the CLR. This is because the WinRT type system has far fewer features, which make it much more restrictive than the CLR’s type system.

I also don’t think it makes a lot of sense to implement a WinRT component with C# that could be consumed by native C/C++ code. Developers using native C/C++ to implement their application are probably doing so because they are very concerned about performance and/or memory consump- tion. They are unlikely to want to take advantage of a WinRT component implemented with managed code, because this forces the CLR to load into their process and thus increases their memory require- ments and performance due to the garbage collections and just-in-time compiling of code. For this reason, most WinRT components (like those that ship with Windows itself) are implemented in native code. Of course, there may be some parts of a native C++ app where performance is not so sensitive and, at these times, it may make sense to leverage .NET Framework functionality in order to improve productivity. For example, Bing Maps uses native C++ to draw its UI by using DirectX, but it also uses C# for its business logic.

So, it seems to me that the sweet spot for C#-implemented WinRT components is for Windows Store app developers who want to build their user interface with HTML and CSS and then use JavaScript as the glue code to tie the UI with business logic code that is implemented in a C# WinRT component. Another scenario would be to leverage existing Framework Class Library functionality (like Windows Communication Foundation) from an HTML/JavaScript app. Developers working with HTML and JavaScript are already willing to accept the kind of perfor- mance and memory consumption that comes with a browser engine and may be willing to even further accept the additional performance and memory consumption that comes along with also using the CLR.

To build a WinRT component with C#, you must first create a Microsoft Visual Studio “Windows Runtime Component” project. What this really does is create a normal class library project; however, the C# compiler will be spawned with the /t:winmdobj command line switch in order to produce a file with a .winmdobj file extension. With this switch specified, the compiler emits some IL code differ- ently than it normally would. For example, WinRT components add and remove delegates to events differently than how the CLR does it, so the compiler emits different code for an event’s add and re­ move methods when this compiler switch is specified. I’ll show how to explicitly implement an event’s add and remove methods later in this section.

After the compiler produces the .winmdobj file, the WinMD export utility (WinMDExp.exe) is spawned passing to it the .winmdobj, .pdb, and .xml (doc) files produced by the compiler. The WinMDExp.exe utility examines your file’s metadata, ensuring that your types adhere to the various WinRT type system rules, as discussed at the beginning of this chapter. The utility also modifies the metadata contained in the .winmdobj file; it does not alter the IL code at all. Specifically, the utility maps any CLR types to the equivalent WinRT types. For example, references to the .NET Framework’s IList<String> type are changed to WinRT’s IVector<String> type. The output of the WinMD- Exp.exe utility is a .winmd file that other programming languages can consume.

You can use the .NET Framework’s Intermediate Disassembler utility (ILDasm.exe) to inspect the

contents of a .winmd file. By default, ILDasm.exe shows you the raw contents of the file. However,


ILDasm.exe supports a /project command-line switch that shows you what the metadata would look like after the CLR projected the WinRT types into their .NET Framework equivalents.

The following code demonstrates how to implement various WinRT components in C#. The components leverage many of the features discussed throughout this chapter, and there are a lot of comments throughout the code to explain what is going on. If you need to implement a WinRT com- ponent in C#, I’d suggest using the code I show here as a model.

       
   
 
 

 

/****************************************************************************** Module: WinRTComponents.cs

Notices: Copyright (c) 2012 by Jeffrey Richter

******************************************************************************/

 

using System;

using System.Collections.Generic; using System.Linq;

using System.Runtime.InteropServices.WindowsRuntime; using System.Threading;

using System.Threading.Tasks; using Windows.Foundation;

using Windows.Foundation.Metadata;

 

// The namespace MUST match the assembly name and cannot be "Windows" namespace Wintellect.WinRTComponents {

// [Flags] // Must not be present if enum is int; required if enum is uint public enum WinRTEnum : int { // Enums must be backed by int or uint

None, NotNone

}

 

// Structures can only contain core data types, String, & other structures

// No constructors or methods are allowed public struct WinRTStruct {

public Int32 ANumber; public String AString;


public WinRTEnum AEnum; // Really just a 32­bit integer

}

 

 

// Delegates must have WinRT­compatible types in the signature (no BeginInvoke/EndInvoke) public delegate String WinRTDelegate(Int32 x);

 

 

// Interfaces can have methods, properties, & events but cannot be generic. public interface IWinRTInterface {

// Nullable<T> marshals as IReference<T> Int32? InterfaceProperty { get; set; }

}

 

// Members without a [Version(#)] attribute default to the class's

// version (1) and are part of the same underlying COM interface

// produced by WinMDExp.exe. [Version(1)]

// Class must be derived from Object, sealed, not generic,

// implement only WinRT interfaces, & public members must be WinRT types public sealed class WinRTClass : IWinRTInterface {

// Public fields are not allowed

 

#region Class can expose static methods, properties, and events

public static String StaticMethod(String s) { return "Returning " + s; } public static WinRTStruct StaticProperty { get; set; }

 

// In JavaScript 'out' parameters are returned as objects with each

// parameter becoming a property along with the return value

public static String OutParameters(out WinRTStruct x, out Int32 year) {

x = new WinRTStruct { AEnum = WinRTEnum.NotNone, ANumber = 333, AString = "Jeff" }; year = DateTimeOffset.Now.Year;

return "Grant";

}

#endregion

 

// Constructor can take arguments but not out/ref arguments public WinRTClass(Int32? number) { InterfaceProperty = number; }

 

public Int32? InterfaceProperty { get; set; }

 

// Only ToString is allowed to be overridden public override String ToString() {

return String.Format("InterfaceProperty={0}",

InterfaceProperty.HasValue ? InterfaceProperty.Value.ToString() : "(not set)");

}

 

public void ThrowingMethod() {

throw new InvalidOperationException("My exception message");

 

// To throw a specific HRESULT, use COMException instead

//const Int32 COR_E_INVALIDOPERATION = unchecked((Int32)0x80131509);

//throw new COMException("Invalid Operation", COR_E_INVALIDOPERATION);

}

 

#region Arrays are passed, returned OR filled; never a combination

public Int32 PassArray([ReadOnlyArray] /* [In] implied */ Int32[] data) {


// NOTE: Modified array contents MAY not be marshaled out; do not modify the array return data.Sum();

}

 

public Int32 FillArray([WriteOnlyArray] /* [Out] implied */ Int32[] data) {

// NOTE: Original array contents MAY not be marshaled in;

// write to the array before reading from it

for (Int32 n = 0; n < data.Length; n++) data[n] = n; return data.Length;

}

 

public Int32[] ReturnArray() {

// Array is marshaled out upon return return new Int32[] { 1, 2, 3 };

}

#endregion

 

// Collections are passed by reference

public void PassAndModifyCollection(IDictionary<String, Object> collection) { collection["Key2"] = "Value2"; // Modifies collection in place via interop

}

 

#region Method overloading

// Overloads with same # of parameters are considered identical to JavaScript public void SomeMethod(Int32 x) { }

 

[Windows.Foundation.Metadata.DefaultOverload] // Makes this method the default overload public void SomeMethod(String s) { }

#endregion

 

#region Automatically implemented event public event WinRTDelegate AutoEvent;

 

public String RaiseAutoEvent(Int32 number) { WinRTDelegate d = AutoEvent;

return (d == null) ? "No callbacks registered" : d(number);

}

#endregion

 

#region Manually implemented event

// Private field that keeps track of the event's registered delegates private EventRegistrationTokenTable<WinRTDelegate> m_manualEvent = null;

 

// Manual implementation of the event's add and remove methods public event WinRTDelegate ManualEvent {

add {

// Gets the existing table, or creates a new one if the table is not yet initialized return EventRegistrationTokenTable<WinRTDelegate>

.GetOrCreateEventRegistrationTokenTable(ref m_manualEvent)

.AddEventHandler(value);

}


remove {

EventRegistrationTokenTable<WinRTDelegate>

.GetOrCreateEventRegistrationTokenTable(ref m_manualEvent)

.RemoveEventHandler(value);

}

}

 

public String RaiseManualEvent(Int32 number) {

WinRTDelegate d = EventRegistrationTokenTable<WinRTDelegate>

.GetOrCreateEventRegistrationTokenTable(ref m_manualEvent).InvocationList; return (d == null) ? "No callbacks registered" : d(number);

}

#endregion

 

#region Asynchronous methods

// Async methods MUST return IAsync[Action|Operation](WithProgress)

// NOTE: Other languages see the DataTimeOffset as Windows.Foundation.DateTime public IAsyncOperationWithProgress<DateTimeOffset, Int32> DoSomethingAsync() {

// Use the System.Runtime.InteropServices.WindowsRuntime.AsyncInfo's Run methods to

// invoke a private method written entirely in managed code

return AsyncInfo.Run<DateTimeOffset, Int32>(DoSomethingAsyncInternal);

}

 

// Implement the async operation via a private method using normal .NET technologies private async Task<DateTimeOffset> DoSomethingAsyncInternal(

CancellationToken ct, IProgress<Int32> progress) {

 

for (Int32 x = 0; x < 10; x++) {

// This code supports cancellation and progress reporting ct.ThrowIfCancellationRequested();

if (progress != null) progress.Report(x * 10);

await Task.Delay(1000); // Simulate doing something asynchronously

}

return DateTimeOffset.Now; // Ultimate return value

}

 

public IAsyncOperation<DateTimeOffset> DoSomethingAsync2() {

// If you don’t need cancellation & progress, use

// System.WindowsRuntimeSystemExtensions’ AsAsync[Action|Operation] Task

// extension methods (these call AsyncInfo.Run internally)

return DoSomethingAsyncInternal(default(CancellationToken), null).AsAsyncOperation();

}

#endregion

 

// After you ship a version, mark new members with a [Version(#)] attribute

// so that WinMDExp.exe puts the new members in a different underlying COM

// interface. This is required since COM interfaces are supposed to be immutable. [Version(2)]

public void NewMethodAddedInV2() {}

}

}


The following JavaScript code demonstrates how to access all of the previous WinRT components and features.

 

function () {

// Make accessing the namespace more convenient in the code var WinRTComps = Wintellect.WinRTComponents;

 

// NOTE: The JavaScript VM projects WinRT APIs via camel casing

 

// Access WinRT type's static method & property

var s = WinRTComps.WinRTClass.staticMethod(null); // NOTE: JavaScript pass "null" here! var struct = { anumber: 123, astring: "Jeff", aenum: WinRTComps.WinRTEnum.notNone }; WinRTComps.WinRTClass.staticProperty = struct;

s = WinRTComps.WinRTClass.staticProperty; // Read it back

 

// If the method has out parameters, they and the return value

// are returned as an object’s properties

var s = WinRTComps.WinRTClass.outParameters(); var name = s.value; // Return value

var struct = s.x; // an 'out' parameter

var year = s.year; // another 'out' parameter

 

// Construct an instance of the WinRT component var winRTClass = new WinRTComps.WinRTClass(null); s = winRTClass.toString(); // Call ToString()

 

// Demonstrate throw and catch

try { winRTClass.throwingMethod(); } catch (err) { }

 

// Array passing

var a = [1, 2, 3, 4, 5];

var sum = winRTClass.passArray(a);

 

// Array filling

var arrayOut = [7, 7, 7]; // NOTE: fillArray sees all zeros!

var length = winRTClass.fillArray(arrayOut); // On return, arrayOut = [0, 1, 2]

 

// Array returning

a = winRTClass.returnArray(); // a = [ 1, 2, 3]

 

// Pass a collection and have its elements modified

var localSettings = Windows.Storage.ApplicationData.current.localSettings; localSettings.values["Key1"] = "Value1"; winRTClass.passAndModifyCollection(localSettings.values);

// On return, localSettings.values has 2 key/value pairs in it

 

// Call overloaded method

winRTClass.someMethod(5); // Actually calls SomeMethod(String) passing "5"

 

// Consume the automatically implemented event var f = function (v) { return v.target; };

winRTClass.addEventListener("autoevent", f, false); s = winRTClass.raiseAutoEvent(7);


// Consume the manually implemented event winRTClass.addEventListener("manualevent", f, false); s = winRTClass.raiseManualEvent(8);

 

// Invoke asynchronous method supporting progress, cancelation, & error handling var promise = winRTClass.doSomethingAsync();

promise.then(

function (result) { console.log("Async op complete: " + result); }, function (error) { console.log("Async op error: " + error); }, function (progress) {

console.log("Async op progress: " + progress);

//if (progress == 30) promise.cancel(); // To test cancelation

});

}


 


 
 

 


PART V

Threading

CHAPTER 26.....................................Thread Basics............ 669

CHAPTER 27......Compute-Bound Asynchronous Operations............ 691

CHAPTER 28.............I/O-Bound Asynchronous Operations............ 727

CHAPTER 29....Primitive Thread Synchronization Constructs............ 757

CHAPTER 30.......Hybrid Thread Synchronization Constructs............ 789


C HA P T E R 2 6

Thread Basics

In this chapter:

Why Does Windows Support Threads?.................................................. 669

Thread Overhead.................................................................................. 670

Stop the Madness................................................................................. 674

CPU Trends........................................................................................... 677

CLR Threads and Windows Threads....................................................... 678

Using a Dedicated Thread to Perform an Asynchronous Compute-Bound Operation 678

Reasons to Use Threads......................................................................... 681

Thread Scheduling and Priorities......................................................... 683

Foreground Threads vs. Background Threads.......................................... 688

What Now?........................................................................................... 689

 

In this chapter, I introduce the basic concepts concerning threads, and I offer a way for developers to conceptualize about them and their use. I’ll explain why Windows introduced the concept of threads, CPU trends, the relationship between common language runtime (CLR) threads and Windows threads, the overhead associated with using threads, how Windows schedules threads, the Microsoft .NET Framework classes that expose thread properties, and much more.

The chapters in Part V, “Threading,” of this book explain how Windows and the CLR work together to provide a threading architecture. It is my hope that after reading these chapters, you will take away a foundation of knowledge that will allow you to effectively use threads to design and build respon- sive, reliable, and scalable applications and components.

 

 


Date: 2016-03-03; view: 859


<== previous page | next page ==>
Nbsp;   CLR Projections and WinRT Component Type System Rules | Nbsp;   Why Does Windows Support Threads?
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.067 sec.)