Mover do C# para C++/WinRT

Tip

Se você já leu este tópico antes e está voltando a ele com uma tarefa específica em mente, pode ir diretamente para a seção Localizar conteúdo com base na tarefa que você está executando deste tópico.

Este tópico cataloga de forma abrangente os detalhes técnicos envolvidos na portabilidade do código-fonte em um projeto C# para seu equivalente em C++/WinRT.

Para obter um estudo de caso de portabilidade de um dos exemplos de aplicativo UWP (Plataforma Universal do Windows), confira o tópico complementar Como portar a amostra de área de transferência para o C++/WinRT do C#. Você pode adquirir prática e experiência em portabilidade seguindo o passo a passo e fazendo a portabilidade da amostra por conta própria durante as etapas.

Como se preparar e o que esperar

O estudo de caso Como portar a amostra de área de transferência para o C++/WinRT do C# ilustra exemplos dos tipos de decisões de design de software que você tomará ao portar um projeto para o C++/WinRT. Portanto, é uma boa ideia se preparar para a portabilidade, obtendo uma compreensão sólida de como o código existente funciona. Dessa forma, você terá uma boa visão geral da funcionalidade do aplicativo e da estrutura do código e, em seguida, as decisões tomadas sempre o levarão adiante e na direção certa.

Em termos dos tipos de alterações de portabilidade a serem esperadas, você pode agrupá-las em quatro categorias.

  • Portar a projeção de linguagem. O Windows Runtime (WinRT) é projetado em várias linguagens de programação. Cada uma dessas projeções de linguagem foi projetada para se sentir idiomática para a linguagem de programação em questão. Para C#, alguns tipos de Windows Runtime são projetados como tipos de .NET. Portanto, por exemplo, você estará traduzindo System.Collections.Generic.IReadOnlyList<T> de volta para Windows. Foundation.Collections.IVectorView<T>. Também no C#, algumas operações de Windows Runtime são projetadas como recursos convenientes de linguagem C#. Um exemplo é que em C# você usa a sintaxe do += operador para registrar um delegado de manipulação de eventos. Portanto, você estará traduzindo recursos de linguagem como esse de volta para a operação fundamental que está sendo executada (registro de evento, neste exemplo).
  • Sintaxe da linguagem de porta. Muitas dessas alterações são transformações mecânicas simples, substituindo um símbolo por outro. Por exemplo, alteração do ponto (.) para dois-pontos (::).
  • Portar procedimento de linguagem. Algumas delas podem ser alterações simples e repetitivas (como myObject.MyProperty para myObject.MyProperty()). Outros precisam de alterações mais profundas (por exemplo, portar um procedimento que envolva o uso de System.Text.StringBuilder para um que envolva o uso de std::wostringstream).
  • Tarefas de migração específicas do C++/WinRT. Alguns detalhes da Windows Runtime são tratados implicitamente pelo C#, nos bastidores. Esses detalhes são feitos explicitamente em C++/WinRT. Um exemplo é que você usa um .idl arquivo para definir suas classes de runtime.

Após o índice baseado em tarefas a seguir, o restante das seções neste tópico são estruturadas de acordo com a taxonomia acima.

Localizar conteúdo com base na tarefa que você está executando

Task Content
Criar um componente de Windows Runtime (WRC) Determinadas funcionalidades podem ser obtidas (ou determinadas APIs chamadas) somente com C++. Você pode encapsular essa funcionalidade em um WRC do C++/WinRT e, em seguida, consumir o WRC a partir de, por exemplo, um aplicativo C#. Consulte Componentes do Windows Runtime com C++/WinRT e Se você estiver desenvolvendo uma classe de tempo de execução em um componente do Windows Runtime.
Portar um método assíncrono É uma boa ideia que a primeira linha de um método assíncrono em uma classe de runtime C++/WinRT seja auto lifetime = get_strong(); (confira Como acessar com segurança o ponteiro this em uma corrotina de membro de classe).

