Microsoft Azure

CyberNanny: удаленный доступ через распределенные компоненты

Эйнджел Эрнандез Матос

Продукты и технологии:

Microsoft Azure, Kinect, C++, ASP.NET

В статье рассматриваются:

  • общая архитектура решения;
  • архитектура Kinect;
  • локально развертываемые неуправляемые компоненты;
  • размещаемые в облаке управляемые компоненты.

Исходный код можно скачать по ссылке.

Эта статья о приложении под названием CyberNanny, которое я недавно создал, чтобы видеть свою дочку, Миранду, из любого места в любое время. Приложение написано на Visual C++ (MFC) и объединяет в себе такие разные технологии, как Kinect и его SDK, Microsoft Azure, веб-сервисы и автоматизация Office через Outlook. Проект размещен на CodePlex (cybernanny.codeplex.com), где можно посмотреть его код или внести в него свой вклад.

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

C++ был — и остается — рабочей лошадкой во многих компаниях, разрабатывающих ПО. Стандарт C++ 11 выводит этот язык на новый уровень. Его можно описать тремя прилагательными: современный, элегантный и крайне быстрый. Кроме того, MFC все еще используется, и Microsoft обновляет ее при каждом выпуске очередной версии своего компилятора Visual C++.

Технология Kinect великолепна, если не сказать больше; она меняет то, как мы взаимодействуем с играми и компьютерами. А благодаря тому, что Microsoft снабдила разработчиков Kinect SDK, перед ними открылся мир новых возможностей в создании приложений, требующих взаимодействия с человеком. Любопытно, что Kinect SDK основан на COM (равно как и новая модель программирования в Windows 8, названная Windows Runtime и часто сокращаемая до WinRT). Этот SDK также доступен в языках Microsoft .NET Framework.

Одно из требований к CyberNanny заключалось в надежной доставке сообщений через очередь с высокой доступностью, и Microsoft Azure реализует это требование.

Microsoft Azure — это предложение Microsoft Platform as a Service (PaaS), которое существует уже несколько лет. Эта платформа предоставляет множество сервисов, позволяющих создавать решения поверх них (например, Compute и Storage). Одно из требований к CyberNanny заключалось в надежной доставке сообщений через очередь с высокой доступностью, и Microsoft Azure реализует это требование.

Использование веб-сервисов из неуправляемого кода возможно через Windows Web Services API (WWSAPI), введенный в Windows 7. У меня есть публикация в блоге (bit.ly/LiygQY), где я описал WPF-приложение, реализующее неуправляемый компонент, который использует WWSAPI. Важно упомянуть, что WWSAPI встроен в ОС, поэтому нет нужды что-либо скачивать или устанавливать, кроме Windows SDK (чтобы получить заголовочные и lib-файлы).

Зачем заново изобретать колесо? Одним из требований к CyberNanny была возможность отправлять сообщения по электронной почте с прикрепленными картинками, поэтому, чтобы не писать собственный класс для обработки электронной почты, я предпочел для этой задачи задействовать функциональность Outlook. Это позволило мне сосредоточиться на главной цели: создании распределенного приложения для присмотра за моей дочкой.

Эта статья состоит из четырех главных разделов:

  • обзор общей архитектуры решения;
  • архитектура Kinect;
  • локально развертываемые компоненты (неуправляемые);
  • размещаемые в облаке компоненты (управляемые).

Обзор общей архитектуры решения

Концепция CyberNanny проста (рис. 1), но в ней есть некоторые движущиеся части. Ее можно кратко описать как толстый клиент, написанный на Visual C++, который захватывает кадры через датчик Kinect. Эти кадры потом можно использовать как снимок, прикрепляемый к новому сообщению электронной почты, формируемому в Outlook через механизм автоматизации. Приложение уведомляется о необработанных запросах следующим образом: по таймеру порождается новый поток, который опрашивает очередь, размещенную в Microsoft Azure. Запросы ставятся в очередь через веб-страницу ASP.NET.

