Создание компонентов среды выполнения Windows с помощью C# и Visual Basic

Вы можете использовать управляемый код для создания собственных типов среда выполнения Windows и их упаковки в компонент среда выполнения Windows. Компонент можно использовать в приложениях универсальная платформа Windows (UWP), написанных на C++, JavaScript, Visual Basic или C#. В данном разделе описываются правила создания компонентов и рассматриваются некоторые аспекты поддержки среды выполнения Windows в .NET. Как правило, такая поддержка разрабатывается таким образом, чтобы быть прозрачной для разработчиков для .NET. Однако при создании компонента, использующего JavaScript или C++, следует учитывать различия в том, как эти языки поддерживают среду выполнения Windows.

Если вы создаете компонент для использования только в приложениях UWP, написанных на Visual Basic или C#, и компонент не содержит элементов управления UWP, рассмотрите возможность использования шаблона библиотеки классов вместо шаблона проекта компонент среда выполнения Windows в Microsoft Visual Studio. У простой библиотеки классов меньше ограничений.

Примечание

Для создания компонента среды выполнения Windows разработчики C#, создающие классические приложения в .NET 6 или более поздней версии, могут использовать C#/WinRT. См. статью Создание компонентов среды выполнения Windows с помощью C#/WinRT.

Объявление типов в компонентах среда выполнения Windows

На внутреннем уровне типы среда выполнения Windows в компоненте могут использовать любые функции .NET, которые разрешены в приложении UWP. Дополнительные сведения см. в разделе .NET для приложений UWP.

С внешней стороны члены типов могут предоставлять только среда выполнения Windows типы для их параметров и возвращаемых значений. В следующем списке описаны ограничения на типы .NET, предоставляемые компонентом среда выполнения Windows.

  • Поля, параметры и возвращаемые значения всех открытых типов и членов в ваших компонентах должны относиться к типам среды выполнения Windows. Это ограничение включает в себя среда выполнения Windows типы, которые вы создаете, а также типы, предоставляемые самим среда выполнения Windows. Он также включает несколько типов .NET. Включение этих типов является частью поддержки, которую предоставляет .NET, чтобы обеспечить естественное использование среда выполнения Windows в управляемом коде— код, как представляется, использует знакомые типы .NET вместо базовых типов среда выполнения Windows. Например, можно использовать примитивные типы .NET, такие как Int32 и Double, некоторые фундаментальные типы, такие как DateTimeOffset и Uri, а также некоторые часто используемые универсальные типы интерфейса, такие как IEnumerable<T> (IEnumerable(Of T) в Visual Basic) и IDictionary<TKey,TValue>. Обратите внимание, что аргументы типа этих универсальных типов должны быть среда выполнения Windows типами. Это рассматривается в разделах Передача среда выполнения Windows типов в управляемый код и Передача управляемых типов в среда выполнения Windows далее в этом разделе.

  • Открытые классы и интерфейсы могут содержать методы, свойства и события. Можно объявить делегаты для событий или использовать делегат EventHandler<T> . Открытый класс или интерфейс не может:

    • быть универсальными;
    • Реализуйте интерфейс, который не является среда выполнения Windows интерфейсом (однако можно создать собственные интерфейсы среда выполнения Windows и реализовать их).
    • Наследуйте типы, которые не находятся в среда выполнения Windows, например System.Exception и System.EventArgs.
  • Корневое пространство имен всех открытых типов должно совпадать с именем сборки, а имя сборки не должно начинаться на «Windows».

    Совет. По умолчанию имена пространств имен в проектах Visual Studio совпадают с именами сборок. В Visual Basic оператор Namespace для данного пространства имен по умолчанию в коде не отображается.

  • Открытые структуры не могут иметь членов, отличных от открытых полей, а эти поля должны иметь тип значения или строковый тип.

  • Открытые классы должны быть запечатанными (NotInheritable в Visual Basic). Если для модели программирования требуется полиморфизм, можно создать открытый интерфейс и реализовать его в классах, которые должны быть полиморфными.

