本記事は、COM Advent Calendar 2014 – Qiitaの24日目の記事です。


最後(CoUninitializeの前)に、Windowsストアアプリ用APIであるWindows Runtime API (WinRT API)を使うサンプルコードも取り上げようと思いました。「WinRT APIはCOMなんだよ」を示すべく、ネイティブのC++でWinRT APIを使うサンプルコードです。

なお、Windowsストアアプリではなく、デスクトップアプリ、しかもstd::coutで出力するコンソールアプリです(参考:デスクトップ アプリからのWinRT API利用 | ++C++; // 未確認飛行 C ブログ)。

#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <windows.h>
#include <roapi.h>
#include <wrl/wrappers/corewrappers.h>
#include <windows.data.json.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlutil.h>
 
#pragma comment(lib, "runtimeobject.lib")
 
int main()
{
  using namespace ABI::Windows::Data::Json;
  using Microsoft::WRL::Wrappers::HStringReference;
 
  try
  {
    ATLENSURE_SUCCEEDED(RoInitialize(RO_INIT_MULTITHREADED));
 
    HStringReference jsonObjectClass(
      RuntimeClass_Windows_Data_Json_JsonObject);
    ATL::CComPtr<IJsonObjectStatics> jsonObjectStatics;
    ATLENSURE_SUCCEEDED(RoGetActivationFactory(
      jsonObjectClass.Get(), IID_PPV_ARGS(&jsonObjectStatics)));
 
    HStringReference text(LR"json(
{
  "hoge": 103,
  "piyo": 201
}
)json");
 
    boolean succeeded;
    ATL::CComPtr<IJsonObject> jsonObject;
    ATLENSURE_SUCCEEDED(
      jsonObjectStatics->TryParse(text.Get(), &jsonObject, &succeeded));
    ATLENSURE(succeeded);
 
    HStringReference key(L"piyo");
    double x;
    ATLENSURE_SUCCEEDED(jsonObject->GetNamedNumber(key.Get(), &x));
    std::cout << "piyo: " << x << std::endl;
  }
  catch (const ATL::CAtlException& e)
  {
    std::clog
      << std::hex << std::showbase;
      << e.m_hr << ' ' << ATL::AtlGetErrorDescription(e) << std::endl;
    return 1;
  }
  RoUninitialize();
}

どれくらいCOMかというと、上記のI~型はみな元を辿るとIUnknownになるインタフェースなのでATL::CComPtrで扱えますし、関数の戻り値はHRESULTなのでATLENSURE_SUCCEEDEDマクロ(失敗値なら例外を投げる)が使えるというくらいCOMです。

上記に相当するC++/CXのコードを下に載せます。

#include <iostream>
 
template<typename Tr>
inline std::wostream& operator<<(
  std::basic_ostream<wchar_t, Tr>& os,
  Platform::String^ s)
{
  return os << s->Data();
}
 
[Platform::MTAThread]
int main()
{
  using namespace Windows::Data::Json;
 
  try
  {
    Platform::String^ text = R"json(
{
  "hoge": 103,
  "piyo": 201
}
)json";
 
    JsonObject^ jsonObject;
    bool succeeded = JsonObject::TryParse(text, &jsonObject);
    if (!succeeded)
      throw ref new Platform::FailureException();
 
    double x = jsonObject->GetNamedNumber("piyo");
    std::cout << "piyo: " << x << std::endl;
  }
  catch (Platform::Exception^ e)
  {
    std::wclog
      << std::hex << std::showbase;
      << e->HResult << L' ' << e->ToString() << std::endl;
    return 1;
  }
}

ここからは従来とのCOMとの差異、あるいはWinRT API特有の話です。

WinRT APIには、CoGetClasObjectに相当するRoGetActivationFactoryとCoCreateInstanceに相当するRoActivateInstanceがあります。今回使っているのは見てのとおりRoGetActivationFactoryです。

クラスの指定方法はGUID (CLSID)から文字列になりました。RuntimeClass_Windows_Data_Json_JsonObjectはヘッダファイルでL”Windows.Data.Json.JsonObject”と定義されています。

RoGetActivationFactoryから得られるオブジェクトは、IClassFactoryに相当するIActivationFactoryのほか、場合によってクラス固有のインタフェースを実装しています。

I(クラス名)RuntimeClassFactory
仮引数を持つコンストラクタがある場合(このインタフェースにコンストラクタを呼び出すメソッドが定義される)
I(クラス名)Statics
staticメンバーを持つ場合

これらのインタフェースの定義は、ABI名前空間の中に元々と同じ名前空間の階層が作られた中にあります。また、Windows SDKには<名前空間名.h>のヘッダでこれらが定義されています。

今回は、JsonObject.TryParse静的メソッドのため、<windows.data.json.h>のABI::Windows::Data::Json::IJsonObjectStaticsを使っています。

あとは、上のコードを眺めていれば気付くことも多いと思いますが、こんな感じです。

  • 文字列はHSTRINGで取り扱う(HStringReferenceは書き換え不可文字列のラッパークラスです)。
  • boolはtypedef unsigned char boolean;になっている。
  • クラスに対応するインタフェースI(クラス)が定義されている。
  • 戻り値がHRESULTになり、本来の戻り値は最後の引数に移動している。

なお、自作コンポーネントの場合は、MSDNライブラリの方法: winmdidl.exe と midlrt.exe を使用して、Windows メタデータから .h ファイルを作成するに書かれている手順で、同様のヘッダーファイルが得られるのだと思います。


もちろん、WinRTをC++から使うコードのほとんどがスマートポインタとしてMicrosoft::WRL::ComPtrを使っていることは認識しています。今回は従来のCOMっぽさの演出のため、意図してATL::CComPtrを使いました。

今回、一番苦労したのはデスクトップアプリでまともに使えるWinRT APIクラスを見つけることでした。Windows Runtime APIs for desktop appsを眺め、簡単に使えそうなものを探しました。Clipboard.SetContentは成功するけどなぜか機能しない、Clipboard.GetContentは非同期だから面倒、などという具合でちょうど良いものを見つけるまでに難航しました。

明日はいよいよ最後、予告どおりCoUninitializeに関する話の予定です。


スポンサード リンク

この記事のカテゴリ

  • ⇒ WinRT APIを使ってみる
  • ⇒ WinRT APIを使ってみる
  • ⇒ WinRT APIを使ってみる