Compartilhar via


Criar APIs com C++/WinRT

Este tópico mostra como criar APIs C++/WinRT usando a struct base winrt::implements, direta ou indiretamente. Sinônimos parar criar, neste contexto, são produzir ou implementar. Este tópico aborda os seguintes cenários para a implementação de APIs em um tipo C++/WinRT, nesta ordem.

Observação

Este tópico aborda os componentes do Windows Runtime, mas apenas no contexto do C++/WinRT. Se você estiver buscando conteúdo sobre componentes do Windows Runtime que abrange todas as linguagens do Windows Runtime, consulte Componentes do Windows Runtime.

  • Você não está criando uma classe do Windows Runtime (classe de tempo de execução); você apenas implementará uma ou mais interfaces do Windows Runtime para consumo local dentro de seu aplicativo. Você irá derivar diretamente de winrt::implements neste caso, e implementará funções.
  • Você está criando uma classe de runtime. Você poderá estar criando um componente a ser consumido a partir de um aplicativo. Ou você poderá estar criando um tipo a ser consumido a partir da interface do usuário XAML e, nesse caso, você estará implementando e consumindo uma classe de runtime dentro da mesma unidade de compilação. Nesses casos, você permitirá que as ferramentas gerem classes que derivam de winrt::implements.

Em ambos os casos, o tipo que implementa as APIs C++/WinRT é denominado tipo de implementação.

Importante

É importante distinguir o conceito de um tipo de implementação daquele de um tipo projetado. O tipo projetado está descrito em Consumir APIs com C++/WinRT.

Se você não estiver criando uma classe de runtime

O cenário mais simples é onde o tipo está implementando uma interface do Windows Runtime e você consumirá esse tipo no mesmo aplicativo. Nesse caso, o tipo não precisa ser uma classe de runtime, apenas uma classe C++ comum. Por exemplo, você pode estar escrevendo um aplicativo com base em CoreApplication.

Se o tipo for referenciado pela interface do usuário XAML, ele realmente precisará ser uma classe de runtime, mesmo que esteja no mesmo projeto que o XAML. Nesse caso, confira a seção se você estiver criando uma classe de runtime para ser referenciada na interface do usuário XAML.

Observação

Para saber mais sobre como instalar e usar a VSIX (Extensão do Visual Studio) para C++/WinRT e o pacote do NuGet (que juntos fornecem um modelo de projeto e suporte ao build), confira Suporte ao Visual Studio para C++/WinRT.

No Visual Studio, o modelo de projeto Visual C++>Windows Universal>Core App (C++/WinRT) ilustra o padrão CoreApplication. O padrão começa com a passagem de uma implementação de Windows::ApplicationModel::Core::IFrameworkViewSource para CoreApplication::Run.

using namespace Windows::ApplicationModel::Core;
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    IFrameworkViewSource source = ...
    CoreApplication::Run(source);
}

CoreApplication usa a interface para criar a primeira exibição do aplicativo. Conceitualmente, IFrameworkViewSource tem esta aparência.

struct IFrameworkViewSource : IInspectable
{
    IFrameworkView CreateView();
};

Conceitualmente, mais uma vez, a implementação de CoreApplication::Run faz isto.

void Run(IFrameworkViewSource viewSource) const
{
    IFrameworkView view = viewSource.CreateView();
    ...
}

Portanto, como desenvolvedor, implemente a interface IFrameworkViewSource. C++/WinRT têm o modelo de struct base winrt::implements para tornar mais fácil a implementação de uma interface (ou várias) sem precisar recorrer à programação de estilo COM. Você só precisa derivar o tipo de implementose, depois, implementar as funções da interface. Veja aqui como fazer isso.

// App.cpp
...
struct App : implements<App, IFrameworkViewSource>
{
    IFrameworkView CreateView()
    {
        return ...
    }
}
...

Isso é feito pelo IFrameworkViewSource. A próxima etapa será retornar um objeto que implementa a interface IFrameworkView. Você poderá optar por implementar essa interface no Aplicativo também. Este próximo exemplo de código representa um aplicativo básico que receberá, pelo menos, uma janela em funcionamento na área de trabalho.

// App.cpp
...
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    IFrameworkView CreateView()
    {
        return *this;
    }

    void Initialize(CoreApplicationView const &) {}

    void Load(hstring const&) {}

    void Run()
    {
        CoreWindow window = CoreWindow::GetForCurrentThread();
        window.Activate();

        CoreDispatcher dispatcher = window.Dispatcher();
        dispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
    }

    void SetWindow(CoreWindow const & window)
    {
        // Prepare app visuals here
    }

    void Uninitialize() {}
};
...

Como seu tipo de Aplicativo é um IFrameworkViewSource, você poderá passar apenas um para Executar.

using namespace Windows::ApplicationModel::Core;
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    CoreApplication::Run(winrt::make<App>());
}

Se você estiver criando uma classe de tempo de execução em um componente do Windows Runtime

Se o tipo estiver empacotado em um componente do Windows Runtime para consumo de outro binário (o outro binário geralmente é um aplicativo), precisará ser uma classe de runtime. Declare uma classe de runtime em um arquivo de IDL (linguagem IDL da Microsoft) (.idl) (confira Como fatorar classes de runtime em arquivos MIDL (.idl)).

Cada arquivo IDL resulta em um arquivo .winmd, e o Visual Studio mescla todos eles em um único arquivo com o mesmo nome do namespace raiz. O arquivo .winmd final será aquele que os consumidores de seu componente consultarão.

Veja abaixo um exemplo de declaração de uma classe de runtime em um arquivo IDL.

