Ответы на часто задаваемые вопросы о C++/WinRT

Ответы на часто возникающие вопросы о разработке и использовании интерфейсов API среды выполнения Windows с помощью C++/WinRT.

Внимание

Заметки о выпуске C++/WinRT см. в разделе Новости и изменения в C++/WinRT 2.0.

Примечание.

Если ваш вопрос связан с сообщением об ошибке, которое вы увидели, обратитесь также к статье Устранение неполадок C++/WinRT.

Где можно найти примеры приложений C++/WinRT?

Как изменить целевую платформу проекта C++/WinRT на более позднюю версию Windows SDK?

См. сведения в разделе How to retarget your C++/WinRT project to a later version of the Windows SDK (Как изменить целевую платформу проекта C++/WinRT на более позднюю версию Windows SDK).

Почему мой новый проект не компилируется после перехода на C++/WinRT 2.0?

Полный набор изменений (включая критические изменения) см. в разделе News, and changes, in C++/WinRT 2.0 (Новости и изменения в C++/WinRT 2.0). Например, если вы используете основанный на диапазоне оператор for в коллекции среды выполнения Windows, то теперь нужно будет добавить #include <winrt/Windows.Foundation.Collections.h>.

Почему мой новый проект не компилируется? Я использую Visual Studio 2017 (версия 15.8.0 или более поздней) и пакет SDK версии 17134

Если вы используете Visual Studio 2017 (версии 15.8.0 или выше) и разрабатываете проект для пакета Windows SDK версии 10.0.17134.0 (Windows 10 версии 1803), то созданный проект C++/WinRT может не скомпилироваться и вызвать ошибку C3861 о том, что идентификатор from_abi не найден и другие ошибки, полученные в base.h. Следует разрабатывать проект с более поздней (лучше соответствующей) версией Windows SDK или задать свойство проекта C/C++>Язык>Режим совместимости: Нет (кроме того, если параметр /permissive- отображается в свойстве проекта C/C++>Командная строка в разделе Дополнительные параметры, удалите его).

Как устранить ошибку сборки The C++/WinRT VSIX no longer provides project build support. Please add a project reference to the Microsoft.Windows.CppWinRT Nuget package (C++/WinRT VSIX больше не обеспечивает поддержку сборки проекта. Добавьте ссылку на пакет Nuget Microsoft.Windows.CppWinRT в проект)?

Установите пакет NuGet Microsoft.Windows.CppWinRT в проект. Дополнительные сведения см. в разделе Earlier versions of the VSIX extension (Более ранние версии расширения VSIX).

Как можно настроить поддержку сборки в пакете NuGet?

Поддержка сборки C++/WinRT (свойства и цели) описана в файле сведений пакета NuGet Microsoft.Windows.CppWinRT.

Каковы требования для расширения Visual Studio (VSIX) C++/WinRT?

Дополнительные сведения о версии 1.0.190128.4 расширения VSIX и более поздних версиях см. в разделе о поддержке C++/WinRT в Visual Studio. Дополнительные сведения о других версиях см. в разделе Earlier versions of the VSIX extension (Более ранние версии расширения VSIX).

Что такое класс среды выполнения?

Класс среды выполнения — это тип, который можно активировать и использовать через современные интерфейсы COM, обычно через границы исполняемого файла. Тем не менее класс среды выполнения может также использоваться в единице компиляции, которая его реализует. Класс среды выполнения объявляется в языке описания интерфейса (IDL) и может реализоваться в стандартной версии C++ с использованием C++/WinRT.

Что означают понятия тип проекции и тип реализации?

Если вы лишь используете класс среды выполнения Windows (класс среды выполнения), то вы имеете дело с типом проекции. C++/WinRT представляет собой языковую проекцию, поэтому типы проекции являются частью среды выполнения Windows, которая проецируется в C++ с помощью C++/WinRT. Дополнительные сведения см. в статье Consume APIs with C++/WinRT (Использование API-интерфейсов с C++/WinRT).

Тип реализации содержит реализацию класса среды выполнения, поэтому он доступен только в проекте, который реализует класс среды выполнения. При работе в проекте, который реализует классы среды выполнения (проекте компонента среды выполнения Windows или проекте, использующем пользовательский интерфейс XAML), очень важно понимать различия между вашим типом реализации класса среды выполнения и типом проекции, представляющим класс среды выполнения, проецируемый в C++/WinRT. Дополнительные сведения см. в разделе Author APIs with C++/WinRT (Создание API-интерфейсов с использованием C++/WinRT).

