[Yiwentong] Учебник начального уровня по смешанному программированию на языках C/C++ и Go (завершено на платформе Windows)

вставьте сюда описание изображения

I. Обзор

Язык Go может выполнять смешанное программирование C+GO с помощью встроенного инструмента cgo. Этот инструмент находится в pkg\tool в каталоге установки go, а его исходный код находится в src\runtime\cgo.Конечно, эта статья не намерен использовать cgo в качестве вводного руководства.Углубленное исследование принципа реализации cgo, только с точки зрения Hello World, чтобы на самом деле испытать cgo (в конце статьи есть различные ресурсы, которые я собрал для углубленного изучения ).


2. Начните с самого простого (встроенный код C)

Компилятор Go по умолчанию отключает функцию кросс-компиляции, потому что включение cgo ухудшит переносимость программ Go, и развертывание станет проблематичным. Чистый код Go, безусловно, лучше, но иногда, когда необходимая программная библиотека не может найти версию Go, это единственный способ с этим справиться. С положительной стороны, без cgo Go не был бы тем, чем он является сегодня, потому что он может унаследовать почти полувековое наследие программного обеспечения C/C++, а cgo также является ключом к запуску программ Go на Android и iOS.

Открыть 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 неimport "C" " , это простой код Go. Обратите внимание, что между кодом C и не может быть пустых строк , а на пакет «C» нельзя ссылаться с помощью обычных операторов импорта, и он может существовать только как эксклюзивная строка. После этого в обычной части кода Go мы можем использовать C.Add для ссылки на эту функцию языка C. Этот метод обращения к C-коду inline очень прост, и нет никакой разницы в процессе компиляции по сравнению с обычным чистым Go, только ввод или напрямую .import "C" go build main.go go run main.go


3. Ссылка на библиотеку, написанную на языке C

В большинстве случаев нашей целью является обращение к сторонним программным библиотекам через cgo.Независимо от открытого или закрытого исходного кода, они в основном существуют в виде библиотек.Поговорим о том, как обращаться к статическому коду, скомпилированному на языке C в Go код Связывание функций в библиотеке. Для простоты мы сначала смоделируем статическую библиотеку ссылок на языке C и создадим следующие два файла:

Привет:

#include <stdio.h>
#include "hello.h"

void SayHello()
{
    
    
    printf("Hello, world!\n");
}

привет.ч:

void SayHello();

Используйте gcc, чтобы скомпилировать их в статическую библиотеку (суффикс .a):
$ gcc -c hello.c
$ ar -crv libhello.a hello.o
После ввода вышеуказанных двух строк команд мы получаем статическую библиотеку 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 — это два переключателя компиляции и компоновки языка C, где CFLAGS указывает путь к заголовочному файлу, а LDFLAGS указывает путь к файлу библиотеки и имя файла библиотеки.

PS: файл заголовка относится к файлу с суффиксом .h после #include, а файл библиотеки относится к файлу, оканчивающемуся на .a или .so В Windows он существует в форме .lib или .dll. ${SRCDIR} представляет текущий каталог, который мы обычно используем. Это историческая проблема C/C++, заключающаяся в том, что файлы библиотек не могут использовать относительные пути. Через ${SRCDIR} относительные пути могут быть замаскированы. Например, если есть абсолютный путь c:\test\hello, ${SRCDIR }\lib автоматически расширяется до c:\test\hello\lib.

CFLAGS через -I установим текущий каталог в качестве пути поиска файла заголовка (.h). LDFLAGS использует -L${SRCDIR} для установки пути поиска файла библиотеки (.a) в текущем каталоге, а -lhello означает, что конкретной ссылкой является библиотека libhello.a .

Примечание: -lhello означает связать библиотеку libhello.a, которая на самом деле сокращена как hello после удаления "lib" и суффикса ".a", который является подпрограммой языка C. Кроме того, метод компоновки библиотеки динамической компоновки (файл .so) такой же, при условии, что файл библиотеки, который мы предоставляем, является библиотекой динамической компоновки libhello.so, настройки здесь точно такие же, как -lhello.

Введите go build main.goили go run main.goзапустите, чтобы отобразить:

Привет, мир!
Преуспевать!

Хотя печатаются только две строки, «Hello, world!» здесь на самом деле является результатом вызова функции SayHello() в библиотеке libhello.a языка C, а «Succeed!» — результатом вызова обычной функции Go. стандартный Результат библиотеки fmt, между ними есть существенная разница.


