Windows Azure

CyberNanny: 分散コンポーネントによるリモート アクセス

Angel Hernandez Hernandez

コード サンプルのダウンロード

ここでは、CyberNanny というアプリケーションについて説明します。このアプリケーションは、自宅にいる幼い娘 Miranda を、場所や時間を問わずにリモートで確認できるよう最近作成しました。このアプリケーションは Visual C++ (MFC) で記述し、Kinect、Kinect SDK、Windows Azure、Web サービス、Outlook による Office オートメーションなど、さまざまなテクノロジを使っています。このプロジェクトは CodePlex (cybernanny.codeplex.com、英語) でホストされています。CodePlex では、コードの確認や、プロジェクトへの関与が可能です。

アプリケーションの基本部分に入る前に、アプリケーションの作成に使用したテクノロジについて簡単に説明します。

C++ は、多くのソフトウェア企業での主力言語で、現在も変わっていません。とは言え、新しい標準 C++ 11 は新しいレベルに達しています。3 つの言葉で表すとすれば、近代的で、洗練された、超高速な言語となるでしょう。また、MFC が引き続き使用されており、マイクロソフトが Visual C++ コンパイラを新しくリリースするたびにアップグレードされています。

Kinect テクノロジは、控えめに言ってもすばらしいものです。このテクノロジによって、ゲームやコンピューターの操作方法が変わりました。さらに、マイクロソフトが開発者に SDK を提供したことで、人間との対話が必要なソフトウェアを作成する場合に新しい可能性が開かれます。しかし、興味深いことに、Kinect SDK は COM (および Windows 8 の新しいプログラミング モデルであり、WinRT とも表記される Windows Runtime) を基盤としています。この SDK は Microsoft .NET Framework 言語でも使用できます。

Windows Azure は、数年前から利用が開始されたマイクロソフトの Platform as a Service (PaaS) で、ソリューション (コンピューティングやストレージなど) を追加作成できる一連のサービスを提供します。CyberNanny の要件の 1 つは、可用性の高いキューを利用する信頼性の高いメッセージ配信ですが、これは Windows Azure によって実現されます。

Windows 7 から導入された Windows Web Services API (WWSAPI) を使用すると、Web サービスをネイティブに使用できます。WWSAPI を使用してネイティブ コンポーネントを実装する Windows Presentation Foundation (WPF) アプリケーションについては、筆者のブログ記事 (bit.ly/LiygQY、英語) で説明しています。重要なのは、WWSAPI が OS 内に組み込まれているため、(ヘッダー ファイルと lib ファイルが同梱されている) Windows SDK 以外にダウンロードやインストールが必要ないことです。

無駄な労力を費やしたくありません。CyberNanny の要件の 1 つに画像を添付して電子メールを送信する機能がありますが、独自の電子メール クラスを記述するのではなく、Outlook で提供される機能を再利用することで、この機能を用意することにしました。その結果、娘を見守るための分散アプリケーションを作成するという中心的な目標に重点を置くことができます。

この記事は、次の 4 つの主要セクションで構成しています。

  1. 全般的なアーキテクチャ ソリューションの概要
  2. Kinect のアーキテクチャ
  3. ローカルに展開するコンポーネント (ネイティブ)
  4. クラウドでホストするコンポーネント (マネージ)

全般的なアーキテクチャ ソリューションの概要

CyberNanny の考え方はシンプルですが (図 1 参照)、心動かされる部分もあります。このアプリケーションを端的に言うと、Visual C++ で記述した、Kinect センサーからフレームを取得するシック クライアントです。取得したフレームは、オートメーションによって Outlook で作成した新しい電子メールに添付する画像として使用します。アプリケーションは、タイマーによって呼び出されるスレッドを起動することで、保留中の要求に関する通知を受け取ります。このスレッドが、Windows Azure でホストされているキューにポーリングします。要求は、ASP.NET Web ページからキューに追加されます。

CyberNanny Architecture
図 1 CyberNanny のアーキテクチャ

