Вызов API для среды выполнения Windows в классических приложениях

В этой статье описывается, как настроить в проектах классических приложений использование API-интерфейсов среды выполнения Windows (WinRT), предоставляемых в ОС Windows, и добавить в классические приложения современные возможности Windows 11 и Windows 10.

Некоторые API среды выполнения Windows (WinRT) не поддерживаются в классических приложениях. Дополнительные сведения см. в статье API среды выполнения Windows не поддерживаются в классических приложениях.

Настройка проекта .NET для использования API среды выполнения Windows

Для проектов .NET существует несколько вариантов.

  • Начиная с .NET 6 вы можете указать моникер целевой платформы (TFM) в файл проекта для получения доступа к API-интерфейсам WinRT. Этот вариант поддерживается в проектах, предназначенных для Windows 10 версии 1809 или более поздней.
  • Для более ранних версий .NET можно установить пакет NuGet Microsoft.Windows.SDK.Contracts, чтобы добавить все необходимые ссылки в проект. Этот параметр поддерживается в проектах, предназначенных для Windows 10 версии 1803 или более поздней.
  • Если проект предназначен одновременно для .NET 6 (или более поздний выпуск) и более ранних версий, то можно настроить файл проекта для использования обоих вариантов.

.NET 6 и более поздних версий: использование моникера целевой платформы

Этот вариант поддерживают только проекты, использующие .NET 6 (или более поздней версии) и предназначенные для Windows 10 версии 1809 или более позднего выпуска. При указании TFM для определенной версии ОС Windows в файле проекта в соответствующий целевой пакет Windows SDK добавляется ссылка. Дополнительные сведения об этом сценарии см. в записи блога о вызове API Windows в .NET.

  1. В Visual Studio щелкните правой кнопкой мыши проект в Обозревателе решений и выберите Изменить файл проекта. Файл проекта будет выглядеть примерно так.

    Примечание

    В приведенном ниже примере показан параметр OutputType средства WinExe, который указывает на исполняемый файл графического пользовательского интерфейса Windows (и не позволяет открыть окно консоли при запуске приложения). Если в вашем приложении нет графического пользовательского интерфейса, значение OutputType будет другим. API-интерфейсы WinRT можно вызывать из приложений с графическим пользовательским интерфейсом, консольных приложений и библиотек для Windows. Кроме того, значение TargetFramework может не совпадать с примером ниже.

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
      </PropertyGroup>
    </Project>
    
  2. Замените значение элемента TargetFramework одной из указанных ниже строк, оставив все остальные значения без изменений.

    • net6.0-windows10.0.17763.0 — если приложение предназначено для Windows 10, версия 1809.
    • net6.0-windows10.0.18362.0 — если приложение предназначено для Windows 10, версия 1903.
    • net6.0-windows10.0.19041.0 — если приложение предназначено для Windows 10, версия 2004.
    • net6.0-windows10.0.22000.0 — если приложение предназначено для Windows 11.

    Например, следующий элемент используется для проекта, предназначенного для Windows 10 версии 2004.

    <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
    

    В более поздних версиях .NET можно заменить это значение соответствующей версией, например net6.0-windows10.0.19041.0.

  3. Сохраните изменения и закройте файл проекта.

API-интерфейсы WinRT не поддерживаются в .NET 6 или более поздних версиях

В .NET 6 и более поздних версиях используется несколько API-интерфейсов среды выполнения Windows (WinRT) в пространстве имен Windows.UI, которое не поддерживается. Для интерфейсов API, перечисленных ниже, в пространстве имен WinUI (Microsoft.UI) существуют эквивалентные версии API (например, Microsoft.UI.Text). Следующие API-интерфейсы WinRT не поддерживаются в .NET 6 и более поздних версиях:

Поддержка нескольких версий ОС Windows

Свойство TargetFramework для конкретной версии ОС Windows определяет версию пакета Windows SDK, с которым компилируется приложение. Это свойство определяет набор доступных API-интерфейсов во время сборки и предоставляет значения по умолчанию для TargetPlatformVersion и TargetPlatformMinVersion (если они не заданы явно). Свойство TargetPlatformVersion не нужно явно определять в файле проекта, так как оно автоматически задается версией ОС TargetFramework.

