Componentes do Windows Runtime com C++/CX

Observação

Este tópico existe para ajudar você a manter seu aplicativo em C++/CX. Entretanto, recomendamos usar C++/WinRT para novos aplicativos. C++/WinRT é uma projeção de linguagem C++17 completamente moderna e padrão para APIs do WinRT (Windows Runtime), implementada como uma biblioteca com base em cabeçalho e arquivo, projetada para fornecer acesso de primeira classe à API moderna do Windows. Para aprender a criar um componente do Windows Runtime usando C++/WinRT, confira Componentes do Windows Runtime com C++/WinRT.

Este tópico mostra como usar o C++/CX para criar um componente do Tempo de Execução do Windows, um componente que pode ser chamado de um aplicativo Universal do Windows criado usando qualquer linguagem do Tempo de Execução do Windows (C#, Visual Basic, C++ ou JavaScript).

Há vários motivos para criar um componente do Tempo de Execução do Windows em C++.

  • Para obter a vantagem de desempenho do C++ em operações complexas ou computacionalmente intensivas.
  • Para reutilizar o código que já foi escrito e testado.

Quando você cria uma solução que contém um projeto JavaScript ou .NET e um projeto de componente do Tempo de Execução do Windows, os arquivos de projeto JavaScript e a DLL compilada são mesclados em um pacote, que você pode depurar localmente no simulador ou remotamente em um dispositivo vinculado. Você também pode distribuir apenas o projeto de componente como um SDK de extensão. Para obter mais informações, consulte Criando um Software Development Kit.

Em geral, ao codificar seu componente C++/CX, use a biblioteca C++ regular e os tipos internos, exceto no limite da ABI (interface binária abstrata) em que você está passando dados de e para o código em outro pacote .winmd. Lá, use os tipos do Tempo de Execução do Windows e a sintaxe especial compatível com o C++/CX para criar e manipular esses tipos. Além disso, em seu código C++/CX, use tipos como delegate e event para implementar eventos que podem ser gerados do seu componente e manipulados em JavaScript, Visual Basic, C++ ou C#. Para obter mais informações sobre a sintaxe do C++/CX, consulte Referência da linguagem Visual C++ (C++/CX).

Regras de maiúsculas e minúsculas e nomenclatura

JavaScript

O JavaScript diferencia maiúsculas de minúsculas. Portanto, você deve seguir estas convenções de maiúsculas e minúsculas:

  • Ao fazer referência a namespaces e classes C++, use o mesmo uso de maiúsculas e minúsculas usado no lado C++.
  • Ao chamar métodos, use maiúsculas e minúsculas concatenadas, mesmo que o nome do método esteja em maiúsculas no lado C++. Por exemplo, um método C++ GetDate() deve ser chamado do JavaScript como getDate().
  • Um nome de classe ativável e um nome de namespace não podem conter caracteres UNICODE.

.NET

As linguagens .NET seguem suas regras normais de maiúsculas e minúsculas.

Instanciando o objeto

Somente os tipos do Tempo de Execução do Windows podem ser passados pelo limite da ABI. O compilador gerará um erro se o componente tiver um tipo como std::wstring como um tipo de retorno ou parâmetro em um método público. Os tipos internos de extensões de componente do Visual C++ (C++/CX) incluem os escalares usuais, como int e double, e também seus equivalentes typedef int32, float64 e assim por diante. Para obter mais informações, consulte Sistema de Tipos (C++/CX).

// ref class definition in C++
public ref class SampleRefClass sealed
{
    // Class members...

    // #include <valarray>
public:
    double LogCalc(double input)
    {
        // Use C++ standard library as usual.
        return std::log(input);
    }

};
//Instantiation in JavaScript (requires "Add reference > Project reference")
var nativeObject = new CppComponent.SampleRefClass();
//Call a method and display result in a XAML TextBlock
var num = nativeObject.LogCalc(21.5);
ResultText.Text = num.ToString();

Tipos internos do C++/CX, tipos de biblioteca e tipos do Tempo de Execução do Windows

Uma classe ativável (também conhecida como classe ref) é aquela que pode ser instanciada de outra linguagem, como JavaScript, C# ou Visual Basic. Para ser consumível de outro idioma, um componente deve conter pelo menos uma classe ativável.

Um componente do Tempo de Execução do Windows pode conter várias classes ativáveis públicas, bem como classes adicionais que são conhecidas apenas internamente pelo componente. Aplique o atributo WebHostHidden a tipos C++/CX que não devem ser visíveis para JavaScript.

Todas as classes públicas devem residir no mesmo namespace raiz que tem o mesmo nome que o arquivo de metadados do componente. Por exemplo, uma classe denominada A.B.C.MyClass poderá ser instanciada somente se for definida em um arquivo de metadados denominado A.winmd ou A.B.winmd ou A.B.C.winmd. O nome da DLL não precisa coincidir com o nome do arquivo .winmd.

O código do cliente cria uma instância do componente usando a palavra-chave new (New in Visual Basic) da mesma forma que para qualquer classe.

Uma classe ativável deve ser declarada como public ref class sealed. A palavra-chave ref class informa ao compilador para criar a classe como um tipo compatível com o Tempo de Execução do Windows, e a palavra-chave sealed especifica que a classe não pode ser herdada. No momento, o Tempo de Execução do Windows não dá suporte a um modelo de herança generalizado; um modelo de herança limitado dá suporte à criação de controles XAML personalizados. Para obter mais informações, consulte Classes e structs ref (C++/CX).

Para C++/CX, todos os primitivos numéricos são definidos no namespace padrão. O namespace Platform contém classes C++/CX específicas do sistema de tipos do Tempo de Execução do Windows. Isso inclui a classe Platform::String e a classe Platform::Object . Os tipos de coleção concretos, como a classe Platform::Collections::Map e a classe Platform::Collections::Vector , são definidos no namespace Platform::Collections . As interfaces públicas que esses tipos implementam são definidas no Namespace Windows::Foundation::Collections (C++/CX). São esses tipos de interface que são consumidos por JavaScript, C# e Visual Basic. Para obter mais informações, consulte Sistema de Tipos (C++/CX).

Método que retorna um valor do tipo interno

    // #include <valarray>
public:
    double LogCalc(double input)
    {
        // Use C++ standard library as usual.
        return std::log(input);
    }
//Call a method
var nativeObject = new CppComponent.SampleRefClass;
var num = nativeObject.logCalc(21.5);
document.getElementById('P2').innerHTML = num;

Método que retorna um struct de valor personalizado

namespace CppComponent
{
    // Custom struct
    public value struct PlayerData
    {
        Platform::String^ Name;
        int Number;
        double ScoringAverage;
    };

    public ref class Player sealed
    {
    private:
        PlayerData m_player;
    public:
        property PlayerData PlayerStats
        {
            PlayerData get(){ return m_player; }
            void set(PlayerData data) {m_player = data;}
        }
    };
}

Para passar structs de valor definidos pelo usuário na ABI, defina um objeto JavaScript que tenha os mesmos membros que o struct de valor definido em C++/CX. Em seguida, você pode passar esse objeto como um argumento para um método C++/CX para que o objeto seja convertido implicitamente no tipo C++/CX.

// Get and set the value struct
function GetAndSetPlayerData() {
    // Create an object to pass to C++
    var myData =
        { name: "Bob Homer", number: 12, scoringAverage: .357 };
    var nativeObject = new CppComponent.Player();
    nativeObject.playerStats = myData;

    // Retrieve C++ value struct into new JavaScript object
    var myData2 = nativeObject.playerStats;
    document.getElementById('P3').innerHTML = myData.name + " , " + myData.number + " , " + myData.scoringAverage.toPrecision(3);
}

Outra abordagem é definir uma classe que implemente IPropertySet (não mostrado).

Nas linguagens .NET, basta criar uma variável do tipo definido no componente C++/CX.

private void GetAndSetPlayerData()
{
    // Create a ref class
    var player = new CppComponent.Player();

    // Create a variable of a value struct
    // type that is defined in C++
    CppComponent.PlayerData myPlayer;
    myPlayer.Name = "Babe Ruth";
    myPlayer.Number = 12;
    myPlayer.ScoringAverage = .398;

    // Set the property
    player.PlayerStats = myPlayer;

    // Get the property and store it in a new variable
    CppComponent.PlayerData myPlayer2 = player.PlayerStats;
    ResultText.Text += myPlayer.Name + " , " + myPlayer.Number.ToString() +
        " , " + myPlayer.ScoringAverage.ToString();
}

Métodos sobrecarregados

Uma classe ref pública C++/CX pode conter métodos sobrecarregados, mas o JavaScript tem capacidade limitada de diferenciar métodos sobrecarregados. Por exemplo, ele pode dizer a diferença entre essas assinaturas:

public ref class NumberClass sealed
{
public:
    int GetNumber(int i);
    int GetNumber(int i, Platform::String^ str);
    double GetNumber(int i, MyData^ d);
};

Mas não pode dizer a diferença entre estes:

int GetNumber(int i);
double GetNumber(double d);

Em casos ambíguos, você pode garantir que o JavaScript sempre chame uma sobrecarga específica aplicando o atributo Windows::Foundation::Metadata::D efaultOverload à assinatura do método no arquivo de cabeçalho.

Esse JavaScript sempre chama a sobrecarga atribuída:

var nativeObject = new CppComponent.NumberClass();
var num = nativeObject.getNumber(9);
document.getElementById('P4').innerHTML = num;

.NET

As linguagens .NET reconhecem sobrecargas em uma classe ref C++/CX da mesma forma que em qualquer classe .NET.

Datetime

No Tempo de Execução do Windows, um objeto Windows::Foundation::D ateTime é apenas um inteiro assinado de 64 bits que representa o número de intervalos de 100 nanossegundos antes ou depois de 1º de janeiro de 1601. Não há métodos em um objeto Windows:Foundation::D ateTime. Em vez disso, cada linguagem projeta o DateTime da maneira nativa dessa linguagem: o objeto Date em JavaScript e os tipos System.DateTime e System.DateTimeOffset no .NET.

public  ref class MyDateClass sealed
{
public:
    property Windows::Foundation::DateTime TimeStamp;
    void SetTime(Windows::Foundation::DateTime dt)
    {
        auto cal = ref new Windows::Globalization::Calendar();
        cal->SetDateTime(dt);
        TimeStamp = cal->GetDateTime(); // or TimeStamp = dt;
    }
};

Quando você passa um valor DateTime do C++/CX para o JavaScript, o JavaScript o aceita como um objeto Date e o exibe por padrão como uma cadeia de caracteres de data de formato longo.

function SetAndGetDate() {
    var nativeObject = new CppComponent.MyDateClass();

    var myDate = new Date(1956, 4, 21);
    nativeObject.setTime(myDate);

    var myDate2 = nativeObject.timeStamp;

    //prints long form date string
    document.getElementById('P5').innerHTML = myDate2;

}

Quando uma linguagem .NET passa um System.DateTime para um componente C++/CX, o método o aceita como um Windows::Foundation::D ateTime. Quando o componente passa um Windows::Foundation::D ateTime para um método .NET, o método Framework o aceita como um DateTimeOffset.

private void DateTimeExample()
{
    // Pass a System.DateTime to a C++ method
    // that takes a Windows::Foundation::DateTime
    DateTime dt = DateTime.Now;
    var nativeObject = new CppComponent.MyDateClass();
    nativeObject.SetTime(dt);

    // Retrieve a Windows::Foundation::DateTime as a
    // System.DateTimeOffset
    DateTimeOffset myDate = nativeObject.TimeStamp;

    // Print the long-form date string
    ResultText.Text += myDate.ToString();
}

Coleções e matrizes

As coleções são sempre passadas pelo limite da ABI como identificadores para tipos do Tempo de Execução do Windows, como Windows::Foundation::Collections::IVector^ e Windows::Foundation::Collections::IMap^. Por exemplo, se você retornar um identificador para um Platform::Collections::Map, ele será convertido implicitamente em um Windows::Foundation::Collections::IMap^. As interfaces de coleção são definidas em um namespace separado das classes C++/CX que fornecem as implementações concretas. As linguagens JavaScript e .NET consomem as interfaces. Para obter mais informações, consulte Coleções (C++/CX) e Array e WriteOnlyArray (C++/CX).

Passando IVector

// Windows::Foundation::Collections::IVector across the ABI.
//#include <algorithm>
//#include <collection.h>
Windows::Foundation::Collections::IVector<int>^ SortVector(Windows::Foundation::Collections::IVector<int>^ vec)
{
    std::sort(begin(vec), end(vec));
    return vec;
}
var nativeObject = new CppComponent.CollectionExample();
// Call the method to sort an integer array
var inVector = [14, 12, 45, 89, 23];
var outVector = nativeObject.sortVector(inVector);
var result = "Sorted vector to array:";
for (var i = 0; i < outVector.length; i++)
{
    outVector[i];
    result += outVector[i].toString() + ",";
}
document.getElementById('P6').innerHTML = result;

As linguagens .NET veem IVector<T> como IList<T>.

private void SortListItems()
{
    IList<int> myList = new List<int>();
    myList.Add(5);
    myList.Add(9);
    myList.Add(17);
    myList.Add(2);

    var nativeObject = new CppComponent.CollectionExample();
    IList<int> mySortedList = nativeObject.SortVector(myList);

    foreach (var item in mySortedList)
    {
        ResultText.Text += " " + item.ToString();
    }
}

Passando IMap

// #include <map>
//#include <collection.h>
Windows::Foundation::Collections::IMap<int, Platform::String^> ^GetMap(void)
{    
    Windows::Foundation::Collections::IMap<int, Platform::String^> ^ret =
        ref new Platform::Collections::Map<int, Platform::String^>;
    ret->Insert(1, "One ");
    ret->Insert(2, "Two ");
    ret->Insert(3, "Three ");
    ret->Insert(4, "Four ");
    ret->Insert(5, "Five ");
    return ret;
}
// Call the method to get the map
var outputMap = nativeObject.getMap();
var mStr = "Map result:" + outputMap.lookup(1) + outputMap.lookup(2)
    + outputMap.lookup(3) + outputMap.lookup(4) + outputMap.lookup(5);
document.getElementById('P7').innerHTML = mStr;

As linguagens .NET consulte IMap e IDictionary<K, V>.

private void GetDictionary()
{
    var nativeObject = new CppComponent.CollectionExample();
    IDictionary<int, string> d = nativeObject.GetMap();
    ResultText.Text += d[2].ToString();
}

Propriedades

Uma classe ref pública em extensões de componente C++/CX expõe membros de dados públicos como propriedades, usando a palavra-chave property. O conceito é idêntico às propriedades do .NET. Uma propriedade trivial se assemelha a um membro de dados porque sua funcionalidade está implícita. Uma propriedade não trivial tem acessadores get e set explícitos e uma variável privada nomeada que é o "repositório de backup" para o valor. Neste exemplo, a variável de membro privado _propertyAValue é o repositório de backup para PropertyA. Uma propriedade pode disparar um evento quando seu valor é alterado e um aplicativo cliente pode se registrar para receber esse evento.

//Properties
public delegate void PropertyChangedHandler(Platform::Object^ sender, int arg);
public ref class PropertyExample  sealed
{
public:
    PropertyExample(){}

    // Event that is fired when PropertyA changes
    event PropertyChangedHandler^ PropertyChangedEvent;

    // Property that has custom setter/getter
    property int PropertyA
    {
        int get() { return m_propertyAValue; }
        void set(int propertyAValue)
        {
            if (propertyAValue != m_propertyAValue)
            {
                m_propertyAValue = propertyAValue;
                // Fire event. (See event example below.)
                PropertyChangedEvent(this, propertyAValue);
            }
        }
    }

    // Trivial get/set property that has a compiler-generated backing store.
    property Platform::String^ PropertyB;

private:
    // Backing store for propertyA.
    int m_propertyAValue;
};
var nativeObject = new CppComponent.PropertyExample();
var propValue = nativeObject.propertyA;
document.getElementById('P8').innerHTML = propValue;

//Set the string property
nativeObject.propertyB = "What is the meaning of the universe?";
document.getElementById('P9').innerHTML += nativeObject.propertyB;

As linguagens .NET acessam propriedades em um objeto C++/CX nativo da mesma forma que fariam em um objeto .NET.

private void GetAProperty()
{
    // Get the value of the integer property
    // Instantiate the C++ object
    var obj = new CppComponent.PropertyExample();

    // Get an integer property
    var propValue = obj.PropertyA;
    ResultText.Text += propValue.ToString();

    // Set a string property
    obj.PropertyB = " What is the meaning of the universe?";
    ResultText.Text += obj.PropertyB;

}

Representantes e eventos

Um delegado é um tipo do Tempo de Execução do Windows que representa um objeto de função. Você pode usar delegados em conexão com eventos, retornos de chamada e chamadas de método assíncronas para especificar uma ação a ser executada posteriormente. Como um objeto de função, o delegado fornece segurança de tipo permitindo que o compilador verifique o tipo de retorno e os tipos de parâmetro da função. A declaração de um delegado se assemelha a uma assinatura de função, a implementação se assemelha a uma definição de classe e a invocação se assemelha a uma invocação de função.

Incluindo um ouvinte de eventos

Você pode usar a palavra-chave event para declarar um membro público de um tipo delegado especificado. O código do cliente assina o evento usando os mecanismos padrão fornecidos na linguagem específica.

public:
    event SomeHandler^ someEvent;

Este exemplo usa o mesmo código C++ da seção de propriedades anterior.

function Button_Click() {
    var nativeObj = new CppComponent.PropertyExample();
    // Define an event handler method
    var singlecasthandler = function (ev) {
        document.getElementById('P10').innerHTML = "The button was clicked and the value is " + ev;
    };

    // Subscribe to the event
    nativeObj.onpropertychangedevent = singlecasthandler;

    // Set the value of the property and fire the event
    var propValue = 21;
    nativeObj.propertyA = 2 * propValue;

}

Nas linguagens .NET, assinar um evento em um componente C++ é o mesmo que assinar um evento em uma classe .NET:

//Subscribe to event and call method that causes it to be fired.
private void TestMethod()
{
    var objWithEvent = new CppComponent.PropertyExample();
    objWithEvent.PropertyChangedEvent += objWithEvent_PropertyChangedEvent;

    objWithEvent.PropertyA = 42;
}

//Event handler method
private void objWithEvent_PropertyChangedEvent(object __param0, int __param1)
{
    ResultText.Text = "the event was fired and the result is " +
         __param1.ToString();
}

Adicionando vários ouvintes de eventos para um evento

O JavaScript tem um método addEventListener que permite que vários manipuladores assinem um único evento.

public delegate void SomeHandler(Platform::String^ str);

public ref class LangSample sealed
{
public:
    event SomeHandler^ someEvent;
    property Platform::String^ PropertyA;

    // Method that fires an event
    void FireEvent(Platform::String^ str)
    {
        someEvent(Platform::String::Concat(str, PropertyA->ToString()));
    }
    //...
};
// Add two event handlers
var multicast1 = function (ev) {
    document.getElementById('P11').innerHTML = "Handler 1: " + ev.target;
};
var multicast2 = function (ev) {
    document.getElementById('P12').innerHTML = "Handler 2: " + ev.target;
};

var nativeObject = new CppComponent.LangSample();
//Subscribe to the same event
nativeObject.addEventListener("someevent", multicast1);
nativeObject.addEventListener("someevent", multicast2);

nativeObject.propertyA = "42";

// This method should fire an event
nativeObject.fireEvent("The answer is ");

Em C#, qualquer número de manipuladores de eventos pode assinar o evento usando o operador +=, conforme mostrado no exemplo anterior.

Enumerações

Uma enumeração do Tempo de Execução do Windows em C++/CX é declarada usando enumeração de classe pública; ele se assemelha a uma enumeração com escopo no C++ padrão.

public enum class Direction {North, South, East, West};

public ref class EnumExampleClass sealed
{
public:
    property Direction CurrentDirection
    {
        Direction  get(){return m_direction; }
    }

private:
    Direction m_direction;
};

Os valores de enumeração são passados entre C++/CX e JavaScript como inteiros. Opcionalmente, você pode declarar um objeto JavaScript que contém os mesmos valores nomeados que a enumeração C++/CX e usá-lo da seguinte maneira.

var Direction = { 0: "North", 1: "South", 2: "East", 3: "West" };
//. . .

var nativeObject = new CppComponent.EnumExampleClass();
var curDirection = nativeObject.currentDirection;
document.getElementById('P13').innerHTML =
Direction[curDirection];

Tanto o C# quanto o Visual Basic têm suporte à linguagem para enumerações. Essas linguagens veem uma classe de enumeração pública do C++ da mesma forma que veriam uma enumeração do .NET.

Métodos assíncronos

Para consumir métodos assíncronos expostos por outros objetos do Tempo de Execução do Windows, use a classe de tarefa (Tempo de Execução de Simultaneidade). Para obter mais informações, consulte Paralelismo de tarefas (tempo de execução de simultaneidade).

Para implementar métodos assíncronos em C++/CX, use a função create_async definida em ppltasks.h. Para obter mais informações, consulte Criando operações assíncronas em C++/CX para aplicativos UWP. Para obter um exemplo, consulte Passo a passo da criação de um componente do Tempo de Execução do Windows C++/CX e chamá-lo de JavaScript ou C#. As linguagens .NET consomem métodos assíncronos C++/CX da mesma forma que fariam com qualquer método assíncrono definido no .NET.

Exceções

Você pode lançar qualquer tipo de exceção definido pelo Tempo de Execução do Windows. Você não pode derivar tipos personalizados de nenhum tipo de exceção do Tempo de Execução do Windows. No entanto, você pode lançar COMException e fornecer um HRESULT personalizado que pode ser acessado pelo código que captura a exceção. Não há como especificar uma mensagem personalizada em uma COMException.

Dicas de depuração

Ao depurar uma solução JavaScript que tem uma DLL de componente, você pode definir o depurador para habilitar o script passo a passo ou o código nativo no componente, mas não os dois ao mesmo tempo. Para alterar a configuração, selecione o nó do projeto JavaScript no Gerenciador de Soluções e escolha Propriedades, Depuração, Tipo de Depurador.

Certifique-se de selecionar os recursos apropriados no designer de pacotes. Por exemplo, se você estiver tentando abrir um arquivo de imagem na biblioteca de Imagens do usuário usando as APIs do Tempo de Execução do Windows, marque a caixa de seleção Biblioteca de Imagens no painel Recursos do designer de manifesto.

Se o código JavaScript não parece estar reconhecendo as propriedades ou métodos públicos no componente, verifique se no JavaScript você está usando maiúsculas e minúsculas. Por exemplo, o método LogCalc C++/CX deve ser referenciado como logCalc em JavaScript.

Se você remover um projeto de componente do Tempo de Execução do Windows C++/CX de uma solução, também deverá remover manualmente a referência do projeto do JavaScript. Se isso não for feito, as operações subsequentes de depuração ou build serão executadas. Se necessário, você pode adicionar uma referência de assembly à DLL.