ソリューションを実行およびテストするには、次のものが必要です。

  • Kinect センサー (今回は Xbox 360 のセンサーを使用しました)
  • Windows Azure サブスクリプション
  • Kinect SDK

Kinect のアーキテクチャ

開発プロジェクトでは、アーキテクチャの観点から処理内容や実装方法を適切に理解することが重要です。今回の Kinect についても例外ではありません。マネージ コードとネイティブ コードの開発者向けに、マイクロソフトから SDK が提供されています。以下に、Kinect の基盤となるアーキテクチャを示します (図 2 参照)。

Kinect for Windows Architecture
図 2 Kinect for Windows アーキテクチャ

図 2 の丸数字は、以下の意味を表します。

  1. Kinect ハードウェア: Kinect センサーや USB ハブなどのハードウェア コンポーネント。センサーは USB ハブを通じてコンピューターに接続されます。
  2. Kinect ドライバー: Kinect 用の Windows ドライバーは、SDK をセットアップするプロセスの一環としてインストールされます。これについては、この後説明します。Kinect ドライバーは、以下の機能をサポートします。
    • カーネル モードのオーディオ デバイスとしての Kinect のマイクアレイ。これは、Windows の標準オーディオ API を通じてアクセスできます。
    • オーディオとビデオのストリーミング コントロール。色、深度、および骨格をストリーミングできます。
    • デバイス列挙機能。これにより、複数の Kinect をアプリケーションから使用できます。
  3. オーディオ コンポーネントとビデオ コンポーネント: Kinect のナチュラル ユーザー インターフェイス (NUI)。骨格追跡や、オーディオ イメージ、カラー イメージ、および深度イメージの取得を行います。
  4. DirectX メディア オブジェクト (DMO): マイクアレイのビーム形成や音源探索を行います。
  5. Windows 7 の標準 API: Windows 7 のオーディオ、音声、およびメディアの API。これについては Windows 7 SDK および Microsoft Speech SDK の説明を参照してください。

ビデオ コンポーネントを使用してフレームを取得する方法を例を挙げて説明します。取得したフレームは、電子メールに添付するため JPEG ファイルとして保存し、Direct2D によってレンダリングします。

Nui_Core クラス: Kinect センサーの必要な機能をカプセル化する Nui_Core というクラスを作成しました。アプリケーションには、このオブジェクトのインスタンスが 1 つ存在します。アプリケーションでは、コンピューターに接続された物理デバイスを表す INuiSensor 型のメンバーを使ってセンサーを操作します。Kinect SDK は COM ベースのため、前述のインターフェイス (およびアプリケーションで使用するその他の COM インターフェイス) は、スマート ポインターによって管理されます (例: CComPtr<INuiSensor> m_pSensor;)。

センサーによってフレームを取得する手順は、次のとおりです。

  1. NuiGetSensorCount を呼び出し、使用可能なセンサーがあるかどうかを確認する。
  2. NuiCreateSensorByIndex を呼び出し、Kinect センサーのインスタンスを作成する。
  3. D2D1CreateFactory を呼び出し、Direct2D リソースを作成するための ファクトリ オブジェクトを作成する。
  4. アプリケーションに必要なストリームごとにイベントを作成する。
  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 と組み合わせて使用するには別のサポート クラスが必要です。このクラスでは、今回使用するビットマップのように、取得したフレームに使用できるリソースを確保します。

メイン メソッドは、Initialize、Draw、および EnsureResources の 3 つです。各メソッドについて説明していきましょう。

