Mover do C++/CX para C++/WinRT

Este tópico é o primeiro de uma série que descreve como você pode portar o código-fonte em seu projeto C++/CX para o equivalente dele em C++/WinRT.

Caso o projeto também use os tipos da WRL (Biblioteca de Modelos C++ do Tempo de Execução do Windows), confira como mover para o C++/WinRT a partir da WRL.

Estratégias para portar

Vale destacar que a portabilidade de C++/CX para C++/WinRT é geralmente simples, com a única exceção da migração de tarefas PPL (Biblioteca de Padrões Paralelos) para corrotinas. Os modelos são diferentes. Não há um mapeamento um a um natural de tarefas PPL para corrotinas e não há uma forma simples de portar mecanicamente o código que funciona para todos os casos. Para obter ajuda com esse aspecto específico da portabilidade e suas opções de interoperação entre os dois modelos, confira Assincronia e interoperabilidade entre C++/WinRT e C++/CX.

As equipes de desenvolvimento normalmente relatam que, depois que elas concluem a etapa de portabilidade do código assíncrono, o restante do trabalho de portabilidade é, em grande parte, mecânico.

Portabilidade em uma passagem

Se você estiver em uma posição de poder portar todo o seu projeto em uma passagem, precisará apenas deste tópico para obter as informações necessárias (e não precisará dos tópicos de interoperabilidade após este). Recomendamos que você comece criando um projeto no Visual Studio usando um dos modelos de projeto C++/WinRT (confira o Suporte ao Visual Studio para C++/WinRT). Em seguida, mova os arquivos de código-fonte para o novo projeto e porte todo o código-fonte do C++/CX para C++/WinRT como você costuma fazer.

Como alternativa, se você preferir fazer o trabalho de portabilidade em seu projeto C++/CX existente, será necessário adicionar o suporte do C++/WinRT a ele. As etapas a serem seguidas para você fazer isso são descritas em Adicionar suporte do C++/WinRT a um projeto C++/CX. Quando você terminar a portabilidade, terá transformado o que era um projeto C++/CX puro em um projeto C++/WinRT puro.

Observação

Se você tiver um projeto de componente do Windows Runtime, a portabilidade em uma passagem será sua única opção. Um projeto de componente do Windows Runtime escrito em C++ deve conter todo o código-fonte do C++/CX ou todo o código-fonte do C++/WinRT. Eles não podem coexistir nesse tipo de projeto.

Portar um projeto gradualmente

Com a exceção de projetos de componente do Windows Runtime, como mencionado na seção anterior, se o tamanho ou a complexidade de sua base de código obrigar você a portar seu projeto gradualmente, será necessário um processo de portabilidade no qual, por um momento, os códigos C++/CX e C++/WinRT coexistem no mesmo projeto. Além de ler este tópico, confira também Interoperabilidade entre C++/WinRT e C++/CX e Assincronia e interoperabilidade entre C++/WinRT e C++/CX. Esses tópicos fornecem informações e exemplos de código que mostram como interoperar entre as duas projeções de linguagem.

Para preparar um projeto para um processo de portabilidade gradual, uma opção é adicionar suporte do C++/WinRT ao seu projeto C++/CX. As etapas a serem seguidas para você fazer isso são descritas em Adicionar suporte do C++/WinRT a um projeto C++/CX. Em seguida, você pode portar gradualmente de lá.

Outra opção é criar um projeto no Visual Studio usando um dos modelos de projeto C++/WinRT (confira Suporte ao Visual Studio para C++/WinRT). Em seguida, adicione o suporte do C++/CX a esse projeto. As etapas a serem seguidas para você fazer isso são descritas em Adicionar suporte do C++/CX a um projeto C++/WinRT. Você pode começar a migrar seu código-fonte para ele e portar um pouco do código-fonte do C++/CX para o C++/WinRT.

Em ambos os casos, você vai interoperar (as duas formas) entre o código do C++/WinRT e qualquer do C++/CX que você ainda não tiver portado.

Observação

O C++/CX e o SDK do Windows declaram tipos no namespace raiz Windows. Um tipo do Windows projetado no C++/WinRT tem o mesmo nome totalmente qualificado como o tipo do Windows, mas ele é colocado no namespace C++ winrt. Esses namespaces distintos permitem que você faça a portabilidade do C++/CX para o C++/WinRT em seu próprio ritmo.

Portar um projeto XAML gradualmente

Importante