Архитектура CyberNanny
Рис. 1. Архитектура CyberNanny

Kinect for Xbox 360 Kinect for Xbox 360
Capture Frames Захват кадров
Polling Queue Опрос очереди
Microsoft Azure Web Role (2 Cores) WCF Service Queue Microsoft Azure
Веб-роль (2 узла)
Очередь WCF-сервиса
Smartphone Смартфон
Request Запрос
Tablet Планшет
Happy Dad Счастливый папочка
Laptop Лэптоп
Windows 7 Laptop Лэптоп с Windows 7
If there's a request, take picture, compose e-mail, attach picture and send Если есть запрос, делаем снимок, формируем почтовое сообщение, прикрепляем снимок и отправляем
Send an E-mail Отправка электронной почты

Для запуска и тестирования этого решения у вас должны быть:

  • датчик Kinect (я использовал версию для Xbox 360);
  • подписка Microsoft Azure;
  • Kinect SDK.

Архитектура Kinect

Хорошее понимание того, как устроены вещи и как их можно реализовать, крайне важно в любом проекте разработки, и случай с Kinect — не исключение. Microsoft предоставила SDK для разработчиков, использующих как управляемый, так и неуправляемый код. Я опишу архитектуру Kinect, показанную на рис. 2.

Архитектура Kinect for Windows
Рис. 2. Архитектура Kinect for Windows

Applications Приложения
NUI API NUI API
Windows Core Audio and Speech APIs Windows Core Audio API и Speech API
DMO Codec for Mic Array DMO-кодек для микрофонной решетки
Device Setup Настройка устройства
Device  Access Доступ к устройству
Video Stream Control Управление видеопотоком
Audio Stream Control Управление аудиопотоком
User Mode Пользовательский режим
WinUSB Device Stack Стек устройств WinUSB
WinUSB Camera Stack Стек камер WinUSB
USBAudio Audio Stack Стек аудиоустройств USBAudio
Kernel Mode Режим ядра
Kernel-Mode Drivers for Kinect for Windows Драйверы режима ядра для Kinect for Windows
USB Hub USB-концентратор
Hardware Аппаратный уровень
Motor Мотор
Cameras Камеры
Audio Mic Array Микрофонная решетка
Kinect Sensor Датчик Kinect
Kinect for Windows SDK Kinect for Windows SDK
Windows Components Windows-компоненты
User-Created Components Компоненты, создаваемые пользователем

Цифры в кружках на рис. 2 соответствуют следующему.

  1. Аппаратная часть Kinect Аппаратный компоненты, включая Kinect и USB-концентратор, через который этот датчик подключается к компьютеру.

  2. Драйверы Kinect Windows-драйверы для Kinect, устанавливаемые в процессе установки SDK, как описывается в этой статье. Драйверы Kinect поддерживают:

    • микрофонную решетку (microphone array) Kinect как аудиоустройство режима ядра, к которому можно обращаться через стандартные аудио-API в Windows;
    • управление потоковыми аудио и видео (цвет, глубина и скелет);
    • функции перечисления устройств, позволяющие приложению использовать более одного датчика Kinect.

    Хорошее понимание того, как устроены вещи и как их можно реализовать, крайне важно в любом проекте разработки, и случай с Kinect — не исключение.

  3. Аудио- и видеокомпоненты Kinect Natural User Interface (NUI) для отслеживания скелета, аудио, цвета и глубины изображения.

  4. DirectX Media Object (DMO) Необходим для формирования диаграммы направленности микрофонной решетки и локализации источника звука.

  5. Стандартные API в Windows 7 API-интерфейсы для звука, речи и медиа в Windows 7, описанные в Windows 7 SDK и Microsoft Speech SDK.