// MyRuntimeClass.idl
namespace MyProject
{
    runtimeclass MyRuntimeClass
    {
        // Declaring a constructor (or constructors) in the IDL causes the runtime class to be
        // activatable from outside the compilation unit.
        MyRuntimeClass();
        String Name;
    }
}

Esse IDL declara uma classe do Windows Runtime (tempo de execução). Uma classe de runtime é um tipo que pode ser ativado e consumido por meio de interfaces COM modernas, normalmente entre limites executáveis. Quando você adiciona um arquivo IDL ao seu projeto e ao build, a cadeia de ferramentas C++/WinRT (midl.exe e cppwinrt.exe) gera um tipo de implementação. Para ver um exemplo do fluxo de trabalho do arquivo IDL em ação, consulte Controles XAML; associar a uma propriedade de C++/WinRT.

Usando o exemplo de IDL acima, o tipo de implementação é um struct stub do C++ chamado winrt::MyProject::implementation::MyRuntimeClass nos arquivos de código fonte denominados \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h e MyRuntimeClass.cpp.

O tipo de implementação tem esta aparência.

// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
    struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
    {
        MyRuntimeClass() = default;

        winrt::hstring Name();
        void Name(winrt::hstring const& value);
    };
}

// winrt::MyProject::factory_implementation::MyRuntimeClass is here, too.

Observe que o padrão de polimorfismo associado a F sendo usado (MyRuntimeClass usa a si mesmo como um argumento de modelo para sua base, MyRuntimeClassT). Isso também é chamado de padrão de modelo curiosamente recorrente (CRTP). Se você seguir a cadeia de herança para cima, você se deparará com MyRuntimeClass_base.

Você pode simplificar a implementação de propriedades simples usando as Bibliotecas de Implementação do Windows (WIL). Este é o procedimento:

// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
    struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
    {
        MyRuntimeClass() = default;

        wil::single_threaded_rw_property<winrt::hstring> Name;
    };
}

Ver Propriedades simples.

template <typename D, typename... I>
struct MyRuntimeClass_base : implements<D, MyProject::IMyRuntimeClass, I...>

Então, nesse cenário, na raiz da hierarquia de herança está o modelo de struct base winrt::implements mais uma vez.

Para saber mais, obter códigos e ver um passo a passo da criação APIs em um componente do Windows Runtime, confira Componentes do Windows Runtime com C++/WinRT e Criar eventos em C++/WinRT.

Se você estiver criando uma classe de runtime para ser referenciada em sua interface de usuário XAML

Se o tipo é referenciado por sua interface de usuário XAML, ele precisará ser uma classe de runtime, mesmo que esteja no mesmo projeto que o XAML. Embora eles geralmente sejam ativados entre limites executáveis, uma classe de runtime pode ser usada dentro da unidade de compilação que a implementa.

Nesse cenário, você estará criando e consumindo as APIs. O procedimento para a implementação da classe de tempo de execução é basicamente o mesmo para um componente do Windows Runtime. Portanto, confira a seção anterior Se você estiver criando uma classe de tempo de execução em um componente do Windows Runtime. O único detalhe que difere é que, no IDL, a cadeia de ferramentas C++/WinRT gera não apenas um tipo de implementação, mas também um tipo projetado. É importante entender que dizer apenas "MyRuntimeClass", neste cenário, pode ser ambíguo. Há várias entidades com esse nome e de tipos diferentes.

  • MyRuntimeClass é o nome de uma classe de tempo de execução. Mas isso é uma abstração: que quer dizer declarado no IDL e implementado em alguma linguagem de programação.
  • MyRuntimeClass é o nome do struct do C++ winrt::MyProject::implementation::MyRuntimeClass, que é a implementação C++/WinRT da classe de tempo de execução. Como vimos, se houver projetos de implementação e consumo separados, então, esse struct existirá somente no projeto de implementação. Esse é o tipo de implementação ou a implementação. Esse tipo é gerado (pela ferramenta cppwinrt.exe) nos arquivos \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h e MyRuntimeClass.cpp.
  • MyRuntimeClass é o nome do tipo projetado na forma do struct do C++ winrt::MyProject::MyRuntimeClass. Se houver projetos de implementação e consumo separados, então, esse struct existirá somente no projeto de consumo. Esse é o tipo projetado ou a projeção. Esse tipo é gerado (por cppwinrt.exe) no arquivo \MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.h.

Tipo projetado e tipo de implementação

Estas são as partes do tipo projetado relevantes para este tópico.

// MyProject.2.h
...
namespace winrt::MyProject
{
    struct MyRuntimeClass : MyProject::IMyRuntimeClass
    {
        MyRuntimeClass(std::nullptr_t) noexcept {}
        MyRuntimeClass();
    };
}

Para obter um exemplo de um passo a passo para implementar a interface INotifyPropertyChanged em uma classe de runtime, confira os controles XAML; associar a uma propriedade C++/WinRT.

O procedimento para o consumo da classe de runtime neste cenário está descrito em Consumir APIs com C++/WinRT.

Como fatorar classes de runtime em arquivos MIDL (.idl)

Os modelos de projeto e item do Visual Studio produzem um arquivo IDL separado para cada classe de runtime. Isso fornece uma correspondência lógica entre um arquivo IDL e seus arquivos de código-fonte gerados.

No entanto, se você consolidar todas as classes de runtime do projeto em um único arquivo IDL, isso poderá melhorar significativamente o tempo de build. Se, de outra forma, você tiver dependências import complexas (ou circulares) entre eles, a consolidação poderá ser realmente necessária. Talvez você ache mais fácil criar e examinar as classes de runtime se elas estiverem juntas.