Portabilidade de Task: confira Ação assíncrona.
Portabilidade de Task<T>: confira Operação assíncrona.
Portabilidade de async void: confira Método fire-and-forget.
Migrar uma classe Primeiro, determine se a classe precisa ser uma classe de runtime ou se ela pode ser uma classe comum. Para ajudá-lo a decidir isso, confira o início das APIs de Autor com C++/WinRT. Em seguida, consulte as próximas três linhas abaixo.
Portar uma classe de runtime Uma classe que compartilha funcionalidades fora do aplicativo C++ ou uma classe usada na associação de dados XAML. Veja se você está criando uma classe de runtime em um componente Windows Runtime ou se está criando uma classe de runtime a ser referenciada na interface do usuário XAML.

Esses links descrevem isso com mais detalhes, mas uma classe de runtime deve ser declarada em IDL. Se o project já contiver um arquivo IDL (por exemplo, Project.idl), recomendamos que você declare qualquer nova classe de runtime nesse arquivo. No IDL, declare todos os métodos e membros de dados que serão usados fora do aplicativo ou que serão usados no XAML. Depois de atualizar o arquivo IDL, recompile e examine os arquivos stub gerados (.h e .cpp) na pasta Generated Files do seu projeto (no Gerenciador de Soluções, com o nó do projeto selecionado, verifique se Mostrar Todos os Arquivos está ativado). Compare os arquivos stub com os arquivos já em seu projeto, adicionando arquivos ou adicionando/atualizando assinaturas de função conforme necessário. A sintaxe do arquivo Stub está sempre correta, portanto, recomendamos que você a use para minimizar erros de build. Depois que os stubs em seu projeto corresponderem aos arquivos de stub, você poderá prosseguir e implementá-los portando o código C#.
Portar uma classe comum Confira Se você não estiver criando uma classe de runtime.
ID do autor Introdução ao Microsoft Interface Definition Language 3.0
Se você estiver criando uma classe de runtime a ser referenciada na interface do usuário XAML
Consumindo objetos da marcação XAML
Definir suas classes de runtime no IDL
Migrar uma coleção Coleções com C++/WinRT
Disponibilizando uma fonte de dados para marcação XAML
Contêiner associativo
Acesso a membros de vetor
Portar um evento Delegado de manipulador de eventos como membro de classe
Revogar delegado do manipulador de eventos
Portar um método De C#: private async void SampleButton_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e) { ... }
Para o arquivo C++/WinRT .h : fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&);
Para o arquivo C++/WinRT .cpp : fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...}
Sequências de caracteres da porta Manipulação de cadeia de caracteres em C++/WinRT
ToString
Construção de strings
Conversão boxing e unboxing de uma cadeia de caracteres
Conversão de tipo C#: o.ToString()
C++/WinRT: to_hstring(static_cast<int>(o))
Consulte também ToString.

C#: (Value)o
C++/WinRT: unbox_value<Value>(o)
é gerado em caso de falha na conversão unboxing. Confira também Conversão boxing e unboxing.

C#: o as Value? ?? fallback
C++/WinRT: unbox_value_or<Value>(o, fallback)
Retorna o fallback se o unboxing falhar. Confira também Conversão boxing e unboxing.

C#: (Class)o
C++/WinRT: o.as<Class>()
É gerado em caso de falha na conversão.

C#: o as Class
C++/WinRT: o.try_as<Class>()
Retorna nulo se a conversão falhar.

Alterações que envolvem a projeção de linguagem