Нужно ли объявлять конструктор в IDL моего класса среды выполнения?

Только если класс среды выполнения предназначен для использования извне его единицы компиляции (это компонент среды выполнения Windows, предназначенный для общего использования клиентскими приложениями среды выполнения Windows). Дополнительные сведения о назначении и последствиях объявления конструкторов в IDL см. в разделе Runtime class constructors (Конструкторы классов среды выполнения).

Почему в компиляторе появляется ошибка "C3779: consume_Something: функцию, возвращающую "auto", нельзя использовать, предварительно не определив"?

Вы используете объект среда выполнения Windows без включения соответствующего файла заголовка пространства имен. Включите заголовок с именем, соответствующим пространству имен API, и повторно выполните сборку. Дополнительные сведения см. в разделе C++/WinRT projection headers (Заголовки проекции C++/WinRT).

Почему компоновщик дает мне ошибку "LNK2019: неразрешенный внешний символ"?

Если неразрешенный символ — это свободная функция среды выполнения Windows, такая как RoInitialize, необходимо явно включить библиотеку WindowsApp.lib в проект. Проекция C++/WinRT зависит от некоторых из этих свободных (не являющихся членами) функций и точек входа. Если вы используете один из шаблонов проектов Расширение C++/WinRT для Visual Studio (VSIX) для вашего приложения, WindowsApp.lib компонуется автоматически. В противном случае вы можете использовать параметры компоновки проекта, чтобы включить библиотеку, или можете сделать это в исходном коде.

#pragma comment(lib, "windowsapp")

Важно разрешить любые возможные ошибки компоновщика, привязав WindowsApp.lib вместо альтернативной библиотеки статических ссылок, иначе ваше приложение не пройдет тестирование с помощью комплекта сертификации приложений для Windows, используемого Visual Studio и Microsoft Store для проверки отправок (это означает, что ваше приложение не сможет успешно попасть в Microsoft Store).

Если неразрешенный символ является конструктором, возможно, вы забыли включить файл заголовка пространства имен для создаваемого класса. Включите заголовок с именем пространства имен класса и перестройте. Дополнительные сведения см. в разделе C++/WinRT projection headers (Заголовки проекции C++/WinRT).

Почему возникает исключение "Класс не зарегистрирован"?

Может возникнуть ситуация, когда при создании класса среды выполнения или попытке получить доступ к статическому члену отображается исключение в среде выполнения со значением HRESULT для REGDB_E_CLASSNOTREGISTERED.

Одной из причин является сбой при загрузке компонента среды выполнения Windows. Убедитесь, что файл метаданных среды выполнения Windows для компонента (.winmd) имеет то же имя, что и двоичный файл компонента ( .dll), которое также совпадает с именем проекта и именем корневого пространства имен. Кроме того, убедитесь, что метаданные среды выполнения Windows и двоичный файл были правильно скопированы в процессе сборки в папку Appx использующего их приложения. Кроме того, убедитесь, что файл приложения AppxManifest.xml (также расположенный в папке Appx) содержит элемент <InProcessServer>, правильно объявляющий активируемый класс и имя двоичного файла.

Универсальное создание. Эта ошибка может возникнуть, если вы пытаетесь создать локально реализуемый экземпляр класса среды выполнения через любые конструкторы типа проекции (а не через конструктор std::nullptr_t). Чтобы сделать это, вам нужна функция C++/WinRT 2.0, которая часто называется универсальным созданием. Если вы хотите использовать эту функцию, изучите дополнительные сведения и примеры кода в статье о предоставлении согласия на использование универсального создания и прямого обращения к реализации.

Чтобы создать локально реализуемый экземпляр среды выполнения, который не требует универсального создания, воспользуйтесь инструкциями из статьи Элементы управления XAML; привязка к свойству C++/WinRT.

Следует ли реализовывать Windows::Foundation::IClosable и если да, то каким образом?

Если у вас есть класс среды выполнения, который освобождает ресурсы в своем деструкторе, и этот класс среды предназначен для использования извне его единицы компиляции (это компонент среды выполнения Windows, предназначенный для общего использования клиентскими приложениями среды выполнения Windows), то рекомендуется также реализовать IClosable для поддержки применения вашего класса среды выполнения языками, в которых отсутствует детерминированное завершение. Убедитесь, что ресурсы освобождаются при вызове деструктора, IClosable::Close или их обоих. IClosable::Close можно вызывать произвольное число раз.