Я продемонстрирую, как я использовал видеокомпонент для захвата кадров, которые потом сохранялись как JPEG-файлы для отправки по электронной почте. Рендеринг захваченных кадров осуществляется средствами Direct2D.

Класс Nui_Core Я написал класс Nui_Core, который инкапсулирует функциональность, нужную мне от датчика Kinect. В приложении создается только один экземпляр этого класса. Приложение взаимодействует с датчиком через член типа INuiSensor, представляющий физическое устройство, подключенное к компьютеру. Важно помнить, что Kinect SDK основан на COM, поэтому вышеупомянутый интерфейс — равно как и все остальные COM-интерфейсы, широко применяемые в этом приложении, — управляются смарт-указателями (например, CComPtr<INuiSensor> m_pSensor;).

Захват кадров с датчика осуществляется следующим образом.

  1. Проверяем, доступен ли датчик; для этого вызываем NuiGetSensorCount.
  2. Создаем экземпляр датчика Kinect вызовом NuiCreateSensorByIndex.
  3. Создаем объект фабрики вызовом D2D1CreateFactory (для создания Direct2D-ресурсов).
  4. Создаем события для каждого потока данных (stream), необходимого приложению.
  5. Открываем потоки данных вызовом NuiImageStreamOpen.
  6. Обрабатываем захваченные данные (кадр).

Подготовив экземпляр Nui_Core, вы можете легко сделать снимок по требованию, вызвав метод TakePicture, показанный на рис. 3.

Рис. 3. Метод TakePicture

void Nui_Core::TakePicture(std::shared_ptr<BYTE>& imageBytes, int& bytesCount) {
  byte *bytes;
  NUI_IMAGE_FRAME imageFrame;
  NUI_LOCKED_RECT LockedRect;
  if (SUCCEEDED(m_pSensor->NuiImageStreamGetNextFrame(m_hVideoStream,
    m_millisecondsToWait, &imageFrame))) {
    auto pTexture = imageFrame.pFrameTexture;
    pTexture->LockRect(0, &LockedRect, NULL, 0);
    if (LockedRect.Pitch != 0) {
      bytes = static_cast<BYTE *>(LockedRect.pBits);
      m_pDrawColor->Draw(bytes, LockedRect.size);
    }
    pTexture->UnlockRect(0);
    imageBytes.reset(new BYTE[LockedRect.size]);
    memcpy(imageBytes.get(), bytes, LockedRect.size);
    bytesCount = LockedRect.size;
    m_pSensor->NuiImageStreamReleaseFrame(m_hVideoStream, &imageFrame);
  }
}

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

Не забывайте по окончании работы с датчиком выключать его вызовом NuiShutdown; кроме того, нужно освобождать описатели, использовавшиеся вами.

Класс DrawDevice Как упоминалось ранее, средства рендеринга предоставляются Direct2D; вот почему для использования Nui_Core требуется другой вспомогательный класс. Этот класс отвечает за обеспечение доступности ресурсов для захваченного кадра, в данном случае — битовой карты.

Три основных метода класса DrawDevice: Initialize, Draw и EnsureResources. Я опишу каждый из них.

Initialize Отвечает за настройку трех членов типа DrawDevice. В приложении имеется элемент управления «вкладки» с тремя вкладками, поэтому для каждой из них предусмотрен свой член (определяют режимы просмотра Color, Skeletal и Depth). Каждая вкладка — это окно, в котором визуализируется соответствующий кадр. InitializeColorView, показанный в следующем коде, — хороший пример вызова метода Initialize:

bool Nui_Core::InitializeColorView() {
  auto width = m_rect.Width();
  auto height = m_rect.Height();
  m_pDrawColor = std::shared_ptr<DrawDevice>(new DrawDevice());
  return (m_pDrawColor.get()->Initialize(m_views[TAB_VIEW_1]->m_hWnd,
  m_pD2DFactory.p, 640, 320, NULL));
}

