Consumo de API con C++/WinRT

En este tema se muestra cómo consumir las API de C++/WinRT, independientemente de que formen parte de Windows, las haya implementado un proveedor de componentes de terceros o las hayas implementado tú mismo.

Importante

Para que los ejemplos de código de este tema sean breves y fáciles de probar, puede reproducirlos mediante la creación de un nuevo proyecto de Aplicación de consola Windows (C++/WinRT) y la copia y pegado del código. Sin embargo, no se pueden usar tipos de Windows Runtime personalizados arbitrarios (de terceros) desde una aplicación desempaquetada de esta manera. Solo puede utilizar tipos de Windows así.

Para usar tipos de Windows Runtime personalizados (de terceros) desde una aplicación de consola, deberá proporcionar a la aplicación una identidad de paquete para que pueda resolver el registro de los tipos personalizados consumidos. Para más información, consulte Proyecto de paquete de aplicación de Windows.

También puede crear un nuevo proyecto a partir de las plantillas de proyecto de aplicación vacía (C++/WinRT) , aplicación principal (C++/WinRT) o componente de Windows Runtime (C++/WinRT) . Esos tipos de aplicación ya tienen una identidad de paquete.

Si la API está en un espacio de nombres de Windows

Este es el caso más común en el que consumirás una API de Windows Runtime. Para cada tipo de un espacio de nombres de Windows definido en los metadatos, C++/WinRT define un equivalente de C++ descriptivo (denominado el tipo proyectado). Un tipo proyectado tiene el mismo nombre completo que el tipo de Windows, pero se colocado en el espacio de nombres winrt de C++ mediante la sintaxis de C++. Por ejemplo, Windows::Foundation::Uri se proyecta en C++/WinRT como winrt::Windows::Foundation::Uri.

Este es un ejemplo de código sencillo. Si quieres copiar y pegar los ejemplos de código siguientes directamente en el archivo de código fuente principal de un proyecto de aplicación de consola de Windows (C++/WinRT) , establece primero No utilizar encabezados precompilados en las propiedades del proyecto.

// main.cpp
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

int main()
{
    winrt::init_apartment();
    Uri contosoUri{ L"http://www.contoso.com" };
    Uri combinedUri = contosoUri.CombineUri(L"products");
}

El encabezado incluido winrt/Windows.Foundation.h forma parte del SDK, que se encuentra dentro de la carpeta %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt\. Los encabezados de dicha carpeta contienen tipos de espacios de nombres de Windows proyectados en C++/WinRT. En este ejemplo, winrt/Windows.Foundation.h contiene winrt::Windows::Foundation::Uri, que es el tipo proyectado para la clase en tiempo de ejecución Windows::Foundation::Uri.

Sugerencia

Cada vez que quieras usar un tipo desde un espacio de nombres de Windows, incluye el encabezado C++/WinRT correspondiente a dicho espacio de nombres. Las directivas using namespace son opcionales, pero recomendables.

En el ejemplo de código anterior, tras inicializar C++/WinRT, apilamos/asignamos un valor del tipo proyectado winrt::Windows::Foundation::Uri a través de uno de sus constructores documentados públicamente [Uri(String), en este ejemplo]. Para este ejemplo, el caso de uso más común, eso es normalmente todo lo que tienes que hacer. Una vez que tengas un valor del tipo proyectado de C++/WinRT, puedes tratarlo como si fuera una instancia del tipo Windows Runtime real, ya que tiene los mismos miembros.

De hecho, dicho valor proyectado es un proxy; esencialmente no es más que un puntero inteligente a un objeto de respaldo. Los constructores del valor proyectado llaman a RoActivateInstance para crear una instancia de la clase de Windows Runtime de respaldo (en este caso es Windows.Foundation.Uri) y almacenan la interfaz predeterminada de dicho objeto en el nuevo valor proyectado. Como se ilustra a continuación, las llamadas a los miembros del valor proyectado realmente se delegan, a través del puntero inteligente, al objeto de respaldo, que es donde se producen los cambios de estado.

El tipo Windows::Foundation::Uri proyectado