Нужно ли вызывать IClosable::Close для классов среды выполнения, которые я использую?

IClosable существует для поддержки языков, в которых отсутствует детерминированное завершение. Поэтому в общем случае вам не нужно вызывать IClosable::Close из C++/WinRT. Но есть и некоторые исключения.

  • Бывают очень редкие случаи состязания завершения работы или полувзаимной блокировки, в которых следует вызывать метод IClosable::Close. К примеру, если вы используете типы Windows.UI.Composition, может возникнуть ситуация, в которой вам понадобится удалить объекты в установленной последовательности, а не оставлять задачу их уничтожения программе-оболочке C++/WinRT.
  • Если вы не можете гарантировать, что у вас осталась последняя ссылка на объект (так как она была передана другим API, где может сохраниться), то вызов IClosable::Close — хороший вариант.
  • Если возникают сомнения, можно вызвать метод IClosable::Close вручную, не дожидаясь, пока оболочка вызовет его при удалении.

Итак, если вы знаете, что у вас есть последняя ссылка, вы можете разрешить деструктору оболочки выполнить свою работу. Чтобы выполнить операцию закрытия, прежде чем последняя ссылка исчезнет, необходимо вызвать Close. Чтобы обеспечить защиту от исключений, следует вызвать Close в типе resource-acquisition-is-initialization (RAII), чтобы закрытие выполнялось при очистке. В C++/WinRT нет оболочки unique_close, но вы можете создать собственную.

Можно ли использовать LLVM/Clang для компиляции с C++/WinRT?

Мы не поддерживаем цепочку инструментов LLVM и Clang для C++/WinRT, но мы используем ее для внутренней проверки соответствия стандартам C++/WinRT. Например, если вы хотите имитировать то, что мы делаем внутри, вы можете поэкспериментировать, как описано ниже.

Перейдите на страницу загрузки LLVM, найдите пункт Download LLVM 6.0.0 (Скачать LLVM 6.0.0)>Pre-Built Binaries (Стандартные двоичные файлы) и скачайте Clang для Windows (64-разрядная версия). Во время установки добавьте LLVM в системную переменную PATH, чтобы вызывать LLVM из командной строки. В целях этого эксперимента можно пропустить любые ошибки Failed to find MSBuild toolsets directory (Не удалось найти каталог цепочки инструментов MSBuild) и MSVC integration install failed (Не удалось установить интеграцию MSVC), если они отображаются. Существует множество способов вызова LLVM/Clang. В примере ниже показан только один из них.

C:\ExperimentWithLLVMClang>type main.cpp
// main.cpp
#pragma comment(lib, "windowsapp")
#pragma comment(lib, "ole32")

#include <winrt/Windows.Foundation.h>
#include <stdio.h>
#include <iostream>

using namespace winrt;

int main()
{
    winrt::init_apartment();
    Windows::Foundation::Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    std::wcout << rssFeedUri.Domain().c_str() << std::endl;
}

C:\ExperimentWithLLVMClang>clang-cl main.cpp /EHsc /I ..\.. -Xclang -std=c++17 -Xclang -Wno-delete-non-virtual-dtor -o app.exe

C:\ExperimentWithLLVMClang>app
windows.com

Так как C++/WinRT использует функции стандарта C++ 17, вам потребуются все флаги компилятора, необходимые для обеспечения такой поддержки. Эти флаги в разных компиляторах отличаются.

Visual Studio — это средство разработки, которое мы поддерживаем и рекомендуем для C++/WinRT. См. сведения в разделе о поддержке C++/WinRT в Visual Studio.

Почему у созданной функции реализации для свойства только для чтения нет квалификатора const?

Когда вы объявляете свойство только для чтения в MIDL 3.0, вы можете ожидать, что средство cppwinrt.exe создаст для вас функцию реализации, которая квалифицирована const (константная функция обрабатывает этот указатель как константу).

Мы рекомендуем использовать константу везде, где это возможно, но само средство cppwinrt.exe не пытается выяснить, какие функции реализации могут быть константой, а какие нет. Вы можете сделать любую из ваших функций реализации константной, как в этом примере.