Draw Визуализирует кадр на соответствующей вкладке. Принимает аргумент типа Byte*, захваченный датчиком. Как и в видеороликах, эффект анимации достигается последовательной визуализацией статических кадров.

EnsureResources Отвечает за создание битовой карты по запросу метода Draw.

Локально развертываемые компоненты (неуправляемые)

Проект CyberNanny состоит из следующих компонентов.

  • Приложение:
    • CCyberNannyApp (наследуется от CWinApp). В приложении один член типа Nui_Core для взаимодействия с датчиком.
  • UI-элементы:
    • CCyberNannyDlg (основное окно, наследуется от CDialogEx);
    • CAboutDlg (диалог About, наследуется от CDialogEx).
  • Клиент веб-сервиса:
    • файлы, автоматически генерируемые после выполнения WSUTIL применительно к сервису, имеют формат Web Services Description Language (WSDL). Эти файлы содержат сообщения, структуры и методы, предоставляемые веб-сервисом на основе WCF.
  • Объекты Outlook:
    • чтобы манипулировать какими-либо объектами Outlook, вы должны импортировать их в свой проект, выбрав Add MFC Class в окне ActiveX Control Wizard. Объекты, используемые в этом приложении, — Application, Attachment, Mail-Item и Namespace.
  • Прокси:
    • это собственный класс, который инкапсулирует процедуру создания объектов, нужных для взаимодействия с WWSAPI.
  • Вспомогательные классы:
    • используются для поддержки такой функциональности приложения, как преобразование битовой карты в JPEG для уменьшения размера файла, предоставление оболочки для отправки сообщений по электронной почте и взаимодействия с Outlook и др.

При запуске приложения происходят следующие события.

  1. Вызовом RegisterWindowMessage определяется новое оконное сообщение. Это необходимо для добавления элементов в список событий при обработке запросов. Дело в том, что вы не можете напрямую модифицировать UI-элементы из любого потока, отличного от UI-потока, — иначе вы получите исключение, связанное с недопустимым вызовом между потоками. Все это управляется MFC-инфраструктурой обработки сообщений.
  2. Вы инициализируете член Nui_Core и настраиваете пару таймеров (один из них обновляет текущее время в строке состояния, а другой запускает поток для опроса очереди, чтобы проверить наличие в ней необработанного запроса).
  3. Датчик Kinect начинает захват кадров, но приложение не получает снимок, если в очереди нет запроса. Метод ProcessRequest отвечает за получение снимка, его сериализацию на диск, запись в средство просмотра событий и запуск механизма автоматизации Outlook, как показано на рис. 4.

Рис. 4. Вызов метода ProcessRequest

void CCyberNannyDlg::ProcessRequest(_request request) {
  if (!request.IsEmpty) {
    auto byteCount = 0;
    ImageFile imageFile;
    std::shared_ptr<BYTE> bytes;
    m_Kinect.TakePicture(bytes, byteCount);
    imageFile.SerializeImage(bytes, byteCount);
    EventLogHelper::LogRequest(request);
    m_emailer.ComposeAndSend(request.EmailRecipient,
    imageFile.ImageFilePath_get());
    imageFile.DeleteFile();
  }
}

Кадр, изначально захваченный Kinect, является битовой картой, которая занимает примерно 1,7 Мб (что неудобно для отправки по почте, а значит, битовую карту нужно конвертировать в JPEG-изображение). Кроме того, она перевернута, поэтому требуется поворот изображения на 180°. С этой целью выполняется пара вызовов GDI+. Эта функциональность инкапсулирована в класс ImageFile.

Класс ImageFile служит оболочкой для операций с GDI+. Два основных метода этого класса представляют собой следующее.

  1. SerializeImage Этот метод принимает shared_ptr<BYTE>, который содержит байты захваченного кадра, подлежащие сериализации в изображение, а также счетчик байтов. Кроме того, изображение поворачивается вызовом метода RotateFlip.
  2. GetEncoderClsid Как упоминалось, файл изображения слишком велик для использования в качестве вложения в почтовом сообщении, поэтому его нужно кодировать в более компактный формат (JPEG, например). GDI+ предоставляет функцию GetImageEncoders, позволяющую выяснить, какие конвертеры доступны в системе.