Construtores de classe de runtime

Veja a seguir alguns pontos que devem ser observados nas listagens que vimos acima.

  • Cada construtor que você declarar em seu IDL faz com que um construtor seja gerado tanto no tipo de implementação quanto no tipo projetado. Os construtores declarados de IDL são usados para consumir a classe de runtime de uma unidade de compilação diferente.
  • Quer você tenha ou não construtores declarados em IDL, uma sobrecarga de construtor que usa std::nullptr_t será gerada em no tipo projetado. Chamar o construtor std::nullptr_t é a primeira de duas etapas para consumir a classe de runtime na mesma unidade de compilação. Para obter mais detalhes e um exemplo de código, confira as informações sobre consumir APIs com C++/WinRT.
  • Se você estiver consumindo a classe de runtime na mesma unidade de compilação, você também poderá implementar construtores não padrão diretamente no tipo de implementação (que está em MyRuntimeClass.h).

Observação

Se você espera que sua classe de runtime seja consumida por uma unidade de compilação diferente (o que é comum), inclua construtores em seu IDL (pelo menos um construtor padrão). Ao fazer isso, você também terá uma implementação do alocador junto com seu tipo de implementação.

Se você quiser criar e consumir a classe de runtime somente dentro da mesma unidade de compilação, não declare nenhum construtor no IDL. Você não precisará de uma implementação de alocador, e ela não será gerada. O construtor padrão do tipo de implementação será excluído, mas é possível editá-lo facilmente e usá-lo como padrão.

Se quiser criar e consumir a classe de runtime somente dentro da mesma unidade de compilação, e precisar de parâmetros do construtor, crie s construtores necessários diretamente no tipo de implementação.

Métodos, propriedades e eventos de classe de runtime

Já vimos que o fluxo de trabalho é usar o IDL para declarar sua classe de runtime e seus membros e, em seguida, a ferramenta gera protótipos e implementações de stub para você. Quanto a esses protótipos gerados automaticamente para os membros da classe de runtime, você pode editá-los para que eles passem tipos diferentes dos tipos que você declarar em seu IDL. No entanto, você poderá fazer isso somente enquanto o tipo que declarar no IDL possa ser encaminhado para o tipo que declarar na versão implementada.

Aqui estão alguns exemplos.

  • Você pode relaxar os tipos de parâmetro. Por exemplo, se no IDL seu método usar uma SomeClass, você poderá optar por alterá-la para IInspectable em sua implementação. Isso funciona porque qualquer SomeClass pode ser encaminhada para IInspectable (o inverso, obviamente, não funcionaria).
  • Você pode aceitar um parâmetro copiável por valor, em vez de por referência. Por exemplo, altere SomeClass const& para SomeClass. Isso é necessário quando você precisa evitar capturar uma referência em uma corrotina (consulte Passagem de parâmetros).
  • Você pode relaxar o valor retornado. Por exemplo, você pode alterar void para winrt::fire_and_forget.

Os dois últimos são muito úteis quando você está escrevendo um manipulador de eventos assíncronos.

Criação de instâncias, retorno de tipos de implementação e interfaces

Nesta seção, teremos como exemplo um tipo de implementação denominado MyType, que implementa as interfaces IStringable e IClosable.

Você pode derivar a MyType diretamente de winrt::implements (não é uma classe de runtime).

#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

struct MyType : implements<MyType, IStringable, IClosable>
{
    winrt::hstring ToString(){ ... }
    void Close(){}
};

Ou você poderá gerá-la em um IDL (esta é uma classe de runtime).

// MyType.idl
namespace MyProject
{
    runtimeclass MyType: Windows.Foundation.IStringable, Windows.Foundation.IClosable
    {
        MyType();
    }    
}

Você não pode alocar diretamente seu tipo de implementação.

MyType myimpl; // error C2259: 'MyType': cannot instantiate abstract class

Porém, você pode ir de MyType a um objeto IStringable ou IClosable que você pode usar ou retornar como parte da projeção chamando o modelo de função winrt::make. make retorna a interface padrão do tipo de implementação.

IStringable istringable = winrt::make<MyType>();

Observação

No entanto, se você estiver fazendo referência a seu tipo na interface de usuário XAML, haverá um tipo de implementação e um tipo projetado no mesmo projeto. Nesse caso, make retorna uma instância do tipo projetado. Para obter um exemplo de código desse cenário, confira os controles XAML; associar a uma propriedade C++/WinRT.

Só podemos usar istringable (no exemplo de código acima) para chamar os membros da interface IStringable. Mas a interface C++/WinRT (que é uma interface projetada) deriva de winrt::Windows::Foundation::IUnknown. Portanto, você poderá chamar IUnknown::as (ou IUnknown::try_as) nela para consultar outros tipos ou interfaces projetadas, que você também poderá usar ou retornar.

Dica

Um cenário em que você não deve chamar as ou try_as é a derivação de classe de runtime (“classes combináveis”). Quando um tipo de implementação compõe outra classe, não chame as ou try_as para executar uma QueryInterface não marcada ou marcada da classe que está sendo composta. Em vez disso, acesse o membro de dados (this->) m_inner e chame as ou try_as nele. Para obter mais informações, confira Derivação de classe de runtime neste tópico.

istringable.ToString();
IClosable iclosable = istringable.as<IClosable>();
iclosable.Close();

Se você precisar acessar todos os membros da implementação, e depois retornar uma interface para um chamador, use o modelo de função winrt::make_self. make_self retorna um winrt::com_ptr embalando o tipo de implementação. É possível acessar os membros de todas as suas interfaces (usando o operador de seta), retorná-los para o chamador em sua forma atual, ou poderá chamá-los como estão nelas e retornar o objeto de interface resultante para um chamador.

