[Yiwentong] C/C++ と Go 言語の混合プログラミングの入門レベルのチュートリアル (Windows プラットフォームで完了)

ここに画像の説明を挿入

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

このライブラリの実際の格納場所をさまざまな方法 (たとえばwhereisfindこの種の命令) で見つける必要があり、非常に面倒です。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 は気軽に入力できます。Libshello ライブラリの実際の保存場所を示し、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

おすすめ

転載: blog.csdn.net/rockage/article/details/131357305