До сих пор я рассматривал, как приложение использует датчик Kinect и как из захваченных кадров создается картинка, отправляемая по электронной почте. Теперь я покажу, как вызывать WCF-сервис, размещенный в Microsoft Azure.

Кадр, изначально захваченный Kinect, является битовой картой, которая занимает примерно 1,7 Мб (что неудобно для отправки по почте, а значит, битовую карту нужно конвертировать в JPEG-изображение). Кроме того, она перевернута, поэтому требуется поворот изображения на 180°.

WWSAPI, введенный в Windows 7, позволяет разработчикам, использующим неуправляемый код, легко работать с веб- или WCF-сервисами, не беспокоясь о деталях коммуникационного взаимодействия (сокетах). Первый шаг в использовании сервиса — получение WSDL, который применяется с WSUTIL и в свою очередь генерирует код на C для прокси сервиса, который представляет собой структуры данных, необходимые сервису. Существует альтернатива под названием Casablanca (bit.ly/JLletJ) — она поддерживает клиент-серверное взаимодействие в облаке для неуправляемого кода, но ее не было на момент написания CyberNanny.

Как правило, полученный WSDL сохраняется на диске, а затем WSDL-файл вместе со связанными файлами схемы используется как ввод для WSUTIL. Учитывайте один аспект в работе со схемами. Их нужно скачивать наряду с WSDL, а иначе WSUTIL будет жаловаться в процессе генерации файлов. Определить нужные схемы можно, проверив параметр .xsd в разделе схемы в WSDL-файле:

wsutil /wsdl:cybernanny.wsdl /xsd:cybernanny0.xsd
  cybernanny1.xsd cybernanny2.xsd cybernanny3.xsd
  /string:WS_STRING

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

Конечные файлы можно добавить в решение, а затем вызывать сервис через эти файлы. Для работы с WWSAPI требуются четыре основных объекта:

  1. WS_HEAP;
  2. WS_ERROR;
  3. WS_SERVICE_PROXY;
  4. WS_CHANNEL_PROPERTY.

Эти объекты обеспечивают взаимодействие между клиентом и сервисом. Функциональность вызова сервиса я поместил в класс Proxy.

Большинство WWSAPI-функций возвращает HRESULT, поэтому отладка и устранение ошибок могут оказаться весьма трудной задачей. Но не бойтесь: вы можете включить трассировку из Windows Event Viewer и увидеть точную причину сбоя данной функции. Чтобы включить трассировку, выберите Applications and Services Logs | Microsoft | WebServices | Tracing (щелкните правой кнопкой мыши для ее включения).

Это в основном исчерпывает описание неуправляемых компонентов решения. Более подробную информацию можно извлечь из исходного кода, размещенного на сайте CodePlex. Следующий раздел посвящен последнему компоненту решения — Microsoft Azure.

Размещаемые в облаке компоненты (управляемые)

Пожалуйста, обратите внимание, что это не учебное пособие по Microsoft Azure, а просто описание компонентов Microsoft Azure, применяемых в CyberNanny. За более глубокой и детальной информацией обращайтесь на веб-сайт Microsoft Azure windowsazure.com. Платформа Microsoft Azure (рис. 5) включает следующие сервисы:

  • Microsoft Azure Compute;
  • Microsoft Azure Storage;
  • Microsoft Azure SQL Database;
  • Microsoft Azure AppFabric;
  • Microsoft Azure Marketplace;
  • Microsoft Azure Virtual Network.

Сервисы платформы Microsoft Azure
Рис. 5. Сервисы платформы Microsoft Azure

