Componentes do Windows Runtime com C# e Visual Basic

Você pode usar o código gerenciado para criar seus próprios tipos de Windows Runtime e empacotá-los em um componente Windows Runtime. Você pode usar seu componente em aplicativos Plataforma Universal do Windows (UWP) escritos em C++, JavaScript, Visual Basic ou C#. Este tópico descreve as regras para a criação de um componente e discute alguns aspectos do suporte do .NET ao Windows Runtime. Em geral, esse suporte foi projetado para ser transparente para o programador do .NET. No entanto, ao criar um componente a ser usado com JavaScript ou C++, você precisa estar ciente das diferenças na maneira como essas linguagens dão suporte ao Windows Runtime.

Se você estiver criando um componente para uso somente em aplicativos UWP escritos em Visual Basic ou C#, e o componente não contiver controles UWP, considere usar o modelo biblioteca de classes em vez do modelo de projeto componente Windows Runtime no Microsoft Visual Studio. Existem menos restrições em uma biblioteca de classes simples.

Observação

Para desenvolvedores do C# que estão escrevendo aplicativos de área de trabalho no .NET 6 ou posterior, use C#/WinRT para criar um componente do Windows Runtime. Confira Criar componentes do Windows Runtime com C#/WinRT.

Declarando tipos em componentes Windows Runtime

Internamente, os tipos de Windows Runtime em seu componente podem usar qualquer funcionalidade do .NET permitida em um aplicativo UWP. Para obter mais informações, consulte .NET para aplicativos UWP.

Externamente, os membros de seus tipos podem expor apenas Windows Runtime tipos para seus parâmetros e valores retornados. A lista a seguir descreve as limitações em tipos .NET expostos de um componente Windows Runtime.

  • Os campos, os parâmetros e os valores de retorno de todos os tipos públicos e membros no componente devem ser tipos do Windows Runtime. Essa restrição inclui os tipos de Windows Runtime que você cria, bem como os tipos fornecidos pelo próprio Windows Runtime. Ele também inclui vários tipos .NET. A inclusão desses tipos faz parte do suporte que o .NET fornece para habilitar o uso natural do Windows Runtime no código gerenciado— seu código parece usar tipos .NET familiares em vez dos tipos de Windows Runtime subjacentes. Por exemplo, você pode usar tipos primitivos do .NET, como Int32 e Double, certos tipos fundamentais, como DateTimeOffset e Uri, e alguns tipos de interface genérica comumente usados, como IEnumerable<T> (IEnumerable(Of T) no Visual Basic) e IDictionary<TKey,TValue>. Observe que os argumentos de tipo desses tipos genéricos devem ser Windows Runtime tipos. Isso é discutido nas seções Passando tipos de Windows Runtime para código gerenciado e Passando tipos gerenciados para o Windows Runtime, mais adiante neste tópico.

  • Classes públicas e interfaces podem conter métodos, propriedades e eventos. Você pode declarar delegados para seus eventos ou usar o delegado EventHandler<T> . Uma classe ou interface pública não pode:

    • Ser genérica.
    • Implemente uma interface que não seja uma interface Windows Runtime (no entanto, você pode criar suas próprias interfaces Windows Runtime e implementá-las).
    • Derivam de tipos que não estão no Windows Runtime, como System.Exception e System.EventArgs.
  • Todos os tipos públicos devem ter um namespace raiz correspondente ao nome do assembly, e o nome do assembly não deve começar com "Windows".

    Dica. Por padrão, os projetos no Visual Studio têm os nomes de namespace correspondentes ao nome do assembly. No Visual Basic, a instrução Namespace desse namespace padrão não é mostrada no código.

  • As estruturas públicas não podem ter membros que não sejam campos públicos, e esses campos devem ser tipos de valor ou cadeias de caracteres.

  • As classes públicas devem ser sealed (NotInheritable no Visual Basic). Se o modelo de programação exigir polimorfismo, você poderá criar uma interface pública e implementar essa interface nas classes que devem ser polimórficas.

Depuração do componente

Se o aplicativo UWP e o componente forem criados com código gerenciado, você poderá depurá-los ao mesmo tempo.