Category C# C++/WinRT Consulte também
Objeto não tipado object, ou System.Object Windows::Foundation::IInspectable Portando o método EnableClipboardContentChangedNotifications
Namespaces de projeção using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
Tamanho de uma coleção collection.Count collection.Size() Portando o método BuildClipboardFormatsOutputString
Tipo de coleção típico Ilist<T> e Adicionar para adicionar um elemento. IVector<T> e Acréscimo para adicionar um elemento. Se você usar um std::vector em algum ponto, então use push_back para adicionar um elemento.
Tipo de coleção somente para leitura IReadOnlyList<T> IVectorView<T> Portando o método BuildClipboardFormatsOutputString
Delegado de manipulador de eventos como membro de classe myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); Portando o método EnableClipboardContentChangedNotifications
Revogar delegado de manipulador de eventos myObject.EventName -= Handler; myObject.EventName(token); Portando o método EnableClipboardContentChangedNotifications
Contêiner associativo IDictionary<K, V> IMap<K, V>
Acesso a membros de vetor x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

Registrar/revogar um manipulador de eventos

No C++/WinRT, você tem várias opções sintacticas para registrar/revogar um delegado do manipulador de eventos, conforme descrito em Manipular eventos usando delegados em C++/WinRT. Confira também Como portar o método EnableClipboardContentChangedNotifications.

Às vezes, por exemplo, quando um destinatário de evento (um objeto que manipula um evento) está prestes a ser destruído, você deseja revogar um manipulador de eventos para que a origem do evento (o objeto que está gerando o evento) não chame um objeto destruído. Consulte Revogar um delegado registrado. Em casos como esse, crie uma variável membro event_token para os manipuladores de eventos. Para obter um exemplo, consulte Portando o método EnableClipboardContentChangedNotifications.

Você também pode registrar um manipulador de eventos na marcação XAML.

<Button x:Name="OpenButton" Click="OpenButton_Click" />

No C#, seu método OpenButton_Click pode ser privado e o XAML ainda poderá conectá-lo ao evento ButtonBase.Click gerado pelo OpenButton.

No C++/WinRT, seu método OpenButton_Click deve ser público no tipo de implementaçãose você quiser registrá-lo na marcação XAML. Se você registrar um manipulador de eventos apenas no código imperativo, o manipulador de eventos não precisará ser público.

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
        void OpenButton_Click(
            winrt::Windows::Foundation::IInspectable const& sender,
            winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args);
    }
};

Como alternativa, você pode tornar o registro da página XAML um amigo do tipo de implementação e o OpenButton_Click privado.

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
    private:
        friend MyPageT;
        void OpenButton_Click(
            winrt::Windows::Foundation::IInspectable const& sender,
            winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args);
    }
};

Um último cenário é quando o projeto C# que você está portando se associa ao manipulador de eventos da marcação (para obter mais informações sobre esse cenário, confira Funções em x:Bind).

<Button x:Name="OpenButton" Click="{x:Bind OpenButton_Click}" />

Você poderia apenas alterar essa marcação para o mais simples Click="OpenButton_Click". Ou, caso prefira, você pode manter essa marcação como está. Tudo o que você precisa fazer para dar suporte a ele é declarar o manipulador de eventos no IDL.

void OpenButton_Click(Object sender, Microsoft.UI.Xaml.RoutedEventArgs e);

Note

Declare a função como void mesmo se você implementá-la como Disparar e esquecer.

Alterações que envolvem a sintaxe do idioma

Category C# C++/WinRT Consulte também
Modificadores de acesso public \<member\> public:
    \<member\>
