📜 ⬆️ ⬇️

Windows runtime. CLR type system and interaction

With the release of Windows 8, a new class library, Windows Runtime, has become available to developers. WinRT components can be used in Windows Store and desktop applications ; in unmanaged C / C ++ code, in JavaScript, as well as in C # and Visual Basic.

Windows Runtime Metadata

Internally, WinRT components are COM (Component Object Model) components, which now use metadata to describe their APIs. These metadata are stored in files with the * .winmd extension and represent an updated version of .NET metadata that is encoded in accordance with the rules of Section 2 (Metadata Definition and Semantics) of the ECMA-335 standard. Since the usual .NET Framework assemblies are encoded using the same standard, it means that you can use familiar tools (such as ildasm.exe , the Object Browser) to view the contents of these files.
For the most part, viewing the WinMD file using the ildasm.exe utility is very similar to viewing a standard managed assembly. There are several differences that can be seen - first of all, that the WinMD files, in general, do not contain any Intermediate Language (IL) instructions. Instead, these files describe the API provided by the Windows Runtime. The implementation of these interfaces can be completely separated from their definition, and in essence, can be written in native code. However, for developers of managed applications, the details of the WinRT API implementation are irrelevant, because managed code must see only the API definitions it calls. Behind the scenes, the Common Language Runtime (CLR) and the Windows operating system connect API definitions and implementations for you.

For example, in the Windows.Foundation.winmd metadata file (located in the% WinDir% \ System32 \ WinMetadata directory) you can find the following type of Windows.Foundation.Collections.PropertySet , whose constructor does not contain a body, because the type is implemented in the native code.

image
')
However, the metadata that describes this type allows the CLR to get an instance of an implementation when calling a class constructor.
When viewing the Windows Runtime metadata, you can also notice that the type and assembly definitions use the new WindowsRuntime keyword.

image

This keyword is context-sensitive and is interpreted differently depending on where it is applied. For example, if a keyword marks a type definition (TypeDef), then this type is subject to the rules of the Windows Runtime type system and a call of this type should be treated as a call to the WinRT API.

CLR interaction with WinRT components

CRL supports interaction with COM components via Callable Wrapper (RCW) and COM Callable Wrapper (CCW) wrappers . Thus, in the CLR, the reference to the WinRT object is a reference to the RCW , which in turn contains a reference to the WinRT object. Accordingly, the managed code interacts with the RCW , which is essentially the interface between your code and the WinRT object.

image

Similarly, in the Windows Runtime, a CLR object reference is a reference to a CCW , which in turn contains a reference to a CLR object. The Windows Runtime then interacts with the CCW to access the functionality of the managed object.

WinRT types and managed code

Despite the fact that the Windows Runtime type system is similar to the CLR type system, you can notice that some of the types used in the API definitions do not match the types used in managed code. In order for .NET developers to create applications using familiar technologies, the CLR hides some types of WinRT and provides access to them through others. In general, there are three types of types in Windows Runtime and some of them look different in managed code:

Interestingly, there is still a small category of types that appear in irrelevant places in the Windows metadata encodings. These are the .NET Framework types, which are used simply to describe the WinRT types. For example, the delegates of the Windows Runtime are encoded with the base System.MulticastDelegate type, but this does not mean that all the delegates of the Windows Runtime are derived from managed code. Instead, the base type is simply used as a metadata marker to indicate that the type is a delegate type.

image

Projection Types

When the CLR projects (maps) WinRT types, it does two things:

Thus, the CLR presents the IVector <T> interface as an IList <T> , the IVR type definition of the CLR is considered as a type definition with a private scope, rather than a public one. Similarly, the Windows.UI.Xaml.Controls.UIElementCollection type that implements the IVector <UIElement> has been updated by the CLR. The IVector <T> interface implementation is redirected to the IList <T> so that the UIElementCollection in the managed code implements the IList <UIElement> interface.
Usually ildasm.exe shows the raw view of the WinMD file without including any redirects. To view the contents of a WinMD file with redirections enabled, you must specify the / project parameter. This option allows you to see how the CLR displays the metadata on disk.

Base type

The WinRT components do not have a common base class, but all Windows runtime classes must implement the IInspectable interface, which in turn inherits from the IUnknown interface (which is not surprising). However, for .NET developers, all WinRT types look like types derived from System.Object and, accordingly, inherit methods such as Equals , GetHashCode , etc. This is made possible by the fact that the CLR marshals objects at runtime to convert types between WinRT and .NET views.

Structures

The WinRT structures, in contrast to the significant CLR types, can contain only open fields of one of the basic types or they can be another WinRT structure. Thus, the following code will generate an error at compile time:

public struct MyStruct { // 'MyStruct' contains non-public field 'MyStruct.i'. // Windows Runtime structures can contain only public fields. private Int32 i; //Windows Runtime structures can contain only fields. public MyStruct(Int32 i) { this.i = i; } //Windows Runtime structures can contain only fields. public void MyFunc() { } } 

In addition, WinRT structures cannot define constructors or contain helper methods. However, some CLR structures, for convenience, project on their own, thereby providing developers with helper methods and constructors. These include, for example, the structure of Windows.Foundation.Point , Windows.Foundation.Size and Windows.Foundation.Rect .

Strings

The System.String type in WinRT is represented as HSTRING . When you call a Windows runtime method, the CLR converts any .NET Framework string to HSTRING before calling the method. Similarly, the CLR converts any strings returned from the runtime method to the System.String type. There is one more feature - the WinRT type system does not allow strings to be null. Instead of null, use String.Empty to pass in an empty string. When trying to pass null as a string in the WinRT function, the CLR will throw an ArgumentNullException . In the same way, you will never see that the WinRT function can return a null string; this can only be an empty string.

Null compatible types

The WinRT API uses the Windows.Foundation.IReference <T> interface to define a null-compatible meaningful type, which the CLR projects onto its own System.Nullable <T> . For example, if a method in the WinMD file has the following signature:

