Mover de C para C++/WinRT#

Tip

Se já leu este tema antes e está a regressar a ele com uma tarefa específica em mente, pode saltar para a secção Encontrar conteúdo com base na tarefa que está a realizar .

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

Para um estudo de caso sobre a portabilidade de um dos exemplos de aplicações da Plataforma Universal do Windows (UWP), veja o tópico complementar Portar o exemplo do Clipboard para C++/WinRT a partir de C#. Pode ganhar prática e experiência em portabilidade seguindo esse guia passo a passo e adaptando o exemplo por si, à medida que progride.

Como preparar-se e o que esperar

O estudo de caso Porting the Clipboard sample do C# para C++/WinRT ilustra exemplos dos tipos de decisões de design de software que tomará ao portar um projeto para C++/WinRT. Por isso, é uma boa ideia preparar-se para a portabilidade, adquirindo uma compreensão sólida de como funciona o código existente. Assim, terá uma boa visão geral da funcionalidade da aplicação e da estrutura do código, e depois as decisões que tomar o levarão sempre para a frente, na direção certa.

Quanto ao tipo de alterações de portabilidade a esperar, pode agrupá-las em quatro categorias.

  • Adapte a projeção de linguagem. O Windows Runtime (WinRT) é projetado para várias linguagens de programação. Cada uma dessas projeções de linguagem foi concebida para parecer idiomática para a linguagem de programação em questão. Para C#, alguns tipos de Windows Runtime são projetados como tipos .NET. Por exemplo, vais traduzir System.Collections.Generic.IReadOnlyList<T> novamente para Windows.Foundation.Collections.IVectorView<T>. Também em C#, algumas operações do Windows Runtime são projetadas como funcionalidades convenientes da linguagem C#. Um exemplo é que em C# usas a += sintaxe do operador para registar um delegado de gestão de eventos. Assim, vais traduzir funcionalidades da linguagem como essa de volta para a operação fundamental que está a ser realizada (registo de eventos, neste exemplo).
  • Sintaxe da linguagem de porta. Muitas destas alterações são transformações mecânicas simples, substituindo um símbolo por outro. Por exemplo, mudar ponto (.) para dois pontos (::).
  • Procedimento de linguagem do porto. Algumas destas podem ser mudanças simples e repetitivas (como myObject.MyProperty para myObject.MyProperty()). Outros necessitam de alterações mais profundas (por exemplo, portar um procedimento que envolve o uso do System.Text.StringBuilder para um que envolva o uso do std::wostringstream).
  • Tarefas relacionadas com portabilidade que são específicas para C++/WinRT. Certos detalhes do Windows Runtime são tratados implicitamente pelo C#, nos bastidores. Esses detalhes são feitos explicitamente em C++/WinRT. Um exemplo é que usas um .idl ficheiro para definir as tuas classes de runtime.

Após o índice baseado em tarefas que se segue, as restantes secções deste tópico são estruturadas de acordo com a taxonomia acima.

Encontre conteúdo com base na tarefa que está a realizar

Tarefa Content
Criar um componente do Windows Runtime (WRC) Certas funcionalidades só podem ser alcançadas (ou chamadas certas APIs) com C++. Podes incorporar essa funcionalidade num WRC C++/WinRT e depois consumir o WRC de (por exemplo) uma aplicação C#. Consulte componentes do Windows Runtime com C++/WinRT e Se estiver a criar uma classe de runtime num componente do Windows Runtime.
Portar um método assíncrono É aconselhável que a primeira linha de um método assíncrono numa classe de runtime do C++/WinRT seja auto lifetime = get_strong(); (ver Acesso seguro ao ponteiro this numa corrotina de membro de uma classe).