winrt::com_ptr<MyType> myimpl = winrt::make_self<MyType>();
myimpl->ToString();
myimpl->Close();
IClosable iclosable = myimpl.as<IClosable>();
iclosable.Close();

A classe MyType não é parte da projeção; ela é a implementação. Mas, dessa forma você poderá chamar seus métodos de implementação diretamente, sem a sobrecarga de uma chamada de função virtual. No exemplo acima, mesmo que MyType::ToString use a mesma assinatura do método projetado em IStringable, você chamará o método não virtual diretamente, sem cruzar a interface binária do aplicativo (ABI). O com_ptr simplesmente contém um ponteiro para o struct MyType, portanto você também poderá acessar qualquer outro detalhe interno de MyType por meio da variável myimpl e do operador de seta.

Nos casos em que você tem um objeto de interface e sabe que é uma interface na implementação, será possível voltar para a implementação usando o modelo de função winrt::get_self. Novamente, esta é uma técnica que evita chamadas de função virtuais e permite obter diretamente na implementação.

Observação

Se você ainda não instalou o SDK do Windows, versão 10.0.17763.0 (Windows 10, versão 1809) ou posterior, você precisará chamar winrt::from_abi em vez de winrt::get_self.

Veja um exemplo. Há outro exemplo no artigo Implementar a classe personalizada BgLabelControl.

void ImplFromIClosable(IClosable const& from)
{
    MyType* myimpl = winrt::get_self<MyType>(from);
    myimpl->ToString();
    myimpl->Close();
}

Mas somente o objeto original da interface mantém uma referência. Se você quiser mantê-lo nele, poderá chamar com_ptr::copy_from.

winrt::com_ptr<MyType> impl;
impl.copy_from(winrt::get_self<MyType>(from));
// com_ptr::copy_from ensures that AddRef is called.

O tipo de implementação em si não deriva de winrt::Windows::Foundation::IUnknown, portanto, ele não tem a função as. Mesmo assim, como você pode ver na função ImplFromIClosable acima, é possível acessar os membros de todas as suas interfaces. Porém, se você fizer isso, não retorne a instância do tipo de implementação bruta ao chamador. Em vez disso, use uma das técnicas já exibidas e retorne uma interface projetada ou um com_ptr.

Se tiver uma instância do tipo de implementação e precisar passá-la para uma função que espera o tipo projetado correspondente, você poderá fazer isso como mostrado no código de exemplo abaixo. Há um operador de conversão no tipo de implementação (desde que o tipo de implementação tenha sido gerado pela ferramenta cppwinrt.exe), que torna isso possível. Você poderá passar um valor do tipo de implementação diretamente para um método que espera um valor do tipo projetado correspondente. A partir de uma função de membro do tipo de implementação, você poderá passar *this para um método que espera um valor do tipo projetado correspondente.

// MyClass.idl
import "MyOtherClass.idl";
namespace MyProject
{
    runtimeclass MyClass
    {
        MyClass();
        void MemberFunction(MyOtherClass oc);
    }
}

// MyClass.h
...
namespace winrt::MyProject::implementation
{
    struct MyClass : MyClassT<MyClass>
    {
        MyClass() = default;
        void MemberFunction(MyProject::MyOtherClass const& oc) { oc.DoWork(*this); }
    };
}
...

// MyOtherClass.idl
import "MyClass.idl";
namespace MyProject
{
    runtimeclass MyOtherClass
    {
        MyOtherClass();
        void DoWork(MyClass c);
    }
}

// MyOtherClass.h
...
namespace winrt::MyProject::implementation
{
    struct MyOtherClass : MyOtherClassT<MyOtherClass>
    {
        MyOtherClass() = default;
        void DoWork(MyProject::MyClass const& c){ /* ... */ }
    };
}
...

//main.cpp
#include "pch.h"
#include <winrt/base.h>
#include "MyClass.h"
#include "MyOtherClass.h"
using namespace winrt;

// MyProject::MyClass is the projected type; the implementation type would be MyProject::implementation::MyClass.

void FreeFunction(MyProject::MyOtherClass const& oc)
{
    auto defaultInterface = winrt::make<MyProject::implementation::MyClass>();
    MyProject::implementation::MyClass* myimpl = winrt::get_self<MyProject::implementation::MyClass>(defaultInterface);
    oc.DoWork(*myimpl);
}
...

Derivação de classe de tempo de execução

Você pode criar uma classe de tempo de execução que deriva de outra classe de tempo de execução, desde que a classe base seja declarada como "não selada". O termo do Tempo de Execução do Windows para derivação de classe é "classes combináveis". O código para implementar uma classe derivada depende se a classe base é fornecida por outro componente ou pelo mesmo componente. Felizmente, você não precisa aprender essas regras — basta copiar as implementações de exemplo da pasta de saída sources produzida pelo compilador cppwinrt.exe.

Considere este exemplo.

// MyProject.idl
namespace MyProject
{
    [default_interface]
    runtimeclass MyButton : Windows.UI.Xaml.Controls.Button
    {
        MyButton();
    }

    unsealed runtimeclass MyBase
    {
        MyBase();
        overridable Int32 MethodOverride();
    }

    [default_interface]
    runtimeclass MyDerived : MyBase
    {
        MyDerived();
    }
}

No exemplo acima, MyButton é derivado do controle Button XAML, que é fornecido por outro componente. Nesse caso, a implementação se parece com a implementação de uma classe não combinável:

namespace winrt::MyProject::implementation
{
    struct MyButton : MyButtonT<MyButton>
    {
    };
}