Para um projeto que usa XAML, em um determinado momento, todos os tipos de página XAML precisam ser totalmente C++/CX ou totalmente C++/WinRT. Você ainda pode misturar o C++/CX e o C++/WinRT fora dos tipos de página XAML dentro do mesmo projeto (em seus modelos e viewmodels e em outro lugar).

Para esse cenário, o fluxo de trabalho que recomendamos é criar um projeto C++/WinRT e copiar o código-fonte e a marcação do projeto C++/CX. Desde que todos os tipos de página XAML sejam C++/WinRT, você poderá adicionar novas páginas XAML com Projeto>Adicionar Novo Item...>Visual C++>Página em branco (C++/WinRT).

Como alternativa, é possível usar um WRC (componente do Windows Runtime) para fatorar o código do projeto XAML C++/CX à medida que você faz a portabilidade.

  • Você pode criar um projeto WRC C++/CX, migar o máximo possível do código do C++/CX para esse projeto e alterar o projeto XAML para C++/WinRT.
  • Ou você pode criar um projeto WRC C++/WinRT, deixar o projeto XAML como C++/CX e começar a portar o C++/CX para o C++/WinRT e migar o código de resultado do projeto XAML para o projeto de componente.
  • Você também pode ter um projeto de componente C++/CX em conjunto com um projeto de componente C++/WinRT dentro da mesma solução, fazer referência a ambos no seu projeto de aplicativo e realizar a portabilidade gradual de um para o outro. Novamente, confira Interoperabilidade entre C++/WinRT e C++/CX para obter mais detalhes sobre como usar as duas projeções de linguagem no mesmo projeto.

Primeiras etapas para portar um projeto C++/CX para C++/WinRT

Não importa qual será a estratégia de portabilidade (portabilidade em uma passagem ou portabilidade gradual), a primeira etapa é preparar seu projeto para a portabilidade. Veja uma recapitulação do que foi descrito em Estratégias de portabilidade em termos do tipo de projeto com o qual você começará e como configurá-lo.

  • Portabilidade em uma passagem. Crie um projeto no Visual Studio usando um dos modelos de projeto C++/WinRT. Mova os arquivos do seu projeto C++/CX para o novo projeto e porte o código-fonte do C++/CX.
  • Portar um projeto que não é XAML gradualmente. Você pode optar por adicionar suporte do C++/WinRT ao projeto C++/CX (confira Adicionar suporte do C++/WinRT a um projeto C++/CX) e portar gradualmente. Ou você pode optar por criar um projeto C++/WinRT e adicionar suporte do C++/CX a ele (confira Adicionar suporte do C++/CX a um projeto C++/WinRT), mover os arquivos e portar gradualmente.
  • Portar um projeto XAML gradualmente. Crie um projeto C++/WinRT, mova os arquivos e a porte gradualmente. A qualquer momento, os tipos de página XAML devem estar inteiramente em C++/WinRT ou inteiramente em C++/CX.

O restante deste tópico se aplica independentemente da estratégia de portabilidade escolhida. Ele contém um catálogo de detalhes técnicos envolvidos na portabilidade do código-fonte do C++/CX para o C++/WinRT. Se você estiver portando gradualmente, convém conferir Interoperabilidade entre C++/WinRT e C++/CX e Assincronia e interoperabilidade entre C++/WinRT e C++/CX.

Convenções de nomenclatura de arquivo

Arquivos de marcação XAML

Origem do arquivo C++/CX C++/WinRT
Arquivos XAML do desenvolvedor MyPage.xaml
MyPage.xaml.h
MyPage.xaml.cpp
MyPage.xaml
MyPage.h
MyPage.cpp
MyPage.idl (veja abaixo)
Arquivos XAML gerados MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.g.h

Observe que o C++/WinRT remove o .xaml dos nomes de arquivo *.h e *.cpp.

O C++/WinRT adiciona um arquivo de desenvolvedor adicional, o arquivo MIDL (.idl) . O C++/CX gera de forma automática esse arquivo internamente, adicionando a ele todos os membros públicos e protegidos. No C++/WinRT, você mesmo adiciona e cria o arquivo. Para obter mais detalhes, exemplos de código e um passo a passo da criação de IDL, confira Controles XAML; associação a uma propriedade do C++/WinRT.

Confira também Como fatorar classes de runtime em arquivos MIDL (.idl)

classes de runtime

O C++/CX não impõe restrições quanto aos nomes dos arquivos de cabeçalho; é comum colocar várias definições de classe de runtime em um único arquivo de cabeçalho, especialmente para classes pequenas. Mas o C++/WinRT exige que cada classe de runtime tenha seu próprio arquivo de cabeçalho com o mesmo nome de classe.