Значение TargetPlatformMinVersion можно переопределить, чтобы оно было меньше значения TargetPlatformVersion (определяется версией в свойстве TargetFramework). Благодаря этому приложение сможет работать в более ранних версиях ОС. Например, в файле проекта можно задать приведенные ниже значения, чтобы обеспечить для приложения поддержку Windows 10 версии 1809.

<Project Sdk="Microsoft.NET.Sdk">
 <PropertyGroup>
   <OutputType>WinExe</OutputType>
   <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
   <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
 </PropertyGroup>
</Project>

Обратите внимание, что при установке в качестве значения TargetPlatformMinVersion версии ниже, чем значение TargetPlatformVersion, есть вероятность того, что будут вызваны недоступные API-интерфейсы. При вызове API-интерфейсов WinRT, которые доступны не во всех поддерживаемых версиях ОС, рекомендуется защищать эти вызовы с помощью проверок ApiInformation. Дополнительные сведения см. в статье Приложения с адаптивным к версии кодом.

Предшествующие версии .NET: установка пакета NuGet Microsoft.Windows.SDK.Contracts

Задайте этот параметр, если приложение использует .NET Core 3.x или .NET Framework. Этот параметр поддерживается в проектах, предназначенных для Windows 10 версии 1803 или более поздней.

  1. Убедитесь, что ссылки на пакеты активны:

    1. В Visual Studio, выберите Инструменты -> Диспетчер пакетов NuGet -> Параметры диспетчера пакетов.
    2. Убедитесь, что для формата управления пакетами по умолчанию установлено значение PackageReference.
  2. В Visual Studio щелкните правой кнопкой мыши проект в обозревателе решений и выберите элемент Управление пакетами NuGet.

  3. В окне диспетчера пакетов NuGet выберите вкладку Обзор и найдите Microsoft.Windows.SDK.Contracts.

  4. Когда пакет Microsoft.Windows.SDK.Contracts будет найден, выберите на правой панели диспетчера пакетов NuGet нужную версию пакета в зависимости от версии Windows 10, на которую нужно ориентировать приложение:

    • 10.0.19041.xxxx — вариант для Windows 10 версии 2004;
    • 10.0.18362.xxxx — вариант для Windows 10 версии 1903;
    • 10.0.17763.xxxx — вариант для Windows 10 версии 1809;
    • 10.0.17134.xxxx — вариант для Windows 10 версии 1803.
  5. Нажмите кнопку Установить.

Настройка проектов, предназначенных для нескольких версий .NET