Ao testar seu componente como parte de um aplicativo UWP usando C++, você pode depurar código gerenciado e nativo ao mesmo tempo. O padrão é somente código nativo.

Para depurar códigos em C++ nativo e gerenciado

  1. Abra o menu de atalho do projeto do Visual C++ e escolha Propriedades.
  2. Nas páginas de propriedades, em Propriedades de Configuração, escolha Depuração.
  3. Escolha Tipo de Depurador e, na caixa de listagem suspensa, altere Native Only para Misto (Gerenciado e Nativo). Selecione OK.
  4. Defina pontos de interrupção nos códigos nativo e gerenciado.

Quando você está testando seu componente como parte de um aplicativo UWP usando JavaScript, por padrão, a solução está no modo de depuração javaScript. No Visual Studio, não é possível depurar JavaScript e código gerenciado ao mesmo tempo.

Para depurar código gerenciado em vez de JavaScript

  1. Abra o menu de atalho do projeto JavaScript e escolha Propriedades.
  2. Nas páginas de propriedades, em Propriedades de Configuração, escolha Depuração.
  3. Escolha Tipo de Depurador e, na caixa de listagem suspensa, altere Script Only para Managed Only. Selecione OK.
  4. Defina pontos de interrupção no código gerenciado e depure como sempre.

Passagem de tipos do Windows Runtime para código gerenciado

Conforme mencionado anteriormente na seção Declarando tipos em componentes Windows Runtime, determinados tipos .NET podem aparecer nas assinaturas de membros de classes públicas. Isso faz parte do suporte que o .NET fornece para habilitar o uso natural do Windows Runtime no código gerenciado. Ele inclui tipos primitivos e algumas classes e interfaces. Quando o componente é usado do JavaScript ou do código C++, é importante saber como seus tipos .NET aparecem para o chamador. Consulte Passo a passo da criação de um componente de Windows Runtime C# ou Visual Basic e chamando-o de JavaScript para obter exemplos com JavaScript. Esta seção aborda os tipos mais usados.

No .NET, tipos primitivos como a estrutura Int32 têm muitas propriedades e métodos úteis, como o método TryParse . Por outro lado, tipos primitivos e estruturas no Windows Runtime só têm campos. Quando você passa esses tipos para o código gerenciado, eles parecem ser tipos .NET e você pode usar as propriedades e os métodos de tipos .NET como faria normalmente. A lista a seguir resume as substituições que são feitas automaticamente no IDE:

  • Para os primitivos Windows Runtime Int32, Int64, Single, Double, Boolean, String (uma coleção imutável de caracteres Unicode), Enum, UInt32, UInt64 e Guid, usam o tipo do mesmo nome no namespace System.
  • Para UInt8, use System.Byte.
  • Para Char16, use System.Char.
  • Para a interface IInspectable , use System.Object.

Se o C# ou o Visual Basic fornecer uma linguagem palavra-chave para qualquer um desses tipos, você poderá usar a linguagem palavra-chave em vez disso.

Além dos tipos primitivos, alguns tipos de Windows Runtime básicos e comumente usados aparecem no código gerenciado como seus equivalentes do .NET. Por exemplo, suponha que o código JavaScript use a classe Windows.Foundation.Uri e você queira passá-la para um método C# ou Visual Basic. O tipo equivalente no código gerenciado é a classe .NET System.Uri e esse é o tipo a ser usado para o parâmetro de método. Você pode saber quando um tipo de Windows Runtime aparece como um tipo .NET, pois o IntelliSense no Visual Studio oculta o tipo de Windows Runtime quando você está escrevendo código gerenciado e apresenta o tipo .NET equivalente. (Normalmente, os dois tipos têm o mesmo nome. No entanto, observe que a estrutura Windows.Foundation.DateTime aparece no código gerenciado como System.DateTimeOffset e não como System.DateTime.)

Para alguns tipos de coleção comumente usados, o mapeamento é entre as interfaces implementadas por um tipo de Windows Runtime e as interfaces implementadas pelo tipo .NET correspondente. Assim como acontece com os tipos mencionados acima, você declara tipos de parâmetro usando o tipo .NET. Isso oculta algumas diferenças entre os tipos e torna a escrita de código .NET mais natural.

