Componentes do Windows Runtime com C# e Visual Basic
Você pode usar o código gerenciado para criar seus próprios tipos do Tempo de Execução do Windows e empacotá-los em um componente do Tempo de Execução do Windows. Você pode usar seu componente em aplicativos UWP (Plataforma Universal do Windows) 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 de Biblioteca de Classes em vez do modelo de projeto Componente do Tempo de Execução do Windows no Microsoft Visual Studio. Há 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.
Declaração de tipos em componentes do Windows Runtime
Internamente, os tipos do Tempo de Execução do Windows 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 tipos do Tempo de Execução do Windows para seus parâmetros e valores retornados. A lista a seguir descreve as limitações nos tipos .NET que são expostos de um componente do Tempo de Execução do Windows.
Os campos, parâmetros e valores retornados de todos os tipos e membros públicos em seu componente devem ser tipos do Tempo de Execução do Windows. Essa restrição inclui os tipos do Tempo de Execução do Windows que você cria, bem como os tipos fornecidos pelo próprio Tempo de Execução do Windows. Ele também inclui vários tipos de .NET. A inclusão desses tipos faz parte do suporte que o .NET fornece para habilitar o uso natural do Tempo de Execução do Windows no código gerenciado — seu código parece usar tipos .NET familiares em vez dos tipos subjacentes do Tempo de Execução do Windows. Por exemplo, você pode usar tipos primitivos do .NET, como Int32 e Double, determinados tipos fundamentais, como DateTimeOffset e Uri, e alguns tipos de interface genéricos 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 tipos do Tempo de Execução do Windows. Isso é discutido nas seções Passando tipos do Tempo de Execução do Windows para o código gerenciado e Passando tipos gerenciados para o Tempo de Execução do Windows, mais adiante neste tópico.
Classes e interfaces públicas podem conter métodos, propriedades e eventos. Você pode declarar delegados para seus eventos ou usar o delegado T EventHandler<.> Uma classe ou interface pública não pode:
- Seja genérico.
- Implemente uma interface que não seja uma interface do Tempo de Execução do Windows (no entanto, você pode criar suas próprias interfaces do Tempo de Execução do Windows e implementá-las).
- Derivam de tipos que não estão no Tempo de Execução do Windows, como System.Exception e System.EventArgs.
Todos os tipos públicos devem ter um namespace raiz que corresponda ao nome do assembly e o nome do assembly não deve começar com "Windows".
Dica. Por padrão, os projetos do Visual Studio têm nomes de namespace que correspondem ao nome do assembly. No Visual Basic, a instrução Namespace para esse namespace padrão não é mostrada em seu código.
As estruturas públicas não podem ter membros além de campos públicos, e esses campos devem ser tipos de valor ou cadeias de caracteres.
As classes públicas devem ser lacradas (NotInheritable no Visual Basic). Se o seu modelo de programação exigir polimorfismo, você poderá criar uma interface pública e implementá-la nas classes que devem ser polimórficas.
Depurando seu componente
Se o aplicativo UWP e o componente forem criados com código gerenciado, você poderá depurar os dois ao mesmo tempo.
Ao testar seu componente como parte de um aplicativo UWP usando C++, você pode depurar o código gerenciado e nativo ao mesmo tempo. O padrão é apenas código nativo.
Para depurar o código C++ nativo e o código gerenciado
- Abra o menu de atalho do projeto do Visual C++ e escolha Propriedades.
- Nas páginas de propriedades, em Propriedades de configuração, escolha Depuração.
- Escolha Tipo de Depurador e, na caixa de listagem suspensa, altere Nativo Somente para Misto (Gerenciado e Nativo). Escolha OK.
- Defina pontos de interrupção no código 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, você não pode depurar JavaScript e código gerenciado ao mesmo tempo.
Para depurar código gerenciado em vez de JavaScript
- Abra o menu de atalho do seu projeto JavaScript e escolha Propriedades.
- Nas páginas de propriedades, em Propriedades de configuração, escolha Depuração.
- Escolha Tipo de Depurador e, na caixa de listagem suspensa, altere Somente Script para Somente Gerenciado. Escolha OK.
- Defina pontos de interrupção no código gerenciado e depure como de costume.
Passando tipos do Tempo de Execução do Windows para código gerenciado
Conforme mencionado anteriormente na seção Declarando tipos em componentes do Tempo de Execução do Windows, 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 Tempo de Execução do Windows em 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 os tipos .NET aparecem para o chamador. Consulte Passo a passo da criação de um componente do Tempo de Execução do Windows em C# ou Visual Basic e chamá-lo do JavaScript para obter exemplos com JavaScript. Esta seção discute os tipos comumente 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 e estruturas primitivos no Tempo de Execução do Windows têm apenas 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 dos tipos .NET como faria normalmente. A lista a seguir resume as substituições que são feitas automaticamente no IDE:
- Para os primitivos do Tempo de Execução do Windows Int32, Int64, Single, Double, Boolean, String (uma coleção imutável de caracteres Unicode), Enum, UInt32, UInt64 e Guid, use o tipo de 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 palavra-chave de linguagem para qualquer um desses tipos, você poderá usar a palavra-chave de linguagem.
Além dos tipos primitivos, alguns tipos básicos do Tempo de Execução do Windows comumente usados aparecem no código gerenciado como seus equivalentes do .NET. Por exemplo, suponha que seu código JavaScript use a classe Windows.Foundation.Uri e você queira passá-lo 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 do Tempo de Execução do Windows aparece como um tipo .NET, pois o IntelliSense no Visual Studio oculta o tipo do Tempo de Execução do Windows 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 do Tempo de Execução do Windows 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 mais comuns desses tipos de interface genéricos, juntamente com outros mapeamentos comuns de classe e interface. Para obter uma lista completa dos tipos do Tempo de Execução do Windows que o .NET mapeia, consulte mapeamentos do .NET de tipos do Tempo de Execução do Windows.
Windows Runtime | .NET |
---|---|
T Versátil<> | IEnumerable<T> |
IVector<T> | Ilusão<> |
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, você pode usar qualquer uma das interfaces que ele implementa como um tipo de parâmetro ou tipo de retorno de um membro. Por exemplo, você pode passar ou retornar um Dictionary int, string> (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 que um tipo gerenciado implementa. Por exemplo, se você retornar Dictionary<int, string> para o código JavaScript, ele aparecerá como IDictionary<int, string>, independentemente da interface especificada como o tipo de retorno. Isso significa que, se a primeira interface não incluir um membro que aparece em interfaces posteriores, esse membro não ficará visível para o JavaScript.
No Tempo de Execução do Windows, 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, ele 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 esse exemplo no tópico Passo a passo da criação de um componente do Tempo de Execução do Windows em C# ou Visual Basic e chamá-lo do JavaScript.
Passando tipos gerenciados para o Tempo de Execução do Windows
Conforme discutido na seção anterior, alguns tipos do Tempo de Execução do Windows podem aparecer como tipos .NET nas assinaturas dos membros do componente ou nas assinaturas dos membros do Tempo de Execução do Windows 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 para o código do outro lado como o tipo correspondente do Tempo de Execução do Windows. 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 do Tempo de Execução do Windows em C# ou Visual Basic e chamando-o do JavaScript.
Métodos sobrecarregados
No Tempo de Execução do Windows, 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 você pode chamar do JavaScript. Por exemplo, no código a seguir, a sobrecarga que usa um int (Integer no 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 força 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.
Você não pode aplicar o atributo e DefaultOverloadAttributaos construtores. Todos os construtores em uma classe devem ter diferentes números de parâmetros.
Implementando IStringable
A partir do Windows 8.1, o Tempo de Execução do Windows inclui uma interface IStringable cujo método único, IStringable.ToString, fornece suporte básico à formatação comparável ao fornecido por Object.ToString. Se você optar por implementar IStringable em um tipo gerenciado público exportado em um componente do Tempo de Execução do Windows, as seguintes restrições se aplicarão:
Você pode definir a interface IStringable somente em uma relação de "implementação de classe", como o seguinte código 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 sendo 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 das 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 sempre deve substituir a implementação da classe base. Você pode ocultar uma implementação ToString somente invocando-a em uma instância de classe fortemente tipada.
Observação
Em uma variedade de condições, as chamadas de código nativo para um tipo gerenciado que implementa IStringable ou oculta sua implementação 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 do Tempo de Execução do Windows 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 represente 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. Se você usar um construtor para criar a tarefa, deverá chamar seu método Task.Start antes de retorná-la.
Um método que usa await
(Await
no Visual Basic) requer a async
palavra-chave (Async
no Visual Basic). Se você expor esse método de um componente do Tempo de Execução do Windows, aplique a 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 a relatórios de cancelamento ou progresso, você pode 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 uma operação assíncrona do Tempo de Execução do 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
O código JavaScript a seguir mostra como o método pode ser chamado usando um objeto WinJS.Promise . A função que é 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 relatórios de cancelamento ou progresso, use a classe AsyncInfo para gerar uma tarefa iniciada e conectar os recursos de cancelamento e relatório de progresso da tarefa com os recursos de cancelamento e relatório de progresso da interface apropriada do Tempo de Execução do Windows. Para obter um exemplo que dá suporte a relatórios de cancelamento e progresso, consulte Passo a passo da criação de um componente do Tempo de Execução do Windows em C# ou Visual Basic e chamá-lo 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 T> IProgress.< Se você usar uma função lambda C#, forneça um parâmetro de token, mas ignore-o. 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 ao cancelamento ou ao relatório de progresso, considere adicionar sobrecargas que não tenham parâmetros para um token de cancelamento ou a interface IProgress<T> .
Acionamento de exceções
Você pode lançar qualquer tipo de exceção incluído nos aplicativos do .NET para Windows. Você não pode declarar seus próprios tipos de exceção públicos em um componente do Tempo de Execução do Windows, mas pode declarar e lançar tipos não públicos.
Se o componente não manipular a exceção, uma exceção correspondente será gerada no código que chamou o componente. A maneira como a exceção aparece para o chamador depende da maneira como o idioma de chamada dá suporte ao Tempo de Execução do Windows.
Em JavaScript, a exceção aparece como um objeto no qual a mensagem de exceção é substituída por um rastreamento de pilha. Ao depurar seu aplicativo no Visual Studio, você pode ver o texto da mensagem original exibido na caixa de diálogo de exceção do depurador, identificada como "Informações do WinRT". Não é possível acessar o texto da mensagem original a partir do código JavaScript.
Dica. Atualmente, o rastreamento de pilha contém o tipo de exceção gerenciada, mas não recomendamos analisar o rastreamento para identificar o tipo de exceção. Em vez disso, use um valor HRESULT conforme descrito posteriormente nesta seção.
Em C++, a exceção aparece como uma exceção de 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 será lançada. O texto da mensagem da exceção gerenciada não está disponível para o código C++. Se uma exceção de plataforma específica foi lançada, o texto da mensagem padrão para esse tipo de exceção será exibido; caso contrário, nenhum texto de mensagem será exibido. Consulte Exceções (C++/CX).
Em C# ou Visual Basic, a exceção é uma exceção gerenciada normal.
Ao lançar uma exceção do componente, você pode facilitar o tratamento da exceção por um chamador JavaScript ou C++ lançando 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 é lançada no chamador JavaScript ou C++.
Declarando e levantando eventos
Quando você declara um tipo para manter os dados do evento, derive de Object em vez de EventArgs, pois EventArgs não é um tipo do Tempo de Execução do Windows. Use EventHandler<TEventArgs> como o tipo do evento e use o tipo de argumento de evento 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 Tempo de Execução do Windows 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 em Passo a passo de como criar um componente do Tempo de Execução do Windows em C# ou Visual Basic e chamá-lo do JavaScript.
Se você implementar acessadores de eventos personalizados (declarar um evento com a palavra-chave Custom , no Visual Basic), deverá seguir o padrão de evento do Tempo de Execução do Windows em sua implementação. Consulte Eventos personalizados e acessadores de eventos em componentes do Tempo de Execução do Windows. 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 do Tempo de Execução do Windows para seu próprio uso, você pode descobrir que a funcionalidade que ele encapsula é útil para outros desenvolvedores. Você tem duas opções para empacotar um componente para distribuição a outros desenvolvedores. Consulte Distribuindo um componente gerenciado do Tempo de Execução do Windows.
Para obter mais informações sobre os recursos da linguagem Visual Basic e C# e o suporte do .NET para o Tempo de Execução do Windows, 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. |