namespace winrt::MyProject::factory_implementation
{
    struct MyButton : MyButtonT<MyButton, implementation::MyButton>
    {
    };
}

Por outro lado, no exemplo acima, MyDerived é derivado de outra classe no mesmo componente. Nesse caso, a implementação requer um parâmetro de modelo adicional especificando a classe de implementação para a classe base.

namespace winrt::MyProject::implementation
{
    struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
    {                                     // ^^^^^^^^^^^^^^^^^^^^^^
    };
}

namespace winrt::MyProject::factory_implementation
{
    struct MyDerived : MyDerivedT<MyDerived, implementation::MyDerived>
    {
    };
}

Em ambos os casos, sua implementação pode chamar um método da classe base qualificando-o com o alias de tipo base_type:

namespace winrt::MyProject::implementation
{
    struct MyButton : MyButtonT<MyButton>
    {
        void OnApplyTemplate()
        {
            // Call base class method
            base_type::OnApplyTemplate();

            // Do more work after the base class method is done
            DoAdditionalWork();
        }
    };

    struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
    {
        int MethodOverride()
        {
            // Return double what the base class returns
            return 2 * base_type::MethodOverride();
        }
    };
}

Dica

Quando um tipo de implementação compõe outra classe, não chame as ou try_as para executar uma QueryInterface não marcada ou marcada da classe que está sendo composta. Em vez disso, acesse o membro de dados (this->) m_inner e chame as ou try_as nele.

Derivando de um tipo que tem um construtor não padrão

ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) é um exemplo de um construtor não padrão. Não há nenhum construtor padrão, portanto, para construir um ToggleButtonAutomationPeer, você precisará passar um owner. Consequentemente, se derivar de ToggleButtonAutomationPeer, será necessário fornecer um construtor que leve um owner e passe-o para a base. Vamos ver um exemplo disso na prática.

// MySpecializedToggleButton.idl
namespace MyNamespace
{
    runtimeclass MySpecializedToggleButton :
        Windows.UI.Xaml.Controls.Primitives.ToggleButton
    {
        ...
    };
}
// MySpecializedToggleButtonAutomationPeer.idl
namespace MyNamespace
{
    runtimeclass MySpecializedToggleButtonAutomationPeer :
        Windows.UI.Xaml.Automation.Peers.ToggleButtonAutomationPeer
    {
        MySpecializedToggleButtonAutomationPeer(MySpecializedToggleButton owner);
    };
}

O construtor gerado para o tipo de implementação tem esta aparência.

// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
    (MyNamespace::MySpecializedToggleButton const& owner)
{
    ...
}
...

A única informação ausente é que você precisa passar esse parâmetro do construtor para a classe base. Lembra-se do padrão de polimorfismo F associado, mencionado acima? Depois de se familiarizar com os detalhes desse padrão, conforme usado pelo C++/WinRT, você poderá descobrir como sua classe base é chamada (ou você poderá examinar apenas o arquivo de cabeçalho da classe de implementação). É como chamar o construtor da classe base nesse caso.

// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
    (MyNamespace::MySpecializedToggleButton const& owner) : 
    MySpecializedToggleButtonAutomationPeerT<MySpecializedToggleButtonAutomationPeer>(owner)
{
    ...
}
...

O construtor de classe base espera um ToggleButton. E MySpecializedToggleButton é um ToggleButton.

Até você fazer a edição descrita acima (para passar esse parâmetro do construtor para a classe base), o compilador sinalizará o construtor e mostrará que não há um construtor padrão apropriado disponível em um tipo chamado (neste caso) MySpecializedToggleButtonAutomationPeer_base<MySpecializedToggleButtonAutomationPeer> . Essa é, na verdade, a classe base da classe base do seu tipo de implementação.

Namespaces: tipos projetados, tipos de implementação e fábricas

Como você viu anteriormente neste tópico, uma classe de runtime C++/WinRT existe na forma de mais de uma classe C++ em mais de um namespace. Sendo assim, o nome MyRuntimeClass tem um significado no namespace winrt::MyProject e um significado diferente no namespace winrt::MyProject::implementation. Observe qual namespace que você tem no contexto atualmente e, em seguida, use os prefixos de namespace se precisar de um nome de um namespace diferente. Vamos examinar com mais detalhes os namespaces em questão.

  • winrt::MyProject. Este namespace contém tipos projetados. Um objeto de tipo projetado é um proxy; ele é essencialmente um ponteiro inteligente para um objeto de backup, podendo esse objeto de backup ser implementado aqui em seu projeto ou em outra unidade de compilação.
  • winrt::MyProject::implementation. Este namespace contém tipos de implementação. Um objeto de um tipo de implementação não é um ponteiro; ele é um valor, um objeto de pilha C++ completo. Não construa um tipo de implementação diretamente; em vez disso, chame winrt::make passando o tipo de implementação como o parâmetro de modelo. Mostramos exemplos de winrt::make em ação anteriormente neste tópico, e há outro exemplo em Controles XAML; associar a uma propriedade de C++/WinRT. Confira também Diagnosticar alocações diretas.
  • winrt::MyProject::factory_implementation. Este namespace contém fábricas. Um objeto no namespace dá suporte a IActivationFactory.

Esta tabela mostra a qualificação de namespace mínima que você precisa usar em diferentes contextos.

O namespace que está no contexto Para especificar o tipo projetado Para especificar o tipo de implementação
winrt::MyProject MyRuntimeClass implementation::MyRuntimeClass
winrt::MyProject::implementation MyProject::MyRuntimeClass MyRuntimeClass

Importante