Portando o método Button_Click
Acessar um membro de dados this.variable this->variable  
Ação assíncrona async Task ... IAsyncAction ... Interface IAsyncAction, simultaneidade e operações assíncronas com C++/WinRT
Operação assíncrona async Task<T> ... IAsyncOperation<T> ... Interface IAsyncOperation, simultaneidade e operações assíncronas com C++/WinRT
Método de disparar e esquecer (implica assincronicidade) async void ... winrt::fire_and_forget ... Portando o método CopyButton_Click, Disparar e esquecer
Acessar uma constante enumerada E.Value E::Value Portando o método DisplayChangedFormats
Aguardar cooperativamente await ... co_await ... Portando o método CopyButton_Click
Coleção de tipos projetados em um campo privado private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); std::vector
<MyNamespace::MyRuntimeClass>
m_myRuntimeClasses;
Construção do GUID private static readonly Guid myGuid = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1"); winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };
Separador de espaço de nomes A.B.T A::B::T
Null null nullptr Portando o método UpdateStatus
Obter um objeto de tipo typeof(MyType) winrt::xaml_typename<MyType>() Portando a propriedade Scenarios
Declaração de parâmetro para um método MyType MyType const& Passagem de parâmetros
Declaração de parâmetro para um método assíncrono MyType MyType Passagem de parâmetro
Chamar um método estático T.Method() T::Method()
Cordas string, ou System.String winrt::hstring Manipulação de cadeia de caracteres em C++/WinRT
Literal de cadeia de caracteres "a string literal" L"a string literal" Portando o construtor, Current e FEATURE_NAME
Tipo inferido (ou deduzido) var auto Portando o método BuildClipboardFormatsOutputString
Diretiva using using A.B.C; using namespace A::B::C; Portando o construtor, Current e FEATURE_NAME
Literal de cadeia de caracteres textual/bruta @"verbatim string literal" LR"(raw string literal)" Portando o método DisplayToast

Note

Se um arquivo de cabeçalho não contiver uma using namespace diretiva para um determinado namespace, você precisará qualificar totalmente todos os nomes de tipo para esse namespace ou, pelo menos, qualificá-los o suficiente para que o compilador os encontre. Para obter um exemplo, consulte Portando o método DisplayToast.

Como portar classes e membros

Você precisará decidir, para cada tipo de C#, se deve portá-lo para um tipo do Windows Runtime ou para uma classe/estrutura/enumeração C++ comum. Para obter mais informações e exemplos detalhados que ilustram como tomar essas decisões, confira Como portar a amostra de área de transferência para o C++/WinRT do C#.

Uma propriedade C# normalmente se torna uma função de acessador, uma função de modificador e um membro de dados de backup. Para obter mais informações e um exemplo, confira Como portar a propriedade IsClipboardContentChangedEnabled.

Para campos não estáticos, torne-os membros de dados do tipo de implementação.

Um campo estático em C# torna-se uma função acessora estática e/ou mutadora do C++/WinRT. Para obter mais informações e um exemplo, confira Como portar o construtor, Current e FEATURE_NAME.

Para funções-membro, novamente, você precisará decidir para cada uma se ela deve ou não estar na IDL, ou se é uma função-membro pública ou privada do seu tipo de implementação. Para obter mais informações e exemplos de como decidir, consulte IDL para o tipo MainPage.

Migrando a marcação XAML e os arquivos de recursos

No caso de Como portar a amostra de área de transferência para o C++/WinRT do C#, pudemos usar a mesma marcação XAML (incluindo os recursos) e os arquivos de ativos em todo o projeto C# e C++/WinRT. Em alguns casos, serão necessárias edições na marcação para alcançar isso. Consulte Como copiar o XAML e os estilos necessários para concluir a migração de MainPage.

Alterações que envolvem procedimentos dentro do idioma

