本記事はGo Advent Calendar 2017 その2の21日目の記事です。
Windows Runtime API
Windows Runtime API(WinRT API)は、ここ最近のWindowsに追加された新しいAPIです。このAPIはC#などから使いやすいように出来ていて、一見.NETのマネージドコードのように見えます。しかし実際の所、COMをベースしたネイティブコードAPIでした。それならば、Goから呼び出せるのではないかと考えて試した内容が本記事になります。
go-ole
GoにはCOMのライブラリとして有名なgo-oleがあります。その中を覗くとwinrt.goというファイルがあり、既にWinRTに対応済みでした。そのため、このgo-oleを使えばWinRT APIを呼び出すことができます。
APIの呼び出し
下記のコードはgo-oleを使用して、WinRT APIのWindows.Data.Json.JsonObjectを呼び出すメイン処理です(ソース全体はこちら)。この処理自体は、Jsonデータ{"name":"Go2", "date":"2017/12/21"}をパースしてその値の表示を行っています。
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独自のものがあります。
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で提供されてます。C:\Windows\System32\WinMetadataフォルダを見るとWinRT APIのカテゴリごとに.winmd拡張子で置いてあります。
インタフェース用の構造体
Excel等のCOMオブジェクトにはIDispatchが実装してあるため、メソッド名を文字列で渡すことで呼び出すことができます。これはgo-oleでもサポートされており便利な機能なのですが、WinRTにはこのIDispatchが実装されていません。メタデータを使用するのがWinRTの方針のようです。
今回は代わりにどうしたかというと、下記のようなインタフェース用の構造体を作成しました。
// 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をベースにしている点が従来の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
-
この記事ではGoのinterfaceの話は出てきません。すべてCOMのインターフェースです。 ↩