Quando quiser retornar um tipo projetado de sua implementação, tenha cuidado para não criar uma instância do tipo de implementação escrevendo MyRuntimeClass myRuntimeClass;. As técnicas e o código corretos para esse cenário são mostrados anteriormente neste tópico, na seção Criando instâncias e retornando tipos de implementação e interfaces.

O problema com MyRuntimeClass myRuntimeClass; nesse cenário é que ele cria um objeto winrt::MyProject::implementation::MyRuntimeClass na pilha. Esse objeto (do tipo de implementação) se comporta como o tipo projetado de algumas maneiras, é possível invocar métodos nele da mesma forma e ele até mesmo é convertido em um tipo de projetado. Mas o objeto é destruído, de acordo com as regras normais de C++, quando o escopo é encerrado. Portanto, se você tiver retornado um tipo projetado (um ponteiro inteligente) para o objeto, esse ponteiro estará pendente.

Esse tipo de bug com corrupção de memória é difícil de diagnosticar. Portanto, para compilações de depuração, uma asserção C++/WinRT ajuda a identificar esse erro usando um detector de pilha. No entanto, as corrotinas são alocadas no heap, de modo que você não obterá ajuda com esse erro se ele ocorrer dentro de uma corrotina. Para saber mais, confira Diagnosticar alocações diretas.

Usando tipos projetados e tipos de implementação com vários recursos de C++/WinRT

Veja vários lugares onde um recurso de C++/WinRT espera um tipo e qual tipo ele espera (tipo projetado, tipo de implementação ou ambos).

Recurso Aceita Observações
T (representando um ponteiro inteligente) Projetado Consulte o aviso em Namespaces: tipos projetados, tipos de implementação e fábricas sobre o uso do tipo de implementação por engano.
agile_ref<T> Ambos Se você usar o tipo de implementação, o argumento do construtor deverá ser com_ptr<T>.
com_ptr<T> Implementação Usar o tipo projetado gera o erro: 'Release' is not a member of 'T'.
default_interface<T> Ambos Se você usar o tipo de implementação, a primeira interface implementada será retornada.
get_self<T> Implementação Usar o tipo projetado gera o erro: '_abi_TrustLevel': is not a member of 'T'.
guid_of<T>() Ambos Retorna o GUID da interface padrão.
IWinRTTemplateInterface<T>
Projetado Usar o tipo de implementação compila, mas isso é um erro consulte o aviso em Namespaces: tipos projetados, tipos de implementação e fábricas.
make<T> Implementação Usar o tipo projetado gera o erro: 'implements_type': is not a member of any direct or indirect base class of 'T'
make_agile(T const&amp;) Ambos Se você usar o tipo de implementação, o argumento deverá ser com_ptr<T>.
make_self<T> Implementação Usar o tipo projetado gera o erro: 'Release': is not a member of any direct or indirect base class of 'T'
name_of<T> Projetado Se usar o tipo de implementação, você receberá o GUID em cadeias de caracteres da interface padrão.
weak_ref<T> Ambos Se você usar o tipo de implementação, o argumento do construtor deverá ser com_ptr<T>.

Aceitar a construção uniforme e o acesso direto de implementação

Esta seção descreve um recurso do C++/WinRT 2.0 que é aceito, embora esteja habilitado por padrão para novos projetos. Para um projeto existente, será necessário aceitar configurando a ferramenta cppwinrt.exe. No Visual Studio, defina a propriedade do projeto Common Properties>C++/WinRT>Optimized como Yes. Isso tem o efeito de adicionar <CppWinRTOptimized>true</CppWinRTOptimized> ao arquivo de projeto. E tem o mesmo efeito que adicionar a opção ao invocar cppwinrt.exe da linha de comando.

A opção -opt[imize] habilita o que geralmente é chamado de construção uniforme. Com a construção uniforme (ou unificada), você usa a própria projeção de linguagem do C++/WinRT para criar e usar seus tipos de implementação (tipos implementados por seu componente, para consumo por aplicativos) com eficiência e sem nenhuma dificuldade de carregador.

Antes de descrever o recurso, vamos mostrar primeiro a situação sem a construção uniforme. Para ilustrar, começaremos com o exemplo da classe do Windows Runtime.

// MyClass.idl
namespace MyProject
{
    runtimeclass MyClass
    {
        MyClass();
        void Method();
        static void StaticMethod();
    }
}

Conforme o desenvolvedor do C++ se familiariza com a biblioteca do C++/WinRT, convém usar a classe como esta.

using namespace winrt::MyProject;

MyClass c;
c.Method();
MyClass::StaticMethod();

E isso seria perfeitamente razoável, desde que o código de consumo mostrado não resida no mesmo componente que implementa essa classe. Como uma projeção de linguagem, o C++/WinRT protege você, como desenvolvedor, da ABI (a interface binária de aplicativo baseada em COM definida pelo Windows Runtime). O C++/WinRT não chama diretamente na implementação; ele viaja pela ABI.

Consequentemente, na linha de código em que você está construindo um objeto MyClass (MyClass c;), a projeção do C++/WinRT chama RoGetActivationFactory para recuperar a classe ou a fábrica de ativação e, em seguida, o usa essa fábrica para criar o objeto. A última linha, da mesma forma, usa a fábrica para fazer o que parece ser uma chamada de método estático. Tudo isso exige que as classes sejam registradas e que seu módulo implemente o ponto de entrada DllGetActivationFactory. O C++/WinRT tem um cache de fábrica muito rápido; portanto, nada disso causa um problema para um aplicativo que consome seu componente. O problema é que, em seu componente, você acabou de fazer algo um pouco problemático.