A portar de Task, veja Ação Assíncrona.
Portando de Task<T>, ver operação assíncrona.
Ao portar de async void, consulte o método "fire-and-forget".
Portar uma classe Primeiro, determine se a classe precisa de ser uma classe em tempo de execução ou se pode ser uma classe comum. Para te ajudar a decidir isso, vê logo o início das APIs de Autor com C++/WinRT. Depois, veja as três linhas seguintes abaixo.
Migrar uma classe de tempo de execução Uma classe que partilha funcionalidades fora da aplicação C++, ou uma classe usada em ligação de dados XAML. Veja se está a criar uma classe de runtime num componente do Windows Runtime, ou se está a criar uma classe de runtime para ser referenciada na sua interface XAML.

Esses links descrevem isto com mais detalhe, mas uma classe de runtime deve ser declarada no IDL. Se o seu project já contiver um ficheiro IDL (por exemplo, Project.idl), então recomendamos que declare qualquer nova classe de runtime nesse ficheiro. No IDL, declare quaisquer métodos e membros de dados que sejam usados fora da sua aplicação, ou que sejam usados em XAML. Depois de atualizar o ficheiro IDL, recompile e consulte os ficheiros stub gerados (.h e .cpp) na pasta Generated Files do seu projeto (no Explorador de Soluções, com o nó do projeto selecionado, certifique-se de que Mostrar Todos os Ficheiros está ativado). Compare os ficheiros stub com os que já estão no seu projeto, adicionando ficheiros ou adicionando/atualizando assinaturas de funções conforme necessário. A sintaxe dos ficheiros stub está sempre correta, por isso recomendamos que a utilize para minimizar erros de compilação. Quando os stubs do teu projeto coincidem com os dos ficheiros stub, podes implementá-los portando o código C#.
Migrar uma classe comum Consulte Se não estiver a criar uma classe de tempo de execução.
Autor IDL Introdução à Linguagem de Definição de Interface Microsoft 3.0
Se estiveres a criar uma classe em tempo de execução para ser referenciada na tua interface XAML
Consumir objetos a partir da marcação XAML
Defina as suas classes de runtime no IDL
Migrar uma coleção Coleções com C++/WinRT
Disponibilizar uma fonte de dados para marcação XAML
Contentor associativo
Acesso a membros vetoriais
Migrar um evento Delegado de processamento de eventos como membro da classe
Revogar delegado do processador de eventos
Portar um método De C#: private async void SampleButton_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e) { ... }
Para o ficheiro C++/WinRT .h : fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&);
Para o ficheiro C++/WinRT .cpp : fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...}
Cordas de porta Gestão de strings em C++/WinRT
ToString
Construção de cordas
Boxe e desembalagem de uma corda
Conversão de tipos (fundição tipográfica) C#: o.ToString()
C++/WinRT: to_hstring(static_cast<int>(o))
Veja também ToString.

C#: (Value)o
C++/WinRT: unbox_value<Value>(o)
Lança se o unboxing falhar. Veja também Boxing and unboxing.

C#: o as Value? ?? fallback
C++/WinRT: unbox_value_or<Value>(o, fallback)
Retorna o valor predefinido se o desencapsulamento falhar. Veja também Boxe e unboxing.

C#: (Class)o
C++/WinRT: o.as<Class>()
Lança se a conversão falhar.

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

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

Category C# C++/WinRT Consulte também
Objeto não tipado object, ou System.Object Windows::Foundation::IInspectable Portabilidade do método EnableClipboardContentChangedNotifications
Espaços de nomes de projeções using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
Dimensão de uma coleção collection.Count collection.Size() Portabilidade do método BuildClipboardFormatsOutputString
Tipo típico de coleção IList<T>, e Add para adicionar um elemento. IVector<T>, e Add para adicionar um elemento. Se usares std::vector em qualquer lugar, então usa push_back para adicionar um elemento.
Tipo de coleção de apenas leitura IReadOnlyList<T> IVectorView<T> Portabilidade do método BuildClipboardFormatsOutputString
Delegado de manipulador de eventos como membro da classe myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); Portabilidade do método EnableClipboardContentChangedNotifications
Revogar delegado do processador de eventos myObject.EventName -= Handler; myObject.EventName(token); Portabilidade do método EnableClipboardContentChangedNotifications
Contentor associativo IDictionary<K, V> IMap<K, V>
Acesso aos membros de um vetor x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