Cuando el valor de contosoUri queda fuera de ámbito, se destruye y libera su referencia a la interfaz predeterminada. Si esta es la última referencia al objeto Windows.Foundation.Uri de Windows Runtime de respaldo, el objeto de respaldo se destruye también.

Sugerencia

Un tipo proyectado es un contenedor sobre un tipo de Windows Runtime que se usa para consumir sus API. Por ejemplo, una interfaz proyectada es un contenedor sobre una interfaz de Windows Runtime.

Encabezados de proyección de C++/WinRT

Para consumir las API de espacio de nombres de Windows desde C++/WinRT, incluye encabezados desde la carpeta %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt. Debe incluir los encabezados correspondientes a cada espacio de nombres que use.

Por ejemplo, para el espacio de nombres Windows::Security::Cryptography::Certificates, las definiciones de tipo de C++/WinRT equivalentes residen en winrt/Windows.Security.Cryptography.Certificates.h. Incluir ese encabezado le proporciona acceso a todos los tipos del espacio de nombres Windows::Security::Cryptography::Certificates.

A veces, un encabezado de espacio de nombres incluirá partes de encabezados de espacio de nombres relacionados, pero no debe basarse en este detalle de implementación. Incluya explícitamente los encabezados de los espacios de nombres que use.

Por ejemplo, el método Certificate::GetCertificateBlob devuelve una interfaz Windows::Storage::Secuencias::IBuffer. Antes de llamar al método Certificate::GetCertificateBlob, debe incluir el archivo de encabezado del espacio de nombres winrt/Windows.Storage.Streams.h para asegurarse de que puede recibir y operar en el Windows::Storage::Streams::IBuffer devuelto.

Olvidar incluir los encabezados de espacio de nombres necesarios antes de usar tipos en ese espacio de nombres es un origen común de errores de compilación.

Acceso a miembros a través del objeto, de una interfaz o de la ABI

Con la proyección de C++/WinRT, la representación del runtime de una clase de Windows Runtime no es más que las interfaces ABI subyacentes. Sin embargo, para tu comodidad, puedes escribir código frente a las clases de la manera que el autor pretendía. Por ejemplo, puedes llamar al método ToString de un identificador URI como si fuese un método de la clase (de hecho, en modo no visible, es un método en la otra interfaz IStringable).

WINRT_ASSERT es una definición de macro y se expande a _ASSERTE.

Uri contosoUri{ L"http://www.contoso.com" };
WINRT_ASSERT(contosoUri.ToString() == L"http://www.contoso.com/"); // QueryInterface is called at this point.

Esta comodidad se logra a través de una consulta en la interfaz adecuada. Pero siempre tienes el control. Puedes optar por sacrificar un poco de dicha comodidad a cambio de mejorar algo el rendimiento, lo que se logra recuperando la interfaz IStringable y usándola directamente. En el siguiente ejemplo de código, se obtiene un puntero de interfaz IStringable real en tiempo de ejecución (a través de una consulta puntual). Después, la llamada a ToString es directa y evita cualquier otra llamada a QueryInterface.

...
IStringable stringable = contosoUri; // One-off QueryInterface.
WINRT_ASSERT(stringable.ToString() == L"http://www.contoso.com/");

Puedes elegir esta técnica si sabes que vas a llamar a varios métodos en la misma interfaz.

A propósito, si deseas acceder a miembros en el nivel de la ABI, puedes hacerlo. El ejemplo de código siguiente muestra cómo. En Interoperabilidad entre C++/WinRT y la ABI, hay más detalles y ejemplos de código.

#include <Windows.Foundation.h>
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
using namespace winrt::Windows::Foundation;

int main()
{
    winrt::init_apartment();
    Uri contosoUri{ L"http://www.contoso.com" };

    int port{ contosoUri.Port() }; // Access the Port "property" accessor via C++/WinRT.

    winrt::com_ptr<ABI::Windows::Foundation::IUriRuntimeClass> abiUri{
        contosoUri.as<ABI::Windows::Foundation::IUriRuntimeClass>() };
    HRESULT hr = abiUri->get_Port(&port); // Access the get_Port ABI function.
}