Initialize: DrawDevice 型の 3 つのメンバーをセットアップします。アプリケーションには 3 つのタブを備えたタブ コントロールがあり、、タブ ([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 型のメンバーを 1 つ含みます。
  • UI 要素
    • CCyberNannyDlg (メイン ウィンドウ。CDialogEx から継承します)。
    • CAboutDlg (ダイアログ ボックス関連。CDialogEx から継承します)。
  • Web サービス クライアント
    • サービスに対して WSUTIL を実行後に自動生成されるファイル。Web Services Description Language (WSDL)。このファイルには、WCF Web サービスによって公開されるメッセージ、構造体、およびメソッドが格納されます。
  • Outlook オブジェクトのクラス
    • 一部の Outlook オブジェクトを操作するには、ActiveX コントロール ウィザードで [Add MFC Class] (MFC クラスの追加) を選択し、それらのオブジェクトをプロジェクトにインポートする必要があります。このソリューションで使用するオブジェクトは、アプリケーション、添付ファイル、メール アイテム、および名前空間です。
  • プロキシ
    • WWSAPI を操作するのに必要なオブジェクトの作成をカプセル化するカスタム クラスです。
  • ヘルパー クラス
    • アプリケーションの機能 (ファイル サイズの縮小を目的としたビットマップの JPEG への変換機能など) をサポートするのに使用し、電子メールを送信したり Outlook を操作したりするためのラッパーを提供します。

アプリケーションを実行すると、次の処理が行われます。

  1. Register-WindowMessage を呼び出すことによって、新しいウィンドウ メッセージが定義されます。これは、要求が処理されたときにイベントの一覧に項目を追加するためです。UI 要素は、UI スレッド以外のスレッドから直接変更できないため、この処理は必須です。この処理を行わないと、不適切なスレッド間呼び出しが行われます。これは、MFC メッセージング インフラストラクチャによって管理されます。
  2. Nui_Core メンバーを初期化して、2 つのタイマー (ステータス バーの現在時刻を更新するタイマーと、キューをポーリングして保留中の要求があるかどうかを確認するためのスレッドを開始するタイマー) を設定します。
  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 MB です (電子メールには不適切なサイズなので、JPEG 画像に変換する必要があります)。また、上下が逆になっているため 180 度回転させることも必要です。これは、GDI+ への呼び出しをいくつか作成することで行います。この機能は、ImageFile クラスにカプセル化しています。

ImageFile クラスは、GDI+ の操作を実行するためのラッパーとして機能します。メイン メソッドは次の 2 つです。

  1. SerializeImage: このメソッドは shared_ptr<BYTE> を受け取ります。shared_ptr<BYTE> には、画像としてシリアル化する、取得したフレームのバイト列とバイト数が格納されています。この画像は RotateFlip メソッドを呼び出して回転します。
  2. GetEncoderClsid: 先ほど説明したように、画像のファイル サイズは添付ファイルとしては大きすぎるため、フットプリントの小さい形式 (JPEG など) にエンコードする必要があります。GDI+ には、そのシステムで使用可能なエンコーダーを特定できる GetImageEncoders 関数が用意されています。

ここまでで、アプリケーションから Kinect センサーを使用する方法と、取得したフレームから電子メールに添付する画像を作成する方法を説明しました。次に、Windows Azure でホストされる WCF サービスを呼び出す方法について説明します。

ネイティブ開発者は、Windows 7 で導入された WWSAPI によって、通信 (ソケット) の詳細を気にしないで、簡単かつ便利な方法で Web サービスや WCF サービスを利用することができます。サービスを利用するにあたっての最初の手順は、サービス プロキシ (サービスに必要なデータ構造) の codegen C コードを生成する WSUTIL を使用するための WSDL を用意することです。代替として、クラウド ベースのクライアント サーバー通信をネイティブ コードでサポートできる Casablanca (bit.ly/JLletJ、英語) というプロジェクトもありますが、CyberNanny 作成当時は利用できませんでした。

WSDL を取得してディスクに保存し、その WSDL ファイルおよび関連するスキーマ ファイルを WSUTIL への入力として使用するのが一般的です。1 つ考慮する必要があるのがスキーマです。スキーマは、WSDL と一緒にダウンロードする必要があります。そうしないと、ファイルの作成時に WSUTIL でエラーが発生します。必須のスキーマは、WSDL ファイルのスキーマ セクションの .xsd パラメーターを確認することで簡単に特定できます。

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

結果のファイルをソリューションに追加できます。その後、codegen ファイルからサービスを呼び出します。WWSAPI で使用する必要のある主なオブジェクトは、次の 4 つです。

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

これらのオブジェクトにより、クライアントとサービス間の対話が可能になります。サービスを呼び出す機能は、Proxy クラスにまとめました。

ほとんどの WWSAPI 関数は HRESULT を返すので、エラーのデバッグは困難になる場合があります。しかし、心配はいりません。Windows イベント ビューアーからトレースすることが可能になり、特定の関数でエラーが発生する正確な理由を確認できます。トレースを有効にするには、[アプリケーションとサービス ログ]、[Microsoft]、[Windows]、[WebServices] を順に展開し、[Tracing] を右クリックします。

これにより、ソリューションのほとんどのネイティブ コンポーネントを確認できます。詳細については、前述の CodePlex サイトでソース コードを参照してください。次は、ソリューションの Windows Azure コンポーネントについて説明します。

クラウドでホストするコンポーネント (マネージ)

これは、Windows Azure に関する広範なチュートリアルではなく、CyberNanny の Windows Azure コンポーネントに関する説明であることに注意してください。詳細については、Windows Azure Web サイト (windowsazure.com) を参照してください。Windows Azure プラットフォーム (図 5) は、以下のサービスで構成されています。

  • Windows Azure コンピューティング
  • Windows Azure ストレージ
  • Windows Azure SQL データベース
  • Windows Azure AppFabric
  • Windows Azure Marketplace
  • Windows Azure 仮想ネットワーク

Windows Azure Platform Services
図 5 Windows Azure プラットフォームのサービス

CyberNanny には、高い可用性を確保するために 2 つのコアを割り当てた Web ロールしかありません。ノードの 1 つがエラーになると、プラットフォームが正常なノードに切り替えます。Web ロールは ASP.NET アプリケーションで、メッセージ アイテムのみをキューに追加します。追加したメッセージは、その後 CyberNanny によって取り出されます。また、キューの処理を行う Web ロールに含まれている WCF サービスもあります。

Windows Azure のロールは、クラウドで実行される個別のコンポーネントです。クラウドの各インスタンスは、仮想マシン (VM) のインスタンスに対応します。したがって、CyberNanny の場合は 2 台の VM を割り当てています。

CyberNanny には、IIS で実行している Web アプリケーション (ASPX ページのみ、または WCF サービスのいずれか) である Web ロールがあります。この Web ロールには、HTTP/HTTPS エンドポイントを使用してアクセスできます。また、Worker ロールという別の種類のロールも存在します。Worker ロールはバックグラウンド処理アプリケーション (財務計算用など) で、インターネットに接続している内部エンドポイントを公開する機能が備わっています。

このアプリケーションでは、Windows Azure ストレージで提供されているキューを利用することもできます。これにより、信頼性の高いストレージとメッセージ配信を実現できます。キューの優れた点は、利用するのに特別なコードを記述する必要がないことです。キューに似た特定の構造を使用してデータ ストレージをセットアップする必要もありません。それらの機能は、プラットフォームに初めから用意されているためです。

高い可用性とスケーラビリティの他に Windows Azure プラットフォームから得られるメリットの 1 つは、Visual Studio から Windows Azure ソリューションの開発、テスト、配置などを行うための共通性に加え、ソリューションをビルドするための共通語として .NET が用意されていることです。

動作検出や音声認識など、他にも CyberNanny に追加したいと考えている優れた機能があります。このソフトウェアを使用したい、またはプロジェクトに関与したいという方は、遠慮はいりません。使用したテクノロジは、現在利用可能なものです。それらのテクノロジは "異なるもののように" 見えますが、相互運用でき、互いに適切に動作します。

コーディングを楽しんでください。

Angel Hernandez Matos は、Avanade Australia でエンタープライズ アプリケーション チームのマネージャーを務めています。オーストラリアのシドニーを拠点に活動していますが、出身はベネズエラのカラカスです。彼は、8 年連続で Microsoft MVP を受賞しており、現在は Visual C++ の MVP です。12 歳からソフトウェアを作成しており、自分のことを "実在するおたく" だと思っています。

この記事のレビューに協力してくれた技術スタッフの Scott Berry、Diego Dagum、Yonghwi Kwon、および Nish Sivakumar に心より感謝いたします。