Category C# C++/WinRT Consulte também
Gerenciamento de tempo de vida em um método assíncrono N/A auto lifetime{ get_strong() }; ou
auto lifetime = get_strong();
Portando o método CopyButton_Click
Disposal using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
Portando o método CopyImage
Objeto de construção new MyType(args) MyType{ args } ou
MyType(args)
Portando a propriedade Scenarios
Criar referência não inicializada MyType myObject; MyType myObject{ nullptr }; ou
MyType myObject = nullptr;
Portando o construtor, Current e FEATURE_NAME
Construir objeto em variável com args var myObject = new MyType(args); auto myObject{ MyType{ args } }; ou
auto myObject{ MyType(args) }; ou
auto myObject = MyType{ args }; ou
auto myObject = MyType(args); ou
MyType myObject{ args }; ou
MyType myObject(args);
Portando o método Footer_Click
Construir objeto em variável sem args var myObject = new T(); MyType myObject; Portando o método BuildClipboardFormatsOutputString
Sintaxe abreviada para inicialização de objeto var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
Operação de vetor em lote var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
Portando o método CopyButton_Click
Iterar na coleção foreach (var v in c) for (auto&& v : c) Portando o método BuildClipboardFormatsOutputString
Capturar uma exceção catch (Exception ex) catch (winrt::hresult_error const& ex) Portando o método PasteButton_Click
Detalhes da exceção ex.Message ex.message() Portando o método PasteButton_Click
Obter o valor de uma propriedade myObject.MyProperty myObject.MyProperty() Portando o método NotifyUser
Definir o valor de uma propriedade myObject.MyProperty = value; myObject.MyProperty(value);
Incrementar um valor de propriedade myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
Para cadeias de caracteres, alterne para um construtor
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
Cadeia de caracteres de linguagem para a cadeia de caracteres do Windows Runtime N/A winrt::hstring{ s }
Construção de strings StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Construção de strings
Interpolação de cadeia de caracteres $"{i++}) {s.Title}" winrt::to_hstring e/ou winrt::hstring::operator+ Portando o método OnNavigatedTo
Cadeia de caracteres vazia para comparação System.String.Empty winrt::hstring::empty Portando o método UpdateStatus
Criar cadeia de caracteres vazia var myEmptyString = String.Empty; winrt::hstring myEmptyString{ L"" };
Operações de dicionário map[k] = v; // replaces any existing
v = map[k]; // throws if not present
map.ContainsKey(k)
map.Insert(k, v); // replaces any existing
v = map.Lookup(k); // throws if not present
map.HasKey(k)
Conversão de tipo (geração em caso de falha) (MyType)v v.as<MyType>() Portando o método Footer_Click
Conversão de tipo (nulo em caso de falha) v as MyType v.try_as<MyType>() Portando o método PasteButton_Click
Elementos XAML com x:Name são propriedades MyNamedElement MyNamedElement() Portando o construtor, Current e FEATURE_NAME
Alternar para o thread da interface do usuário CoreDispatcher.RunAsync DispatcherQueue.TryEnqueue ou winrt::resume_foreground Portando o método NotifyUser e portando o método HistoryAndRoaming
Construção de elemento de interface do usuário em código imperativo em uma página XAML Consulte a construção do elemento de interface do usuário Consulte a construção do elemento de interface do usuário

As seções a seguir entram em mais detalhes sobre alguns dos itens na tabela.

Construção de elemento de interface

Esses exemplos de código mostram a construção de um elemento de interface do usuário no código imperativo de uma página XAML.

