I. 概要
Go 言語は、組み込みの cgo ツールを通じて C+GO 混合プログラミングを実行できます。このツールは go インストール ディレクトリの pkg\tool に配置され、ソース コードは src\runtime\cgo にあります。もちろん、この記事ではそうではありませんcgo を入門チュートリアルとして使用するつもりです。cgo の実装原理を詳しく調査し、Hello World の観点からのみ cgo を実際に体験します (記事の最後には、詳細な学習のために収集したさまざまなリソースがあります) )。
2. 最も単純な (インライン C コード) から始めます。
cgo をオンにすると Go プログラムの移植性が悪くなり、デプロイメントが面倒になるため、デフォルトの Go コンパイラはクロスコンパイル機能をオフにします。Pure Go コードの方が確かに優れていますが、必要なソフトウェア ライブラリで Go バージョンが見つからない場合には、これが唯一の対処方法である場合があります。良い面としては、cgo がなければ Go は今日の姿にはなっていなかったでしょう。Go は半世紀近くにわたる C/C++ ソフトウェアの伝統を継承することができ、cgo は Android や iOS で Go プログラムを実行するための鍵でもあるからです。
cgo を開くのは非常に簡単で、環境変数 CGO_ENABLED を 1 に設定するだけです。
Windows: set CGO_ENABLED=1
Linux に似たプラットフォーム: export CGO_ENABLED=1
このプログラムを入力してください:
main.go:
package main
/*
int Add(int a, int b){
return a+b;
}
*/
import "C"
import "fmt"
func main() {
a := C.int(10)
b := C.int(20)
c := C.Add(a, b)
fmt.Println(c) // 30
}
上記のコードでは、先頭に「コメント」があり、次の行が ですimport "C"
。このコメントは実際のコメントではなく、C 言語のコードであることに注意してください。Go 言語の仕様では、前のimport "C"
コメント行をプリアンブルと呼びます. . であり、実際、Go 言語にはパッケージ「 Cimport "C"
」がありません。これは単純な Go コードです。C コードと の間に空行を入れることはできず、パッケージ「C」は通常のインポート ステートメントでは参照できず、排他行としてのみ存在できることに注意してください。その後、通常の Go コード部分で、C.Add を使用してこの C 言語関数を参照できます。C コードをインラインで参照するこの方法は非常に簡単で、通常の純粋な Go と比較してコンパイルプロセスに違いはなく、入力するか直接入力するだけです。import "C"
go build main.go
go run main.go
3. C言語で書かれたライブラリを参照する
多くの場合、サードパーティのソフトウェアライブラリをcgo経由で参照することが目的です オープンソース、クローズドソースを問わず、基本的にライブラリの形で存在します C言語でコンパイルされた静的コードをGoで参照する方法についてお話しますコード: ライブラリ内の関数をリンクします。簡単にするために、最初に C 言語の静的リンク ライブラリをシミュレートし、次の 2 つのファイルを作成します。
こんにちはC:
#include <stdio.h>
#include "hello.h"
void SayHello()
{
printf("Hello, world!\n");
}
こんにちは。h:
void SayHello();
gcc を使用してそれらを静的リンク ライブラリ (接尾辞は .a) にコンパイルします。
$ gcc -c hello.c
$ar -crv libhello.a hello.o
上記の 2 行のコマンドを入力すると、libhello.a の静的リンク ライブラリが得られます。
PS: gcc および ar コマンドについては、MinGW ソフトウェアに含まれています。ここではインストールについては説明しません。以前のブログ投稿を参照してください。プロセスも非常に簡単です。
別の Go ファイル
main.go を作成します。
package main
/*
#cgo CFLAGS: -I${SRCDIR}
#cgo LDFLAGS: -L${SRCDIR} -lhello
#include "hello.h"
*/
import "C"
import (
"fmt"
)
func main() {
C.SayHello()
fmt.Println("Succeed!")
}
CFLAGS と LDFLAGS は、2 つの C 言語コンパイルおよびリンク スイッチです。CFLAGS はヘッダー ファイルのパスを指定し、LDFLAGS はライブラリ ファイル パスとライブラリ ファイル名を指定します。
PS: ヘッダー ファイルは #include に続く拡張子 .h が付いたファイルを指し、ライブラリ ファイルは .a または .so で終わるファイルを指し、Windows では .lib または .dll の形式で存在します。${SRCDIR} は現在のディレクトリを表し、通常使用する「.」です。ライブラリ ファイルで相対パスを使用できないのは、C/C++ の歴史的な問題です。${SRCDIR} を介して、相対パスを偽装して使用できます。たとえば、絶対パス c:\test\hello がある場合、${SRCDIR }\lib は自動的に c:\test\hello\lib に展開されます。
-Iを使用したCFLAGS は、現在のディレクトリをヘッダー ファイル (.h) の検索パスとして設定します。LDFLAGS は、 -L${SRCDIR} を使用して、現在のディレクトリにライブラリ ファイル (.a) 検索パスを設定します。 -lhello は、特定のリンクがライブラリlibhello.aであることを意味します。
注: -lhello は、ライブラリ libhello.a をリンクすることを意味します。実際には、C 言語のルーチンである「lib」とサフィックス「.a」を削除した後、hello と省略されます。また、ダイナミックリンクライブラリ(.soファイル)のリンク方法も同様で、提供するライブラリファイルがダイナミックリンクライブラリlibhello.soであるとすると、ここでの設定は-lhelloと全く同じになります。
次のように入力go build main.go
またはgo run main.go
実行して表示します。
こんにちは世界!
成功した!
2 行しか出力されていませんが、ここでの「Hello, world!」は実際には C 言語の libhello.a ライブラリの SayHello() 関数を呼び出した結果であり、「Succeed!」は通常の Go 関数を呼び出した結果です。 standard ライブラリ fmt の結果、この 2 つの間には本質的な違いがあります。
4 番目に、C++ ライブラリを参照します。
相対的に言えば、C++ と Go 間のクロスコーディングの方が面倒なようです cgo は C 言語と Go 言語の間のブリッジですが、原理的に C++ クラスを直接サポートすることはできず、C 言語の関数インターフェイスのセットを追加することしかできません。 C++ クラス Go と CGO の間の橋渡しとなり、Go と C++ が迂回的に接続できるようになります。オープンソースの Go プロジェクトで "xxx-bridge" がよく見られるのはこのためで、これがある限り、ほとんどのプロジェクトは C++ ライブラリを参照しています。
まず、C++ ライブラリを作成し、myLib ディレクトリを作成して、その中に 2 つの C++ ファイルを作成します。
$ mkdir myLib
$cd myLib
こんにちは。cpp:
#include "hello.h"
#include <iostream>
void hello() {
std::cout << "Hello, World!\nThis message comes from a CPP function!" << std::endl;
}
こんにちは。h:
void hello();
前の例と同様に、静的リンク ライブラリを作成します。
$ g++ -c hello.cpp
(今回は gcc の代わりに g++ が使用されることに注意してください)
$ar crs libhello.a hello.o
次に、プロジェクトのルート ディレクトリに戻り、2 つの C++ ファイルを「ブリッジ」として作成します。
$cd ..
hellobridge.cpp :
#include "hellobridge.h"
#include "mylib/hello.h"
void CallHello()
{
hello(); // 调用库中的hello()函数
}
hellobridge.h :
#ifdef __cplusplus
extern "C" {
#endif
void CallHello();
#ifdef __cplusplus
}
#endif
このファイルは CGO で使用するため、C++ ソースファイルに含める場合は C 言語仕様の名前装飾規則を採用し、文で記述する必要がありますextern "C"
。
最後に Go メイン プログラムを作成します。
こんにちは。ゴー:
package main
/*
package main
/*
#cgo CXXFLAGS: -std=c++0x
#cgo LDFLAGS: -L${SRCDIR}/mylib -lhello
#cgo CPPFLAGS: -Wno-unused-result
#include "hellobridge.h"
*/
import "C"
func main() {
C.CallHello()
}
今回は、CXXFLAGSコンパイラ スイッチを設定して、cgo に C++ コードであることを伝えます。
注: go env 環境変数は、C および C++ の実行可能コンパイラに対応する CC と CXX に分かれています (どこでも実行するには PATH コマンドに配置する必要があります)。CFLAGS を設定すると、cgo は自動的に C 言語を開きます。コンパイラは機能し (デフォルトは gcc)、CXXFLAGS スイッチが設定されている場合、cgo は自動的に C++ コンパイラ (デフォルトは g++) を使用することを選択します。そして、コンパイラ cgo が動作するためにどのように「コマンド」を使うかを心配する必要はありません。そのロジックは非常に単純です。つまり、2 つのコンパイル スイッチによって判断されます。強制的に CC を g++ に設定する必要はありません。 cgo はコンパイルに C++ を使用しますが、これは自滅的です。
これら 3 つのスイッチについて:
CFLAGS : C 言語コンパイル パラメータ
CXXFLAGS : C++ 固有のコンパイル パラメータ
CPPFLAGS : C および C++ の共通コンパイル パラメータ
この例では、-Wno-unused-result
「未使用」変数をキャンセルするようにコンパイラに指示するパラメータが見つかりました。 警告、このパラメータは共通です。 C および C++ なので、CPPFLAGに配置されます。
上記のファイルをすべて作成したら、 go build -o hello.exe または go run と入力します。
こんにちは世界!
このメッセージは CPP 関数から来ています。
注: 今回は、コンパイルに参加する hello.go ファイルだけでなく、他の 2 つのブリッジ ファイル (hellobridge.cpp と hellobridge.h) もコンパイルに参加するため、上記の例のように hello を個別にコンパイルすることはできません。 go build hello.go
.go ファイルを使用するには、代わりにフォルダー全体をコンパイルする必要があります。
5. pkg-config を使用する
このセクションはこの記事の焦点ではありませんが、pkg-config ツールは広く使用されており、cgo にもそれに対応するドッキング パラメーターがあるため、ここで報告します。
pkg-config はネイティブ Linux 上のツールです (Win 版もあります)。その主な機能はライブラリ ファイルの参照操作を簡素化することです。サードパーティのライブラリの場合、CFLAGS や LDFLAGS などのパラメータの結合は非常に面倒です。この操作は pkg-config を使用することである程度簡略化できます。その動作ロジックは実際には非常に単純であり、単に自動拡張機能として理解することができます。たとえば、ライブラリ hello があり、そのヘッダ ファイルは /usr/local/include に格納され、ライブラリ ファイルは /usr に格納されます。 /local/lib 、コンパイル時にこれを行っていました。
gcc hello.cpp -I/usr/local/include -L/usr/local/lib -lhello -o hello.exe
このライブラリの実際の格納場所をさまざまな方法 (たとえばwhereis
、find
この種の命令) で見つける必要があり、非常に面倒です。pkg-config を使用すると、次のようになります。
gcc hello.cpp `pkg-config -cflags -libs hello` -o hello.exe
このうち、「」で囲まれた内容は自動的に展開されます。
-I/usr/local/include -L/usr/local/lib -lhello
この方法では、ライブラリの名前だけを知る必要があり、ライブラリがどこに保存されているかを気にする必要がないため、時間と心配が節約されます。
各ライブラリは、CFLAGS や LDFLAGS などのプリセット レコードを含む .pc という接尾辞を持つファイルを事前に作成します。pkg-config がこのファイルを取得すると、一致したコンテンツが自動的に展開され、これらの .pc ファイルの保存場所は次のとおりです。と呼ばれるPKG_CONFIG_PATH
、hello ライブラリを使用して pkg-config の使用法を示しましょう。
まず、これは Linux ツールですが、いわゆる Windows バージョンを見つけるのに苦労する必要はありません。MSYS2 または MinGW をインストールするときにこのツールが付属しているためです。MSYS2 を直接使用するには、MSYS2 を入力するだけです。コマンド
$を使用してecho $PKG_CONFIG_PATH
、すべての .pc ファイルがこれら 2 つのディレクトリに配置されていることを確認します。
/mingw64/lib/pkgconfig:/mingw64/share/pkgconfig
コマンドを入力します。
$cd /mingw64/lib/pkgconfig
ファイルhello.pcを作成します。
Name: Hello
Description: Hello World Cgo Test.
Version: 1.0.0
Libs: -Lc:/test/myLib -lhello
Cflags: -Ic:/test/myLib
Name と description は気軽に入力できます。Libsはhello ライブラリの実際の保存場所を示し、Cflags はhello ライブラリのヘッダー ファイルの保存場所を示します。
保存して終了し、Enter;
#pkg-config --list-all
画面に大量のパッケージ名が表示されます。注意深く見てください。Hello ライブラリもその中にあるはずです。
入力:
#Display pkg-config --cflags --libs hello
:
-Ic:/test/myLib -Lc:/test/myLib -lhello
これは、pkg-config が hello ライブラリを検出し、それを自動的に展開できることを意味します。
ここで、gcc または g++ コマンドを使用してコンパイルすると、次のことが可能になります。
gcc hello.cpp `pkg-config -cflags -libs hello` -o hello.exe
自動的に次のように展開されます。
gcc hello.cpp -Ic:/test/myLib -Lc:/test/myLib -lhello -o hello.exe
ヒント: Windows 上の 2 つのシェルは、cmd であろうと powershell であろうと、「pkg-config -cflags -libs hello」形式を認識できません。また、この形式のように $(pkg-config -cflags -libs hello) を使用しようとしました。拡張することはできません。ただし、既に MSYS2 がインストールされているシステムの場合、これは大きな問題ではなく、どのシェルを入力するかが異なるだけです。
Go 側に戻ると、pkg-config を採用した後、ヘッダー ファイルとライブラリ ファイルのパスを指定する必要がなくなりました。変更されたコードは次のとおりです。
package main
/*
#cgo pkg-config: hello
#cgo CXXFLAGS: -std=c++0x
#cgo CPPFLAGS: -Wno-unused-result
#include "hellobridge.h"
*/
import "C"
func main() {
C.CallHello()
}
古いコードは次のとおりです。#cgo LDFLAGS: -L${SRCDIR}/mylib -lhello
そして今必要なのはpkg-config: hello
次だけです。それでいいです。私たちには何も関係ありません。ライブラリは自分たちで書いたものです。もちろん、どこに保存されているかは知っていますが、別の観点から見ると、このライブラリは他の人に提供されることになっています。それを使えば、彼は多くの手間を省くことができます。
6. 追記
cgo の入り口は非常に奥が深く、この記事は入門のみを目的としており、go が c を参照する方法についてのみ説明しており、c 参照の方法や、変数、配列、構造体などのさまざまな変換の問題については触れていません。そしてポインタ。深く学ぶための学習教材をいくつか集めました。
公式マニュアル:
https://pkg.go.dev/cmd/cgo
https://go.dev/blog/cgo
CGO:
https://chai2010.cn/advanced-go-programming-book/ch2-cgo/ch2-01-hello-cgo.html
https://www.cntofu.com/book/19/0.13.md
https: //www.cnblogs.com/lidabo/p/6068448.html
https://bastengao.com/blog/2017/12/go-cgo-cpp.html
https://fasionchan.com/golang/practices/call- c/
C/C++:
https://blog.51cto.com/u_15091053/2652800
https://www.cnblogs.com/52php/p/5681711.html