Отладка компонента

Если приложение UWP и компонент созданы с помощью управляемого кода, их можно отлаживать одновременно.

При тестировании компонента в составе приложения UWP с помощью C++ можно одновременно отлаживать управляемый и машинный код. По умолчанию может отлаживаться только машинный код.

Одновременная отладка машинного кода C++ и управляемого кода

  1. Откройте контекстное меню проекта Visual C++ и выберите пункт Свойства.
  2. На страницах свойств в разделе Свойства конфигурации выберите Отладка.
  3. Выберите Тип отладчика и в раскрывающемся списке измените значение Только машинный код на Смешанный (управляемый и машинный). Нажмите кнопку ОК.
  4. Установите точки останова в машинном и управляемом коде.

При тестировании компонента в составе приложения UWP с помощью JavaScript решение по умолчанию находится в режиме отладки JavaScript. В Visual Studio невозможно отлаживать JavaScript и управляемый код одновременно.

Отладка управляемого кода вместо кода JavaScript

  1. Откройте контекстное меню проекта JavaScript и выберите пункт Свойства.
  2. На страницах свойств в разделе Свойства конфигурации выберите Отладка.
  3. Выберите Тип отладчика и в раскрывающемся списке замените значение Только скрипт на Только управляемый код. Нажмите кнопку ОК.
  4. Установите точки останова в управляемом коде и выполните отладку обычным образом.

Передача типов среды выполнения Windows в управляемый код

Как упоминалось ранее в разделе Объявление типов в компонентах среда выполнения Windows, некоторые типы .NET могут отображаться в сигнатурах членов открытых классов. Это часть поддержки, которую предоставляет .NET для естественного использования среда выполнения Windows в управляемом коде. Эти возможности включают простые типы, а также некоторые классы и интерфейсы. При использовании компонента из JavaScript или кода C++ важно знать, как типы .NET отображаются вызывающему объекту. Примеры с помощью JavaScript см. в статье Пошаговое руководство по созданию компонента C# или Visual Basic среда выполнения Windows и его вызову из JavaScript. В этом разделе рассматриваются часто используемые типы.

В .NET примитивные типы, такие как структура Int32 , имеют множество полезных свойств и методов, таких как метод TryParse . В среде выполнения Windows, напротив, простые типы имеют только поля. При передаче этих типов в управляемый код они кажутся типами .NET, и вы можете использовать свойства и методы типов .NET, как обычно. В следующем списке перечислены подстановки, которые автоматически выполняются в интегрированной среде разработки:

  • Для среда выполнения Windows примитивов Int32, Int64, Single, Double, Boolean, String (неизменяемая коллекция символов Юникода), Enum, UInt32, UInt64 и Guid используйте тип того же имени в пространстве имен System.
  • Для UInt8 используйте System.Byte.
  • Для Char16 используйте System.Char.
  • Для интерфейса IInspectable используйте System.Object.

Если C# или Visual Basic предоставляет ключевое слово языка для любого из этих типов, вы можете использовать ключевое слово языка.

В дополнение к примитивным типам некоторые базовые, часто используемые среда выполнения Windows типы отображаются в управляемом коде в качестве эквивалентов .NET. Например, предположим, что код JavaScript использует класс Windows.Foundation.Uri и вы хотите передать его в метод C# или Visual Basic. Эквивалентным типом в управляемом коде является класс .NET System.Uri , который используется для параметра method. Вы можете определить, когда тип среда выполнения Windows отображается как тип .NET, так как IntelliSense в Visual Studio скрывает тип среда выполнения Windows при написании управляемого кода и представляет эквивалентный тип .NET. (Обычно два типа имеют одинаковые имена. Однако обратите внимание, что структура Windows.Foundation.DateTime отображается в управляемом коде как System.DateTimeOffset , а не Как System.DateTime.)