var myTextBlock = new TextBlock()
{
    Text = "Text",
    Style = (Microsoft.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
    winrt::unbox_value<Microsoft::UI::Xaml::Style>(
        Resources().Lookup(
            winrt::box_value(L"MyTextBlockStyle")
        )
    )
);

ToString()

Os tipos C# fornecem o método Object.ToString .

int i = 2;
var s = i.ToString(); // s is a System.String with value "2".

O C++/WinRT não disponibiliza esse recurso diretamente, mas você pode recorrer a alternativas.

int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".

O C++/WinRT também dá suporte a winrt::to_hstring para um número limitado de tipos. Você precisará adicionar sobrecargas para os tipos adicionais que deseja converter em cadeia de caracteres.

Linguagem Converter int em string Converter enumeração em string
C# string result = "hello, " + intValue.ToString();
string result = $"hello, {intValue}";
string result = "status: " + status.ToString();
string result = $"status: {status}";
C++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

No caso de converter um enum em cadeia de caracteres, você precisará fornecer a implementação de winrt::to_hstring.

namespace winrt
{
    hstring to_hstring(StatusEnum status)
    {
        switch (status)
        {
        case StatusEnum::Success: return L"Success";
        case StatusEnum::AccessDenied: return L"AccessDenied";
        case StatusEnum::DisabledByPolicy: return L"DisabledByPolicy";
        default: return to_hstring(static_cast<int>(status));
        }
    }
}

Em geral, essas conversões em cadeia de caracteres são consumidas implicitamente pela vinculação de dados.

<TextBlock>
You have <Run Text="{Binding FlowerCount}"/> flowers.
</TextBlock>
<TextBlock>
Most recent status is <Run Text="{x:Bind LatestOperation.Status}"/>.
</TextBlock>

Essas associações executarão winrt::to_hstring da propriedade associada. No caso do segundo exemplo (o StatusEnum), você deve fornecer sua própria sobrecarga de winrt::to_hstring, caso contrário, você receberá um erro do compilador.

Veja também Portando o método Footer_Click.

Construção de strings

Para a criação de cadeia de caracteres, O C# tem um tipo StringBuilder interno.

Category C# C++/WinRT
Construção de strings StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Anexar uma cadeia de caracteres do Windows Runtime, preservando os nulos builder.Append(s); builder << std::wstring_view{ s };
Adicionar uma nova linha builder.Append(Environment.NewLine); builder << std::endl;
Acessar o resultado s = builder.ToString(); ws = builder.str();

Veja também a portabilidade do método BuildClipboardFormatsOutputString e a portabilidade do método DisplayChangedFormats.

Execução de código na thread principal da interface do usuário

Este exemplo é obtido do exemplo de scanner de código de barras.

Quando você deseja trabalhar no thread principal da interface do usuário em um projeto C#, normalmente usa o método DispatcherQueue.TryEnqueue (ou o CoreDispatcher.RunAsync mais antigo na UWP). Veja a aparência do padrão em C#.

private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    DispatcherQueue.TryEnqueue(() =>
    {
        // Do work on the main UI thread here.
    });
}

É muito mais simples expressar isso em C++/WinRT. Observe que estamos aceitando parâmetros por valor na suposição de que queremos acessá-los após o primeiro ponto de suspensão (o co_await, neste caso). Para obter mais informações, consulte passagem de parâmetros.

winrt::fire_and_forget Watcher_Added(DeviceWatcher sender, winrt::DeviceInformation args)
{
    co_await DispatcherQueue();
    // Do work on the main UI thread here.
}

Se você precisar fazer o trabalho com uma prioridade diferente do padrão, confira a função winrt::resume_foreground, que tem uma sobrecarga que recebe uma prioridade. Para obter exemplos de código que mostram como aguardar uma chamada para winrt::resume_foreground, confira Programando com a afinidade de thread em mente.

Definir suas classes de runtime no IDL

Confira IDL para o tipo MainPage e Consolidar os arquivos .idl.

Incluir os arquivos de cabeçalho do namespace do Windows C++/WinRT de que você precisa

No C++/WinRT, sempre que você quiser usar um tipo de um dos namespaces do Windows, precisará incluir o arquivo de cabeçalho correspondente do namespace do Windows no C++/WinRT. Para obter um exemplo, consulte Portando o método NotifyUser.

Conversão boxing e unboxing

O C# faz a conversão boxing automática de escalares em objetos. O C++/WinRT exige que você chame a função winrt::box_value explicitamente. Ambas as linguagens exigem que você desencaixote explicitamente. Consulte Boxing e unboxing com C++/WinRT.

Nas tabelas a seguir, usaremos essas definições.

C# C++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
Operation C# C++/WinRT
Boxe o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Conversão unboxing i = (int)o;
s = (string)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

O C++/CX e o C# gerarão exceções se você tentar fazer a conversão unboxing de um ponteiro nulo em um tipo de valor. O C++/WinRT considera isso um erro de programação e falha. No C++/WinRT, use a função winrt::unbox_value_or se quiser lidar com o caso em que o objeto não é do tipo que você pensou que era.

