GoからWindows Runtime APIを使用する方法

本記事はGo Advent Calendar 2017 その2の21日目の記事です。

# Windows Runtime API
[Windows Runtime API(WinRT API)](https://ja.wikipedia.org/wiki/Windows%E3%83%A9%E3%83%B3%E3%82%BF%E3%82%A4%E3%83%A0)は、ここ最近のWindowsに追加された新しいAPIです。このAPIはC#などから使いやすいように出来ていて、一見.NETのマネージドコードのように見えます。しかし実際の所、COMをベースしたネイティブコードAPIでした。それならば、Goから呼び出せるのではないかと考えて試した内容が本記事になります。

# go-ole
GoにはCOMのライブラリとして有名な[go-ole](https://github.com/go-ole/go-ole)があります。その中を覗くとwinrt.goというファイルがあり、既にWinRTに対応済みでした。そのため、このgo-oleを使えばWinRT APIを呼び出すことができます。

# APIの呼び出し
下記のコードはgo-oleを使用して、[WinRT APIのWindows.Data.Json.JsonObject](https://docs.microsoft.com/en-us/uwp/api/windows.data.json.jsonobject)を呼び出すメイン処理です（[ソース全体はこちら](https://gist.github.com/tobi-c/1ec7008d3f6dec913f83f78d7a47a5c3)）。この処理自体は、Jsonデータ`{"name":"Go2", "date":"2017/12/21"}`をパースしてその値の表示を行っています。

```go:go
func main() {
	// WinRT API用の初期化メソッド
	ole.RoInitialize(RO_INIT_MULTITHREADED)

	// IJsonObjectStaticsの取得(戻り値の型はIInspectable)
	inspectable1, _ := ole.RoGetActivationFactory(JsonObjectClass, IJsonObjectStaticsIID)

	// IInspectableからIJsonObjectStaticsに変換
	jsonObjectStatics := (*IJsonObjectStatics)(unsafe.Pointer(inspectable1))

	// IJsonObjectStaticsのParseメソッド呼び出し
	json := `{"name":"Go2", "date":"2017/12/21"}`
	inspectable2, _ := jsonObjectStatics.Parse(json)

	// Parseメソッドの結果をIJsonObjectインタフェースに変換
	var jsonObject *IJsonObject
	inspectable2.PutQueryInterface(IJsonObjectIID, &jsonObject)

	// IJsonObjectインタフェースのメソッド呼び出し
	name, _ := jsonObject.GetNamedString("name")
	fmt.Println("name:", name)
	date, _ := jsonObject.GetNamedString("date")
	fmt.Println("date:", date)

	// Parseメソッドの結果をIStringableインタフェースに変換
	var stringable *IStringable
	inspectable2.PutQueryInterface(IStringableIID, &stringable)

	// IStringableインタフェースのメソッド呼び出し
	str, _ := stringable.ToString()
	fmt.Println("ToString:", str)
}

```

```:出力
name: Go2
date: 2017/12/21
ToString: {"name":"Go2","date":"2017/12/21"}
```

見ての通り、オブジェクトの取得やインタフェース[^1]変換のコードが大半です。これらの処理は従来のCOMに存在したものとWinRT独自のものがあります。

[^1]: この記事ではGoのinterfaceの話は出てきません。すべてCOMのインターフェースです。

## RoInitialize, RoGetActivationFactory
WinRT専用の関数として`RoInitialize`と`RoGetActivationFactory`が用意されています。これらはCOMにあった`CoInitialize`と`CoGetClassObject`に該当します。それぞれ`RoInitialize`はWinRT使用前に呼び出すWinRT初期化用関数、`RoGetActivationFactory`はクラスオブジェクトを取得する関数になります。 

## JsonObjectクラスオブジェクトの取得方法
`RoGetActivationFactory`にクラス名とIID(Interface ID)を渡すことで、クラスオブジェクトを取得できます。先ほどのコードでは、`JsonObject`クラスオブジェクトを取得して`Parse`メソッドを呼び出しています。

COM的には、`RoGetActivationFactory`で指定したIIDが`IJsonObjectStatics`インタフェースのIDであるため、`IJsonObjectStatics`が持つParseメソッドを呼び出せています。このあたりのインタフェースやメソッドの情報は`WinRTメタデータ`に記述されています。

## WinRTメタデータ
WinRTでは、別途メタデータファイルとしてクラスやメソッドなどの定義情報などを用意しています。この情報は主に.NETから使用するために存在し、[ファイルフォーマットも.NETのCIL](https://en.wikipedia.org/wiki/Windows_Runtime#Metadata)で提供されてます。`C:\Windows\System32\WinMetadata`フォルダを見るとWinRT APIのカテゴリごとに`.winmd`拡張子で置いてあります。

## インタフェース用の構造体
Excel等のCOMオブジェクトには`IDispatch`が実装してあるため、メソッド名を文字列で渡すことで呼び出すことができます。これはgo-oleでもサポートされており便利な機能なのですが、WinRTにはこの`IDispatch`が実装されていません。メタデータを使用するのがWinRTの方針のようです。

今回は代わりにどうしたかというと、下記のようなインタフェース用の構造体を作成しました。

```go:go
// IJsonObjectStatics
type IJsonObjectStatics struct {
	ole.IInspectable
}

type IJsonObjectStaticsVtbl struct {
	ole.IInspectableVtbl
	Parse    uintptr
	TryParse uintptr
}

func (v *IJsonObjectStatics) VTable() *IJsonObjectStaticsVtbl {
	return (*IJsonObjectStaticsVtbl)(unsafe.Pointer(v.RawVTable))
}

func (v *IJsonObjectStatics) Parse(input string) (inspectable *ole.IInspectable, err error) {
	hInput, err := ole.NewHString(input)
	if err != nil {
		return
	}
	defer ole.DeleteHString(hInput)

	hr, _, _ := syscall.Syscall(
		v.VTable().Parse,
		3,
		uintptr(unsafe.Pointer(v)),
		uintptr(unsafe.Pointer(hInput)),
		uintptr(unsafe.Pointer(&inspectable)),
	)

	if hr != 0 {
		err = ole.NewError(hr)
	}
	return
}
```

この構造体は`IJsonObjectStatics`インタフェースとその`Parse`メソッドを実装したものです。

go-oleを参考にして作成しましたが、[`IInspectable`](https://msdn.microsoft.com/ja-jp/library/windows/desktop/br205821)をベースにしている点が従来のCOMと異なります。COMインタフェースは基本的に`IUnknown`を継承しますが、WinRTでは`IInspectable`を継承しているためです（IInspectableはIUnknownを継承しています）。ここを誤るとVtblのオフセットが正しく設定されず、期待するメソッドを呼び出せません。

## QueryInterface
必要なインタフェースに変換するときは`QueryInterface`(`ole.PutQueryInterface`)を使用します。今回はParseした結果(`inspectable2`)を`IJsonObject`や`IStringable`に変換しています。この辺りは従来のCOMと変わりありません。


#まとめ
go-oleを使用してWinRT APIを呼び出しました。正直GoからJsonObjectのAPIを呼び出しても何もうれしくありませんが、`Windows.Devices`などのAPIを呼び出せればBluetoothなど色々な機能を実行できそうです。

また今回はIJsonObjectStatics等の構造体を手書きで実装しましたが、量が多くなるとこの方法では限界があります。クラスの定義情報等を持つメタデータから自動生成等ができれば使い勝手がよくなると思います。

#参考サイト
https://github.com/go-ole/go-ole
http://dev.activebasic.com/egtra/2014/12/24/726/
http://mntone.hateblo.jp/entry/2013/12/14/195907
https://msdn.microsoft.com/ja-jp/magazine/mt422579.aspx