Для некоторых часто используемых типов коллекций сопоставление выполняется между интерфейсами, реализованными среда выполнения Windows типом, и интерфейсами, реализованными соответствующим типом .NET. Как и в случае с упомянутыми выше типами, типы параметров объявляются с помощью типа .NET. Это скрывает некоторые различия между типами и делает написание кода .NET более естественным.

В следующей таблице перечислены наиболее распространенные из этих типов универсальных интерфейсов, а также другие сопоставления часто используемых классов и интерфейсов. Полный список типов среда выполнения Windows, которые сопоставляет .NET, см. в разделе Сопоставления типов среда выполнения Windows .NET.

Среда выполнения Windows .NET
IIterable<T> IEnumerable<T>
IVector<T> IList<T>
IVectorView<T> IReadOnlyList<T>
IMap<K, V> IDictionary<TKey, TValue>
IMapView<K, V> IReadOnlyDictionary<TKey, TValue>
IKeyValuePair<K, V> KeyValuePair<TKey, TValue>
IBindableIterable IEnumerable
IBindableVector IList
Windows.UI.Xaml.Data.INotifyPropertyChanged System.ComponentModel.INotifyPropertyChanged.
Windows.UI.Xaml.Data.PropertyChangedEventHandler System.ComponentModel.PropertyChangedEventHandler
Windows.UI.Xaml.Data.PropertyChangedEventArgs System.ComponentModel.PropertyChangedEventArgs

Если тип реализует несколько интерфейсов, в качестве типа параметра или типа возвращаемого значения члена можно использовать любой из этих интерфейсов. Например, вы можете передать или вернуть значение dictionary<int, string> (Dictionary(Of Integer, String) в Visual Basic) в виде IDictionary<int, string>, IReadOnlyDictionary<int, string> или IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue>>.

Важно!

В коде JavaScript используется интерфейс, занимающий первую позицию в списке интерфейсов, реализуемых управляемым типом. Например, если вы возвращаете Dictionary<int, string> в код JavaScript, он будет отображаться как IDictionary<int, строка> независимо от того, какой интерфейс вы указали в качестве возвращаемого типа. Это означает, что если первый интерфейс не включает элемент, который отображается в последующих интерфейсах, этот элемент не будет видимым в JavaScript.

В среда выполнения Windows IMap<K, V> и IMapView<K, V> выполняются итерации с помощью IKeyValuePair. При передаче их в управляемый код они отображаются как IDictionary<TKey, TValue> и IReadOnlyDictionary<TKey, TValue>, поэтому для их перечисления вы, естественно , используете System.Collections.Generic.KeyValuePair<TKey, TValue> .

Представление интерфейсов в управляемом коде влияет на представление типов, реализующих эти интерфейсы. Например, класс PropertySet реализует IMap<K, V>, который отображается в управляемом коде как IDictionary<TKey, TValue>. PropertySet отображается так, как если бы он реализовал IDictionary<TKey, TValue> вместо IMap<K, V>, поэтому в управляемом коде у него есть метод Add , который ведет себя как метод Add в словарях .NET. Похоже, у него нет метода Insert . Этот пример можно увидеть в разделе Пошаговое руководство по созданию компонента C# или Visual Basic среда выполнения Windows и его вызову из JavaScript.

Передача управляемых типов в среду выполнения Windows

Как обсуждалось в предыдущем разделе, некоторые типы среда выполнения Windows могут отображаться как типы .NET в сигнатурах членов компонента или в сигнатурах среда выполнения Windows членов при их использовании в интегрированной среде разработки. Когда вы передаете типы .NET этим членам или используете их в качестве возвращаемых значений членов компонента, они отображаются в коде с другой стороны как соответствующий тип среда выполнения Windows. Примеры эффектов, которые это может иметь при вызове компонента из JavaScript, см. в разделе "Возвращение управляемых типов из компонента" статьи Пошаговое руководство по созданию компонента C# или Visual Basic среда выполнения Windows и его вызову из JavaScript.

