Compartilhar via


Mudar de 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 de C++/CX para o equivalente em C++/WinRT.

Se o seu projeto também estiver usando tipos da Biblioteca de Modelos C++ (WRL) do Windows Runtime, consulte Mover do WRL para C++/WinRT.

Estratégias para portabilidade

Vale a pena saber que a portabilidade de C++/CX para C++/WinRT geralmente é simples, com uma exceção de migrar de PPL (Parallel Patterns Library) tarefas para coroutines. Os modelos são diferentes. Não há um mapeamento natural um-para-um de tarefas PPL para coroutinas, e não há uma maneira 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, consulte Assincronia e interoperabilidade entre C++/WinRT e C++/CX.

As equipes de desenvolvimento relatam rotineiramente que, uma vez que superam o obstáculo de portar o código assíncrono, o restante do trabalho de migração é em grande parte mecânico.

Migração em uma única etapa

Se você estiver em condição de portar seu projeto inteiro de uma só vez, precisará apenas deste tópico para as informações necessárias (e não precisará dos tópicos de interoperabilidade e que seguem este). Recomendamos que você comece por criar um novo projeto no Visual Studio usando um dos modelos de projeto para C++/WinRT (consulte suporte do Visual Studio para C++/WinRT). Em seguida, mova seus arquivos de código-fonte para esse novo projeto e porte todo o código-fonte C++/CX para C++/WinRT enquanto você faz isso.

Como alternativa, se você preferir fazer o trabalho de portabilidade em seu projeto C++/CX existente, precisará adicionar suporte a C++/WinRT a ele. As etapas a seguir para fazer isso são descritas em Tomando um projeto C++/CX e adicionando suporte ao C++/WinRT. Quando terminar de fazer a portabilidade, você 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 C++/CX ou todo o código-fonte C++/WinRT. Eles não podem coexistir nesse tipo de projeto.

Portando um projeto gradualmente

Com exceção dos projetos de componente do Windows Runtime, conforme mencionado na seção anterior, se o tamanho ou a complexidade da base de código for necessário para portar seu projeto gradualmente, você precisará de um processo de portabilidade no qual, por um tempo, o código C++/CX e C++/WinRT exista lado a lado no mesmo projeto. Além de ler este tópico, consulte também interoperabilidade entre C++/WinRT e C++/CX e Assíncronia e interoperabilidade entre C++/WinRT e C++/CX. Esses tópicos fornecem informações e exemplos de código mostrando como interoperar entre as duas projeções de idioma.

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 seguir para fazer isso são descritas em Tomando um projeto C++/CX e adicionando suporte ao C++/WinRT. A partir daí, você pode portar gradualmente.

Outra opção é criar um novo projeto no Visual Studio usando um dos modelos de projeto do C++/WinRT (consulte suporte do Visual Studio ao C++/WinRT). Em seguida, adicione suporte a C++/CX a esse projeto. As etapas que você segue para fazer isso são descritas em Tomando um projeto C++/WinRT e adicionando suporte para C++/CX. Em seguida, você pode começar a mover o código-fonte para a nova estrutura e a portar parte do código-fonte C++/CX para C++/WinRT ao mesmo tempo.

Em ambos os casos, você interoperará de ambas as formas entre seu código C++/WinRT e qualquer código C++/CX que ainda não tenha portado.

Observação

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

Portar gradualmente um projeto XAML

Importante

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

Para esse cenário, o fluxo de trabalho que recomendamos é criar um novo 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ê pode adicionar novas páginas XAML com Projeto>Adicionar Novo Item...>Visual C++>Página em Branco (C++/WinRT).