C++/CX C++/WinRT
Common.h
ref class A { ... }
ref class B { ... }
Common.idl
runtimeclass A { ... }
runtimeclass B { ... }
A.h
namespace implements {
  struct A { ... };
}
B.h
namespace implements {
  struct B { ... };
}

Menos comum (mas ainda válido) no C++/CX é usar arquivos de cabeçalho nomeados de forma diferente para controles personalizados XAML. Você precisará renomear esse arquivo de cabeçalho para que ele corresponda ao nome de classe.

C++/CX C++/WinRT
A.xaml
<Page x:Class="LongNameForA" ...>
A.xaml
<Page x:Class="LongNameForA" ...>
A.h
partial ref class LongNameForA { ... }
LongNameForA.h
namespace implements {
  struct LongNameForA { ... };
}

Requisitos do arquivo de cabeçalho

O C++/CX não exige que você inclua nenhum arquivo de cabeçalho especial, pois ele gera de forma automática arquivos de cabeçalho com base em arquivos .winmd internamente. No C++/CX, é comum usar diretivas using para namespaces consumidos por nome.

using namespace Windows::Media::Playback;

String^ NameOfFirstVideoTrack(MediaPlaybackItem^ item)
{
    return item->VideoTracks->GetAt(0)->Name;
}

A diretiva using namespace Windows::Media::Playback nos permite escrever MediaPlaybackItem sem um prefixo de namespace. Também alteramos o namespace Windows.Media.Core, porque item->VideoTracks->GetAt(0) retorna um Windows.Media.Core.VideoTrack. Mas não precisamos digitar o nome VideoTrack em nenhum lugar, portanto, não precisamos de uma diretiva using Windows.Media.Core.

Mas o C++/WinRT exige que você inclua um arquivo de cabeçalho correspondente a cada namespace consumido, mesmo que você não o nomeie.

#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.Media.Core.h> // !!This is important!!

using namespace winrt;
using namespace Windows::Media::Playback;

winrt::hstring NameOfFirstVideoTrack(MediaPlaybackItem const& item)
{
    return item.VideoTracks().GetAt(0).Name();
}

Por outro lado, embora o evento MediaPlaybackItem.AudioTracksChanged seja do tipo TypedEventHandler<MediaPlaybackItem, Windows.Foundation.Collections.IVectorChangedEventArgs>, não precisamos incluir winrt/Windows.Foundation.Collections.h porque não usamos esse evento.

O C++/WinRT também exige que você inclua arquivos de cabeçalho para namespaces consumidos pela marcação XAML.

<!-- MainPage.xaml -->
<Rectangle Height="400"/>

O uso da classe Rectangle significa que você precisa adicionar essa inclusão.

// MainPage.h
#include <winrt/Windows.UI.Xaml.Shapes.h>

Se você esquecer um arquivo de cabeçalho, tudo será compilado corretamente, mas você obterá erros de vinculador porque as classes consume_ estão ausentes.

Passagem de parâmetro

Ao escrever o código-fonte do C++/CX, você passa os tipos C++/CX como parâmetros de função como referência de circunflexo (^).

void LogPresenceRecord(PresenceRecord^ record);

No C++/WinRT, para funções síncronas, você precisa usar parâmetros const& por padrão. Isso evita cópia e sobrecarga encaixada. Entretanto, as corrotinas precisam usar passar-por-valor para garantir a captura pelo valor e evitar problemas de tempo de vida (para ver mais detalhes, confira Operações assíncronas e simultaneidade com C++/WinRT).

void LogPresenceRecord(PresenceRecord const& record);
IASyncAction LogPresenceRecordAsync(PresenceRecord const record);

Um objeto C++/WinRT é fundamentalmente um valor que mantém um ponteiro da interface para o objeto do Windows Runtime subjacente. Quando você copia um objeto C++/WinRT, o compilador copia o ponteiro da interface encapsulado, aumentando sua contagem de referência. A consequente destruição da cópia envolve reduzir a contagem de referência. Portanto, apenas incorre na sobrecarga de uma cópia quando necessário.

Referências de variáveis e campo

Ao escrever o código-fonte do C++/CX, você usa as variáveis de circunflexo (^) para fazer referência a objetos do Windows Runtime e de operador seta (->) para desreferenciar uma variável desse tipo.

IVectorView<User^>^ userList = User::Users;

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
    ...