A tabela a seguir lista os tipos de interface genérica mais comuns, além de outros mapeamentos de classe e interface comuns. Para obter uma lista completa de tipos de Windows Runtime mapeados pelo .NET, confira Mapeamentos do .NET de tipos de Windows Runtime.

Windows Runtime .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

Quando um tipo implementa mais de uma interface, é possível usar qualquer uma das interfaces implementadas como um tipo de parâmetro ou um tipo de retorno de um membro. Por exemplo, você pode passar ou retornar um dicionário<int, cadeia de caracteres> (Dictionary(Of Integer, String) no Visual Basic) como IDictionary<int, string>, IReadOnlyDictionary<int, string> ou IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue>>.

Importante

O JavaScript usa a interface que aparece primeiro na lista de interfaces implementadas por um tipo gerenciado. Por exemplo, se você retornar Dictionary<int, string> para código JavaScript, ele será exibido como IDictionary<int, string>, independentemente de qual interface você especificar como o tipo de retorno. Isso significa que, caso a primeira interface não inclua um membro exibido em interfaces posteriores, esse membro não permanece visível para JavaScript.

No Windows Runtime, IMap<K, V> e IMapView<K, V> são iterados usando IKeyValuePair. Quando você os passa para o código gerenciado, eles aparecem como IDictionary<TKey, TValue> e IReadOnlyDictionary<TKey, TValue>, portanto, naturalmente, você usa System.Collections.Generic.KeyValuePair<TKey, TValue> para enumerá-los.

A forma como as interfaces aparecem no código gerenciado afeta a forma como aparecem os tipos que implementam essas interfaces. Por exemplo, a classe PropertySet implementa IMap<K, V>, que aparece no código gerenciado como IDictionary<TKey, TValue>. PropertySet aparece como se tivesse implementado IDictionary<TKey, TValue> em vez de IMap<K, V>, portanto, no código gerenciado, parece ter um método Add , que se comporta como o método Add em dicionários .NET. Ele não parece ter um método Insert . Você pode ver este exemplo no tópico Passo a passo da criação de um componente de Windows Runtime C# ou Visual Basic e chamá-lo do JavaScript.

Passagem de tipos gerenciados para o Windows Runtime

Conforme discutido na seção anterior, alguns tipos de Windows Runtime podem aparecer como tipos .NET nas assinaturas dos membros do componente ou nas assinaturas de membros Windows Runtime quando você os usa no IDE. Quando você passa tipos .NET para esses membros ou os usa como os valores retornados dos membros do componente, eles aparecem no código do outro lado como o tipo de Windows Runtime correspondente. Para obter exemplos dos efeitos que isso pode ter quando o componente é chamado do JavaScript, consulte a seção "Retornando tipos gerenciados do componente" em Passo a passo da criação de um componente de Windows Runtime C# ou Visual Basic e chamando-o do JavaScript.

Métodos sobrecarregados

No Windows Runtime, os métodos podem ser sobrecarregados. No entanto, se você declarar várias sobrecargas com o mesmo número de parâmetros, deverá aplicar o atributo Windows.Foundation.Metadata.DefaultOverloadAttribute a apenas uma dessas sobrecargas. Essa sobrecarga é a única que é possível chamar no JavaScript. Por exemplo, no código a seguir, a sobrecarga que utiliza um int (Integer em Visual Basic) é a sobrecarga padrão.

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

[IMPORTANTE] O JavaScript permite que você passe qualquer valor para OverloadExample e coagi o valor para o tipo exigido pelo parâmetro . Você pode chamar OverloadExample com "quarenta e dois", "42" ou 42,3, mas todos esses valores são passados para a sobrecarga padrão. A sobrecarga padrão no exemplo anterior retorna 0, 42 e 42, respectivamente.

Não é possível aplicar o atributo e DefaultOverloadAttributaos construtores. Todos os construtores em uma classe devem ter números de parâmetros diferentes.

Implementação de IStringable