Scenario C# C++/WinRT
Fazer a conversão unboxing de um inteiro conhecido i = (int)o; i = unbox_value<int>(o);
Se o for nulo System.NullReferenceException Colisão
Se o não for um int convertido System.InvalidCastException Colisão
Fazer a conversão unboxing de int, se for nulo, usar fallback; falhar, em qualquer outra situação i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Fazer a conversão unboxing de int, se possível; usar fallback para qualquer outra situação i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

Para obter um exemplo, consulte Portando o método OnNavigatedTo e portando o método Footer_Click.

Conversão boxing e unboxing de uma cadeia de caracteres

Uma cadeia de caracteres é, de certa forma, um tipo de valor e, de outras maneiras, um tipo de referência. C# e C++/WinRT tratam as cadeias de caracteres de forma diferente.

O tipo ABI HSTRING é um ponteiro para uma cadeia de caracteres com contagem de referências. Mas não deriva de IInspectable, portanto, tecnicamente não é um objeto. Além disso, um HSTRING nulo representa a cadeia de caracteres vazia. A conversão boxing de itens não derivados de IInspectable é feita encapsulando-os dentro de um IReference<T> e o Windows Runtime fornece uma implementação padrão na forma do objeto PropertyValue (os tipos personalizados são relatados como PropertyType::OtherType).

C# representa uma cadeia de caracteres Windows Runtime como um tipo de referência; enquanto C++/WinRT projeta uma cadeia de caracteres como um tipo de valor. Isso significa que uma cadeia de caracteres nula em caixa pode ter representações diferentes dependendo de como você chegou lá.

Behavior C# C++/WinRT
Declarações object o;
string s;
IInspectable o;
hstring s;
Categoria de tipo de cadeia de caracteres Tipo de referência Tipo de valor
null HSTRING é projetado como "" hstring{}
São nulos e "" idênticos? No Yes
Validade do valor nulo s = null;
s.Length gera NullReferenceException
s = hstring{};
s.size() == 0 (válido)
Se você atribuir cadeia de caracteres nula ao objeto o = (string)null;
o == null
o = box_value(hstring{});
o != nullptr
Se você atribuir "" ao objeto o = "";
o != null
o = box_value(hstring{L""});
o != nullptr

Conversões boxing e unboxing básicas.

Operation C# C++/WinRT
Encapsular uma cadeia de caracteres o = s;
A cadeia de caracteres vazia torna-se um objeto não nulo.
o = box_value(s);
A cadeia de caracteres vazia torna-se um objeto não nulo.
Fazer a conversão unboxing de uma cadeia de caracteres conhecida s = (string)o;
O objeto nulo se torna uma cadeia de caracteres nula.
InvalidCastException se não for uma cadeia de caracteres.
s = unbox_value<hstring>(o);
O objeto nulo falha.
Falha se não for uma cadeia de caracteres.
Fazer a conversão unboxing de uma possível cadeia de caracteres s = o as string;
Objeto nulo ou objeto que não é uma string torna-se uma string nula.

OR

s = o as string ?? fallback;
Nulo ou não cadeia de caracteres se torna fallback.
Cadeia de caracteres vazia preservada.
s = unbox_value_or<hstring>(o, fallback);
Nulo ou não cadeia de caracteres se torna fallback.
Cadeia de caracteres vazia preservada.

Tornando uma classe disponível para a extensão de marcação {Binding}

Se você pretende usar a extensão de marcação {Binding} para associar dados ao seu tipo de dados, consulte o objeto Binding declarado usando {Binding}.

Como consumir objetos por meio da marcação XAML

Em um projeto C#, você pode acessar membros privados e elementos nomeados na marcação XAML. Mas, no C++/WinRT, todas as entidades consumidas usando a extensão de marcação XAML {x:Bind} devem ser expostas publicamente no IDL.

Além disso, vincular a um Boolean exibe true ou false em C#, mas exibe Windows.Foundation.IReference`1<Boolean> em C++/WinRT.

Para obter mais informações e exemplos de código, consulte Consuming objects from markup.