Если проект предназначен одновременно для .NET 6 (или более поздней версии) и более ранних версий (включая .NET Core 3.x и .NET Framework), то можно настроить в файле проекта использование моникера целевой платформы (TFM) для автоматического извлечения ссылок на API WinRT для .NET 6 (или более поздней версии) и применять пакет NuGet Microsoft.Windows.SDK.Contracts для более ранних версий.

  1. В Visual Studio щелкните правой кнопкой мыши проект в Обозревателе решений и выберите Изменить файл проекта. В следующем примере показан файл проекта для приложения, использующего .NET Core 3.1.

    Примечание

    В приведенном ниже примере показан параметр OutputType средства WinExe, который указывает на исполняемый файл графического пользовательского интерфейса Windows (и не позволяет открыть окно консоли при запуске приложения). Если в вашем приложении нет графического пользовательского интерфейса, значение OutputType будет другим. API-интерфейсы WinRT можно вызывать из приложений с графическим пользовательским интерфейсом, консольных приложений и библиотек для Windows. Кроме того, значение TargetFramework может не совпадать с примером ниже.

    <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>netcoreapp3.1</TargetFramework>
        <UseWindowsForms>true</UseWindowsForms>
      </PropertyGroup>
    </Project>
    
  2. В файле замените элемент TargetFramework элементом TargetFrameworks (обратите внимание на множественное число). В этом элементе укажите моникеры целевой платформы (TFM) для всех версий .NET, которые вы хотите использовать, разделив их точкой с запятой.

    • Для .NET 6 или более поздней версии используйте один из следующих моникеров целевой платформы (TFM):
      • net6.0-windows10.0.17763.0 — если приложение предназначено для Windows 10, версия 1809.
      • net6.0-windows10.0.18362.0 — если приложение предназначено для Windows 10, версия 1903.
      • net6.0-windows10.0.19041.0 — если приложение предназначено для Windows 10, версия 2004.
    • Для .NET Core 3.x используйте netcoreapp3.0 или netcoreapp3.1.
    • Для .NET Framework используйте net46.

    В следующем примере показано, как настроить проект для использования одновременно .NET Core 3.1 и .NET 6 (для Windows 10 версии 2004).

    <TargetFrameworks>netcoreapp3.1;net6.0-windows10.0.19041.0</TargetFrameworks>
    
  3. После элемента PropertyGroup добавьте элемент PackageReference, включающий условный оператор, который отвечает за установку пакета NuGet Microsoft.Windows.SDK.Contracts для всех версий .NET Core 3.x или .NET Framework, для которых предназначено приложение. Элемент PackageReference должен быть дочерним по отношению к элементу ItemGroup. В приведенном ниже примере показано, как это сделать для .NET Core 3.1.

    <ItemGroup>
      <PackageReference Condition="'$(TargetFramework)' == 'netcoreapp3.1'"
                        Include="Microsoft.Windows.SDK.Contracts"
                        Version="10.0.19041.0" />
    </ItemGroup>
    

    По завершении файл проекта должен выглядеть примерно так.

    <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFrameworks>netcoreapp3.1;net6.0-windows10.0.19041.0</TargetFrameworks>
        <UseWPF>true</UseWPF>
      </PropertyGroup>
      <ItemGroup>
        <PackageReference Condition="'$(TargetFramework)' == 'netcoreapp3.1'"
                         Include="Microsoft.Windows.SDK.Contracts"
                         Version="10.0.19041.0" />
      </ItemGroup>
    </Project>
    
  4. Сохраните изменения и закройте файл проекта.

Настройка проекта классических приложений C++ (Win32) для использования API среды выполнения Windows

Воспользоваться API-интерфейсами WinRT позволяет библиотека C++/WinRT. C++/WinRT — это полностью стандартная современная проекция языка C++17 для API-интерфейсов WinRT, реализованная как библиотека на основе файлов заголовков и предназначенная для предоставления первоклассного доступа к современным API Windows.

Чтобы настроить C++/WinRT для проекта, сделайте следующее:

  • Для новых проектов можно установить расширение C++/WinRT Visual Studio (VSIX) и использовать один из шаблонов проектов C++/WinRT, входящих в это расширение.
  • Для существующих проектов можно установить в проект пакет NuGet Microsoft.Windows.CppWinRT.

Дополнительные сведения об этих вариантах см. в разделе Поддержка Visual Studio для C++/WinRT и VSIX .

Добавление возможностей Windows 10

Теперь все готово для добавления современных видов взаимодействия, которые активируются, когда пользователь запускает ваше приложение в Windows 10. Используйте этот процесс разработки.

Сначала определите, какие возможности нужно добавить

Выбор огромен. Например, можно упростить оформление покупки с помощью API монетизации либо привлечь внимание к приложению, когда вы можете предложить что-либо интересное, например новую фотографию, добавленную другим пользователем.

Toast notification

Даже если пользователи не обращают внимания на ваше сообщение или закрывают его, они могут снова увидеть его в центре уведомлений, а затем щелкнуть сообщение, чтобы открыть приложение. Это позволяет увеличить объем взаимодействия с приложением и обеспечивает дополнительное преимущество в виде тесной интеграции вашего приложения с операционной системой. Мы предложим код для реализации этой возможности чуть позже в этой статье.