Registar/revogar um gestor de eventos

Em C++/WinRT, tens várias opções sintáticas para registar/revogar um delegado handler de eventos, conforme descrito em Handle events, usando delegados em C++/WinRT. Veja também como portar o método EnableClipboardContentChangedNotifications.

Por vezes, por exemplo, quando um destinatário de um evento (um objeto que processa um evento) está prestes a ser destruído, vai querer remover um processador de eventos para que a fonte do evento (o objeto que gera o evento) não invoque um objeto destruído. Ver Revogar um delegado registado. Nesses casos, cria uma variável event_token membro para os teus gestores de eventos. Para um exemplo, veja Portar o método EnableClipboardContentChangedNotifications.

Também pode registar um processador de eventos na linguagem de marcação XAML.

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

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

Em C++/WinRT, o seu método OpenButton_Click deve ser público no seu tipo deimplementação se quiser registar o método em marcação XAML. Se registares um gestor de eventos apenas em código imperativo, então o gestor de eventos não precisa de 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);
    }
};

Em alternativa, pode tornar a página XAML registada como amiga do seu tipo de implementação e OpenButton_Click privada.

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 estás a portar se liga para o gestor de eventos a partir da marcação (para mais contexto sobre esse cenário, vê Funções em x:Bind).

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

Podes simplesmente mudar essa marcação para a mais simples Click="OpenButton_Click". Ou, se preferires, podes manter essa margem como está. Tudo o que tens de fazer para o suportar é declarar o gestor de eventos no IDL.

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

Note

Declara a função como void mesmo que a implementes como Fogo e esqueça.

Alterações que envolvem a sintaxe da linguagem

Category C# C++/WinRT Consulte também
Modificadores de acesso public \<member\> public:
    \<member\>
Portar o método Button_Click
Aceder a um membro de dados this.variable this->variable  
Ação assíncrona async Task ... IAsyncAction ... interface IAsyncAction, Concorrência e operações assíncronas com C++/WinRT
Operação assíncrona async Task<T> ... IAsyncOperation<T> ... Interface IAsyncOperation, Concorrência e operações assíncronas com C++/WinRT
Método fire-and-forget (implica execução assíncrona) async void ... winrt::fire_and_forget ... Migrar o método CopyButton_Click, disparar e esquecer
Aceder a uma constante enumerada E.Value E::Value Portabilidade do método DisplayChangedFormats
Esperar cooperativamente await ... co_await ... Portabilidade do método CopyButton_Click
Coleção de tipos projetados num 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 Portabilidade do método UpdateStatus
Obter um objeto tipo typeof(MyType) winrt::xaml_typename<MyType>() Migrar a propriedade Scenarios
Declaração de parâmetros 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âmetros
Chamar um método estático T.Method() T::Method()
Cordas string, ou System.String winrt::hstring Gestão de strings em C++/WinRT
Literal de cadeia de caracteres "a string literal" L"a string literal" Migrar o construtor, Current e FEATURE_NAME
Tipo inferido (ou deduzido) var auto Portabilidade do método BuildClipboardFormatsOutputString
Diretiva de utilização using A.B.C; using namespace A::B::C; Migrar o construtor, Current e FEATURE_NAME
Verbatim/literal da cadeia bruta @"verbatim string literal" LR"(raw string literal)" Portabilidade do método DisplayToast

Note

Se um ficheiro de cabeçalho não contiver uma using namespace diretiva para um dado namespace, então terá de qualificar totalmente todos os nomes de tipo para esse namespace; ou pelo menos qualificá-los suficientemente para que o compilador os encontre. Para um exemplo, veja Portar o método DisplayToast.

Migração de classes e membros