Em primeiro lugar, não importa a rapidez do cache de fábrica do C++/WinRT, chamar por meio de RoGetActivationFactory (ou até mesmo chamadas subsequentes por meio do cache de fábrica) sempre será mais lento do que chamar diretamente na implementação. Uma chamada a RoGetActivationFactory seguida por IActivationFactory::ActivateInstance seguido por QueryInterface não será obviamente tão eficiente quanto usar a expressão new em C++ para um tipo definido localmente. Como consequência, os desenvolvedores do C++/WinRT experientes estão acostumados a usar as funções auxiliares winrt::make ou winrt::make_self ao criar objetos dentro de um componente.

// MyClass c;
MyProject::MyClass c{ winrt::make<implementation::MyClass>() };

Mas, como você pode ver, isso não é tão conveniente nem conciso. É necessário usar uma função auxiliar para criar o objeto, além de desfazer a ambiguidade entre o tipo de implementação e o tipo projetado.

Em segundo lugar, o uso da projeção para criar a classe significa que sua fábrica de ativação será armazenada em cache. Normalmente, é isso o que você deseja, mas se a fábrica residir no mesmo módulo (DLL) que está realizando a chamada, você terá efetivamente fixado a DLL e impedido que ela nunca fosse descarregada. Em muitos casos, isso não importa; no entanto, alguns componentes do sistema devem dar suporte ao descarregamento.

É aí que entra o termo construção uniforme. Independentemente de o código de criação residir em um projeto que simplesmente consuma a classe ou residir no projeto que realmente implemente a classe, você pode usar livremente a mesma sintaxe para criar o objeto.

// MyProject::MyClass c{ winrt::make<implementation::MyClass>() };
MyClass c;

Quando você cria seu projeto de componente com a opção -opt[imize], a chamada por meio da projeção de idioma é compilada para a mesma chamada eficiente à função winrt::make que cria diretamente o tipo de implementação. Isso torna sua sintaxe simples e previsível e evita qualquer impacto de chamar por meio da fábrica e a fixação do componente no processo. Além dos projetos de componente, isso também é útil para aplicativos XAML. Ignorar RoGetActivationFactory para classes implementadas no mesmo aplicativo permite que você as construa (sem precisar estar registrado) de todas as formas possíveis se elas estivessem fora do componente.

A construção uniforme aplica-se a qualquer chamada atendida pela fábrica nos bastidores. Praticamente, isso significa que a otimização funciona tanto para construtores quanto para membros estáticos. Veja o exemplo original novamente.

MyClass c;
c.Method();
MyClass::StaticMethod();

Sem -opt[imize], a primeira e a última instruções exigem chamadas por meio do objeto de fábrica. Com -opt[imize], nem uma delas exige. E essas chamadas são compiladas diretamente com relação à implementação e, inclusive, têm o potencial de serem embutidas. O que conversa com o outro termo frequentemente usado ao falar sobre -opt[imize], ou seja, o acesso direto de implementação.

As projeções de linguagem são convenientes, mas, quando você pode acessar diretamente a implementação, você pode e deve aproveitar isso para produzir o código mais eficiente possível. O C++/WinRT pode fazer isso por você, sem forçá-lo a deixar a segurança e a produtividade da projeção.

Essa é uma alteração da falha, porque o componente deve cooperar para permitir que a projeção de linguagem alcance e acesse diretamente seus tipos de implementação. Como o C++/WinRT é uma biblioteca somente de cabeçalho, você pode olhar para dentro e ver o que está acontecendo. Sem -opt[imize], o construtor MyClass e o membro StaticMethod são definidos pela projeção como esta.

namespace winrt::MyProject
{
    inline MyClass::MyClass() :
        MyClass(impl::call_factory<MyClass>([](auto&& f){
		    return f.template ActivateInstance<MyClass>(); }))
    {
    }
    inline void MyClass::StaticMethod()
    {
        impl::call_factory<MyClass, MyProject::IClassStatics>([&](auto&& f) {
		    return f.StaticMethod(); });
    }
}

Não é necessário seguir todas as instruções acima; a intenção é mostrar que as duas chamadas envolvem uma chamada para uma função chamada call_factory. Essa é a pista de que essas chamadas envolvem o cache de fábrica e não estão acessando diretamente a implementação. Com -opt[imize], essas mesmas funções não são definidas. Em vez disso, elas são declaradas pela projeção e suas definições são deixadas até o componente.

O componente pode, então, fornecer definições que chamam diretamente para a implementação. Agora, chegamos à alteração da falha. Essas definições são geradas quando você usa -component e -opt[imize], e são exibidas em um arquivo chamado Type.g.cpp, em que Type é o nome da classe de runtime que está sendo implementada. É por isso que você pode atingir vários erros de vinculador quando habilita -opt[imize] pela primeira vez em um projeto existente. É necessário incluir esse arquivo gerado para sua implementação a fim de unir as coisas.

Em nosso exemplo, MyClass.h pode ter essa aparência (independentemente se -opt[imize] está sendo usado).

// MyClass.h
#pragma once
#include "MyClass.g.h"
 
namespace winrt::MyProject::implementation
{
    struct MyClass : ClassT<MyClass>
    {
        MyClass() = default;
 
        static void StaticMethod();
        void Method();
    };
}
namespace winrt::MyProject::factory_implementation
{
    struct MyClass : ClassT<MyClass, implementation::MyClass>
    {
    };
}

Seu MyClass.cpp é o local em que tudo isso se reúne.

#include "pch.h"
#include "MyClass.h"
#include "MyClass.g.cpp" // !!It's important that you add this line!!
 
namespace winrt::MyProject::implementation
{
    void MyClass::StaticMethod()
    {
    }
 