Ao fazer a portabilidade para o código C++/WinRT equivalente, você gastar um bom tempo removendo os circunflexos e alterando o operador seta (->) para o operador ponto (.). Os tipos projetados de C++/ WinRT são valores, e não ponteiros.

IVectorView<User> userList = User::Users();

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList.Size(); ++iUser)
    ...

O construtor padrão para uma referência de ângulo de visão do C++/CX inicializa-o como nulo. Aqui está um exemplo de código C++/CX em que criamos uma variável/campo do tipo correto, mas que não foi inicializada. Em outras palavras, ela não se refere inicialmente a um TextBlock, e pretendemos atribuir a referência mais tarde.

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

Para o equivalente no C++/WinRT, confira Inicialização atrasada.

Propriedades

A extensões de linguagem C++/CX incluem o conceito de propriedades. Ao escrever o código-fonte do C++/CX, você pode acessar uma propriedade como se fosse um campo. O C++ padrão não tem o conceito de propriedade, portanto, no C++/WinRT, você pode obter e definir funções.

Nos exemplos a seguir, XboxUserId, UserState, PresenceDeviceRecords e Size são propriedades.

Recuperar o valor de uma propriedade

Veja como obter o valor de uma propriedade no C++/CX.

void Sample::LogPresenceRecord(PresenceRecord^ record)
{
    auto id = record->XboxUserId;
    auto state = record->UserState;
    auto size = record->PresenceDeviceRecords->Size;
}

O código-fonte do C++/WinRT equivalente chama uma função com o mesmo nome da propriedade, mas sem parâmetros.

void Sample::LogPresenceRecord(PresenceRecord const& record)
{
    auto id = record.XboxUserId();
    auto state = record.UserState();
    auto size = record.PresenceDeviceRecords().Size();
}

Observe que a função PresenceDeviceRecords retorna um objeto do Windows Runtime com uma função Size. Como o objeto retornado também é um tipo projetado do C++/WinRT, desreferenciamos usando o operador ponto para chamar Size.

Definir uma propriedade como um novo valor

Uma propriedade é definida com um novo valor de forma semelhante. Primeiro, no C++/CX.

record->UserState = newValue;

Para fazer o equivalente no C++/WinRT, chame uma função com o mesmo nome da propriedade e passe um argumento.

record.UserState(newValue);

Criar a instância de uma classe

Trabalhe com o objeto do C++/CX por meio de um identificador, conhecido como uma referência de circunflexo (^). Crie um novo objeto por meio da palavra-chave ref new, que, por sua vez, chama RoActivateInstance para ativar uma nova instância da classe de runtime.

using namespace Windows::Storage::Streams;

class Sample
{
private:
    Buffer^ m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
};

Um objeto do C++/WinRT é um valor; portanto, você pode alocá-lo na pilha ou como um campo de um objeto. Nunca use ref new (nem new) para alocar um objeto do C++/WinRT. Nos bastidores, RoActivateInstance ainda está sendo chamado.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
private:
    Buffer m_gamerPicBuffer{ MAX_IMAGE_SIZE };
};

Se um recurso foi dispendioso para inicializar, é comum atrasar a inicialização até que ela seja realmente necessária. Como já mencionado, o construtor padrão para uma referência de ângulo de visão do C++/CX inicializa-o como nulo.

using namespace Windows::Storage::Streams;

class Sample
{
public:
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer^ m_gamerPicBuffer;
};

O mesmo código portado para o C++/WinRT. Observe o uso do construtor std::nullptr_t. Para obter mais informações sobre esse construtor, confira Inicialização atrasada.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer m_gamerPicBuffer{ nullptr };
};

Como o construtor padrão afeta as coleções

Os tipos de coleção C++ usam o construtor padrão, o que pode resultar na construção de um objeto indesejado.

Cenário C++/CX C++/WinRT (incorreto) C++/WinRT (correto)
Variável local, inicialmente vazia TextBox^ textBox; TextBox textBox; // Creates a TextBox! TextBox textBox{ nullptr };
Variável de membro, inicialmente vazia class C {
  TextBox^ textBox;
};
class C {
  TextBox textBox; // Creates a TextBox!
};
class C {
  TextBox textbox{ nullptr };
};
Variável global, inicialmente vazia TextBox^ g_textBox; TextBox g_textBox; // Creates a TextBox! TextBox g_textBox{ nullptr };
Vetor de referências vazias std::vector<TextBox^> boxes(10); // Creates 10 TextBox objects!
std::vector<TextBox> boxes(10);
std::vector<TextBox> boxes(10, nullptr);
Definir um valor em um mapa std::map<int, TextBox^> boxes;
boxes[2] = value;
std::map<int, TextBox> boxes;
// Creates a TextBox at 2,
// then overwrites it!
boxes[2] = value;
std::map<int, TextBox> boxes;
boxes.insert_or_assign(2, value);
Matriz de referências vazias TextBox^ boxes[2]; // Creates 2 TextBox objects!
TextBox boxes[2];
TextBox boxes[2] = { nullptr, nullptr };
Emparelhar std::pair<TextBox^, String^> p; // Creates a TextBox!
std::pair<TextBox, String> p;
std::pair<TextBox, String> p{ nullptr, nullptr };