Como alternativa, você pode usar um componente Windows Runtime para separar o código do projeto XAML C++/CX à medida que o porta.

  • Você pode criar um novo projeto C++/CX WRC, mover o máximo de código C++/CX possível para esse projeto e, em seguida, alterar o projeto XAML para C++/WinRT.
  • Ou você pode criar um novo projeto C++/WinRT WRC, deixar o projeto XAML como C++/CX e começar a portar C++/CX para C++/WinRT e mover o código resultante para fora do projeto XAML e para o projeto de componente.
  • Você também pode ter um projeto de componente C++/CX ao lado de um projeto de componente C++/WinRT na mesma solução, fazer referência a ambos do seu projeto de aplicativo e, gradualmente, portar de um para o outro. Novamente, consulte Interop entre C++/WinRT e C++/CX para mais informações sobre como usar as duas projeções de linguagem no mesmo projeto.

Primeiras etapas na portabilidade de um projeto C++/CX para C++/WinRT

Não importa qual será sua estratégia de portabilidade (portabilidade em uma passagem ou portabilidade gradual), sua primeira etapa é preparar seu projeto para portabilidade. Aqui está uma recapitulação do que descrevemos em — Estratégias para portabilidade de, referindo-se ao tipo de projeto com o qual você começará e como configurá-lo.

  • Portar em uma única passagem. Crie um novo projeto no Visual Studio usando um dos modelos de projeto do C++/WinRT. Mova os arquivos do projeto C++/CX para esse novo projeto e porte o código-fonte C++/CX.
  • Migração de um projeto que não utiliza XAML de forma gradual. Você pode optar por adicionar suporte do C++/WinRT ao seu projeto C++/CX (consulte Tomando um projeto C++/CX e adicionando suporte a C++/WinRT) e portar gradualmente. Ou você pode optar por criar um novo projeto C++/WinRT e adicionar suporte C++/CX ao projeto (consulte Tomando um projeto C++/WinRT e adicionando suporte C++/CX), mover os arquivos e fazer a migração gradualmente.
  • Portando um projeto XAML gradualmente. Crie um novo projeto C++/WinRT, mova os arquivos e porte o código gradualmente. Em determinado momento, os tipos de página XAML devem ser ou todos em C++/WinRT ou todos 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 de C++/CX para C++/WinRT. Se você estiver portando gradualmente, provavelmente também desejará ver 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 C++/WinRT remove o .xaml dos nomes dos arquivos *.h e *.cpp.

C++/WinRT adiciona um arquivo de desenvolvedor adicional, o arquivo Midl (.idl). O C++/CX gera esse arquivo automaticamente 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, consulte controles XAML; associar a uma propriedade C++/WinRT.

Veja também Fatoração de classes de tempo de execução em arquivos Midl (.idl)

Classes de runtime

O C++/CX não impõe restrições 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 requer que cada classe de runtime tenha seu próprio arquivo de cabeçalho com o nome da 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 legal) em C++/CX é usar arquivos de cabeçalho de nome diferente para controles personalizados XAML. Você precisará renomear esse arquivo de cabeçalho para corresponder ao nome da 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 de arquivo de cabeçalho

O C++/CX não exige que você inclua nenhum arquivo de cabeçalho especial, pois ele gera automaticamente arquivos de cabeçalho de .winmd arquivos internamente. É comum em C++/CX usar diretivas using para namespaces que você consome pelo nome.

using namespace Windows::Media::Playback;

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

A using namespace Windows::Media::Playback diretiva nos permite escrever MediaPlaybackItem sem um prefixo de namespace. Também alteramos o namespace , porque retorna umWindows.Media.Core.VideoTrack. Mas não tínhamos que digitar o nome VideoTrack em lugar nenhum, então não precisávamos de uma using Windows.Media.Core diretiva.

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"/>

Usar a classe Rectangle significa que você precisa adicionar esse include.

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

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

Passagem de parâmetro

Ao escrever o código-fonte C++/CX, você passa tipos C++/CX como parâmetros de função como referências de chapéu (^).

void LogPresenceRecord(PresenceRecord^ record);

No C++/WinRT, para funções síncronas, você deve usar const& parâmetros por padrão. Isso evitará cópias e sobrecarga interligada. Mas suas coroutinas devem usar pass-by-value para garantir que eles capturem por valor e evitem problemas de tempo de vida (para obter mais detalhes, consulte Simultaneidade e operações assíncronas com o C++/WinRT).

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