Inicialización demorada

En C++/WinRT, cada tipo proyectado tiene un constructor especial std::nullptr_t de C++/WinRT. A excepción de este, todos los constructores de tipo proyectado (incluido el constructor predeterminado) hacen que se cree un objeto de Windows Runtime de respaldo y le proporcionan un puntero inteligente a él. Por lo tanto, esa regla se aplica siempre que se use el constructor predeterminado, como en variables locales no inicializadas, variables globales no inicializadas y variables de miembro no inicializadas.

Si, por otro lado, quieres crear una variable de un tipo proyectado sin ella y construir en su lugar un objeto de Windows Runtime de respaldo (con el fin de poder retrasar ese trabajo hasta más adelante), puedes hacerlo. Declara la variable o el campo con ese constructor especial std::nullptr_t de C++/WinRT (que la proyección de C++/WinRT inyecta en cada clase en tiempo de ejecución). Usamos ese constructor especial con m_gamerPicBuffer en el ejemplo de código siguiente.

#include <winrt/Windows.Storage.Streams.h>
using namespace winrt::Windows::Storage::Streams;

#define MAX_IMAGE_SIZE 1024

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

private:
    Buffer m_gamerPicBuffer{ nullptr };
};

int main()
{
    winrt::init_apartment();
    Sample s;
    // ...
    s.DelayedInit();
}

Todos los constructores del tipo proyectado excepto el constructor std::nullptr_t hacen que se cree un objeto de Windows Runtime de respaldo. El constructor std:: nullptr_t es básicamente una operación nula. Espera que el objeto proyectado se inicialice más adelante. Por tanto, independientemente de que una clase en tiempo de ejecución tenga un constructor predeterminado, puedes usar esta técnica para que una inicialización retrasada sea eficiente.

Esta consideración afecta a otros lugares en los que se invocando al constructor predeterminado, como en vectores y mapas. Fíjate en este ejemplo de código, para el que necesitarás un proyecto de Aplicación vacía (C++/WinRT) .

std::map<int, TextBlock> lookup;
lookup[2] = value;

La asignación crea un objeto TextBlocke inmediatamente lo sobrescribe con value. Este es el recurso.

std::map<int, TextBlock> lookup;
lookup.insert_or_assign(2, value);

Además, consulta De qué manera el constructor predeterminado afecta a las colecciones.

No inicializar con retraso por error

Ten cuidado de no invocar el constructor std:: nullptr_t por error. La resolución de conflictos del compilador favorece dicho constructor por sobre los constructores de fábrica. Por ejemplo, observa estas dos definiciones de clase en tiempo de ejecución.

// GiftBox.idl
runtimeclass GiftBox
{
    GiftBox();
}

// Gift.idl
runtimeclass Gift
{
    Gift(GiftBox giftBox); // You can create a gift inside a box.
}

Supongamos que queremos construir un objeto Gift que no está dentro de un cuadro (un Gift que se construye con un tipo GiftBox sin inicializar). En primer lugar, echemos un vistazo a la forma incorrecta de hacerlo. Sabemos que hay un constructor de Gift que usa un tipo GiftBox. Sin embargo, nos vemos tentados por pasar un valor GiftBox nulo (mediante la invocación del constructor Gift a través de la inicialización uniforme, tal como lo hacemos a continuación), aunque no obtendremos el resultado que queremos.

// These are *not* what you intended. Doing it in one of these two ways
// actually *doesn't* create the intended backing Windows Runtime Gift object;
// only an empty smart pointer.

Gift gift{ nullptr };
auto gift{ Gift(nullptr) };

Lo que obtenemos aquí es un objeto Gift sin inicializar. No obtenemos un Gift con un tipo GiftBox sin inicializar. Esta es la forma correcta de hacerlo.

// Doing it in one of these two ways creates an initialized
// Gift with an uninitialized GiftBox.

Gift gift{ GiftBox{ nullptr } };
auto gift{ Gift(GiftBox{ nullptr }) };