Перегруженные методы

В среде выполнения Windows методы можно перегружать. Однако при объявлении нескольких перегрузок с одинаковым количеством параметров необходимо применить атрибут Windows.Foundation.Metadata.DefaultOverloadAttribute только к одной из этих перегрузок. Это будет единственная перегрузка, которую можно вызывать из JavaScript. Например, в следующем коде перегрузка, которая принимает значение int (Integer в Visual Basic), является перегрузкой по умолчанию.

public string OverloadExample(string s)
{
    return s;
}

[Windows.Foundation.Metadata.DefaultOverload()]
public int OverloadExample(int x)
{
    return x;
}
Public Function OverloadExample(ByVal s As String) As String
    Return s
End Function

<Windows.Foundation.Metadata.DefaultOverload> _
Public Function OverloadExample(ByVal x As Integer) As Integer
    Return x
End Function

[ВАЖНО] JavaScript позволяет передавать любое значение в OverloadExample и принуждает значение к типу, необходимому для параметра . Вы можете вызвать OverloadExample с "сорок два", "42" или 42.3, но все эти значения передаются перегрузке по умолчанию. Перегрузка по умолчанию в предыдущем примере возвращает значения 0, 42 и 42 соответственно.

Атрибут DefaultOverloadAttribute нельзя применить к конструкторам. Все конструкторы в классе должны иметь различное число параметров.

Реализация IStringable

Начиная с Windows 8.1, среда выполнения Windows включает интерфейс IStringable, один метод которого IStringable.ToString обеспечивает базовую поддержку форматирования, сравнимую с поддержкой Object.ToString. Если вы решили реализовать IStringable в общедоступном управляемом типе, экспортируемом в компоненте среда выполнения Windows, применяются следующие ограничения:

  • Интерфейс IStringable можно определить только в связи "класс реализует", например в следующем коде на C#:

    public class NewClass : IStringable
    

    или в следующем коде Visual Basic:

    Public Class NewClass : Implements IStringable
    
  • Невозможно реализовать IStringable в интерфейсе.

  • Нельзя объявить параметр типа IStringable.

  • IStringable не может быть типом возвращаемого значения метода, свойства или поля.

  • Невозможно скрыть реализацию IStringable от базовых классов с помощью определения метода, например следующего:

    public class NewClass : IStringable
    {
       public new string ToString()
       {
          return "New ToString in NewClass";
       }
    }
    

    Вместо этого реализация IStringable.ToString всегда должна переопределять реализацию базового класса. Вы можете скрыть реализацию ToString , только вызвав ее в строго типизированном экземпляре класса.

Примечание

При различных условиях вызовы из машинного кода в управляемый тип, реализующий IStringable или скрывающий свою реализацию ToString , могут привести к непредвиденному поведению.

Асинхронные операции

Чтобы реализовать асинхронный метод в компоненте, добавьте "Async" в конец имени метода и верните один из интерфейсов среда выполнения Windows, представляющих асинхронные действия или операции: IAsyncAction, IAsyncActionWithProgress TProgress<>, IAsyncOperation<TResult> или IAsyncOperationWithProgress<TResult, TProgress>.

Для реализации асинхронного метода можно использовать задачи .NET (класс Task и универсальный класс Task<TResult> ). Необходимо вернуть задачу, представляющую текущую операцию, например задачу, возвращаемую асинхронным методом, написанным на языке C# или Visual Basic, или задачу, возвращаемую методом Task.Run . При создании задачи с помощью конструктора необходимо перед возвращением задачи вызвать метод Task.Start.

Для метода, использующего await (Await в Visual Basic), требуется async ключевое слово (Async в Visual Basic). Если такой метод предоставляется из компонента среда выполнения Windows, примените async ключевое слово к делегату, передаваемого в метод Run.