struct MyStringable : winrt::implements<MyStringable, winrt::Windows::Foundation::IStringable>
{
    winrt::hstring ToString() const
    {
        return L"MyStringable";
    }
};

Вы можете удалить этот квалификатор const в ToString, если решите, что вам нужно изменить некоторое состояние объекта в его реализации. При этом сделайте так, чтобы каждая из ваших функций-членов была константной или не константной. Другими словами, не перегружайте функцию реализации в const.

Помимо функций реализации константы используются в проекции функций среды выполнения Windows. Рассмотрим следующий код.

int main()
{
    winrt::Windows::Foundation::IStringable s{ winrt::make<MyStringable>() };
    auto result{ s.ToString() };
}

Чтобы вызвать ToString выше, команда Go To Declaration в Visual Studio показывает, что проекция среды выполнения Windows IStringable::ToString в C++/WinRT выглядит следующим образом.

winrt::hstring ToString() const;

Функции в проекции являются константами, независимо от того, как вы решите их реализовать. На самом деле проекция вызывает двоичный интерфейс приложения (ABI), который представляет собой вызов с помощью указателя интерфейса COM. Единственное состояние, с которым взаимодействует спроецированная функция ToString, — это указатель интерфейса COM. Функция является константой, поэтому нет необходимости изменять этот указатель. Это гарантирует, что функция не изменит ссылку IStringable, с помощью которой вы выполняете вызов, а также гарантирует, что вы можете вызывать ToString даже с константной ссылкой на IStringable.

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

У вас есть рекомендации по уменьшению размера кода для двоичных файлов C++/WinRT?

При работе с объектами среды выполнения Windows следует избегать шаблона кодирования, показанного ниже, так как он может оказать негативное влияние на ваше приложение по причине формирования большего количества двоичного кода, чем это необходимо.

anobject.b().c().d();
anobject.b().c().e();
anobject.b().c().f();

В среде выполнения Windows компилятор не может кэшировать ни значение c(), ни интерфейсы для каждого метода, вызываемого через косвенное обращение ("."). Если не вмешаться в процесс, это приведет к увеличению количества виртуальных вызовов и подсчета ссылок. Приведенный выше шаблон может легко создать вдвое больше кода, чем строго необходимо. Вместо этого по возможности используйте показанный ниже шаблон. Он создает намного меньше кода и может также значительно повысить производительность во время выполнения.

auto a{ anobject.b().c() };
a.d();
a.e();
a.f();

Приведенный выше рекомендуемый шаблон применим не только к C++/WinRT, но и ко всем проекциям языка среды выполнения Windows.

Как превратить строку в тип (например, для навигации)?

В конце примера кода представления навигации (в основном на C#) приведен фрагмент кода C++/WinRT, показывающий, как это можно сделать.

Как устранить неоднозначности с GetCurrentTime и (или) TRY?

Файл заголовка winrt/Windows.UI.Xaml.Media.Animation.h объявляет метод с именем GetCurrentTime, а windows.h при этом определяет (через winbase.h) макрос с именем GetCurrentTime. При возникновении двух ошибок компилятор C++ создает ошибку C4002: слишком много аргументов для вызова макросов GetCurrentTime.

Аналогичным образом winrt/Windows.Globalization.h объявляет метод с именем TRY, а afx.h определяет макрос с именем TRY. При возникновении конфликта компилятор C++ выдает сообщение Error C2334: unexpected token(s) preceding {; skipping apparent function body (Ошибка C2334: непредвиденные лексемы перед {; пропуск вероятного тела функции).

Чтобы исправить одну или обе проблемы, вы можете использовать следующий код.

#pragma push_macro("GetCurrentTime")
#pragma push_macro("TRY")
#undef GetCurrentTime
#undef TRY
#include <winrt/include_your_cppwinrt_headers_here.h>
#include <winrt/include_your_cppwinrt_headers_here.h>
#pragma pop_macro("TRY")
#pragma pop_macro("GetCurrentTime")

Как ускорить загрузку символов?

В Visual Studio выберите Сервис>Параметры>Отладка>Символы>, а затем установите флажок Загрузить только указанные модули. Затем можно щелкнуть библиотеки DLL в списке стека правой кнопкой мыши и загрузить отдельные модули.

Примечание.

Если в этой статье вы не нашли ответы на свои вопросы, можете перейти к сообществу разработчиковVisual Studio C++ или воспользоваться тегом c++-winrt на сайте Stack Overflow.