En el ejemplo incorrecto, pasar un valor nullptr literal se resuelve en favor del constructor de inicialización con retraso. Para resolver en favor del constructor de fábrica, el tipo del parámetro debe ser un valor GiftBox. Aun así, tienes la opción de pasar un valor GiftBox con inicialización demorada de forma explícita, tal y como se muestra en el ejemplo correcto.

El siguiente ejemplo también es correcto, ya que el parámetro tiene el tipo GiftBox en vez de std::nullptr_t.

GiftBox giftBox{ nullptr };
Gift gift{ giftBox }; // Calls factory constructor.

La ambigüedad se produce únicamente cuando pasas un valor nullptr literal.

No construir con copia por error

Esta advertencia es similar a la descrita en la sección No inicializar con retraso por error anterior.

Además del constructor de inicialización con retraso, la proyección de C++/WinRT también inserta un constructor de copia en todas las clases en tiempo de ejecución. Se trata de un constructor de parámetro único que acepta el mismo tipo que el objeto que se va a construir. El puntero inteligente resultante apunta al mismo objeto de Windows Runtime que apunta su parámetro de constructor. El resultado es que dos objetos de puntero inteligente apuntan al mismo objeto de respaldo.

Esta es una definición de clase en tiempo de ejecución que vamos a usar en los ejemplos de código.

// GiftBox.idl
runtimeclass GiftBox
{
    GiftBox(GiftBox biggerBox); // You can place a box inside a bigger box.
}

Supongamos que queremos construir un tipo GiftBox dentro de un GiftBox más grande.

GiftBox bigBox{ ... };

// These are *not* what you intended. Doing it in one of these two ways
// copies bigBox's backing-object-pointer into smallBox.
// The result is that smallBox == bigBox.

GiftBox smallBox{ bigBox };
auto smallBox{ GiftBox(bigBox) };

La forma correcta de hacerlo es llamar explícitamente al generador de activación.

GiftBox bigBox{ ... };

// These two ways call the activation factory explicitly.

GiftBox smallBox{
    winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };
auto smallBox{
    winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };

Si la API se implementa en un componente de Windows Runtime

Esta sección se aplica si creaste el componente tú mismo o si este procedió de un proveedor.

Nota

Para más información sobre cómo instalar y usar C++/WinRT Visual Studio Extension (VSIX) y el paquete de NuGet (que juntos proporcionan la plantilla de proyecto y compatibilidad de la compilación), consulta Compatibilidad de Visual Studio para C++/WinRT.

En tu proyecto de aplicación, haz referencia al archivo de metadatos de Windows Runtime del componente de Windows Runtime (.winmd) y realiza la compilación. Durante la compilación, la herramienta cppwinrt.exe genera una biblioteca de C++ estándar que describe completamente o proyecta la superficie de la API para el componente. En otras palabras, la biblioteca generada contiene los tipos proyectados para el componente.

Después, igual que harías en un tipo de espacio de nombres de Windows, incluye un encabezado y crea el tipo proyectado a través de uno de sus constructores. El código de inicio del proyecto de tu aplicación registra la clase en tiempo de ejecución y el constructor del tipo proyectado llama a RoActivateInstance para activar dicha clase desde el componente al que hace referencia.

#include <winrt/ThermometerWRC.h>

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    ThermometerWRC::Thermometer thermometer;
    ...
};

Para obtener más detalles, el código y un tutorial sobre la utilización de las API implementadas en un componente de Windows Runtime, consulte Componentes de Windows Runtime con C++/WinRT y Creación de eventos en C++/WinRT.

Si la API se implementa en el proyecto de consumo

El ejemplo de código de esta sección se toma del tema Controles de XAML; enlazar a una propiedad de C++/WinRT. Consulte ese tema para obtener más detalles, el código y un tutorial acerca de cómo consumir una clase en tiempo de ejecución implementada en el proyecto que la consume.