Для асинхронных действий и операций, которые не поддерживают отчеты об отмене или ходе выполнения, можно использовать метод расширения WindowsRuntimeSystemExtensions.AsAsyncAction или AsAsyncOperation<TResult>, чтобы заключить задачу в соответствующий интерфейс. Например, следующий код реализует асинхронный метод с помощью метода Task.Run<TResult> для запуска задачи. Метод расширения AsAsyncOperation<TResult> возвращает задачу в виде среда выполнения Windows асинхронной операции.

public static IAsyncOperation<IList<string>> DownloadAsStringsAsync(string id)
{
    return Task.Run<IList<string>>(async () =>
    {
        var data = await DownloadDataAsync(id);
        return ExtractStrings(data);
    }).AsAsyncOperation();
}
Public Shared Function DownloadAsStringsAsync(ByVal id As String) _
     As IAsyncOperation(Of IList(Of String))

    Return Task.Run(Of IList(Of String))(
        Async Function()
            Dim data = Await DownloadDataAsync(id)
            Return ExtractStrings(data)
        End Function).AsAsyncOperation()
End Function

В следующем коде JavaScript показано, как метод можно вызвать с помощью объекта WinJS.Promise . Функция, которая передается методу then, выполняется при завершении асинхронного вызова. Параметр stringList содержит список строк, возвращаемых методом DownloadAsStringAsync , а функция выполняет любую необходимую обработку.

function asyncExample(id) {

    var result = SampleComponent.Example.downloadAsStringAsync(id).then(
        function (stringList) {
            // Place code that uses the returned list of strings here.
        });
}

Для асинхронных действий и операций, поддерживающих отчеты об отмене или ходе выполнения, используйте класс AsyncInfo для создания запущенной задачи и подключения функций отчетов об отмене и ходе выполнения задачи с функциями отчетов об отмене и ходе выполнения соответствующего интерфейса среда выполнения Windows. Пример, который поддерживает как отчеты об отмене, так и о ходе выполнения, см. в разделе Пошаговое руководство по созданию компонента C# или Visual Basic среда выполнения Windows и его вызову из JavaScript.

Обратите внимание, что методы класса AsyncInfo можно использовать, даже если асинхронный метод не поддерживает отчеты об отмене или ходе выполнения. Если вы используете лямбда-функцию Visual Basic или анонимный метод C#, не предоставляйте параметры для маркера и интерфейса IProgress<T> . При использовании лямбда-функции C# указывайте параметр токена, но игнорируйте его. Предыдущий пример, в котором использовался метод AsAsyncOperation<TResult> , выглядит следующим образом при использовании перегрузки метода AsyncInfo.Run<TResult>(Func<CancellationToken, Task<TResult>>).

public static IAsyncOperation<IList<string>> DownloadAsStringsAsync(string id)
{
    return AsyncInfo.Run<IList<string>>(async (token) =>
    {
        var data = await DownloadDataAsync(id);
        return ExtractStrings(data);
    });
}
Public Shared Function DownloadAsStringsAsync(ByVal id As String) _
    As IAsyncOperation(Of IList(Of String))

    Return AsyncInfo.Run(Of IList(Of String))(
        Async Function()
            Dim data = Await DownloadDataAsync(id)
            Return ExtractStrings(data)
        End Function)
End Function

Если вы создаете асинхронный метод, который при необходимости поддерживает отчеты об отмене или ходе выполнения, рассмотрите возможность добавления перегрузок, не имеющих параметров для маркера отмены или интерфейса IProgress<T> .

Создание исключений

Можно создавать исключения любого типа, включенного в .NET для приложений для Windows. В компоненте среды выполнения Windows нельзя объявлять собственные открытые типы исключений, но можно объявлять и создавать неоткрытые типы.