Começando com Windows 8.1, o Windows Runtime inclui uma interface IStringable cujo único método, IStringable.ToString, fornece suporte de formatação básica comparável ao fornecido por Object.ToString. Se você optar por implementar IStringable em um tipo gerenciado público exportado em um componente Windows Runtime, as seguintes restrições se aplicarão:

  • Você pode definir a interface IStringable somente em uma relação de "implementações de classe", como o código a seguir em C#:

    public class NewClass : IStringable
    

    Ou o seguinte código do Visual Basic:

    Public Class NewClass : Implements IStringable
    
  • Você não pode implementar IStringable em uma interface.

  • Você não pode declarar um parâmetro como do tipo IStringable.

  • IStringable não pode ser o tipo de retorno de um método, propriedade ou campo.

  • Você não pode ocultar sua implementação IStringable de classes base usando uma definição de método como a seguinte:

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

    Em vez disso, a implementação IStringable.ToString deve sempre substituir a implementação da classe base. Você pode ocultar uma implementação ToString apenas invocando-a em uma instância de classe fortemente tipada.

Observação

Em uma variedade de condições, chamadas de código nativo para um tipo gerenciado que implementa IStringable ou oculta sua implementação de ToString podem produzir um comportamento inesperado.

Operações assíncronas

Para implementar um método assíncrono em seu componente, adicione "Async" ao final do nome do método e retorne uma das interfaces Windows Runtime que representam ações ou operações assíncronas: IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> ou IAsyncOperationWithProgress<TResult, TProgress>.

Você pode usar tarefas do .NET (a classe Task e a classe TResult> de tarefa< genérica) para implementar seu método assíncrono. Você deve retornar uma tarefa que representa uma operação em andamento, como uma tarefa retornada de um método assíncrono escrito em C# ou Visual Basic, ou uma tarefa retornada do método Task.Run . Caso use um construtor para criar a tarefa, você deve chamar o método Task.Start antes de devolvê-lo.

Um método que usa await (Await no Visual Basic) requer o async palavra-chave (Async no Visual Basic). Se você expor esse método de um componente Windows Runtime, aplique o async palavra-chave ao delegado que você passa para o método Run.

Para ações e operações assíncronas que não dão suporte ao cancelamento ou aos relatórios de progresso, é possível usar o método de extensão WindowsRuntimeSystemExtensions.AsAsyncAction ou AsAsyncOperation<TResult> para encapsular a tarefa na interface apropriada. Por exemplo, o código a seguir implementa um método assíncrono usando o método Task.Run<TResult> para iniciar uma tarefa. O método de extensão AsAsyncOperation<TResult> retorna a tarefa como um Windows Runtime operação assíncrona.

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

O código JavaScript a seguir mostra como o método pode ser chamado usando um objeto WinJS.Promise . A função passada para o método then é executada quando a chamada assíncrona é concluída. O parâmetro stringList contém a lista de cadeias de caracteres retornadas pelo método DownloadAsStringAsync e a função faz qualquer processamento necessário.