Дополнительные сведения см. в документации по UWP.

Выберите путь улучшения: дополнить или расширить

Вам часто будут встречаться термины дополнить и расширить, поэтому сейчас мы поясним, какой именно смысл вкладываем в них.

Термин дополнить относится к API-интерфейсам WinRT, которые вы можете вызывать напрямую из классического приложения (независимо от того, применяете ли вы для приложения упаковку в пакет MSIX). При выборе новой возможности Windows 10 определите API-интерфейсы, которые вам необходимы для ее реализации, а затем проверьте, входит ли нужный API в этот список. Это список API-интерфейсов, которые можно вызывать непосредственно из классического приложения. Если выбранный API не входит в этот список, значит, связанные с этим API функции могут работать только в рамках процесса UWP. Сюда часто входят API-интерфейсы, которые отображают элементы XAML UWP, например элемент управления картой UWP или запрос безопасности Windows Hello.

Примечание

API, которые отображают XAML UWP, обычно нельзя напрямую вызывать из классического приложений. Но иногда для них доступны альтернативные подходы. Если вы хотите разместить элементы управления XAML UWP или другие пользовательские визуальные объекты, попробуйте применить XAML Islands (доступны в Windows 10 с версии 1903) и (или) визуальный уровень (доступен в Windows 10 с версии 1803). Эти возможности можно использовать в упакованных и неупакованных классических приложениях.

Если вы решили упаковать классическое приложение в пакет MSIX, у вас есть возможность расширить это приложение, добавив к нему проект UWP. Проект классического приложения по-прежнему остается точкой входа для вашего приложения. Но проект UWP предоставляет доступ ко всем API-интерфейсам, которые не входят в этот список. Классические приложения могут взаимодействовать с процессами UWP с помощью службы приложений. У нас есть множество рекомендаций по настройке этой функции. Если вы хотите добавить возможность, для которой требуется проект UWP, см. раздел Расширение возможностей с помощью компонентов UWP.

Добавьте ссылки на контракты API

Если вы можете вызывать API непосредственно из классического приложения, откройте браузер и найдите справочный раздел для этого API-интерфейса. Под общей информацией об API там представлена таблица с описанием контракта для этого API. Пример такой таблицы приведен ниже.

API contract table

Если у вас есть классическое приложение на основе .NET, добавьте ссылку на этот контракт API и присвойте свойству Копировать локально этого файла значение False. Если у вас есть проект на основе C++, добавьте в раздел Дополнительные каталоги включаемых файлов путь к папке, содержащей этот контракт.

Вызовите API для добавления возможности

Ниже приведен код для отображения окна уведомлений, которое мы уже видели ранее. Эти API-интерфейсы внесены в этот список, а значит, вы можете добавить этот код в классическое приложение и запустить его прямо сейчас.

using Windows.Foundation;
using Windows.System;
using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
...

private void ShowToast()
{
    string title = "featured picture of the day";
    string content = "beautiful scenery";
    string image = "https://picsum.photos/360/180?image=104";
    string logo = "https://picsum.photos/64?image=883";

    string xmlString =
    $@"<toast><visual>
       <binding template='ToastGeneric'>
       <text>{title}</text>
       <text>{content}</text>
       <image src='{image}'/>
       <image src='{logo}' placement='appLogoOverride' hint-crop='circle'/>
       </binding>
      </visual></toast>";

    XmlDocument toastXml = new XmlDocument();
    toastXml.LoadXml(xmlString);

    ToastNotification toast = new ToastNotification(toastXml);

    ToastNotificationManager.CreateToastNotifier().Show(toast);
}
#include <sstream>
#include <winrt/Windows.Data.Xml.Dom.h>
#include <winrt/Windows.UI.Notifications.h>

using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::System;
using namespace winrt::Windows::UI::Notifications;
using namespace winrt::Windows::Data::Xml::Dom;