Если компонент не обрабатывает исключение, соответствующее исключение передается в код, который вызвал данный компонент. Представление этого исключения на вызывающей стороне зависит от того, каким образом язык вызова поддерживает среду выполнения Windows.

  • В JavaScript исключение представляется объектом, в котором сообщение исключения заменено на трассировку стека. При отладке приложения в Visual Studio исходный текст сообщения можно видеть в диалоговом окне исключений отладчика с пометкой "Сведения WinRT". Исходный текст сообщения недоступен из кода JavaScript.

    Совет. В настоящее время трассировка стека содержит тип управляемого исключения, однако анализировать трассировку для определения типа исключения не рекомендуется. Вместо этого лучше использовать значение HRESULT, описанное далее в этом разделе.

  • В C++ исключение является исключением платформы. Если свойство HResult управляемого исключения можно сопоставить с HRESULT конкретного исключения платформы, используется конкретное исключение; В противном случае создается исключение Platform::COMException . Текст сообщения управляемого исключения недоступен в коде C++. Если было создано конкретное исключение платформы, отображается текст сообщения по умолчанию для этого типа исключений; в противном случае текст не отображается. См. статью Исключения (C++/CX).

  • В C# и Visual Basic используется обычное управляемое исключение.

При создании в компоненте исключения можно упростить его обработку в вызывающем коде JavaScript или C++, если создавать исключения неоткрытых типов, свойство HResult которых связано с конкретным компонентом. HRESULT доступен вызывающей объекту JavaScript через свойство number объекта исключения, а вызывающий объект C++ — через свойство COMException::HResult .

Примечание

Используйте отрицательное значение для HRESULT. Положительное значение интерпретируется как успех, и исключение в вызывающем коде JavaScript или C++ не создается.

Объявление и вызов событий

При объявлении типа, в котором будут храниться данные вашего события, создавайте класс, производный от Object, а не от EventArgs, поскольку EventArgs не является типом среды выполнения Windows. Используйте EventHandler<TEventArgs> в качестве типа события, а тип аргумента события — в качестве аргумента универсального типа. Создайте событие так же, как в приложении .NET.

Когда компонент среды выполнения Windows вызывается из JavaScript или C++, событие выполняется в соответствии с шаблоном событий среды выполнения Windows, ожидаемым этими языками. При использовании компонента из C# или Visual Basic событие отображается как обычное событие .NET. Пример приведен в пошаговом руководстве по созданию компонента C# или Visual Basic среда выполнения Windows и его вызову из JavaScript.

При реализации пользовательских методов доступа к событиям (событие объявляется с ключевым словом Custom в Visual Basic) в такой реализации необходимо соблюдать шаблон событий среды выполнения Windows. См. раздел Пользовательские события и методы доступа к событиям в компонентах среда выполнения Windows. Обратите внимание, что при обработке события из кода C# или Visual Basic оно по-прежнему представляется обычным событием .NET.

Дальнейшие действия

После создания компонента среда выполнения Windows для собственного использования вы можете обнаружить, что инкапсулируемые в нем функции полезны для других разработчиков. Есть два способа упаковки компонента для распространения среди других разработчиков. См. раздел Распространение управляемого компонента среды выполнения Windows.

Дополнительные сведения о функциях языка Visual Basic и C#, а также о поддержке среда выполнения Windows в .NET см. в документации по Visual Basic и C#.

Устранение неполадок

Симптом Средство
В приложении C++/WinRT, при работе с компонентом C# среды выполнения Windows, который использует XAML, компилятор создает ошибку в форме "'MyNamespace_XaRuntime componentmlTypeInfo': не является членом 'winrt::MyNamespace'"— где MyNamespace — имя пространства имен компонента среды выполнения Windows. В pch.hфайле используемого приложения C++/WinRT, добавьте #include <winrt/MyNamespace.MyNamespace_XamlTypeInfo.h>, заменив MyNamespace соответствующим образом.