Terá de decidir, para cada tipo de C#, se o deve migrar para um tipo do Windows Runtime ou para uma classe/estrutura/enumeração normal de C++. Para mais informações e exemplos detalhados que ilustrem como tomar essas decisões, veja Portar o exemplo do Clipboard para C++/WinRT a partir do C#.

Uma propriedade C# torna-se tipicamente uma função de acesso, uma função mutadora e um elemento de dados de apoio. Para mais informações e um exemplo, veja Migrar a propriedade IsClipboardContentChangedEnabled.

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

Um campo estático C# torna-se uma função de acesso estática C++/WinRT e/ou uma função modificadora. Para mais informações e um exemplo, veja Porting the constructor, Current e FEATURE_NAME.

Para funções-membro, mais uma vez, terá de decidir, relativamente a cada uma, se deve ou não constar da IDL, ou se é uma função-membro pública ou privada do seu tipo de implementação. Para mais informações e exemplos de como decidir, veja IDL para o tipo MainPage.

Portar marcação XAML e ficheiros de recursos

No caso de portar a amostra do Clipboard para C++/WinRT a partir do C#, conseguimos usar a mesma marcação XAML (incluindo recursos) e ficheiros de assets em todo o projeto C# e C++/WinRT. Em alguns casos, serão necessárias edições na marcação para que isso se consiga. Consulte Copiar o XAML e os estilos necessários para concluir a migração de MainPage.

Alterações que envolvem procedimentos dentro da linguagem

Category C# C++/WinRT Consulte também
Gestão de vida num método assíncrono N/A auto lifetime{ get_strong() }; ou
auto lifetime = get_strong();
Portabilidade do método CopyButton_Click
Eliminação using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
Portabilidade do método CopyImage
Construir objeto new MyType(args) MyType{ args } ou
MyType(args)
Migrar a propriedade Cenários
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);
Portabilidade do método Footer_Click
Construir objeto em variável sem args var myObject = new T(); MyType myObject; Portabilidade do método BuildClipboardFormatsOutputString
Forma abreviada de inicialização de objetos var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
Operação vetorial em massa var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
Portabilidade do método CopyButton_Click
Iterar sobre a coleção foreach (var v in c) for (auto&& v : c) Portabilidade do método BuildClipboardFormatsOutputString
Apanhe uma exceção catch (Exception ex) catch (winrt::hresult_error const& ex) Portar o método PasteButton_Click
Detalhes das exceções ex.Message ex.message() Adaptar o método PasteButton_Click
Obtenha o valor da propriedade myObject.MyProperty myObject.MyProperty() Portabilidade do método NotifyUser
Definir o valor de uma propriedade myObject.MyProperty = value; myObject.MyProperty(value);
Incrementar o valor de uma propriedade myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
Para cordas, mude para um construtor
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
Cadeia de idioma para cadeia do Windows Runtime N/A winrt::hstring{ s }
Construção de cordas StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Construção de cordas
Interpolação de cadeias de caracteres $"{i++}) {s.Title}" winrt::to_hstring, e/ou winrt::hstring::operator+ Portando o método OnNavigatedTo
Cadeia vazia para comparação System.String.Empty winrt::hstring::empty Portabilidade do 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 (lançar em caso de falha) (MyType)v v.as<MyType>() Portabilidade do método Footer_Click
Conversão de tipo (nulo em caso de falha) v as MyType v.try_as<MyType>() Adaptar o método PasteButton_Click
Elementos XAML com x:Name são propriedades MyNamedElement MyNamedElement() Portando o construtor, Current e FEATURE_NAME
Mudar para o tópico UI CoreDispatcher.RunAsync DispatcherQueue.TryEnqueue, ou winrt::resume_foreground Portar o método NotifyUser e portar o método HistoryAndRoaming
Construção de elementos UI em código imperativo numa página XAML Ver construção dos elementos UI Ver construção dos elementos UI

As secções seguintes detalham alguns dos itens da tabela.

Construção dos elementos UI

Estes exemplos de código mostram a construção de um elemento UI 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 oferece esta funcionalidade diretamente, mas pode recorrer a alternativas.

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

