<windows.h> 等から定数を Go に引用したい（import "cgo"とか使わんで）

（本記事はGo3 Advent Calendar 15日目の記事になります。穴が空いていたようなので、埋めさせていただきます)

`import "syscall"` 愛用者の皆さん、こんにちは。

Windows の API を使う時、`import "cgo"` ではなく、`import "syscall"` で DLL をロードするようにすると、ビルドの際、C言語のコンパイラが不要になります。ビルド要件が下がると、それだけ開発したライブラリを使ってもらう確率もあがりますし、プルリクとかもしてもらいやすくなります。なので、少々の手間をかけて `import "syscall"` を使う価値は高いと思います。

でも…関数本体の宣言はともかくとして、たくさんの定数をヘッダファイルから書き写すのはたいへんですよね。

そこで、その手間を節約するツール：[go-importconst](http://github.com/zetamatta/go-importconst) を開発しました。

### go-importconst とは

```const.go
package dos

//go:generate go run github.com/zetamatta/go-importconst
//      <windows.h>
//	RESOURCE_CONNECTED
//	RESOURCE_CONTEXT
//	RESOURCE_GLOBALNET
//	RESOURCE_REMEMBERED
//	RESOURCETYPE_ANY
//	RESOURCETYPE_DISK
//	RESOURCETYPE_PRINT
//	RESOURCEDISPLAYTYPE_NETWORK
//	RESOURCEDISPLAYTYPE_SERVER
//	RESOURCEUSAGE_CONNECTABLE
//	RESOURCEUSAGE_CONTAINER
//	RESOURCEUSAGE_ATTACHED
//	RESOURCEUSAGE_ALL
//	ERROR_NO_MORE_ITEMS
//	CONNECT_UPDATE_PROFILE
//	S_OK
```

というコメントコード(`const.go`)から

```zconst.go
package dos

// Code generated by importconst.go DO NOT EDIT.
const RESOURCE_CONNECTED = 1
const RESOURCE_CONTEXT = 5
const RESOURCE_GLOBALNET = 2
const RESOURCE_REMEMBERED = 3
const RESOURCETYPE_ANY = 0
const RESOURCETYPE_DISK = 1
const RESOURCETYPE_PRINT = 2
const RESOURCEDISPLAYTYPE_NETWORK = 6
const RESOURCEDISPLAYTYPE_SERVER = 2
const RESOURCEUSAGE_CONNECTABLE = 1
const RESOURCEUSAGE_CONTAINER = 2
const RESOURCEUSAGE_ATTACHED = 16
const RESOURCEUSAGE_ALL = 19
const ERROR_NO_MORE_ITEMS = 259
const CONNECT_UPDATE_PROFILE = 1
const S_OK = 0
```

という実際の値を定義した実コード(`zconst.go`)を生成する go generate 用ツールです。

go generate を実行する時は C++コンパイラが必要となりますが、一度生成した `zconst.go` は　C++ コンパイラなしに利用できます。

### 動作原理

（１）
Goのソースのコメントコードを読み取って、対象となる定数名を得る

（２）
次のような C++ のソースを作成する

```zconst.cpp
#include <cstdio>
#include <windows.h>

void p(const char *name,const char *s){
	printf("const %s=\"%s\"\n",name,s);
}
void p(const char *name,int n){
	printf("const %s=%d\n",name,n);
}
void p(const char *name,long n){
	printf("const %s=%ld\n",name,n);
}
void p(const char *name,unsigned long n){
	printf("const %s=%ld\n",name,n);
}
void p(const char *name,double n){
	printf("const %s=%lf\n",name,n);
}

int main()
{
    printf("package dos\n\n");
    printf("// Code generated by importconst_run.go DO NOT EDIT.\n");
    p("RESOURCE_CONNECTED",RESOURCE_CONNECTED);
// 中略
    p("S_OK",S_OK);
    return 0;
}
```

（３）zconst.cpp を C++ コンパイラでコンパイルする
（４）生成された実行ファイル (`a.out` or `a.exe`）を呼び出して、標準出力を `zconst.go` へリダイレクトする
（５）`go fmt` する

ね、かんたんでしょ？

### （１）Goのソースのコメントコードを読み取って、対象となる定数名を得る

実は `importconst` はちょっと前までバッチファイルから呼び出すようになっていたのですが、コマンドラインパラメータにパッケージ名・定数名を全部書かなくちゃいけないので、狭っ苦しくて見栄えが悪くなっていました。

go generate から呼び出すのであれば、Goのソース中にそうした情報を書いた方が綺麗です。

`go help generate` によると

* 環境変数 `%GOFILE%` に go generate が書かれているソースのファイル名
* 環境変数 `%GOLINE%` に go generate が書かれているソースの行位置
* 環境変数 `%GOPACKAGE%` にパッケージ名

が入っているそうです。これらを利用して、go generate が書かれた行以降のコメント行に定数名を並べてゆくような文法にするのがベストでしょう！

そして、定数が `<nnnnn>` や `xxxxxx.h` という名前だった場合、それは定数名ではなく、`#include` すべきヘッダファイル名と解釈させます（`<cstdio>` だけは例外で常に include します）

### （２）C++ のソースを作成する

もう出力例を書いてしまったので、あまり書くことはありませんね。定数の型に応じて、出力の仕方を変えられるよう、出力用関数`p` を多重定義しているところがアイデアでしょうか。
（それも `<iostream>` を使えばいらないんですが、<iostream> 重いから…）

### （３）コンパイルする

gcc 呼ぶだけなので、普通に os.exec を使います。

### （４）a.out/a.exe を呼び出す

同じく呼ぶだけなので、こちらも os.exec を使います。ただ、OS によってコンパイルの出力するデフォルト実行ファイル名が違うので、runtime.GOOS で判断します。
（このツールは Windows でしか使わないと思いますが、どこでも動作できるように越したことはないでしょう）

```go
func nameOfExecutable() string {
	if runtime.GOOS == "windows" {
		return "a.exe"
	} else {
		return "a.out"
	}
}
```

そして、リダイレクト先を `zconst.go` とするのです。

### （５）`go fmt zconst.go` を呼び出す

最初からちゃんと整形済み出力すればいいんですが、ちょっとしたことでズレたりしがちになってしまうので、素直に `go fmt` を読んだほうが安全です。

ここまで来たら、あとは一時的に作ったテンポラリファイル群（`zconst.cpp`,`a.out`,`a.exe`)したらお終いです。ありがとうございました。

最後に
------

最近は多くの Windows-API や定数は `golang.org/x/sys/windows` に定義済みになってしまいましたが、まだ全部網羅されているわけではありません。`importconst` が便利なシーンはまだまだあるでしょう。

以上