Disponibilizando uma fonte de dados para marcação XAML

No C++/WinRT versão 2.0.190530.8 ou posterior, winrt::single_threaded_observable_vector cria um vetor observável que dá suporte a IObservableVector<T> e IObservableVector<IInspectable>. Para obter um exemplo, confira Como portar a propriedade Scenarios.

Você pode escrever seu arquivo MIDL (.idl) assim (veja também Separando classes de runtime em arquivos MIDL (.idl)).

namespace Bookstore
{
    runtimeclass BookSku { ... }

    runtimeclass BookstoreViewModel
    {
        Windows.Foundation.Collections.IObservableVector<BookSku> BookSkus{ get; };
    }

    runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

E implemente assim.

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Bookstore::BookSku>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
	Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> m_bookSkus;
};
...

Para obter mais informações, consulte controles de itens XAML; associar a uma coleção C++/WinRT e Coleções com C++/WinRT.

Disponibilizando uma fonte de dados para marcação XAML (antes do C++/WinRT 2.0.190530.8)

A vinculação de dados XAML requer que uma origem de itens implemente IIterable<IInspectable>, bem como uma das seguintes combinações de interfaces.

  • IObservableVector<IInspectable>
  • IBindableVector e INotifyCollectionChanged
  • IBindableVector e IBindableObservableVector
  • IBindableVector por si só (não responderá às alterações)
  • IVector<IInspectable>
  • IBindableIterable (iterará e salvará elementos em uma coleção privada)

Uma interface genérica como iVector<T> não pode ser detectada em runtime. Cada IVector T< tem um IID (identificador> de interface) diferente, que é uma função de T. Qualquer desenvolvedor pode expandir o conjunto de T arbitrariamente, portanto, claramente, o código de associação XAML nunca pode saber o conjunto completo para consulta. Essa restrição não é um problema para C# porque cada objeto CLR que implementa o IEnumerable<T> implementa automaticamente o IEnumerable. No nível da ABI, isso significa que cada objeto que implementa IObservableVector<T> implementa automaticamente IObservableVector<IInspectable>.

O C++/WinRT não oferece essa garantia. Se uma classe de runtime C++/WinRT implementar IObservableVector<T>, não poderemos supor que uma implementação de IObservableVector<IInspectable> também seja fornecida.

Assim, veja como o exemplo anterior deverá ficar.

...
runtimeclass BookstoreViewModel
{
    // This is really an observable vector of BookSku.
    Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}

E a implementação.

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
    // This is really an observable vector of BookSku.
	Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> m_bookSkus;
};
...

Caso você precise acessar objetos em m_bookSkus, precisará executar o QI novamente para Bookstore::BookSku.

Widget MyPage::BookstoreViewModel(winrt::hstring title)
{
    for (auto&& obj : m_bookSkus)
    {
        auto bookSku = obj.as<Bookstore::BookSku>();
        if (bookSku.Title() == title) return bookSku;
    }
    return nullptr;
}

Classes derivadas

Para derivar de uma classe de tempo de execução, a classe base deve ser componível. O C# não exige que você execute etapas especiais para tornar suas classes redigidas, mas o C++/WinRT o faz. Use a palavra-chave não selada para indicar que deseja que sua classe seja utilizável como uma classe base.

unsealed runtimeclass BasePage : Microsoft.UI.Xaml.Controls.Page
{
    ...
}
runtimeclass DerivedPage : BasePage
{
    ...
}

No arquivo de cabeçalho do tipo de implementação, você deve incluir o arquivo de cabeçalho de classe base antes de incluir o cabeçalho gerado automaticamente para a classe derivada. Caso contrário, você receberá erros como "Uso ilegal desse tipo como uma expressão".

// DerivedPage.h
#include "BasePage.h"       // This comes first.
#include "DerivedPage.g.h"  // Otherwise this header file will produce an error.

namespace winrt::MyNamespace::implementation
{
    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        ...
    }
}

APIs importantes