C++/WinRT também suporta winrt::to_hstring para um número limitado de tipos. Terás de adicionar sobrecargas para quaisquer tipos adicionais que queiras stringificar.

Linguagem Converter int em cadeia de caracteres Transformar enum em cadeia de caracteres
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 string, terá de 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));
        }
    }
}

Estas conversões em cadeia de caracteres são frequentemente utilizadas de forma implícita pela associação de dados.

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

Estas ligações executarão winrt::to_hstring da propriedade vinculada. No caso do segundo exemplo (o StatusEnum), deve fornecer a sua própria sobrecarga de winrt::to_hstring; caso contrário, irá obter um erro de compilação.

Veja também a portabilidade do método Footer_Click.

Construção de cordas

Para construção de cordas, o C# tem um tipo StringBuilder incorporado.

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

Veja também Portar o método BuildClipboardFormatsOutputString e Portar o método DisplayChangedFormats.

Execução de código na thread principal da IU

Este exemplo é retirado da amostra do leitor de códigos de barras.

Quando queres trabalhar na thread principal da interface num projeto C#, normalmente usas o método DispatcherQueue.TryEnqueue (ou o antigo CoreDispatcher.RunAsync no UWP). Aqui está como é o 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. Note que estamos a aceitar parâmetros pelo valor na suposição de que queremos aceder a eles após o primeiro ponto de suspensão (o co_await, neste caso). Para mais informações, veja 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 tiver de efetuar o trabalho com uma prioridade que não a predefinida, consulte a função winrt::resume_foreground, que dispõe de uma sobrecarga que aceita um argumento de prioridade. Para obter exemplos de código que mostram como aguardar uma chamada a winrt::resume_foreground, consulte Programação tendo em conta a afinidade de thread.

Defina as suas classes de runtime no IDL

Veja IDL para o tipo MainPage e Consolide os seus .idl ficheiros.

Inclua os ficheiros de cabeçalho do namespace Windows C++/WinRT que precisa

No C++/WinRT, sempre que quiser utilizar um tipo de um namespace do Windows, tem de incluir o ficheiro de cabeçalho C++/WinRT correspondente ao namespace do Windows. Para um exemplo, veja Portar o método NotifyUser.

Boxe e desembalagem

C# encaixa automaticamente escalares em objetos. O C++/WinRT exige que chames explicitamente a função winrt::box_value . Ambas as línguas exigem que desembales explicitamente. Consulte Boxing e unboxing com C++/WinRT.

Nas tabelas que se seguem, usaremos estas 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");
Unboxing i = (int)o;
s = (string)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

C++/CX e C# criam exceções se tentares abrir um ponteiro nulo para um tipo de valor. O C++/WinRT considera isto um erro de programação, e crasha. Em C++/WinRT, usa a função winrt::unbox_value_or se quiseres lidar com o caso em que o objeto não é do tipo que pensavas.

Scenario C# C++/WinRT
Desencapsular um número 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 encapsulado System.InvalidCastException Colisão
Desempacotar int, usar alternativa se for nulo; falhar se for qualquer outra coisa i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Desempacote int, se possível; use a alternativa para qualquer outro caso i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

Para um exemplo, veja Portar o método OnNavigatedTo e Portar o método Footer_Click.

Boxe e desembalagem de uma corda

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

O tipo ABI HSTRING é um apontador para uma cadeia contada por referência. Mas não deriva de IInspectable, por isso tecnicamente não é um objeto. Além disso, uma HSTRING nula representa a cadeia vazia. O empacotamento de elementos não derivados de IInspectable faz-se envolvendo-os num IReference<T>, e o Windows Runtime fornece uma implementação padrão sob a forma do objeto PropertyValue (os tipos personalizados são indicados como PropertyType::OtherType).

C# representa uma string do Windows Runtime como tipo de referência; enquanto C++/WinRT projeta uma string como tipo de valor. Isto significa que uma cadeia nula em caixa pode ter representações diferentes dependendo de como lá chegaste.

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