Um objeto C++/WinRT é fundamentalmente um valor que contém um ponteiro de interface para o objeto subjacente do Windows Runtime. Quando você copia um objeto C++/WinRT, o compilador copia o ponteiro de interface encapsulado e incrementa sua contagem de referência. A eventual destruição da cópia envolve a diminuição da contagem de referência. Portanto, só assuma a sobrecarga de uma cópia quando necessário.

Referências de variável e de campo

Ao escrever o código-fonte C++/CX, você usa variáveis de chapéu (^) para referenciar objetos do Windows Runtime e o operador de seta (->) para desreferenciar uma variável de chapéu.

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

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

Ao portar para o código equivalente em C++/WinRT, você pode progredir bastante removendo os "chapéus" (operador ^) e alterando o operador de seta (->) para o operador de ponto (.). Os tipos projetados por 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 hat reference C++/CX inicializa-o para null. Aqui está um exemplo de código C++/CX no qual criamos uma variável/campo do tipo correto, mas um que não é inicializado. Em outras palavras, ele não se refere inicialmente a um TextBlock ; pretendemos atribuir uma referência posteriormente.

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

Para o equivalente em C++/WinRT, veja Inicialização retardada.

Propriedades

As extensões de linguagem C++/CX incluem o conceito de propriedades. Ao escrever o código-fonte C++/CX, você pode acessar uma propriedade como se fosse um campo. O C++ Padrão não tem o conceito de uma propriedade, portanto, em C++/WinRT, você chama as funções get e set.

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

Recuperando um valor de uma propriedade

Veja como obter um valor de propriedade em C++/CX.

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

O código-fonte 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 que tem uma função Size . Como o objeto retornado também é um tipo projetado em C++/WinRT, desreferenciamos usando o operador de ponto para chamar Tamanho.

Definindo uma propriedade como um novo valor

Definir uma propriedade como um novo valor segue um padrão semelhante. Primeiro, em C++/CX.

record->UserState = newValue;

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

record.UserState(newValue);

Criar uma instância de uma classe

Você trabalha com um objeto C++/CX por meio de um identificador para ele, comumente conhecido como referência de chapéu (^). Você cria um novo objeto por meio da ref new palavra-chave, 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 C++/WinRT é um valor, podendo alocá-lo na pilha ou como um campo em um objeto. Você nunca usar ref new (nem new) para alocar um objeto 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 for caro para inicializar, é comum atrasar a inicialização dele até que ele seja realmente necessário. Como já mencionado, o construtor padrão de uma referência de chapéu 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 C++/WinRT. Observe o uso do construtor std::nullptr_t. Para obter mais informações sobre esse construtor, consulte 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 coleções

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

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 };
Par std::pair<TextBox^, String^> p; // Creates a TextBox!
std::pair<TextBox, String> p;
std::pair<TextBox, String> p{ nullptr, nullptr };

Mais informações sobre coleções de referências vazias

Sempre que você tiver um Platform::Array^ (consulte Port Platform::Array^) no C++/CX, você tem a opção de portar isso 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 (consulte a tabela acima), não há essa abreviação para criar uma matriz de referências vazias. Você precisa repetir nullptr para cada elemento em uma matriz. Se você tiver muito poucos, os extras serão construídos por padrão.

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

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

Mais sobre o exemplo std::map

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

  • Caso a chave seja encontrada no mapa, retorne uma referência ao valor existente (que você pode substituir).
  • Se a chave não for encontrada no mapa, crie uma nova entrada no mapa que consiste na chave (movida, se móvel) e um valor construído por padrãoe retorne uma referência ao valor (que você pode substituir).

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

Convertendo de uma classe de runtime base em uma derivada

