Windows Runtime (WinRT) предоставляет большой набор новых API разработчикам для Windows. CLR 4.5, которая входит в состав Microsoft .NET Framework 4.5 в Windows 8, позволяет писать управляемый код, естественным образом использующий эти API — так, будто они входят в очередную библиотеку классов. Вы можете добавить ссылку на файл Windows Metadata (WinMD), который определяет нужные вам API, а затем вызывать их так же, как и стандартный управляемый API. Visual Studio автоматически добавляет ссылку на встроенные WinRT API в новые проекты Windows UI, поэтому в своем приложении вы можете просто начать использовать этот новый набор API.
На внутреннем уровне CLR предоставляет инфраструктуру для управляемого кода, позволяющую работать с файлами WinMD, и переходный уровень между управляемым кодом и Windows Runtime. В этой статье я объясню некоторые детали этого. В итоге вы получите более хорошее представление о том, что происходит «за кулисами», когда ваша управляемая программа вызывает какой-либо WinRT API.
Использование файлов WinMD из управляемого кода
WinRT API определены в файлах WinMD, закодированные в файловом формате, который описан в ECMA-335 (bit.ly/sLILI). Хотя у файлов WinMD и файлов сборок .NET Framework общая кодировка, они не одинаковы. Одно из основных отличий в метаданных, которое вытекает из того факта, что система типов WinRT независима от системы типов .NET.
Такие программы, как компиляторы C# и Visual Studio, используют API метаданных CLR (например, IMetaDataImport) для чтения метаданных из сборок .NET Framework, а теперь могут читать метаданные и из файлов WinMD. Поскольку эти метаданные не совсем такие же, как в .NET-сборке, компонент чтения метаданных CLR вставляет адаптер между API метаданных и считываемым файлом WinMD. Это позволяет читать файлы WinMD так, будто это .NET-сборки (рис. 1).
Рис. 1. CLR вставляет адаптер метаданных между файлами WinMD и открытым интерфейсом метаданных
Program | Программа |
CLR Metadata API | API метаданных CLR |
.NET Assembly | .NET-сборка |
Metadata Adapter | Адаптер метаданных |
.winmd | Файл .winmd |
Запустив ILDasm, вы сможете лучше понять изменения, которые вносятся адаптером метаданных CLR в файл WinMD. По умолчанию ILDasm показывает содержимое файла WinMD в неструктурированном виде — так, как он кодируется на диске без адаптера. Однако, если вы укажете при запуске ILDasm параметр командной строки /project, он включит адаптер метаданных, и вы увидите метаданные в той форме, в какой их читают CLR и управляемый инструментарий.
Запуск двух копий ILDasm (одной с параметром /project, а другой без него) позволяет легко увидеть изменения, которые вносит адаптер метаданных CLR в файл WinMD.
Системы типов Windows Runtime и .NET
Одна из основных операций, выполняемых адаптером метаданных, — объединение систем типов WinRT и .NET. На высоком уровне в файле WinMD может появляться пять категорий WinRT-типов, и они должны учитываться CLR. Они перечислены в табл. 1. Рассмотрим каждую категорию подробнее.
Табл. 1. WinRT-типы для файла WinMD
Категория | Примеры |
Стандартные WinRT-типы | Windows.Foundation.Collections.PropertySet, Windows.Networking.Sockets.DatagramSocket |
Элементарные типы | Byte, Int32, String, Object |
Проецируемые типы | Windows.Foundation.Uri, Windows.Foundation.DateTime |
Проецируемые интерфейсы | Windows.Foundation.Collections.IVector<T>, Windows.Foundation.Iclosable |
Типы со вспомогательными методами .NET Framework | Windows.Storage.Streams.IInputStream, Windows.Foundation.IasyncInfo |
Стандартные WinRT-типыc Хотя в CLR есть специальная поддержка многих категорий типов Windows Runtime, большое количество WinRT-типов вообще не обрабатывается CLR. Вместо этого они предоставляются .NET-разработчикам не модифицированными, и их можно использовать как большую библиотеку классов для написания приложений Windows Store.
Элементарные типы Набор элементарных типов кодируется в файл WinMD с применением того же перечисления ELEMENT_TYPE, что и в .NET-сборках. CLR автоматически интерпретирует эти элементарные типы так, будто они являются эквивалентными .NET-типами.
По большей части интерпретация элементарных WinRT-типов как элементарных .NET-типов просто работает. Например, 32-битное целое имеет тот же битовый шаблон в Windows Runtime, что и в .NET, поэтому CLR может без проблем обрабатывать WinRT DWORD как .NET System.Int32. Но есть два важных исключения: строки и объекты.
В Windows Runtime строки представляются типом данных HSTRING, который вовсе не совпадает с .NET-типом System.String. Аналогично ELEMENT_TYPE_OBJECT рассматривается .NET как System.Object, хотя в Windows Runtime он означает IInspectable*. Поэтому в случае строк и объектов CLR осуществляет маршалинг объектов в период выполнения для преобразования между WinRT- и .NET-представлениями этих типов. Позднее в этой статье вы увидите, как происходит маршалинг.
Проецируемые типы (projected types) Некоторые фундаментальные .NET-типы имеют свои эквиваленты в системе типов WinRT. Например, в Windows Runtime определены структура TimeSpan и класс Uri, и у них есть свои аналоги в .NET Framework.
Чтобы не заставлять .NET-разработчиков туда-сюда преобразовывать эти фундаментальные типы данных, CLR проецирует WinRT-версию на .NET-эквивалент. Эти проекции в конечном счете объединяют точки, вставляемые CLR между системами типов .NET и WinRT.
Например, SyndicationClient.RetrieveFeedAsync из Windows Runtime принимает WinRT Uri как параметр. Вместо того чтобы требовать от .NET-разработчиков вручную создавать новый экземпляр Windows.Foundation.Uri для передачи в этот API, CLR проецирует данный тип как System.Uri, что позволяет .NET-разработчикам использовать более привычный им тип.
Другой пример проекции — структура Windows.Foundation.HResult, проецируемая CLR в тип System.Exception. В .NET разработчики привыкли видеть информацию об ошибке, передаваемую как исключение, а не HRESULT, поэтому передача таким WinRT API, как IAsycnInfo.ErrorCode, информации об ошибке в структуре HResult для них неестественна. Вместо этого CLR проецирует HResult в Exception, что делает IAsyncInfo.ErrorCode более удобной в использовании для .NET-разработчиков. Вот пример свойства IAsyncInfo ErrorCode, закодированного в Windows.winmd:
.class interface public windowsruntime IAsyncInfo
{
.method public abstract virtual
instance valuetype Windows.Foundation.HResult
get_ErrorCode()
}
А вот свойство IAsyncInfo ErrorCode после проецирования CLR:
.class interface public windowsruntime IAsyncInfo
{
.method public abstract virtual
instance class [System.Runtime]System.Exception
get_ErrorCode()
}
Проецируемые интерфейсы (projected interfaces) Windows Runtime также предоставляет набор фундаментальных интерфейсов, имеющих .NET-эквиваленты. CLR выполняет проецирование типов и в этих интерфейсах, что опять же приводит к слиянию систем типов в этих фундаментальных точках.
Наиболее распространенный пример проецируемых интерфейсов — интерфейсы WinRT-наборов, такие как IVector<T>, IIterable<T> и IMap<K,V>. Разработчики, использующие .NET, знакомы с интерфейсами наборов IList<T>, IEnumerable<T> и IDictionary<K,V>. CLR проецирует интерфейсы WinRT-наборов в их .NET-эквиваленты, а также скрывает WinRT-интерфейсы, чтобы разработчики не имели дела с двумя функционально эквивалентными наборами типов, делающих одно и то же.
В дополнение к проецированию этих типов, когда они указываются как параметры и возвращаемые типы методов, CLR также должна проецировать данные интерфейсы, когда они появляются в списке реализаций интерфейса типа. Например, WinRT-тип PropertySet реализует WinRT-интерфейс IMap<string, object>. Однако CLR будет проецировать PropertySet как тип, реализующий интерфейс IDictionary<string, object>. При выполнении этой проекции члены PropertySet, использующие для реализации IMap<string, object>, скрываются. Вместо этого .NET-разработчики обращаются к PropertySet через соответствующие методы интерфейса IDictionary<string, object>. Вот фрагмент PropertySet, закодированного в Windows.winmd:
.class public windowsruntime PropertySet
implements IPropertySet,
class IMap`2<string,object>,
class IIterable`1<class IKeyValuePair`2<string,object> >
{
.method public instance uint32 get_Size()
{
.override method instance uint32 class
IMap`2<string,object>::get_Size()
}
}
А вот фрагмент PropertySet после проецирования, выполненного CLR:
.class public windowsruntime PropertySet
implements IPropertySet,
class IDictionary`2<string,object>,
class IEnumerable`1<class KeyValuePair`2<string,object> >
{
.method private instance uint32 get_Size()
{
}
}
Обратите внимание на то, что проецируются три типа: IMap<string,object> в IDictionary<string,object>, IKeyValuePair<string,object> в KeyValuePair<string, object> и IIterable<IKeyValuePair> в IEnumerable<KeyValuePair>. Также заметьте, что метод get_Size из IMap скрывается.
Типы со вспомогательными методами .NET Framework В Windows Runtime есть несколько типов, не имеющих точки полного слияния с системой типов .NET, но достаточно важных для большинства приложений; .NET Framework предоставляет вспомогательные методы для работы с такими типами. Два из наиболее важных примеров — WinRT-интерфейсы stream и async.
Хотя CLR не проецирует Windows.Storage.Streams.IRandomAccessStream в System.Stream, она предоставляет набор методов расширения для IRandomAccessStream, которые позволяют вашему коду обрабатывать эти WinRT-потоки данных так, будто они являются .NET-потоками. Например, вы можете легко считать WinRT-поток с помощью .NET StreamReader, вызвав метод расширения OpenStreamForReadAsync.
Windows Runtime предоставляет набор интерфейсов, представляющих асинхронные операции, такие как интерфейс IAsyncInfo. В .NET Framework 4.5 встроена поддержка для ожидания асинхронных операций, которые разработчикам нужно использовать с WinRT API так же, как с .NET API.
С этой целью в .NET Framework включен набор методов расширения GetAwaiter для асинхронных интерфейсов WinRT. Эти методы используются компиляторами C# и Visual Basic для поддержки асинхронных операций WinRT с ожиданием (awaiting). Вот пример:
private async Task<string> ReadFilesync(StorageFolder parentFolder,
string fileName)
{
using (Stream stream = await parentFolder.OpenStreamForReadAsync(fileName))
using (StreamReader reader = new StreamReader(stream))
{
return await reader.ReadToEndAsync();
}
}
Переходы между .NET Framework и Windows Runtime В CLR предусмотрен механизм, позволяющий управляемому коду бесшовно вызывать WinRT API, а Windows Runtime может делать обратные вызовы управляемого кода.
На самом низком уровне Windows Runtime опирается на концепции COM, поэтому не удивительно, что поддержка CLR вызова WinRT API построена на существующей инфраструктуре COM interop.
Одно важное различие между WinRT interop и COM interop в том, насколько меньше конфигурирования требуется для его использования в Windows Runtime. В файлах WinMD содержатся метаданные, описывающие все предоставляемые ими API с четко определенным сопоставлением с системой типов .NET, поэтому использовать в управляемом коде какие-либо атрибуты MarshalAs нет нужды. Аналогично, поскольку Windows 8 содержит файлы WinMD для своих WinRT API, вам не требуется снабжать свое приложение основной interop-сборкой. Вместо нее CLR использует готовые файлы WinMD и определяет о ним все, что ей нужно знать о том, как вызывать WinRT API.
Эти файлы WinMD предоставляют определения управляемых типов, используемых в период выполнения для доступа к Windows Runtime из управляемого кода. Хотя API, которые CLR считывает из файла WinMD, содержат определение метода в формате, легко применяемом из управляемого кода, нижележащий WinRT API использует другую сигнатуру API, иногда называемую сигнатурой двоичного интерфейса приложения (application binary interface, ABI). Один из примеров различия сигнатур API и ABI в том, что — подобно стандартной COM — WinRT API возвращают HRESULT, а в сигнатуре ABI возвращаемое значение на самом деле является выходным параметром. Я покажу пример того, как сигнатура управляемого метода преобразуется в сигнатуру WinRT ABI, когда буду рассматривать, как CLR вызывает WinRT API.
Оболочки, вызываемые исполняющей средой, и оболочки, вызываемые COM
Когда WinRT-объект входит в CLR, он должен быть вызываемым так, будто он является .NET-объектом. Для этого CLR обертывает каждый WinRT-объект в оболочку, вызываемую исполняющей средой (runtime callable wrapper, RCW). RCW — это то, с чем взаимодействует управляемый код, и интерфейс между вашим кодом и используемым им WinRT-объектом.
Соответственно, когда управляемый объект используется из Windows Runtime, он должен быть вызываемым так, будто является WinRT-объектом. В этом случае при передаче в Windows Runtime управляемый объект обертывается в оболочку, вызываемую COM (COM callable wrapper, CCW). Так как Windows Runtime использует ту же инфраструктуру, что и COM, она может взаимодействовать с CCW для доступа к функциональности управляемого объекта (рис. 2).
Рис. 2. Применение оболочек для WinRT- и управляемых объектов
CLR | CLR |
Windows Runtime | Windows Runtime |
RCW | RCW |
CCW | CCW |
WinRT Object | WinRT-объект |
Managed Object | Управляемый объект |
Интерфейсы маршалинга
Когда управляемый код пересекает любую interop-границу, в том числе границы WinRT, должно происходить несколько вещей.
- Преобразование входных управляемых параметров в WinRT-эквиваленты, в том числе создание CCW-оболочек для управляемых объектов.
- Поиск интерфейса, который реализует WinRT-метод, вызываемый из RCW, в котором вызывается этот метод.
- Вызов WinRT-метода.
- Преобразование выходных WinRT-параметров (включая возвращаемые значения) в управляемые эквиваленты.
- Преобразование любых HRESULT от WinRT API в управляемое исключение.
Эти операции происходят в интерфейсе маршалинга (marshaling stub), который CLR генерирует в интересах вашей программы. Именно интерфейсы маршалинга в RCW вызываются управляемым кодом перед переходом в WinRT API. Аналогично, Windows Runtime вызывает сгенерированные CLR интерфейсы маршалинга в CCW, когда происходит переход в управляемый код.
Интерфейсы маршалинга образуют мостик над пропастью между Windows Runtime и .NET Framework. Понимание того, как они работают, поможет вам получить более глубокое представление о том, что происходит при вызовах вашей программой API-средств в Windows Runtime.
Пример вызова
Вообразите WinRT API, который принимает список строк и осуществляет их конкатенацию, вставляя между каждым элементом строку-разделитель. Этот API мог бы иметь такую управляемую сигнатуру:
public string Join(IEnumerable<string> list, string separator)
CLR должна вызвать этот метод так, как он определен в ABI, поэтому ей нужно узнать ABI-сигнатуру метода. К счастью, для получения ABI-сигнатуры по конкретной API-сигнатуре можно применить набор детерминированных преобразований. Первое преобразование — замена проецируемых типов данных их WinRT-эквивалентами, которые возвращают API к форме, в какой она определена в файле WinMD до того, как адаптер метаданных загрузил этот API. В нашем случае IEnumerable<T> на самом деле является проекцией IIterable<T>, поэтому WinMD-представление этой функции выглядит так:
public string Join(IIterable<string> list, string separator)
WinRT-строки хранятся в типе данных HSTRING, поэтому для Windows Runtime эта функция на деле выглядит следующим образом:
public HSTRING Join(IIterable<HSTRING> list, HSTRING separator)
На уровне ABI, где происходит реальный вызов, WinRT API возвращают HRESULT-значения, и возвращаемое значение от этой сигнатуры является выходным параметром. Кроме того, объекты — это указатели, поэтому ABI-сигнатура для этого метода была бы такой:
HRESULT Join(__in IIterable<HSTRING>* list, HSTRING separator, __out HSTRING* retval)
Все WinRT-методы должны быть частью интерфейса, реализуемого объектом. Наш метод Join, например, мог бы быть частью интерфейса IConcatenation, поддерживаемого классом StringUtilities. Перед вызовом метода Join исполняющая среда CLR должна получить указатель на интерфейс IConcatenation.
Задача интерфейса маршалинга заключается в преобразовании исходного управляемого вызова RCW в конечный WinRT-вызов WinRT-интерфейса. В данном случае псевдокод для интерфейса маршалинга мог бы выглядеть так, как показано на рис. 3 (вызовы очистки опущены, чтобы не усложнять общую картину).
Рис. 3. Пример интерфейса маршалинга для вызова из CLR в Windows Runtime
public string Join(IEnumerable<string> list, string separator)
{
// Преобразуем управляемые параметры в WinRT-типы
CCW ccwList = GetCCW(list);
IIterable<HSTRING>* pCcwIterable = ccwList.QueryInterface(IID_IIterable_HSTRING);
HSTRING hstringSeparator = StringToHString(separator);
// Объект, вызываемый управляемым кодом,
// на самом деле является RCW
RCW rcw = this;
// Вам нужно найти указатель на WinRT-интерфейс
// для IConcatenation, реализуемого RCW,
// чтобы вызвать его метод Join
IConcatination* pConcat = null;
HRESULT hrQI = rcw.QueryInterface(IID_ IConcatenation, out pConcat);
if (FAILED(hrQI))
{
// Чаще всего это InvalidCastException из-за того, что
// WinRT-объект возвращает E_NOINTERFACE для интерфейса,
// который содержит вызываемый вами метод
Exception qiError = GetExceptionForHR(hrQI);
throw qiError;
}
// Вызов реального метода Join
HSTRING returnValue;
HRESULT hrCall = pConcat->Join(pCcwIterable, hstringSeparator, &returnValue);
// Если WinRT-метод завершается неудачей,
// преобразуем ошибку в исключение
if (FAILED(hrCall))
{
Exception callError = GetExceptionForHR(hrCall);
throw callError;
}
// Преобразуем выходные параметры из WinRT-типов в .NET-типы
return HStringToString(returnValue);
}
В этом примере первый шаг — преобразование управляемых параметров из их управляемого представления в соответствующее WinRT-представление. В данном случае код создает CCW для параметра list и преобразует параметр System.String в HSTRING.
Следующий шаг — поиск WinRT-интерфейса, который предоставляет реализацию Join. Для этого из QueryInterface вызывается WinRT-объект, обернутый в RCW, в котором управляемый код вызывал Join. Самая распространенная причина того, что при вызове какого-либо WinRT-метода генерируется исключение InvalidCastException, заключается в неудачном завершении QueryInterface-вызова. Одна из причин, по которой это может происходить, — WinRT-объект не реализует все интерфейсы, которые ожидает вызвавший код.
Теперь начинаются реальные действия: interop-интерфейс выполняет реальный вызов WinRT-метода Join, предоставляя ему место для сохранения возвращаемого значения HSTRING. Если WinRT-метод завершается неудачей, он сообщает об ошибке через HRESULT, который interop-интерфейс преобразует в объект-исключение и генерирует его. То есть, если ваш управляемый код видит исключение, сгенерированное в результате вызова WinRT-метода, то скорее всего этот WinRT-метод вернул HRESULT с ошибкой и CLR сгенерировала исключение, чтобы передать в ваш код эту ошибку.
Заключительный шаг — преобразование выходных параметров из WinRT-представления в .NET-форму. В этом примере логическое возвращаемое значение является выходным параметром вызова Join, и его нужно преобразовать из HSTRING в .NET String. Потом это значение может быть возвращено как результат интерфейса (stub).
Вызовы управляемого кода из Windows Runtime
Вызовы, исходящие из Windows Runtime и адресованные управляемому коду, работают аналогичным образом. CLR реагирует на QueryInterface-вызовы, которые компонент Windows Runtime адресует ей через интерфейс, имеющий таблицу виртуальных функций; эта таблица заполняется с помощью методов interop-интерфейса. Эти методы делают то же самое, о чем я рассказывал ранее, но в обратном направлении.
Рассмотрим еще раз случай с Join API с тем исключением, что на этот раз предположим, что он реализован в управляемом коде и вызывается из компонента Windows Runtime. Псевдокод интерфейса (stub), который обеспечивает такой переход, мог бы выглядеть примерно так, как показано на рис. 4.
Рис. 4. Пример интерфейса маршалинга для вызова CLR из Windows Runtime
HRESULT Join(__in IIterable<HSTRING>* list,
HSTRING separator, __out HSTRING* retval)
{
*retval = null;
// Объект, вызываемый неуправляемым кодом,
// на самом деле является CCW
CCW ccw = GetCCWFromComPointer(this);
// Преобразуем WinRT-параметры в управляемые типы
RCW rcwList = GetRCW(list);
IEnumerable<string> managedList = (IEnumerable<string>)rcwList;
string managedSeparator = HStringToString(separator);
string result;
try
{
// Вызываем управляемую реализацию Join
result = ccw.Join(managedList, managedSeparator);
}
catch (Exception e)
{
// Управляемая реализация сгенерировала исключение -
// возвращаем его как ошибку в HRESULT
return GetHRForException(e);
}
// Преобразуем выходное значение
// из управляемого типа в WinRT-тип
*retval = StringToHSTring(result);
return S_OK;
}
Сначала этот код преобразует входные параметры из WinRT-типов данных в управляемые типы. Если исходить из того, что входной список является WinRT-объектом, интерфейс должен получить RCW, представляющий этот объект, чтобы управляемый код мог использовать его. Строковое значение просто преобразуется из HSTRING в System.String.
Далее осуществляется вызов управляемой реализации метода Join в CCW. Если этот метод генерирует исключение, interop-интерфейс захватывает его и преобразует в HRESULT с ошибкой, возвращаемый вызвавшему WinRT-коду. Это объясняет, почему некоторые исключения, генерируемые из управляемого кода, вызываемого компонентами Windows Runtime, не приводят к краху процесса. Если компонент Windows Runtime обрабатывает HRESULT ошибки, то в конечном счете это равносильно захвату и обработке сгенерированного исключения.
Заключительный шаг — преобразование выходного параметра из .NET-типа данных в эквивалентный WinRT-тип данных; в данном случае System.String преобразуется в HSTRING. Затем возвращаемое значение помещается в выходной параметр и возвращает HRESULT, сообщающий об успешном выполнении.
Проецируемые интерфейсы
Ранее я упоминал о том, что CLR будет проецировать некоторые WinRT-интерфейсы в эквивалентные .NET-интерфейсы. Например, IMap<K,V> проецируется в IDictionary<K,V>. То есть любое WinRT-сопоставление доступно как .NET-словарь и наоборот. Для поддержки такого проецирования нужно, чтобы еще один набор интерфейсов (stubs) реализовал WinRT-интерфейс в терминах .NET-интерфейса, в который он проецируется, и наоборот. Нарпимер, у IDictionary<K,V> есть метод TryGetValue, а у IMap<K,V> — нет. Чтобы вызывающий управляемый код мог использовать TryGetValue, CLR предоставляет интерфейс, реализующий этот метод в терминах уже имеющихся методов IMap. Это может выглядеть так, как показано на рис. 5.
Рис. 5. Концептуальная реализация IDictionary в терминах IMap
bool TryGetValue(K key, out V value)
{
// "this" is the IMap RCW
IMap<K,V> rcw = this;
// IMap provides a HasKey and Lookup function, so you can
// implement TryGetValue in terms of those functions
if (!rcw.HasKey(key))
return false;
value = rcw.Lookup(key);
return true;
}
Любое WinRT-сопоставление доступно как .NET-словарь и наоборот.
Заметьте, что в процессе своей работы этот интерфейс преобразования несколько раз обращается к нижележащей реализации IMap. Например, вы написали следующий фрагмент управляемого кода, чтобы выяснить, содержит ли объект Windows.Foundation.Collections.PropertySet ключ «NYJ»:
object value;
if (propertySet.TryGetValue("NYJ", out value))
{
// ...
}
Поскольку вызов TryGetValue определяет, содержит ли набор свойств этот ключ, стек вызовов мог бы выглядеть, как показано в табл. 2.
Табл. 2. Стек вызовов для вызова TryGetValue
Стек | Описание |
PropertySet::HasKey | WinRT-реализация PropertySet |
HasKey_Stub | Интерфейс маршалинга, преобразующий вызов HasKey интерфейса словаря в WinRT-вызов |
TryGetValue_Stub | Интерфейс, реализующий IDictionary в терминах IMap |
Application | Управляемый код приложения, вызывающий PropertySet.TryGetValue |
Заключение
Поддержка Windows Runtime в CLR позволяет разработчикам управляемого кода вызывать WinRT API, определенные в файлах WinMD, так же легко, как и управляемые API, определенные в стандартных .NET-сборках. За кулисами CLR использует адаптер метаданных для проецирования, помогающего объединить системы типов WinRT и .NET. Кроме того, она использует набор interop-интерфейсов, позволяющих .NET-коду вызывать WinRT-методы и наоборот. Все вместе эти методики упрощают разработчикам управляемого кода вызовы WinRT API из приложений Windows Store.