Un tipo que se consume desde la interfaz de usuario de XAML debe ser una clase en tiempo de ejecución, aunque se encuentre en el mismo proyecto que el XAML. Para este escenario, genera un tipo proyectado desde los metadatos de Windows Runtime de la clase en tiempo de ejecución (.winmd). De nuevo, debe incluir un encabezado, pero, luego, puede elegir entre la versión 1.0 o 2.0 de C++ o WinRT con tal de construir la instancia de la clase en tiempo de ejecución. El método de la versión 1.0 usa winrt:: make, mientras que el de la versión 2.0 se conoce como construcción uniforme. Vamos a ver cada uno de ellos en detalle.

Construcción mediante winrt:: make

Comencemos con el método predeterminado (versión 1.0 de C++ /WinRT), ya que resulta útil por lo menos estar familiarizado con ese patrón. Para construir el tipo proyectado, se debe usar el constructor std::nullptr_t. Dicho constructor no realiza ninguna inicialización, por lo que debes asignar un valor a la instancia a través de la función auxiliar winrt::make, pasando todos los argumentos de constructor necesarios. Las clases en tiempo de ejecución implementadas en el mismo proyecto que el código de consumo no es preciso registrarlas ni crear instancias de ellas a través de la activación de Windows Runtime/COM.

Consulte Controles XAML; enlazar a una propiedad C++/WinRT para ver el tutorial completo. En esta sección se muestran extractos de este tutorial.

// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        BookstoreViewModel MainViewModel{ get; };
    }
}

// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
    ...
    private:
        Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
};
...

// MainPage.cpp
...
#include "BookstoreViewModel.h"

MainPage::MainPage()
{
    m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
    ...
}

Construcción uniforme

Con la versión 2.0 de C++ o WinRT y las posteriores, dispone de una forma de construcción optimizada que se conoce como construcción uniforme (consulte Novedades y cambios de C++/WinRT 2.0).

Consulte Controles XAML; enlazar a una propiedad C++/WinRT para ver el tutorial completo. En esta sección se muestran extractos de ese tutorial.

Para usar la construcción uniforme en lugar de winrt::make, necesitará un generador de activación. Una buena opción para generar uno es agregando un constructor a su IDL.

// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

A continuación, en MainPage.h declare e inicialice m_mainViewModel en un solo paso, tal y como se muestra a continuación.

// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
    ...
    private:
        Bookstore::BookstoreViewModel m_mainViewModel;
        ...
    };
}
...

A continuación, en el constructor MainPage de MainPage.cpp, no se requiere el código m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.

Para obtener más información sobre la construcción uniforme y ejemplos de código, consulte Participación en la construcción uniforme y acceso de implementación directa.

Creación de instancias y devolución de tipos proyectados e interfaces

Aquí se muestra un ejemplo del aspecto que podrían tener los tipos e interfaces proyectados en tu proyecto de consumo. Recuerda que los tipos proyectado (como el de este ejemplo) se generan mediante herramientas, no los puedes crear tú.

struct MyRuntimeClass : MyProject::IMyRuntimeClass, impl::require<MyRuntimeClass,
    Windows::Foundation::IStringable, Windows::Foundation::IClosable>

MyRuntimeClass es un tipo proyectado; las interfaces proyectadas incluyen IMyRuntimeClass, IStringable, y IClosable . En este tema se muestran las distintas formas de crear instancias de un tipo proyectado. Aquí tienes un recordatorio y un resumen, y se usa MyRuntimeClass como ejemplo.

// The runtime class is implemented in another compilation unit (it's either a Windows API,
// or it's implemented in a second- or third-party component).
MyProject::MyRuntimeClass myrc1;

// The runtime class is implemented in the same compilation unit.
MyProject::MyRuntimeClass myrc2{ nullptr };
myrc2 = winrt::make<MyProject::implementation::MyRuntimeClass>();
  • Puedes acceder a los miembros de todas las interfaces de un tipo proyectado.
  • Puedes devolver un tipo proyectado a un autor de llamada.
  • Los tipos e interfaces proyectados derivan de winrt::Windows::Foundation::IUnknown. Por consiguiente, puedes llamar a IUnknown::as en un tipo o interfaz proyectados para consultar otras interfaces proyectadas, que también puedes usar o devolver al autor de la llamada. La función de miembro as funciona como QueryInterface.
