Реализация IHttpFilter вызывает нарушение прав доступа для вызова HttpClient::GetStringAsync

Я нахожусь в процессе преобразования аутентификации OAuth в IHttpFilter для использования с HTTP-клиент. Я использую следующий код для тестирования, ожидая, что он просто перенаправит все запросы на HttpBaseProtocolFilter:

#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Http.h>
#include <winrt/Windows.Web.Http.Filters.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Http;
using namespace Windows::Web::Http::Filters;

struct TestHttpFilter : implements<TestHttpFilter, IHttpFilter>
{
    TestHttpFilter(IHttpFilter inner_filter) : inner_filter_{ inner_filter } {}
    IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage const& request) const
    {
        auto const result{ co_await inner_filter_.SendRequestAsync(request) };
        co_return result;
    }
private:
    IHttpFilter inner_filter_{ nullptr };
};

int main()
{
    init_apartment();

    IHttpFilter const base_filter{ HttpBaseProtocolFilter{} };
    IHttpFilter const test_filter{ TestHttpFilter{ base_filter } };
    HttpClient const http_client{ test_filter };

    auto const result{ http_client.GetStringAsync({ L"http://aka.ms/cppwinrt" }).get() };

    printf("Response: %ls!\n", result.c_str());
}

Цепочка фильтров правильно создана и передана в HttpClient c'tor. При выполнении вызова GetStringAsync происходит сбой кода внутри TestHttpFilter::SendRequestAsync со следующей ошибкой:

Exception thrown at <address> in <app>.exe: 0xC0000005: Access violation reading location 0x0000000000000000.

Это выглядит как разыменование нулевого указателя в вызове put_abi внутри заголовочного файла SDK ‹Windows.Web.Http.Filters.h› (пространства имен опущены для краткости):

template <typename D> IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> consume_Windows_Web_Http_Filters_IHttpFilter<D>::SendRequestAsync(HttpRequestMessage const& request) const
{
    IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> operation{ nullptr };
    check_hresult(WINRT_SHIM(IHttpFilter)->SendRequestAsync(get_abi(request), put_abi(operation)));
    return operation;
}

Я не понимаю, где я ошибся с этим. Я надеюсь добраться до сути этого с ответами на следующие вопросы:

  • Что-то не так с реализацией TestHttpFilter?
  • Можно ли использовать HTTP-фильтры с удобными реализациями HttpClient (например, GetStringAsync) или при использовании фильтров необходимо использовать интерфейс SendRequestAsync?
  • Крайне маловероятно, но может ли это быть проблемой сгенерированных заголовков SDK, выбирающих неподходящий c'tor для локального объекта operation?

Я могу воспроизвести проблему, используя официальную версию Windows SDK 10.0.17134.0.


person IInspectable    schedule 23.05.2018    source источник
comment
Вам нужно использовать шаблон функции make‹T›(), чтобы убедиться, что ваша реализация (например, TestHttpFilter) выделена в куче. Шаблон функции make вернет спроецированный тип.   -  person Kenny Kerr    schedule 24.05.2018
comment
@KennyKerr: Это исправило, спасибо! Хотя я до сих пор не совсем уверен, почему. Предположительно, хранилище распределения (куча или стек) — это просто побочный эффект использования шаблона make<>(), а не часть фактического исправления. В конце концов, времени жизни объекта в примере кода достаточно. Я думаю, дело в том, что make<>() возвращает спроецированный тип из типа реализации, который используется типом WinRT (в данном случае HttpClient). Можете ли вы пояснить это для меня, если вы не найдете время, чтобы ответить самостоятельно, чтобы я мог завершить этот вопрос и ответ?   -  person IInspectable    schedule 24.05.2018
comment
Реализация должна быть размещена в куче. Это более тонко (как вы уже догадались), но это простой и безопасный способ получить правильную реализацию без каких-либо ухищрений. Я говорю об этом здесь youtu.be/nOFNc2uTmGs?t=1h3m29s и кратко здесь kennykerr.ca/2016/11/09/cppwinrt-working-with-implementations< /а>. Различные вспомогательные функции также начинают документироваться здесь docs .microsoft.com/en-us/uwp/cpp-ref-for-winrt/winrt Я планирую сделать это немного сложнее, чтобы сделать это неправильно в идеале во время компиляции, но я хочу быть осторожным, чтобы не ограничивать ваши варианты.   -  person Kenny Kerr    schedule 24.05.2018
comment
@KennyKerr: Это было полезно, спасибо. На краткий исторический момент я был уверен, что уловил связь между типами реализации и проецируемыми типами. Только для того, чтобы понять, почему шаблон основного приложения вызывает CoreApplication::Run(App()). С тем, что я узнал, я ожидал увидеть CoreApplication::Run(make<App>()) вместо этого. Во всяком случае, я составил ответ на этот вопрос в надежде, что он хотя бы приблизительно правильный. Тем не менее, обратная связь была бы очень признательна.   -  person IInspectable    schedule 25.05.2018
comment
Эти образцы неверны. :) Они работают из-за жизненного совпадения, но они также должны использовать make‹App›(). Причина, по которой эти примеры написаны таким образом, заключается в том, что я написал их давным-давно, еще до того, как они стали называться C++/WinRT. Самая ранняя версия проекции пыталась сделать так, чтобы класс реализации также выглядел выделенным в стеке, но это было не очень эффективно.   -  person Kenny Kerr    schedule 25.05.2018


Ответы (1)


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

Это различие становится важным, когда вы создаете класс среды выполнения, который должен использоваться API среды выполнения Windows. Рассматриваемый код реализует тип, но не может создать соответствующий спроецированный тип. Ошибка в следующих строках кода:

IHttpFilter const test_filter{ TestHttpFilter{ base_filter } };
HttpClient const http_client{ test_filter };

Первая строка создает экземпляр типа реализации. Вторая строка передает его в c'tor HttpClient, который ожидает проецируемый тип2. Исправление состоит в том, чтобы вместо этого создать спроецированный тип для TestHttpFilter, что удобно сделать с помощью создать шаблон функции:

IHttpFilter const test_filter{ make<TestHttpFilter>(base_filter) };
HttpClient const http_client{ test_filter };

Общий совет:

  • Всегда убедитесь, что вы понимаете, с каким типом вы работаете. Установите соглашения об именах, чтобы различать типы реализации и проецируемые типы.
  • При пересечении ABI (обычно) требуется пройти проецируемый тип. Обычно вы пересекаете ABI при вызове API среды выполнения Windows или при возврате значения из типа реализации.
  • Что бы вы ни делали, не допускайте сбоя во время выполнения. Скорее всего, в этой вселенной есть не более одного человека, который может помочь вам, когда вы туда доберетесь.

1 См. Что означают проецируемый тип и тип реализации?

2 В идеале это должно не компилироваться, хотя я не знаю, как это реализовать без (неоправданного) ограничения универсальности библиотеки общего назначения.

person IInspectable    schedule 25.05.2018