É comum ter uma referência à base que você sabe que se refere a um objeto de um tipo derivado. No C++/CX, você usa dynamic_cast para converter referência para a base em uma referência para a derivada. O dynamic_cast é, na verdade, apenas uma chamada oculta para QueryInterface. Aqui está um exemplo típico: você está tratando um evento alterado de propriedade de dependência e deseja converter de DependencyObject de volta para o tipo real que possui a propriedade de 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. Você também tem a opção de chamar IUnknown::as, em vez disso, que gera 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. Aqui está 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 derivar de uma classe de runtime, a classe base deve ser componível. O C++/CX não exige que você execute nenhuma etapa especial para tornar suas classes composíveis, 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 : Windows.UI.Xaml.Controls.Page
{
    ...
}
runtimeclass DerivedPage : BasePage
{
    ...
}

Na classe de cabeçalho 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>
    {
        ...
    }
}

Manipulação de eventos com delegação

Aqui está um exemplo típico de manipulação de um evento em C++/CX, usando uma função lambda como delegado neste 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 em 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 seu delegado como uma função livre ou como uma função de ponteiro para membro. Para obter mais informações, consulte Manipular eventos usando delegados noC++/WinRT.

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

Revogando um delegado

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

myButton->Click -= token;

Esse é o equivalente em C++/WinRT.

myButton().Click(token);

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

Boxe e desembalagem

C++/CX automaticamente encaixa escalares em objetos. O C++/WinRT exige que você chame a função winrt::box_value explicitamente. Ambos os idiomas exigem que você desencaixote explicitamente. Veja Boxing e unboxing com C++/WinRT.

Nas tabelas a seguir, usaremos essas 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
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# geram exceções se você tentar desencaixotar um ponteiro nulo para um tipo 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.

Cenário C++/CX C++/WinRT
Desencaixotar um inteiro conhecido i = (int)o; i = unbox_value<int>(o);
Se o for nulo Platform::NullReferenceException Colisão
Se o não for um int em caixa Platform::InvalidCastException Colisão
Desencaixotar int, usar fallback se nulo; falhar se for qualquer outra coisa i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Desembrulhe int, se possível; utilize uma alternativa para qualquer outra coisa auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

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++/CX e C++/WinRT tratam as cadeias de caracteres de forma diferente.

O tipo ABI HSTRING é um ponteiro para uma cadeia de caracteres contada por referência. Mas não deriva de IInspectable, portanto, tecnicamente não é um objeto . Além disso, um HSTRING nulo representa a cadeia de caracteres vazia. O empacotamento de coisas não derivadas de IInspectable é feito encapsulando-as dentro de um IReference<T>, e o Windows Runtime fornece uma implementação padrão na forma do objeto PropertyValue (tipos personalizados são relatados como PropertyType::OtherType).

C++/CX representa uma cadeia de caracteres do 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á.

Além disso, C++/CX permite que você desreferencie uma cadeia de caracteres nula^, neste caso, ela se 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
nulo projeta HSTRING como (String^)nullptr hstring{}
Os valores null e "" são idênticos? Sim Sim
Validade do nulo s = nullptr;
s->Length == 0 (válido)
s = hstring{};
s.size() == 0 (válido)
Se você atribuir 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

Conceitos básicos de boxing e unboxing.

Operação C++/CX C++/WinRT
Caixa de uma cadeia de caracteres o = s;
A cadeia de caracteres vazia torna-se nullptr.
o = box_value(s);
A cadeia de caracteres vazia torna-se um objeto não nulo.
Desempacotar 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);
Falhas causadas por objeto nulo.
Falha se não for uma cadeia de caracteres.
Desempacotar uma possível cadeia de caracteres s = dynamic_cast<String^>(o);
O objeto nulo ou um objeto que não é uma string torna-se uma cadeia de caracteres vazia.
s = unbox_value_or<hstring>(o, fallback);
Nulo ou não é uma cadeia de caracteres se torna alternativa.
Cadeia de caracteres vazia preservada.

Operações simultâneas e assíncronas

A PPL (Biblioteca de Padrões Paralelos) (simultaneidade::task, por exemplo) foi atualizada para dar suporte a referências de chapéu C++/CX.

Para C++/WinRT, em vez disso, você deve usar coroutines e co_await. Para obter mais informações e exemplos de código, consulte Concorrência e operações assíncronas com C++/WinRT.