    void MyClass::Method()
    {
    }
}

Portanto, para usar a construção uniforme em um projeto existente, é necessário editar o arquivo .cpp de cada implementação para que você #include <Sub/Namespace/Type.g.cpp> depois da inclusão (e da definição) da classe de implementação. Esse arquivo fornece as definições dessas funções que a projeção deixou indefinida. Veja a aparência dessas definições dentro do arquivo MyClass.g.cpp.

namespace winrt::MyProject
{
    MyClass::MyClass() :
        MyClass(make<MyProject::implementation::MyClass>())
    {
    }
    void MyClass::StaticMethod()
    {
        return MyProject::implementation::MyClass::StaticMethod();
    }
}

E isso conclui perfeitamente a projeção com chamadas eficientes diretamente para a implementação, evitando chamadas ao cache de fábrica e atendendo ao vinculador.

A última coisa que o -opt[imize] faz por você é alterar a implementação do module.g.cpp do seu projeto (o arquivo que ajuda você a implementar as exportações DllGetActivationFactory e DllCanUnloadNow de sua DLL) de tal forma que os builds incrementais tendem a ser muito mais rápidos ao eliminar o acoplamento de tipo forte que era exigido pelo C++/WinRT 1.0. Isso é geralmente conhecido como fábricas de tipo apagado. Sem -opt[imize], o arquivo module.g.cpp gerado para seu componente começa incluindo as definições de todas as suas classes de implementação, the MyClass.h neste exemplo. Em seguida, ele cria diretamente a fábrica de implementação para cada classe como esta.

if (requal(name, L"MyProject.MyClass"))
{
    return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}

Novamente, você não precisa seguir todos os detalhes. É útil ver que isso requer a definição completa para todas as classes implementadas por seu componente. Isso pode ter um efeito significativo sobre seu loop interno, uma vez que qualquer alteração em uma única implementação fará module.g.cpp ser recompilado. Com o -opt[imize], isso não acontece mais. Em vez disso, duas coisas acontecem com o arquivo module.g.cpp gerado. A primeira é que ele não inclui mais nenhuma classe de implementação. Neste exemplo, ele não incluirá MyClass.h. Em vez disso, ele cria as fábricas de implementação sem conhecer sua implementação.

void* winrt_make_MyProject_MyClass();
 
if (requal(name, L"MyProject.MyClass"))
{
    return winrt_make_MyProject_MyClass();
}

Obviamente, não há necessidade de incluir suas definições, e cabe ao vinculador resolver a definição da função winrt_make_Component_Class. É claro que você não precisa pensar nisso, porque o arquivo MyClass.g.cpp gerado para você (e que você anteriormente incluiu para dar suporte à construção uniforme) também define essa função. Veja a totalidade do arquivo MyClass.g.cpp gerado para este exemplo.

void* winrt_make_MyProject_MyClass()
{
    return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}
namespace winrt::MyProject
{
    MyClass::MyClass() :
        MyClass(make<MyProject::implementation::MyClass>())
    {
    }
    void MyClass::StaticMethod()
    {
        return MyProject::implementation::MyClass::StaticMethod();
    }
}

Como você pode ver, a função winrt_make_MyProject_MyClass cria diretamente a fábrica de sua implementação. Isso tudo significa que você pode alterar qualquer implementação determinada e que o module.g.cpp não precisa ser recompilado. Somente quando você adiciona ou remove classes do Windows Runtime que o module.g.cpp será atualizado e precisará ser recompilado.

Como substituir métodos virtuais da classe base

A classe derivada poderá ter problemas com métodos virtuais se a classe base e a derivada forem classes definidas pelo aplicativo, mas o método virtual for definido em uma classe avô do Windows Runtime. Na prática, isso acontecerá se for feita a derivação de classes XAML. O restante desta seção continua após o exemplo dado em Classes derivadas.

namespace winrt::MyNamespace::implementation
{
    struct BasePage : BasePageT<BasePage>
    {
        void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };

    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };
}

A hierarquia é Windows::UI::Xaml::Controls::Page< –BasePage< –DerivedPage. O método BasePage::OnNavigatedFrom substitui corretamente Page::OnNavigatedFrom, mas DerivedPage::OnNavigatedFrom não substitui BasePage::OnNavigatedFrom.

Aqui, DerivedPage reutiliza a vtable IPageOverrides de BasePage, o que significa que ela não substitui o método IPageOverrides::OnNavigatedFrom. Uma possível solução exige que a própria BasePage seja uma classe de modelo e tenha sua implementação inteiramente em um arquivo de cabeçalho, mas isso torna as coisas complicadas e inaceitáveis.

Como alternativa, declare o método OnNavigatedFrom como explicitamente virtual na classe base. Dessa forma, quando a entrada de vtable para DerivedPage::IPageOverrides::OnNavigatedFrom chamar BasePage::IPageOverrides::OnNavigatedFrom, o produtor chamará BasePage::OnNavigatedFrom, que (devido à sua natureza virtual) acabará chamando DerivedPage::OnNavigatedFrom.

namespace winrt::MyNamespace::implementation
{
    struct BasePage : BasePageT<BasePage>
    {
        // Note the `virtual` keyword here.
        virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };

    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };
}

Isso exige que todos os membros da hierarquia de classe concordem com o valor retornado e os tipos de parâmetro do método OnNavigatedFrom. Se eles discordarem, você deverá usar a versão acima como o método virtual e encapsular as alternativas.

Observação

Sua IDL não precisa declarar o método substituído. Para obter mais detalhes, confira Implementando métodos que podem ser substituídos.

APIs importantes