В-четвертых, обратитесь к библиотеке C++

Условно говоря, кросс-кодирование между C++ и Go кажется более проблематичным.cgo — это мост между языком C и языком Go, но в принципе он не может напрямую поддерживать классы C++ и может только добавлять набор интерфейсов функций языка C как Классы C++ Мост между Go и CGO, позволяющий Go и C++ соединяться окольными путями. Вот почему мы часто видим «xxx-bridge» в проектах с открытым исходным кодом Go.Пока это происходит, большинство проектов ссылаются на библиотеки C++.

Сначала мы создаем библиотеку C++, создаем каталог myLib, а затем создаем в нем два файла 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;
}

привет.ч:

void hello();

Как и в предыдущем примере, создайте статическую библиотеку ссылок:
$ g++ -c hello.cpp (Обратите внимание, что на этот раз используется g++ вместо gcc)
$ ar crs libhello.a hello.o
Затем вернитесь в корневой каталог проекта и создайте два файла C++ в качестве «мостов»:
$cd ..

hellobridge.cpp :

#include "hellobridge.h"
#include "mylib/hello.h"

void CallHello()
{
    
    
    hello(); // 调用库中的hello()函数
}

хеллобридж.ч :

#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 делятся на CC и CXX, которые соответствуют исполняемым компиляторам C и C++ (необходимо поместить в команду PATH для выполнения в любом месте), когда мы установим CFLAGS, cgo автоматически откроет язык C компилятор работает (по умолчанию gcc), и когда переключатель CXXFLAGS установлен, cgo автоматически выбирает использование компилятора C++ (по умолчанию g++). И нам не нужно беспокоиться о том, как "командовать" компилятором cgo, его логика очень проста, то есть он оценивается двумя ключами компиляции. Нам не нужно устанавливать CC в g++, чтобы заставить cgo использовать C++ для компиляции, это будет саморазрушением.

Об этих трех переключателях:
CFLAGS : параметры компиляции языка C
CXXFLAGS : уникальные параметры компиляции C++
CPPFLAGS : общие параметры компиляции C и C++
В этом примере мы обнаружили параметр -Wno-unused-result, который указывает компилятору отменить «неиспользуемые» переменные. C и C++, поэтому он помещен в CPPFLAG .

После создания всех вышеуказанных файлов введите go build -o hello.exe или go run.

Привет, мир!
Это сообщение исходит от функции CPP!

Примечание. Поскольку на этот раз у нас есть не только файл hello.go для участия в компиляции, но и два других файла моста: 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

Таким образом, нам нужно только знать имя библиотеки, и нам не нужно заботиться о том, где хранится библиотека, что экономит время и нервы.

Каждая библиотека предварительно создаст файл с суффиксом .pc, который содержит предустановленные записи, такие как CFLAGS и LDFLAGS.Когда pkg-config извлечет этот файл, он автоматически расширит соответствующее содержимое, и эти файлы .pc Место хранения вызвали PKG_CONFIG_PATH, давайте воспользуемся нашей библиотекой hello, чтобы продемонстрировать использование pkg-config.

Прежде всего, хотя это инструмент для Linux, на самом деле нам не нужно искать его так называемую версию для Windows, потому что он поставляется с этим инструментом при установке MSYS2 или MinGW. Нам нужно только ввести MSYS2, чтобы использовать его напрямую.Используйте команду:
$ echo $PKG_CONFIG_PATH
, чтобы узнать, что все файлы .pc размещены в этих двух каталогах:
/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

Имя и описание можно ввести случайно, Libs указывает фактическое место хранения нашей библиотеки hello, а Cflags указывает место хранения файла заголовка библиотеки hello.
Сохраняем и выходим, вводим;
# 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

Совет Pit: две оболочки под Windows не могут распознать формат `pkg-config -cflags -libs hello`, будь то cmd или powershell, я также пытался использовать $(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 очень глубок Эта статья предназначена только для ознакомительных целей и говорит только о том, как идут ссылки 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/С++:
https://blog.51cto.com/u_15091053/2652800
https://www.cnblogs.com/52php/p/5681711.html

Supongo que te gusta

Origin blog.csdn.net/rockage/article/details/131357305
Recomendado
Clasificación