2020/01/01ツイート2014/10/22cgoでGolangとC++ライブラリをリンクするとき、何が起きているのか関東も秋が深まり、「紅葉を見にいこうよう」と言ってスベるシーズンがやってきました。みなさんいかがお過ごしでしょうか、れもんです。自社サイトでGoやるよって発表したので最近はずっとGoを書いているのですが、ついに難題がやってきました。C++で書かれたライブラリをGoで使うというやつです。今日は、GoからC++のライブラリを使おうとするときに何が起きているのかという話と、それゆえにこのオプションを指定するとドツボにはまるのでやめた方がいいよという話です。GoからC++を使うときの基本的な考え方はRubyとかPerlでC++のライブラリを使うときと同じです。いつものセオリーでやってみることにしました。C++なら素直にSWIG使えよって話もあるんですが、何事も最初は挑戦だってことで手で書きます。そのいつものセオリーとは何かというと、C++のライブラリをCインタフェースで使えるブリッジを書いてCライブラリとして別言語から使う、というやつですね。基本的にC++でライブラリを作ると、明示しない限りリンケージはC++になっていますので、別言語で使おうとするとマングリング問題で呼ぶのが大変です。"C"したCスタイルの関数でC++オブジェクトのポインタを返して、オブジェクトのメンバ関数を呼ぶのは第一引数をオブジェクトのポインタにしたC関数経由にする...というやつです。WindowsのCOMと似たような感じですね。何という人のために簡単に説明します。分かってる人は読み飛ばしてオッケーです。char*)という関数を考えます。test.oでコンパイルして中間ファイルを生成して、中のシンボルをみるnmコマンドで調べてみます。printfHoge関数が_Z4HogeiPKcというシンボル名で格納されています。これが、C++のマングリング(名前修飾)です。なるかというと、C++は名前同じで引数が違う関数を許容するので名前がシンボル名になってるとリンクする関数を解決できないからですね。このよくわからん_Z4HogeiPKcという文字列は、デマングルすることで可読性のある文字列に戻すことができます。それがc++filtコマンドです。printfこんな感じで読めるようになりました。ドヤッ!で、別言語から呼ぶには_Z4HogeiPKcという関数名で呼べばいいんですが、さすがにマングリング規則を覚えてマングリングした関数名をすぐ書ける人はほとんどいないはずです。"C"というリンケージ方法の変更です。さっきのソースコードをこう変更します。str);}}これをもう一度コンパイルしてnmコマンドにかけると、シンボル名が変わります。printfこの状態なら、他の言語からHogeという関数名で呼び出しが可能です。そんな感じでC++のライブラリをCでラップしたのを書いたのですが、これをGoから呼び出すところで1日ほどハマりました。golangでC/C++のライブラリを使うときにはcgoという仕組みでライブラリをリンクします。"C"する前にコメントでCヘッダと#cgoディレクティブを書くわけですが、これを書くと絶対失敗するので書いてはいけないオプションというのがあるのでそれのご紹介、というのが今日の本題です。c++を指定してはいけないという点です。逆にこれを指定するとどういう悲劇が起こるかを知るには、cgo(gc)のビルドプロセスを知る必要があります。4.4.7を使っても同じです)。[build|run]に-xオプションをつけて実行すると実際に実行しているコマンドが見えます。長いのでここに掲載するのは省略しますが、その処理の中身を見ていくと、こんな感じです(x86-64環境なので6c/6lを使います)。c++を指定したときに何が起こるかという話です。こんな感じのGoとCヘッダをを書きます。前提として、別途Makefileでgccを使ってコンパイルするbridge.cppが別にあって、そこからbridge.aを生成しています。LDFLAGSで指定しています。loop}するとcgoはこんな感じのグルーコードを生成します。-xで出てきたコマンドを1個ずつ手で実行しないとお目にかかれません。c++を指定してしまうと、このCファイルがC++としてコンパイルされてしまい、リンケージがC++になってしまうわけです。ついでにいうと、C++的にはよくないキャストが含まれているので、-fpermissiveを指定しないとコンパイルが通りません。いうことで、これを実行するとこんな感じになります。0x100いやいや、ちゃんと自動生成したCソースに_cgo_32105cabc2a6_Cfunc_Runあるしそれエラーになるわけないじゃない...と昨日1日ずーっと悩みました。今朝電車でゆらゆらしながらずーっと考えてたんですが、よく考えたらcgoのCFLAGS指定はすべてのgcc呼び出しに付加されているので、自動生成したソースコードをC++としてコンパイルさせてしまっていたのでした。"C"を書いてその状態でコンパイルが通る様にするためだったんですね。"C"を解釈できません。いうことは、CじゃなくてC++としてコンパイルしないとダメなんだなーということでcgoディレクティブを追加したのですが、そもそもcgoの吐くソースはCとしてコンパイルしないとダメなので、これは筋が悪かったというところです。__cplusplusのときだけに絞るという、当たり前のことを当たり前のようにやればよい、ということでした。__cplusplus}#endifということで、今日はGoがcgoを使ってネイティブコンパイルするときの流れと、指定すると絶対失敗するのでやめたほうがよいCFLAGSのオプションについてご紹介しました。ここ2日くらいこれが解決する気配なくて本当につらかった...。acidlemonです。鎌倉市在住の普通のITエンジニアです。30年弱住んだ北海道を離れ、鎌倉でまったりぽわぽわしています。