3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go3Advent Calendar 2019

Day 15

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

Last updated at Posted at 2019-12-15

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

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

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

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

そこで、その手間を節約するツール: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++ コンパイラなしに利用できます。

動作原理

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

(2)
次のような 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;
}

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

ね、かんたんでしょ?

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

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

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

go help generate によると

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

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

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

(2)C++ のソースを作成する

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

(3)コンパイルする

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

(4)a.out/a.exe を呼び出す

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

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

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

(5)go fmt zconst.go を呼び出す

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

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

最後に

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

以上

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?