Cloud Applications Облачные приложения
Web/Worker Roles Веб- и рабочие роли
Business Analytics Бизнес-аналитика
SQL Azure Reporting Отчетность SQL Azure
Networking Сетевые сервисы
Traffic Manager Connect Traffic Manager
Connect
HPC HPC
HPC Scheduler Планировщик HPC
Data Management Управление данными
SQL Azure Tables Blobs SQL Azure
Таблицы
Двоичные объекты
Messaging Обмен сообщениями
Queues Service Bus Очереди
Шина сервисов
Caching Кеширование
In-Memory CDN В памяти
CDN
Commerce Коммерция
Marketplace Marketplace
Identity Идентификация
Microsoft Azure Active Directory Microsoft Azure Active Directory
Visual Studio Visual Studio
Eclipse Eclipse
.NET .NET
Java Java
PHP PHP
Node.js Node.js
SDKs SDK

В CyberNanny имеется только веб-роль, которая занимает два узла, чтобы обеспечить высокую доступность. Если один из узлов выйдет из строя, платформа переключится на работоспособный узел. Веб-роль — это приложение ASP.NET, и оно лишь вставляет элементы сообщений в очередь. Эти сообщения потом поступают от CyberNanny. Кроме того, имеется WCF-сервис, который является частью веб-роли, отвечающей за обработку очереди.

Заметьте, что роль в Microsoft Azure — это индивидуальный компонент, выполняемый в облаке, где каждый экземпляр облака соответствует экземпляру виртуальной машины (VM). В случае CyberNanny я использую две VM.

В CyberNanny имеется веб-роль, представляющая собой веб-приложение (не важно, состоит оно только из ASPX-страниц или является набором WCF-сервисов), которое выполняется в IIS. Она доступна через конечные точки HTTP/HTTPS. Существует и другой тип роли — рабочая (Worker Role). Это приложение фоновой обработки (например, для финансовых расчетов), и оно способно предоставлять внешние (доступные из Интернета) и внутренние конечные точки.

Это приложение также использует очередь, предоставляемую Microsoft Azure Storage, которое обеспечивает надежное хранение и доставку сообщений. Изящество этой очереди в том, что вам не приходится писать никакого специализированного кода для ее использования. От вас не требуется и структуризации хранилища данных так, чтобы оно напоминало очередь, потому что вся эта функциональность обеспечивается самой платформой.

Помимо высокой доступности и масштабируемости, еще одно преимущество платформы Microsoft Azure — унификация в выполнении разнообразных процессов, например разработки, тестирования и развертывания решений на основе Microsoft Azure из Visual Studio, а также применение .NET в качестве общепринятого языка при построении решений.

Это приложение также использует очередь, предоставляемую Microsoft Azure Storage, которое обеспечивает надежное хранение и доставку сообщений.

Есть и некоторые другие интересные функции, которые я хотел бы добавить в CyberNanny, например обнаружение движения и распознавание речи. Если вы хотите использовать это ПО или внести свой вклад в данный проект, пожалуйста, не стесняйтесь. Задействованные технологии теперь вполне доступны, и, хотя они выглядят «разными», они способны взаимодействовать и отлично сочетаются друг с другом.

Удачи в кодировании!


Эйнджел Эрнандез Матос (Angel Hernandez Matos)менеджер в группе Enterprise Applications в Avanade Australia. Живет в Сиднее (Австралия), но родом из Каракаса (Венесуэла). Получал звание Microsoft MVP в течение восьми лет подряд, в настоящее время имеет звание MVP в области Visual C++. Пишет программное обеспечение с 12 лет и считает себя «экзистенциальным гиком».

Выражаю благодарность за рецензирование статьи экспертам Скотту Берри (Scott Berry), Диего Дагуму (Diego Dagum), Йонгви Квону (Yonghwi Kwon) и Нишу Сивакумару (Nish Sivakumar).