Mais sobre coleções de referências vazias

Sempre que você tiver uma Platform::Array^ (confira Port Platform::Array^) em C++/CX, você terá a opção de portar para um std:: vector em C++/WinRT (na verdade, qualquer contêiner contíguo) em vez de deixá-lo como uma matriz. Há vantagens em escolher std::vector.

Por exemplo, embora haja uma abreviação para criar um vetor de tamanho fixo de referências vazias (confira a tabela acima), não há nenhuma abreviação desse tipo para criar uma matriz de referências vazias. Você precisa repetir nullptr para cada elemento em uma matriz. Se você tiver poucos, os extras serão construídos por padrão.

Para um vetor, você pode preenchê-la com referências vazias na inicialização (como na tabela acima) ou pode preenchê-la com referências vazias pós-inicialização com um código como este.

std::vector<TextBox> boxes(10); // 10 default-constructed TextBoxes.
boxes.resize(10, nullptr); // 10 empty references.

Mais sobre o exemplo de std::map

O operador subscrito [] para std::map se comporta desta forma.

  • Se a chave for encontrada no mapa, retorne uma referência ao valor existente (que poderá ser substituído).
  • Se a chave não for encontrada no mapa, crie uma entrada no mapa que consiste na chave (movida, se móvel) e um valor construído padrão e retorne uma referência ao valor (que você poderá então substituir).

Em outras palavras, o operador [] sempre cria uma entrada no mapa. Isso é diferente de C#, Java e JavaScript.

Converter de uma classe base de runtime para uma derivada

É comum ter uma referência à base que você sabe que se refere a um objeto de um tipo derivado. No C++/CX, use dynamic_cast para converter a referência à base em uma referência para derivado. O dynamic_cast é apenas uma chamada oculta para QueryInterface. Aqui está um exemplo típico em que você está manipulando um evento de alteração de propriedade de dependência e deseja converter DependencyObject de volta para o tipo real que tem a propriedade da dependência.

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject^ d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ e)
{
    BgLabelControl^ theControl{ dynamic_cast<BgLabelControl^>(d) };

    if (theControl != nullptr)
    {
        // succeeded ...
    }
}

O código C++/WinRT equivalente substitui o dynamic_cast por uma chamada para a função IUnknown::try_as, que encapsula QueryInterface. Em vez disso, você também tem a opção de chamar IUnknown::as, que gerará uma exceção se a consulta para a interface necessária (a interface padrão do tipo que você está solicitando) não for retornada. Veja aqui um exemplo de código C++/WinRT.

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
    {
        // succeeded ...
    }

    try
    {
        BgLabelControlApp::BgLabelControl theControl{ d.as<BgLabelControlApp::BgLabelControl>() };
        // succeeded ...
    }
    catch (winrt::hresult_no_interface const&)
    {
        // failed ...
    }
}

Classes derivadas

Para derivação de uma classe de runtime, a classe base precisa ser combinável. O C++/CX não exige que você execute nenhuma etapa especial para tornar suas classes combináveis, ao contrário do C++/WinRT. Use a palavra-chave não selada para indicar que deseja que a classe seja utilizável como uma classe base.

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

Na classe do cabeçalho de implementação, você precisa incluir o arquivo de cabeçalho da classe base antes de incluir o cabeçalho gerado automaticamente da classe derivada. Caso contrário, você receberá erros como "uso ilícito deste 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>
    {
        ...
    }
}

Processamento de eventos com um delegado

Veja um exemplo típico de processamento de eventos no C++/CX usando uma função lambda como delegado nesse caso.