void UWP::ShowToast()
{
    std::wstring const title = L"featured picture of the day";
    std::wstring const content = L"beautiful scenery";
    std::wstring const image = L"https://picsum.photos/360/180?image=104";
    std::wstring const logo = L"https://picsum.photos/64?image=883";

    std::wostringstream xmlString;
    xmlString << L"<toast><visual><binding template='ToastGeneric'>" <<
        L"<text>" << title << L"</text>" <<
        L"<text>" << content << L"</text>" <<
        L"<image src='" << image << L"'/>" <<
        L"<image src='" << logo << L"'" <<
        L" placement='appLogoOverride' hint-crop='circle'/>" <<
        L"</binding></visual></toast>";

    XmlDocument toastXml;

    toastXml.LoadXml(xmlString.str().c_str());

    ToastNotificationManager::CreateToastNotifier().Show(ToastNotification(toastXml));
}
using namespace Windows::Foundation;
using namespace Windows::System;
using namespace Windows::UI::Notifications;
using namespace Windows::Data::Xml::Dom;

void UWP::ShowToast()
{
	Platform::String ^title = "featured picture of the day";
	Platform::String ^content = "beautiful scenery";
	Platform::String ^image = "https://picsum.photos/360/180?image=104";
	Platform::String ^logo = "https://picsum.photos/64?image=883";

	Platform::String ^xmlString =
		L"<toast><visual><binding template='ToastGeneric'>" +
		L"<text>" + title + "</text>" +
		L"<text>"+ content + "</text>" +
		L"<image src='" + image + "'/>" +
		L"<image src='" + logo + "'" +
		L" placement='appLogoOverride' hint-crop='circle'/>" +
		L"</binding></visual></toast>";

	XmlDocument ^toastXml = ref new XmlDocument();

	toastXml->LoadXml(xmlString);

	ToastNotificationManager::CreateToastNotifier()->Show(ref new ToastNotification(toastXml));
}

Дополнительные сведения см. в статье об адаптивных и интерактивных всплывающих уведомлениях.

Поддержка установочных баз Windows XP, Windows Vista, Windows 7 и Windows 8

Вы можете модернизировать приложение для Windows 10, не создавая новую ветвь и не поддерживая раздельные базы кода.

Если вы хотите создать отдельные двоичные файлы для пользователей Windows 10, используйте условную компиляцию. Если вы предпочитаете создать один набор двоичных файлов, которые развертываются для всех пользователей Windows, используйте проверки во время выполнения.

Давайте вкратце рассмотрим эти варианты.

Условная компиляция

Можно вести одну базу кода и компилировать набор двоичных файлов только для пользователей Windows 10.

Для этого добавьте новую конфигурацию сборки в проект.

Build Configuration

Создайте константу для этой конфигурации сборки, чтобы определить код, который будет вызывать API-интерфейсы WinRT.

Для проектов на основе .NET эта константа называется константой условной компиляции.

Conditional Compilation constant

Для проектов на основе C++ эта константа называется описанием препроцессора.

Preprocessor Definition constant

Добавьте эту константу до всех блоков кода UWP.

[System.Diagnostics.Conditional("_UWP")]
private void ShowToast()
{
 ...
}
#if _UWP
void UWP::ShowToast()
{
 ...
}
#endif

Компилятор выполнит сборку этого кода, только если соответствующая константа определена в активной конфигурации сборки.

Проверки во время выполнения

Можно скомпилировать один набор двоичных файлов для всех пользователей Windows независимо от того, какую версию Windows они используют. В этом случае приложение будет обращаться к API-интерфейсам WinRT, только если пользователь выполняет его как упакованное приложение в среде Windows 10.

Для добавления в код проверок во время выполнения проще всего установить пакет NuGet Desktop Bridge Helpers (Вспомогательные элементы моста для классических приложений), а затем использовать метод IsRunningAsUWP() для отсеивания всего кода, который вызывает API-интерфейсы WinRT. Дополнительные сведения см. в записи блога Мост для классических приложений — определение контекста приложения.

Получение ответов на вопросы

Есть вопросы? Задайте их на Stack Overflow. Наша команда следит за этими тегами. Вы также можете задавать вопросы на наших форумах.