void f(MyProject::MyRuntimeClass const& myrc)
{
    myrc.ToString();
    myrc.Close();
    IClosable iclosable = myrc.as<IClosable>();
    iclosable.Close();
}

Generadores de activación

La forma más práctica y directa de crear un objeto de C++/WinRT es la siguiente.

using namespace winrt::Windows::Globalization::NumberFormatting;
...
CurrencyFormatter currency{ L"USD" };

Sin embargo, puede haber ocasiones en que quieras crear el generador de activación tú mismo y, después, crear objetos a partir de él cuando mejor te venga. Estos son algunos ejemplos, que muestran cómo hacerlo mediante la plantilla de función winrt::get_activation_factory.

using namespace winrt::Windows::Globalization::NumberFormatting;
...
auto factory = winrt::get_activation_factory<CurrencyFormatter, ICurrencyFormatterFactory>();
CurrencyFormatter currency = factory.CreateCurrencyFormatterCode(L"USD");
using namespace winrt::Windows::Foundation;
...
auto factory = winrt::get_activation_factory<Uri, IUriRuntimeClassFactory>();
Uri uri = factory.CreateUri(L"http://www.contoso.com");

Las clases de los dos ejemplos anteriores son tipos de un espacio de nombres de Windows. En el siguiente ejemplo, ThermometerWRC::Thermometer es un tipo personalizado implementado en un componente de Windows Runtime.

auto factory = winrt::get_activation_factory<ThermometerWRC::Thermometer>();
ThermometerWRC::Thermometer thermometer = factory.ActivateInstance<ThermometerWRC::Thermometer>();

Ambigüedades entre miembros y tipos

Cuando una función de miembro tiene el mismo nombre que un tipo, hay una ambigüedad. Las reglas de búsqueda de nombres no completos de C++ en funciones miembro hacen que busque en la clase antes de buscar en los espacios de nombres. La regla substitution failure is not an error (SFINAE, o el fallo de sustitución no es un error) no se aplica (se aplica durante la resolución de sobrecarga de las plantillas de función). Por tanto, si el nombre de la clase no tiene sentido, el compilador no sigue buscando una coincidencia mejor y simplemente informa de un error.

struct MyPage : Page
{
    void DoWork()
    {
        // This doesn't compile. You get the error
        // "'winrt::Windows::Foundation::IUnknown::as':
        // no matching overloaded function found".
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<Style>() };
    }
}

Además, el compilador considera que estás pasando FrameworkElement.Style() (que, en C++/WinRT, es una función miembro) como parámetro de plantilla a IUnknown::as. La solución consiste en forzar que el nombre Style se interprete como el tipo Windows::UI::XAML::Style.

struct MyPage : Page
{
    void DoWork()
    {
        // One option is to fully-qualify it.
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<Windows::UI::Xaml::Style>() };

        // Another is to force it to be interpreted as a struct name.
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<struct Style>() };

        // If you have "using namespace Windows::UI;", then this is sufficient.
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<Xaml::Style>() };

        // Or you can force it to be resolved in the global namespace (into which
        // you imported the Windows::UI::Xaml namespace when you did
        // "using namespace Windows::UI::Xaml;".
        auto style = Application::Current().Resources().
            Lookup(L"MyStyle").as<::Style>();
    }
}

La búsqueda de nombres no completos tiene una excepción especial en caso de que el nombre vaya seguido de ::, en cuyo caso omite las funciones, las variables y los valores de enumeración. Esto te permite hacer cosas como la siguiente.

struct MyPage : Page
{
    void DoSomething()
    {
        Visibility(Visibility::Collapsed); // No ambiguity here (special exception).
    }
}

La llamada a Visibility() se resuelve en el nombre de la función miembro UIElement.Visibility. Pero el parámetro Visibility::Collapsed sigue a la palabra Visibility con ::, por lo que se omite el nombre del método, y el compilador encuentra la clase enum.

API importantes