function asyncExample(id) {

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

Para ações e operações assíncronas que dão suporte a cancelamento ou relatório de progresso, use a classe AsyncInfo para gerar uma tarefa iniciada e conectar os recursos de relatório de cancelamento e progresso da tarefa com os recursos de relatório de cancelamento e progresso da interface de Windows Runtime apropriada. Para obter um exemplo que dá suporte a relatórios de cancelamento e progresso, consulte Passo a passo da criação de um componente de Windows Runtime C# ou Visual Basic e chamando-o do JavaScript.

Observe que você pode usar os métodos da classe AsyncInfo mesmo que seu método assíncrono não dê suporte a relatórios de cancelamento ou progresso. Se você usar uma função lambda do Visual Basic ou um método anônimo C#, não forneça parâmetros para o token e a interface IProgress<T> . Caso você use uma função lambda de C#, forneça um parâmetro token, mas o ignore. O exemplo anterior, que usou o método AsAsyncOperation<TResult> , tem esta aparência quando você usa a sobrecarga do método 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

Se você criar um método assíncrono que, opcionalmente, dê suporte a relatórios de cancelamento ou progresso, considere adicionar sobrecargas que não têm parâmetros para um token de cancelamento ou a interface IProgress<T> .

Acionamento de exceções

É possível acionar qualquer tipo de exceção que esteja incluído no .NET para aplicativos do Windows. Não é possível declarar os próprios tipos de exceção pública em um componente do Tempo de Execução do Windows, mas você pode declarar e acionar tipos não públicos.

Caso o componente não manipule a exceção, uma exceção correspondente é acionada no código que chamou o componente. A maneira como a exceção é exibida para o chamador depende da maneira como a linguagem de chamada dá suporte ao Windows Runtime.

  • Em JavaScript, a exceção é exibida como um objeto no qual a mensagem de exceção é substituída por um rastreamento de pilha. Ao depurar o aplicativo no Visual Studio, você pode ver o texto da mensagem original exibido na caixa de diálogo de exceção do depurador, identificado como "Informações de WinRT". Não é possível acessar o texto da mensagem original no código JavaScript.

    Dica. No momento, o rastreamento de pilha contém o tipo de exceção gerenciado, mas não é recomendável analisar o rastreamento para identificar o tipo de exceção. Em vez disso, use um valor HRESULT conforme descrito mais à frente nesta seção.

  • Em C++, a exceção é exibida como uma exceção da plataforma. Se a propriedade HResult da exceção gerenciada puder ser mapeada para o HRESULT de uma exceção de plataforma específica, a exceção específica será usada; caso contrário, uma exceção Platform::COMException é gerada. O texto da mensagem da exceção gerenciada não está disponível para o código C++. Caso uma exceção de plataforma específica seja acionada, o texto da mensagem padrão desse tipo de exceção é exibido; do contrário, nenhum texto de mensagem é exibido. Consulte Exceções (C++/CX).

  • Em C# ou Visual Basic, a exceção é uma exceção gerenciada normal.

Ao acionar uma exceção no componente, você pode facilitar para um chamador JavaScript ou C++ lidar com a exceção acionando um tipo de exceção não público cujo valor da propriedade HResult é específico do componente. O HRESULT está disponível para um chamador JavaScript por meio da propriedade number do objeto de exceção e para um chamador C++ por meio da propriedade COMException::HResult .

Observação

Use um valor negativo para o HRESULT. Um valor positivo é interpretado como êxito, e nenhuma exceção é acionada no chamador JavaScript ou C++.

Declaração e acionamento de eventos

Quando você declara um tipo para manter os dados do evento, derive de Object, em vez de EventArgs, porque EventArgs não é um tipo do Windows Runtime. Use EventHandler<TEventArgs> como o tipo do evento e use o tipo de argumento event como o argumento de tipo genérico. Gere o evento da mesma forma que faria em um aplicativo .NET.

Quando o componente do Tempo de Execução do Windows é usado em JavaScript ou C++, o evento segue o padrão de evento do Windows Runtime que essas linguagens esperam. Quando você usa o componente do C# ou do Visual Basic, o evento aparece como um evento .NET comum. Um exemplo é fornecido no Passo a passo da criação de um componente de Windows Runtime C# ou Visual Basic e chamá-lo do JavaScript.

Caso implemente acessadores de eventos personalizado (declarar um evento com a palavra-chave Custom, em Visual Basic), você deve seguir o padrão de evento do Windows Runtime na implementação. Consulte Eventos personalizados e acessadores de eventos em componentes Windows Runtime. Observe que, quando você manipula o evento do código C# ou Visual Basic, ele ainda parece ser um evento .NET comum.

Próximas etapas

Depois de criar um componente Windows Runtime para seu próprio uso, você pode descobrir que a funcionalidade encapsulada por ele é útil para outros desenvolvedores. Você tem duas opções para empacotar um componente para distribuição a outros desenvolvedores. Consulte Distribuição de um componente do Tempo de Execução do Windows gerenciado.

Para obter mais informações sobre os recursos da linguagem Visual Basic e C# e o suporte do .NET para o Windows Runtime, consulte a documentação do Visual Basic e do C#.

Solução de problemas

Sintoma Medida
Em um aplicativo C++/WinRT, ao consumir um componente do Windows Runtime com C# que usa o XAML, o compilador produz um erro no formato "'MyNamespace_XamlTypeInfo': não é um membro de 'winrt::MyNamespace'", em que MyNamespace é o nome do namespace do componente do Windows Runtime. Em pch.h no aplicativo C++/WinRT de consumo, adicione #include <winrt/MyNamespace.MyNamespace_XamlTypeInfo.h>substituindo MyNamespace conforme apropriado.