 IReference<bool> Method(IReference<int> i); 

then in managed code, this method will look like this:

 Nullable<bool> Method(Nullable<int> i); 


Delegates

Only WinRT compatible types can be used as the parameter type or return value of a WinRT delegate. Also, delegates with a global (public) scope cannot be declared as nested (in fact, these are general rules for the Windows runtime as a whole). When you pass a delegate object to the Windows Runtime component, the object is wrapped in a CCW wrapper, which is not destroyed by the garbage collector until it is released by the component that uses it. Another interesting fact is that the WinRT delegates do not have the BeginInvoke and EndInvoke methods.

Developments

WinRT components can define events using only WinRT delegate types. There is a Windows.Foundation.EventHandler <T> delegate type that the CLR projects to the .NET Framework System.EventHandler <TEventArgs> delegate type. When you define a member event:

 public event EventHandler<RoutedEventArgs> MyEvent; 

then when compiling this line of code, the compiler turns it into the following instructions:

 private EventRegistrationTokenTable<EventHandler<RoutedEventArgs>> MyEvent; public EventRegistrationToken add_MyEvent(EventHandler<RoutedEventArgs> handler) { return EventRegistrationTokenTable<EventHandler<RoutedEventArgs>> .GetOrCreateEventRegistrationTokenTable(ref MyEvent) .AddEventHandler(handler); } public void remove_MyEvent(EventRegistrationToken token) { EventRegistrationTokenTable<EventHandler<RoutedEventArgs>> .GetOrCreateEventRegistrationTokenTable(ref MyEvent) .RemoveEventHandler(token); } 

As before, the compiler creates a private field and two accessor methods for registering and not subscribing to an event. However, the type of the field and the content of these methods differ from what we are used to ( Delegate.Combine and Delegate.Remove ). The field type is a generic EventRegistrationTokenTable <T> class, whose type argument is the corresponding delegate type. This class is responsible for storing the chain of delegates that represent event handlers. When adding a new handler, the EventRegistrationToken token is returned , which can be used later to remove the event handler.

 public void RaiseEvent() { var list = EventRegistrationTokenTable<EventHandler<RoutedEventArgs>> .GetOrCreateEventRegistrationTokenTable(ref MyEvent) .InvocationList; if (list != null) list(this, new RoutedEventArgs()); } public void Main() { var myClass = new MyClass(); var token = myClass.add_MyEvent(Handler); myClass.RaiseEvent(); myClass.remove_MyEvent(token); myClass.RaiseEvent(); } private void Handler(object sender, RoutedEventArgs args) { Debug.WriteLine("event handling"); } 

In order to raise an event, use the InvocationList property, which returns a delegate whose call list includes all delegates added as event handlers.

Time and date

In WinRT, time and date are represented in UTC by the Windows.Foundation.DateTime structure. The CLR projects this type on the System.DateTimeOffset structure, not on System.DateTime . It is worth noting that DateTime does not contain information about the time zone. Therefore, the date and time returned by the WinRT functions in UTC format, the CLR converts to local time. Conversely, when the DateTimeOffset structure is transferred to the WinRT function, the date and time are converted to the UTC format.

Arrays

WinRT API supports only one-dimensional arrays. Accordingly, the following code will cause a compile-time error:
 // Arrays in Windows Runtime method signatures must be one dimensional. public int[,] MyFunc() { return new int[5, 5]; } 

In managed code, arrays are passed by reference, and changes to the elements of the array will be visible to any code that has a reference to an instance of this array. However, for WinRT this is not always the case, because the contents of the array are marshaled only in the direction that the API defines in its signature using System.Runtime.InteropServices.InAttribute and System.Runtime.InteropServices.OutAttribute . Both attributes apply to method parameters or return values ​​and determine the marshaling direction between managed and unmanaged memory at run time. In the Windows Runtime, parameters can be either read only [InAttribute] or write only [OutAttribute] and cannot be marked for reading and writing at the same time [InAttribute], [OutAttribute]. This means that the contents of the array passed to the method, as well as the array itself, should be designed to be read or written. So, the contents of the array, which is marked with the [InAttribute] attribute, are copied to the method being called, so all changes that the method applies to the array are not visible to the calling object. Similarly, the contents of the array, which is marked with the [OutAttribute] attribute, are set by the called method and copied to the caller, so the called method should not make any assumptions about the contents of the original array.

Collections

When sending a collection, the CLR wraps the collection object in a CCW wrapper and passes the reference to it to the WinRT API. In this case, calls through the wrapper intersect the interaction boundary, which negatively affects the performance. However, unlike arrays, it is possible to perform operations without copying the elements.

Conclusion

Summing up, I note that due to changes in the CLR, developers of managed code can easily adapt to the new Windows Runtime API using familiar technologies. In this article, I described far from all the details of the WinRT and CLR interaction. However, this can serve as a basis for further study and a deeper understanding of the Windows Runtime.

Source: https://habr.com/ru/post/207088/


All Articles