Boxe básico e desencaixe.

Operation C# C++/WinRT
Encapsular uma cadeia de caracteres o = s;
A cadeia vazia torna-se objeto não nulo.
o = box_value(s);
A cadeia vazia torna-se objeto não nulo.
Desencapsular uma cadeia de caracteres conhecida s = (string)o;
O objeto nulo torna-se cadeia nula.
InvalidCastException se não for uma cadeia de caracteres.
s = unbox_value<hstring>(o);
O objeto nulo crasha.
Falha se não for uma cadeia de caracteres.
Desempacotar uma cadeia de caracteres opcional s = o as string;
Objeto nulo ou valor não textual é convertido em string nula.

OR

s = o as string ?? fallback;
Nulo ou não-string torna-se um recurso.
Fio vazio preservado.
s = unbox_value_or<hstring>(o, fallback);
Nulo ou não-string torna-se um recurso.
Fio vazio preservado.

Disponibilizar uma classe para a extensão de marcação {Binding}

Se pretender usar a extensão de marcação {Binding} para estabelecer uma associação de dados ao seu tipo de dados, consulte objeto Binding declarado com {Binding}.

Consumir objetos a partir da marcação XAML

Num projeto C#, pode consumir membros privados e elementos nomeados a partir da marcação XAML. Mas em C++/WinRT, todas as entidades consumidas ao usar a extensão de marcação XAML {x:Bind} devem ser expostas publicamente em IDL.

Além disso, a vinculação a um Boolean apresenta true ou false em C#, mas mostra Windows.Foundation.IReference`1<Boolean> em C++/WinRT.

Para mais informações e exemplos de código, consulte Consumir objetos a partir de marcação.

Disponibilizar uma fonte de dados para marcação XAML

Em C++/WinRT versão 2.0.190530.8 ou posterior, winrt::single_threaded_observable_vector cria um vetor observável que suporta tanto IObservableVector<T> como IObservableVector<IInnspectable>. Para um exemplo, veja a propriedade Porting the Scenarios.

Pode redigir o seu ficheiro Midl (.idl) desta forma (veja também Dividir classes de runtime em ficheiros 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 mais informações, consulte controlos de itens XAML; vincular a uma coleção C++/WinRT e Coleções com C++/WinRT.

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

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

  • IObservableVector<IInspectable>
  • IBindableVector e INotifyCollectionChanged
  • IBindableVector e IBindableObservableVector
  • IBindableVector sozinho (não responde a alterações)
  • IVector<IInspectable>
  • IBindableIterable (irá iterar e guardar elementos numa coleção privada)

Uma interface genérica como a IVector<T> não pode ser detetada em tempo de execução. Cada IVector<T> tem um identificador de interface (IID) diferente, que é uma função de T. Qualquer programador pode expandir o conjunto de T arbitrariamente, pelo que claramente o código de ligação XAML nunca pode saber o conjunto completo a consultar. Essa restrição não é um problema para C# porque todos os objetos CLR que implementam IEnumerable<T> implementam automaticamente IEnumerable. Ao nível da ABI, isso significa que todos os objetos que implementam IObservableVector<T> implementam automaticamente IObservableVector<IInspectable>.

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

Consequentemente, aqui está como o exemplo anterior terá de ser.

...
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;
};
...

Se precisares de aceder a objetos em m_bookSkus, terás de fazer 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 execução, a classe base deve ser componível. C# não exige que tomes medidas especiais para tornar as tuas classes componíveis, mas C++/WinRT sim. Usas a palavra-chave não selada para indicar que queres que a tua classe seja utilizável como classe base.

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

No ficheiro de cabeçalho do seu tipo de implementação, deve incluir o ficheiro de cabeçalho da classe base antes de incluir o cabeçalho gerado automaticamente para a classe derivada. Caso contrário, vais receber erros como "Uso ilegal deste tipo como 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