auto token = myButton->Click += ref new RoutedEventHandler([=](Platform::Object^ sender, RoutedEventArgs^ args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

Esse é o equivalente no C++/WinRT.

auto token = myButton().Click([=](IInspectable const& sender, RoutedEventArgs const& args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

Em vez de uma função lambda, você pode optar por implementar o delegado como uma função livre como uma função de ponteiro para membro. Para obter mais informações, veja como manipular eventos usando delegados no C++/WinRT.

Se estiver fazendo a portabilidade de uma base de código C++/CX em que os eventos e delegados são usados internamente (e não em binários), winrt::delegate ajudará a replicar esse padrão no C++/WinRT. Confira também Delegados parametrizados, sinais simples e retornos de chamada dentro de um projeto.

Revogar um delegado

No C++/CX, use o operador -= para revogar o registro de um evento anterior.

myButton->Click -= token;

Esse é o equivalente no C++/WinRT.

myButton().Click(token);

Para obter mais informações e opções, confira Revogar um delegado registrado.

Conversão boxing e unboxing

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

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

C++/CX C++/WinRT
int i; int i;
String^ s; winrt::hstring s;
Object^ o; IInspectable o;
Operação C++/CX C++/WinRT
Conversão boxing 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 desejar lidar com o caso em que o objeto não é do tipo que você pensou que fosse.

Cenário C++/CX C++/WinRT
Fazer a conversão unboxing de um inteiro conhecido i = (int)o; i = unbox_value<int>(o);
Se o for nulo Platform::NullReferenceException Falha
Se o não for um int convertido Platform::InvalidCastException Falha
Fazer a conversão unboxing de int, se for nulo, usar fallback; falhar, em qualquer outra situação i = o ? (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 auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

Conversão boxing e unboxing de uma cadeia de caracteres

Uma cadeia de caracteres é, de algumas maneiras, um tipo de valor e, de outras, um tipo de referência. O C++/CX e o C++/WinRT tratam as cadeias de caracteres de maneira diferente.

O tipo do ABI HSTRING é um ponteiro para uma cadeia de caracteres de contagem de referências. Mas ele não é derivado de IInspectable, portanto, não é tecnicamente 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).

O C++/CX representa uma cadeia de caracteres do Windows Runtime como um tipo de referência, enquanto o C++/WinRT projeta uma cadeia de caracteres como um tipo de valor. Isso significa que uma cadeia de caracteres nula convertida pode ter diferentes representações, dependendo do procedimento adotado.

Além disso, o C++/CX permite desreferenciar uma String^ nula, caso em que se ela comporta como a cadeia de caracteres "".

Comportamento C++/CX 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
projetos HSTRING nulos como (String^)nullptr hstring{}
São nulos e idênticos ""? Sim Sim
Validade de nulo s = nullptr;
s->Length == 0 (válido)
s = hstring{};
s.size() == 0 (válido)
Se você atribuir uma cadeia de caracteres nula ao objeto o = (String^)nullptr;
o == nullptr
o = box_value(hstring{});
o != nullptr
Se você atribuir "" ao objeto o = "";
o == nullptr
o = box_value(hstring{L""});
o != nullptr

Conversões boxing e unboxing básicas.

Operação C++/CX C++/WinRT
Fazer a conversão boxing de uma cadeia de caracteres o = s;
A cadeia de caracteres vazia torna-se nullptr.
o = box_value(s);
A cadeia de caracteres vazia se torna um objeto não nulo.
Fazer a conversão unboxing de uma cadeia de caracteres conhecida s = (String^)o;
O objeto nulo torna-se uma cadeia de caracteres vazia.
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 = dynamic_cast<String^>(o);
Objeto nulo ou não cadeia de caracteres se torna uma cadeia de caracteres vazia.
s = unbox_value_or<hstring>(o, fallback);
Nulo ou não cadeia de caracteres se torna fallback.
Cadeia de caracteres vazia preservada.

Simultaneidade e operações assíncronas

A PPL (Biblioteca de Padrões Paralelos) (concurrency::task, por exemplo) foi atualizada para dar suporte às referências de ângulo de visão do C++/CX.

Para o C++/WinRT, você deverá usar corrotinas e co_await. Para saber mais e obter exemplos de código, confira Simultaneidade e operações assíncronas com C++/WinRT.

Como consumir objetos por meio da marcação XAML

Em um projeto C++/CX, você pode consumir membros privados e elementos nomeados da marcação XAML. Mas no C++/WinRT, todas as entidades consumidas com o uso da extensão de marcação XAML {x:Bind} precisam ser expostas publicamente em IDL.

Além disso, a associação a um booliano exibe true ou false no C++/CX, mas mostra Windows.Foundation.IReference`1<Boolean> no C++/WinRT.

Para obter mais informações e exemplos de código, confira Como consumir objetos para marcação.

Mapear os tipos Platform do C++/CX para tipos do C++/WinRT

O C++/CX fornece vários tipos de dados no namespace Platform. Esses tipos não são do C++ padrão, portanto, podem ser usados somente ao habilitar as extensões de linguagem do Windows Runtime (propriedade do projeto do Visual Studio C/C++>Geral>Consumir extensão do Windows Runtime>Sim (/ZW) ). A tabela abaixo ajuda a fazer a portabilidade dos tipos Platform para seus equivalentes no C++/WinRT. Depois de fazer isso, como o C++/WinRT é o C++ padrão, você poderá desativar a opção /ZW.

C++/CX C++/WinRT
Platform::Agile^ winrt::agile_ref
Platform::Array^ Confira Fazer a portabilidade de Platform::Array^
Platform::Exception^ winrt::hresult_error
Platform::InvalidArgumentException^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Platform::String^ winrt::hstring

Fazer a portabilidade de Platform::Agile^ para winrt::agile_ref

O tipo Platform:: Agile^ no C++/CX representa uma classe do Windows Runtime que pode ser acessada de qualquer thread. O equivalente do C++/WinRT é winrt::agile_ref.

No C++/CX.

Platform::Agile<Windows::UI::Core::CoreWindow> m_window;

No C++/WinRT.

winrt::agile_ref<Windows::UI::Core::CoreWindow> m_window;

Fazer a portabilidade de Platform::Array^

Nos casos em que o C++/CX exige que você use uma matriz, o C++/WinRT permite que você use qualquer contêiner contíguo. Confira Como o construtor padrão afeta as coleções por um motivo pelo qual std::vector é uma boa opção.

Portanto, sempre que você tiver uma Platform::Array^ em C++/CX, suas opções de portabilidade incluirão o uso de uma lista de inicializadores, um std::array ou um std::vector. Para obter mais informações e exemplos de código, confira Listas de inicializadores padrão e Matrizes e vetores padrão.

Fazer a portabilidade de Platform::Exception^ para winrt::hresult_error

O tipo Platform::Exception^ é produzido no C++/CX quando uma API do Windows Runtime retorna um HRESULT diferente de S_OK. O equivalente do C++/WinRT é winrt::hresult_error.

Para fazer a portabilidade para C++/WinRT, altere todos os códigos que usam Platform::Exception^ para usarem winrt::hresult_error.

No C++/CX.

catch (Platform::Exception^ ex)

No C++/WinRT.

catch (winrt::hresult_error const& ex)

O C++/WinRT fornece essas classes de exceção.

Tipo de exceção Classe base HRESULT
winrt::hresult_error chamar hresult_error::to_abi
winrt::hresult_access_denied winrt::hresult_error E_ACCESSDENIED
winrt::hresult_canceled winrt::hresult_error ERROR_CANCELLED
winrt::hresult_changed_state winrt::hresult_error E_CHANGED_STATE
winrt::hresult_class_not_available winrt::hresult_error CLASS_E_CLASSNOTAVAILABLE
winrt::hresult_illegal_delegate_assignment winrt::hresult_error E_ILLEGAL_DELEGATE_ASSIGNMENT
winrt::hresult_illegal_method_call winrt::hresult_error E_ILLEGAL_METHOD_CALL
winrt::hresult_illegal_state_change winrt::hresult_error E_ILLEGAL_STATE_CHANGE
winrt::hresult_invalid_argument winrt::hresult_error E_INVALIDARG
winrt::hresult_no_interface winrt::hresult_error E_NOINTERFACE
winrt::hresult_not_implemented winrt::hresult_error E_NOTIMPL
winrt::hresult_out_of_bounds winrt::hresult_error E_BOUNDS
winrt::hresult_wrong_thread winrt::hresult_error RPC_E_WRONG_THREAD

Observe que cada classe (por meio da classe base hresult_error) fornece uma função to_abi, que retorna o HRESULT do erro, e uma função message, que retorna a representação da cadeia de caracteres desse HRESULT.

Veja um exemplo de geração de uma exceção no C++/CX.

throw ref new Platform::InvalidArgumentException(L"A valid User is required");

E o equivalente no C++/WinRT.

throw winrt::hresult_invalid_argument{ L"A valid User is required" };

Fazer a portabilidade de Platform::Object^ para winrt::Windows::Foundation::IInspectable

Como todos os tipos do C++/WinRT, winrt::Windows::Foundation::IInspectable é um tipo de valor. Veja como inicializar uma variável desse tipo como null.

winrt::Windows::Foundation::IInspectable var{ nullptr };

Fazer a portabilidade de Platform::String^ para winrt::hstring

Platform::String^ é equivalente ao tipo ABI de HSTRING do Windows Runtime. Para o C++/WinRT, o equivalente é winrt::hstring. Porém, com o C++/WinRT, você pode chamar as APIs do Windows Runtime usando tipos de cadeia de caracteres longas da Biblioteca Padrão do C++, como std::wstring, e/ou literais de cadeias de caracteres longas. Para obter mais detalhes e exemplos de código, confira Processamento da cadeia de caracteres em C++/WinRT.

Com o C++/CX, você pode acessar a propriedade Platform::String::Data para recuperar a cadeia de caracteres como matriz const wchar_t* C-style (por exemplo, para transmiti-la a std::wcout).

auto var{ titleRecord->TitleName->Data() };

Para fazer o mesmo com C++/WinRT, você pode usar a função hstring::c_str para obter uma versão de cadeia de caracteres C-style terminada em null, assim como de std::wstring.

auto var{ titleRecord.TitleName().c_str() };

Quando se trata de implementar APIs que recebem ou retornam cadeias de caracteres, normalmente você altera qualquer código do C++/CX que usa Platform::String^ para usar winrt::hstring.

Veja um exemplo de uma API do C++/CX que recebe a cadeia de caracteres.

void LogWrapLine(Platform::String^ str);

Para o C++/WinRT, é possível declarar a API no MIDL 3.0 dessa forma.

// LogType.idl
void LogWrapLine(String str);

A cadeia de ferramentas do C++/WinRT gera o código-fonte semelhante a este.

void LogWrapLine(winrt::hstring const& str);

ToString()

Os tipos C++/CX fornecem o método Object::ToString.

int i{ 2 };
auto s{ i.ToString() }; // s is a Platform::String^ with value L"2".

O C++/ WinRT não fornece diretamente esse recurso, mas você poderá usar 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 em um número limitado de tipos. Você precisará adicionar sobrecargas para os tipos adicionais que deseja converter em cadeia de caracteres.

Language Converter int em cadeia de caracteres Converter uma enumeração em cadeia de caracteres
C++/CX String^ result = "hello, " + intValue.ToString(); String^ result = "status: " + status.ToString();
C++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

No caso da conversão de uma enumeração 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ê precisará fornecer sua própria sobrecarga de winrt::to_hstring; caso contrário, obterá um erro do compilador.

Construção da cadeia de caracteres

O C++/CX e o C++/WinRT fazem o adiamento para o std::wstringstream padrão para a construção da cadeia de caracteres.

Operação C++/CX C++/WinRT
Acrescentar cadeia de caracteres, preservando nulos stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Acrescentar cadeia de caracteres, parar no primeiro nulo stream << s->Data(); stream << s.c_str();
Extrair resultado ws = stream.str(); ws = stream.str();

Mais exemplos

Nos exemplos abaixo, ws é uma variável do tipo std::wstring. Além disso, embora o C++/CX possa construir uma Platform::String com base em uma cadeia de caracteres de 8 bits, o C++/WinRT não faz isso.

Operação C++/CX C++/WinRT
Construir uma cadeia de caracteres com base no literal String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
Fazer a conversão de std::wstring, preservando nulos String^ s = ref new String(ws.c_str(),
  (uint32_t)ws.size());
winrt::hstring s{ ws };
s = winrt::hstring(ws);
// s = ws; // Doesn't compile
Fazer a conversão de std::wstring, parar no primeiro nulo String^ s = ref new String(ws.c_str()); winrt::hstring s{ ws.c_str() };
s = winrt::hstring(ws.c_str());
// s = ws.c_str(); // Doesn't compile
Fazer a conversão em std::wstring, preservando nulos std::wstring ws{ s->Data(), s->Length };
ws = std::wstring(s>Data(), s->Length);
std::wstring ws{ s };
ws = s;
Fazer a conversão em std::wstring, parar no primeiro nulo std::wstring ws{ s->Data() };
ws = s->Data();
std::wstring ws{ s.c_str() };
ws = s.c_str();
Passar o literal para o método Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
Passar std::wstring para o método Method(ref new String(ws.c_str(),
  (uint32_t)ws.size()); // Stops on first null
Method(ws);
// param::winrt::hstring accepts std::wstring_view

APIs importantes