Consumindo objetos 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 usando a extensão de marcação XAML {x:Bind} devem ser expostas publicamente no IDL.

Além disso, associar a um Booleano 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, consulte Consumindo objetos a partir da marcação.

Mapeando tipos de da Plataforma de C++/CX para tipos C++/WinRT

O C++/CX fornece vários tipos de dados no namespace da plataforma . Esses tipos não são padrão C++, portanto, você só pode usá-los quando habilitar extensões de linguagem do Windows Runtime (propriedade de projeto do Visual Studio C/C++GeralConsumirde Extensão do Windows RuntimeSim (/ZW). A tabela abaixo ajuda você a portar tipos da Platform para seus equivalentes no C++/WinRT. Depois de fazer isso, já que C++/WinRT é padrão C++, você pode desativar a opção /ZW .

C++/CX C++/WinRT
Platform::Agile^ winrt::agile_ref
Platform::Array^ Consulte Port Platform::Array^
Platform::Exception^ winrt::hresult_error
Plataforma::ExceçãoDeArgumentoInválido^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Platform::String^ winrt::hstring

Porta Plataforma::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 C++/WinRT é winrt::agile_ref.

Em C++/CX.

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

Em C++/WinRT.

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

Porta Plataforma::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. Veja Como o construtor padrão afeta coleções para entender por que std::vector é uma boa opção.

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

Porta Plataforma::Exceção^ para winrt::hresult_error

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

Para portar para C++/WinRT, altere todo o código que usa Platform::Exception^ para usar winrt::hresult_error.

Em C++/CX.

catch (Platform::Exception^ ex)

Em 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 (Acesso negado)
winrt::hresult_canceled winrt::hresult_error ERRO_CANCELADO
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 de mensagem , que retorna a representação de cadeia de caracteres desse HRESULT.

Aqui está um exemplo de como gerar uma exceção em C++/CX.

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

E o equivalente em C++/WinRT.

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

Porta Platform::Object^ para winrt::Windows::Foundation::IInspectable

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

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

Porta ::String^ para winrt::hstring

Platform::String^ é equivalente ao tipo de ABI HSTRING do Windows Runtime. Para C++/WinRT, o equivalente é winrt::hstring. Porém, com o C++/WinRT, você pode chamar APIs do Windows Runtime usando tipos de cadeia de caracteres largos da Biblioteca Padrão C++, como std::wstringe/ou literais de cadeia de caracteres larga. Para mais detalhes e exemplos de código, veja Manipulação de strings em C++/WinRT.

Com o C++/CX, você pode acessar a propriedade Platform::String::Data para recuperar a string como uma matriz de const wchar_t* no estilo C (por exemplo, para passá-la para 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 de estilo C terminada em nulo, da mesma forma que você pode em std::wstring.

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

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

Aqui está um exemplo de uma API C++/CX que usa uma cadeia de caracteres.

void LogWrapLine(Platform::String^ str);

Para C++/WinRT, você pode declarar essa API em MIDL 3.0 assim.

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

A cadeia de ferramentas C++/WinRT então gerará um código-fonte para você como 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 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 quaisquer tipos adicionais que você deseja converter para string.

Idioma Converter int para string Converter enum para string
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 de converter uma enumeração em string, é necessário 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));
        }
    }
}

Essas transformações em string geralmente 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 de compilação.

Criação de strings

C++/CX e C++/WinRT adiam para o std::wstringstream padrão para a criação de cadeia de caracteres.

Operação C++/CX C++/WinRT
Anexar cadeia de caracteres, preservando nulos stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Anexar 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, enquanto C++/CX pode construir uma Plataforma::Cadeia de Caracteres de uma cadeia de caracteres de 8 bits, C++/WinRT não faz isso.

Operação C++/CX C++/WinRT
Construir string a partir de literal String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
Converter de std::wstring, preservando os 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
Converter de std::wstringe parar no primeiro caractere 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
Converter 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;
Converter 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 literal para o método Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
Passe 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