15
13

More than 5 years have passed since last update.

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

Posted at

本記事は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"}をパースしてその値の表示を行っています。

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独自のものがあります。

RoInitialize, RoGetActivationFactory

WinRT専用の関数としてRoInitializeRoGetActivationFactoryが用意されています。これらはCOMにあったCoInitializeCoGetClassObjectに該当します。それぞれ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の方針のようです。

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

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をベースにしている点が従来のCOMと異なります。COMインタフェースは基本的にIUnknownを継承しますが、WinRTではIInspectableを継承しているためです(IInspectableはIUnknownを継承しています)。ここを誤るとVtblのオフセットが正しく設定されず、期待するメソッドを呼び出せません。

QueryInterface

必要なインタフェースに変換するときはQueryInterface(ole.PutQueryInterface)を使用します。今回はParseした結果(inspectable2)をIJsonObjectIStringableに変換しています。この辺りは従来の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


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

15
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
13