дровапихта
Qingyun QingCloud, инженер по исследованиям и разработкам, платформа управления мультиоблачными приложениями с открытым исходным кодом, разработчик OpenPitrix, автор кода языка Go, переводчик «Go Language Bible», автор бесплатной книги с открытым исходным кодом «Go Language Advanced Programming». В 2010 году начал участвовать и организовывать перевод ранних документов на языке Go.В 2013 году официально обратился к разработке языка Go.Старший пользователь CGO.
цель
записывать
1. Значение CGO
2. Быстрый старт
3. Преобразование типов
4. Вызов функции
5. Внутренний механизм CGO
6. Практика: упаковка c.qsort
7. Модель памяти
8. Объекты Go и C++
задний план
В конце 2017 года я сначала завершил вторую главу части CGO-программирования «Расширенное программирование на языке Go». В то время GopherChina2018 набирала лекторов, поэтому я попросил Се Мэнцзюня подать заявку на тему совместного использования CGO. Есть две причины для выбора CGO в качестве темы для обмена: во-первых, относительно мало тем для обмена информацией о программировании CGO в стране и за рубежом; во-вторых, я хотел бы воспользоваться этой возможностью, чтобы реорганизовать содержание части программирования CGO. Впервые я поделился темой CGO на Технологическом салоне дельты Жемчужной реки в Шэньчжэне в 2011. Возможно, это последний раз, когда я делился темой CGO, и я надеюсь положить конец проблеме CGO.
Слайды CGO в основном полностью соответствуют части программирования CGO в главе 2 «Продвинутое программирование на Go», поэтому для одного совместного использования слишком много контента. Из-за нехватки времени во время совместного использования сохраняются только основные и важные краткие руководства, преобразования типов, функции, модели памяти и другие части. Тем не менее, содержание этого ppt находится в открытом доступе, и заинтересованные студенты могут просматривать его напрямую или использовать содержание главы CGO «Расширенное программирование на языке Go» в качестве справочного материала. Быть
«Продвинутое программирование на языке Go», глава 2 Программирование CGO:
https://github.com/chai2010/advanced-go-programming-book
1. Значение CGO
1. Серебряной пули не существует, и Go — это не панацея для решения всех проблем.
2. Благодаря CGO вы можете унаследовать программное обеспечение C/C++ за почти полвека и встать на плечи гигантов.
3. Используя CGO, вы можете использовать Go для написания разделяемых библиотек интерфейса C для других систем.
4. CGO — это мост для прямого общения Go с другими языками.
CGO — это технология гарантированного резервного копирования.
Возможные сценарии CGO:
-
Используйте вычислительную мощность видеокарты через OpenGL или OpenCL
-
Анализ изображений с OpenCV
-
Написание расширений Python с помощью Go
-
Написание мобильных приложений на Go
2. Быстрый старт
На самом деле программы CGO могут быть очень простыми: достаточно включить оператор импорта "C", чтобы указать, что CGO включен. Конечно, такого рода программы имеют мало практического применения.
Ниже приведена относительно простая программа CGO: вывод части информации через функцию вывода языка C puts.
БытьБыть
Добавьте оператор
1.1 Вызов пользовательской функции C
БытьБыть
Только что мы использовали puts для вывода строк, и теперь мы можем пойти дальше: реализовать вывод через пользовательские функции. Мы также реализуем пользовательскую функцию Sayhello в комментарии перед оператором импорта «C». Вызов собственных определенных функций для реализации определенных функций — очень важный этап в процессе изучения любого языка программирования.
1.2 Модульность кода C
БытьБыть
Модульность — важный метод программирования. Когда строка операторов в программе слишком длинная, мы хотим разбить код на несколько строк; когда операторы кода достигают определенного уровня, мы разобьем код на функции; если в файле слишком много функций, то Хотелось бы разделить функцию на несколько файлов для реорганизации. Все вышеперечисленное — модульные идеи для упрощения организации кода.
Для предыдущей функции SayHello мы также можем использовать модульные тесты для реорганизации. Предпочтительно создать заголовочный файл hello.h, содержащий объявление функции SayHello. Затем поместите реализацию функции SayHello в файл hello.c. В коде CGO на функцию SayHello можно напрямую ссылаться с помощью #include "hello.h".
1.3 Реализация модулей C на языке Go
Создание заголовочного файла hello.h — важная веха в модульном программировании. Для пользователей функции SayHello нам нужно только знать, что функция SayHello удовлетворяет соглашению о вызовах функции языка C. Что касается того, реализована ли функция SayHello на языке C, на языке C++ или даже на любом другом языке, для пользователей функции SayHello нет никакой разницы. Поэтому мы можем переопределить функцию SayHello на языке Go.
БытьБыть
Заголовочный файл hello.h содержит объявление функции SyaHello, но hello.c становится hello.go, а сама функция реализована на Go from C to Go. Имена функций и типы параметров функций, реализованных в Go и в версии на языке C, практически идентичны (функции C, экспортируемые Go, не поддерживают оформление const), поэтому для пользователей функции SayHello различий не так много. Теперь можно сказать, что я фермер кода Go, который использует мышление C для программирования.
1.4 Нет меча в руке, меч в сердце
На основе модульности мы повторно реализовали функцию SayHello из спецификации языка C в Go. Теперь можно попробовать сломать идею модульного программирования: удалить заголовочный файл hello.h и объединить весь код CGO в один исходный файл Go:
БытьБыть
В настоящее время, хотя в заголовочном файле нет объявления функции, объявление функции SayHello находится в сердцах наших программистов языка Go. Мы вручную объявляем функцию SayHello в CGO с помощью extern. Затем в основной функции вызовите функцию SayHello, которая еще не существует для вывода строки. На самом деле, более 90% этого примера — это код языка Go, но мышление программирования — это язык C.
1.5 Забудь о мече в сердце
В предыдущей реализации хоть и нет меча в руке, но есть меч в сердце: при экспорте функции SayHello по-прежнему используется строковый формат языка Си. По этой причине при выводе строк на языке Go их необходимо сначала преобразовать в строки на языке C; затем при выводе строк на языке C на языке Go их необходимо преобразовать обратно в строки на языке Go; наконец, необходимо освободить промежуточный код. временное создание строки языка C. Это результат мышления программирования в уме, закрепленного строками языка C.
Нам нужно забыть исходную строковую структуру языка C: на самом деле, с точки зрения языка C, строка языка Go также является строкой специального формата.
БытьБыть
Новая реализация принимает строку в формате языка Go в качестве параметра функции SayHello, и в середине не будет дополнительных накладных расходов на преобразование строки.
Вопрос для размышления: функция main и функция SayHello выполняются в одной и той же горутине?
3. Преобразование типов
БытьБыть
В некоторых учебниках по языкам программирования в качестве определения программы используется «структура данных + алгоритм». Структура данных соответствует всем переменным, а структурированные данные — переменным, а алгоритм приближенно можно рассматривать как внутреннюю логику функции. Поэтому, как решить преобразование данных между различными типами переменных, это первая проблема, которую необходимо решить.
В языке C преобразование между различными типами осуществляется достаточно гибко, и даже обычные целые числа и указатели на функции могут свободно преобразовываться напрямую. Но в Go очень строгие ограничения на преобразование между разными типами. Указатели являются основным типом языка C, поэтому преобразование типов вокруг указателей в CGO также является первой проблемой, которую необходимо решить.
Для этой цели язык Go предоставляет небезопасный пакет для предоставления небезопасных преобразований типов. На самом деле небезопасный пакет — очень безопасный пакет, но предпосылка состоит в том, что вы должны полностью понимать лежащий в основе смысл небезопасной операции. Без пакета unsafe программирование CGO было бы невозможно!
Программирование CGO включает преобразования между указателями Go и указателями C, а также преобразования между числовыми типами и указателями. Различные типы преобразования указателя, преобразования строк и срезов, в основном основная компоновка состоит из типов указателей, числовых типов, строк и срезов.
3.1 Указатели — душа небезопасного пакета
БытьБыть
Указатели — это душа языка C и, естественно, душа небезопасного пакета. unsafe.Pointer соответствует указателю типа void языка C, который является объектом, которым должен управлять сборщик мусора сборщика мусора, uintptr является числовым указателем и не участвует в управлении сборщиком мусора. В C uintptr и указатель не сильно отличаются, но в Go это действительно совершенно разные типы. Поскольку указатель языка Go может перемещаться из-за расширения и сжатия стека, сборщик мусора будет автоматически поддерживать изменение указателя при перемещении, а переменная типа uintptr не сможет реализовать автоматическое обновление при перемещении. указатель перемещается.
3.2 небезопасный пакет
БытьБыть
БытьБыть
Каждое использование пакета unsafe имеет соответствующие функции в языке C/C++, и пользователям, знакомым с языком C, должно быть относительно легко понять его.
3.3 Структура строк и срезов Go
БытьБыть
Язык Go и язык C — это разные языки, а CGO — это мост, соединяющий их. CGO также может обеспечить совместное использование данных между ними, а в основе лежит плоская память. Таким образом, строки Go и фрагменты байтов Go с уплощенными структурами памяти будут типами, которые необходимо часто обрабатывать в CGO. Структура строк и срезов определяется в Reflect.StringHeader и Reflect.SliceHeader, а соответствующая структура языка C генерируется в CGO.
GoString и GoSlice совместимы со структурой заголовка. Это обеспечивает совместимость строк и срезов.Если строка преобразуется в срез или переворачивается, некоторая оптимизация представляет собой указатель плюс длину.
3.4 Практика: int32 и C.charуказательвзаимное преобразование
БытьБыть
Первый — это преобразование обычных целочисленных типов в типы указателей. Промежуточный тип указателя unitptr и универсальный указатель типа unsafe.Pointer должны быть оцифрованы как посредники преобразования. На основе этого метода можно привести любой числовой тип к любому типу указателя.
БытьБыть
Этот код в основном является описанием изображения.
3.5 Взаимное преобразование X и Y
БытьБыть
Тогда относительно просто преобразовать X* и Y* друг в друга, и преобразование может быть выполнено через посредника unsafe.Pointer.
БытьБыть
Это преобразование P в Q, которое затем может быть преобразовано в X* и Y*.
3.6 Преобразование между [ ]X и [ ]Y
БытьБыть
Преобразование между различными типами срезов является более сложным. Поскольку срезы являются типами значений, мы не можем преобразовать два объекта с разными типами значений, поскольку эти два типа могут иметь разный размер в памяти.
БытьБыть
Первым шагом в преобразовании различных типов значений является преобразование типа значения в тип указателя (поскольку размер указателя любого типа одинаков). Для слайсов PX и PY преобразуются в два указателя на разные типы слайсов, а базовая структура указателей слайсов соответствует одному и тому же типу Reflect.SliceHeader. Затем с помощью указателей достигается репликация различных типов заголовков слайсов, что должно обеспечить преобразование слайсов X и Y.
3.7Пример: число float64ГруппаОптимизация сортировки
БытьБыть
Например, для сортировки обычного массива float64. Если у ЦП нет инструкций для операций с плавающей запятой, мы можем отсортировать срез float64 как срез int64 (возможно, немного быстрее). Конкретный принцип таков: float64 — это число с плавающей запятой, соответствующее стандарту IEEE754, и когда число с плавающей запятой упорядочивается, оно также упорядочивается как целое число (независимо от нечисел и положительных и отрицательных нулевых проблем).
На платформе AMD64 мы используем предыдущий метод для преобразования [ ]float64 в [ ]int, а затем мы можем использовать sort.Int для сортировки чисел с плавающей запятой.
4. Вызов функции
Синтаксис самого вызова функции упрощается после подготовки аргументов правильных типов, поскольку CGO уже позаботится о низкоуровневых утомительных деталях. Есть 2 направления вызовов функций: самое распространенное — это то, что Go вызывает функцию C, затем функция C вызывает обратно функцию Go, и вложенность этих двух вызовов.
4.1 Go вызывает функции C
БытьБыть
Go вызывает C через виртуальный пакет C, и в конечном итоге C.add превратится в вызов _Cfunc_add. Неявным следствием этого является то, что все символы Go пакета C являются закрытыми и доступны только внутри текущего пакета. Следовательно, при вызовах функций между различными пакетами Go, если есть конструкция, охватывающая символ пакета C, ее практически невозможно скомпилировать (поскольку это частный тип соответствующего пакета и не может использоваться совместно).
Функция C может возвращать не более одного значения. Но C-функции в CGO могут возвращать два значения:
БытьБыть
В стандартной библиотеке языка C есть глобальная переменная errno (кажется, по одной на поток) для сохранения статуса ошибки. Таким образом, статус ошибки функций C возвращается с использованием этой глобальной переменной errno (поскольку функции C могут иметь только одно возвращаемое значение). Чтобы упростить чтение errno, CGO может возвращать errno в качестве второго возвращаемого значения.
БытьБыть
В этом примере SETERRNO сохраняет аргумент errno и возвращает его со вторым возвращающим значением. Хотя второе возвращаемое значение является типом интерфейса ошибок, нижний слой на самом деле соответствует типу SYSCall.ERRNO.
Из-за странного свойства наличия второго возвращаемого значения мы даже получаем возвращаемое значение функции C типа void:
БытьБыть
Эта функция seterrno возвращает тип void, но в качестве CGO первое возвращаемое значение является заполнителем, хотя оно и пустое, его можно вынуть. Таким образом, мы можем увидеть соответствующую реализацию Go для void в CGO: внутреннее значение соответствует байтовому типу type_ctype_void[0], который является типом с размером памяти, равным 0.
Хотя это не имеет практического применения, оно может углубить понимание лежащей в основе реализации CGO.
4.2 Экспорт функций Go
Именно потому, что функции Go можно экспортировать как функции C, экосистема CGO действительно достигла замкнутого цикла. Как говорилось в массовой линии председателя Мао: CGO позволяет фермерам, работающим с кодом Go, перейти с языка C, а также может вернуться к языку C.
БытьБыть
Экспортировать функции C на язык Go очень просто, экспортируйте и добавляйте через аннотацию экспорта. Следует отметить, что имя экспортируемой функции C должно совпадать с именем функции Go, а тип параметра и возвращаемого значения функции должен максимально использовать тип языка C.
CGO создаст заголовочный файл _cgo_export.h, через который можно ссылаться на экспортированные функции C:
БытьБыть
Хотя проще использовать автоматически сгенерированный файл _cgo_export.h, мы не рекомендуем этого делать. На самом деле, мы можем сначала создать заголовочный файл для экспортируемой функции (сначала спроектировать API, а затем реализовать API), то есть сначала определить интерфейс функции C, как это предлагается в главе «Быстрый старт». Затем функция C, определяемая пользовательской папкой заголовка C, реализуется на языке Go.
Теперь мы можем просто объявить экспортированную функцию и использовать ее:
БытьБыть
Разрыв зависимости от файла _cgo_export.h имеет еще одно преимущество: вы можете избавиться от зависимости от файла CGO. Минимизация по-прежнему является целью каждого программиста на C, потому что она значительно уменьшает соответствующие проблемы во время компиляции.
БытьБыть
Но этот пример особенный: параметром экспортируемой функции C является строковый тип языка Go, а соответствующий тип GoString на языке C определен в файле _cgo_export.h. Если вы зависите от файла _cgo_export.h, это косвенно приведет к зависимости от самого себя, то есть циклической зависимости.
Одно из решений — разделить файл и поместить определение и использование функции SayHello в отдельные файлы Go. Но для этой небольшой программы разбить ее на несколько файлов довольно сложно.
Мы не одиноки в этой борьбе, так как в мире есть и другие суслики с такой же проблемой. Поэтому в Go1.10 добавлен новый предопределенный тип _GoString_, который можно использовать для избавления от зависимости от файла _cgo_export.h:
БытьБыть
Таким образом, мы можем построить пример CGO с обратной связью, не разбивая файл.
5. Перейти внутренний механизм
cgo генерирует множество промежуточных файлов, и определение типов промежуточных файлов — это первый шаг к пониманию того, как работает cgo.
5.1 Промежуточные файлы, созданные CGO
Каждый файл CGO будет преобразован в файл Go и файл C с суффиксами .cgo1.go и .cgo2.c соответственно.
БытьБыть
Тогда _cgo_gotypes.go соответствует связующему коду, импортированному из C, к связанным функциям или переменным в языке Go. И _cgo_export.h соответствует экспортируемым функциям и типам Go, а _cgo_export.c соответствует реализации соответствующего кода упаковки.
5.2 Процесс внутреннего вызова Go->C
Сначала создайте простейший пример вызова функции C:
БытьБыть
Хотя код CGO выглядит просто, внутренняя реализация очень сложна.Ниже приведен подробный процесс вызова функции C.sun:
БытьБыть
Белая часть — это код, который мы написали сами, желтая часть — это код, сгенерированный CGO, два светло-желтых столбца слева — пространство языка Go, а правая часть — рабочее пространство языка C. В середине есть две черные горизонтальные полосы, а середина черных горизонтальных полос — это рабочее пространство языка C.
5.3 Процесс внутреннего вызова: C->Go
Создайте пример функции C, экспортированной в Go:
БытьБыть
Поток вызова памяти:
БытьБыть
Соответствующая блок-схема вызова функции:
БытьБыть
Мы не будем повторять детали в пути, а заинтересованные студенты могут изучить их самостоятельно.
6. Практика: упаковка C.qsort
БытьБыть
В связи с нехваткой времени пример qsort не будет расширен, ученики, интересующиеся CGO, могут оспорить его. Функция qsort на языке C является оболочкой для функции, аналогичной sort.Slice из стандартной библиотеки.
Дополнительные сведения см. в ppt или в соответствующей главе главы 2 книги «Расширенное программирование на языке Go».
7. Модель памяти
Описанная выше грамматическая часть является перемещением, принадлежащим CGO, и компилятор может немедленно исправить его, если возникнет проблема с перемещением. Модель памяти — это внутренняя сила CGO, если есть проблема с памятью во время выполнения, это большая проблема, и компилятор не может обнаружить это заранее.
7.1 Выравнивание структуры
БытьБыть
Почему возникает проблема с выравниванием структуры? Это связано с тем, что ЦП предъявляет требования к выравниванию для базовых типов.Только выровненные данные могут повысить эффективность, и даже только выровненные данные могут дать правильные результаты. Один принцип: каждый член каждого элемента массива структур должен быть выровнен.
Это можно игнорировать, если секция выравнивания структуры не задействована в коде C.
БытьБыть
Левая сторона — 32-битное выравнивание, а правая — 64-битное.
7.2 Куча и стек
БытьБыть
Куча и стек — обычные понятия для современных программ. Хотя в языке Go есть кучи и стеки, мы не знаем, где именно.
Я не знаю, где находится куча в языке Go, и я не знаю, где находится стек. Также неизвестно, находятся ли локальные переменные функции в стеке или в куче. Кучи и стеки даже не упоминаются в спецификации Go!
БытьБыть
Когда пользователи языка C впервые увидят функцию getv, они подумают, что это ошибка: возвращается адрес переменной в стеке, а адрес стека будет недействительным после возврата функции! Однако язык Go не является языком C, и явно неразумно использовать стандарт языка C для анализа программы на языке Go. Это так же нелепо, как и кунжутный чиновник девяти рангов, который верхним мечом предыдущей династии убил нынешнего чиновника.
В Go, если переменная должна быть в куче, она находится в куче; если переменная лучше в стеке, она может быть и в стеке.
7.3 Каково влияние GC/динамического стека?
БытьБыть
БытьБыть
Динамический стек — это особенность языка Go: не нужно беспокоиться о том, что вызов глубоко рекурсивной функции не разорвет стек. Но стоимость динамической памяти составляет (Go1.4+), при входе в каждую функцию нужно увеличивать код того, растить ли стек (производительность должна быть немного полной), при этом перемещение стека приводит к изменению адреса переменной в стеке и необходимости синхронного обновления указателя стека, а также делает невозможным передачу указателя на объект Go непосредственно в функцию C (при условии, что память не высвобождается).
7.4 Последовательно-согласованная модель памяти
БытьБыть
Модель последовательной согласованной памяти в основном связана с параллельным программированием и не сильно пересекается с CGO, поэтому здесь она не будет подробно рассматриваться.
7.5 Принципы использования указателей CGO
БытьБыть
7.6 Память C в память Go
БытьБыть
БытьБыть
После того, как память C будет выделена, она не изменится, и ее можно будет уверенно использовать после перехода в языковое пространство Go.
7.7Временно перейти к памяти c
БытьБыть
Память Go, временно переданная в функцию C в качестве параметра, действительна до тех пор, пока функция C не вернется, после чего среда выполнения Go заблокирует указанную память Go. Но за любое преимущество приходится платить: если функция C не вернется в течение 1 часа, горутина, вызывающая функцию C, будет полностью заблокирована.
7.8 C Долговременная память Go Memory
Память Go не может долго храниться в C: потому что указатели памяти Go могут измениться, а сборщик мусора не может управлять указателями Go, хранящимися в C. Компромисс состоит в том, чтобы связать изменяемый указатель с постоянным и уникальным целочисленным идентификатором переменной в языковом пространстве Go.
БытьБыть
БытьБыть
Когда указатель больше не нужен, непосредственно освободите ресурс указателя, соответствующий идентификатору:
БытьБыть
В экспортируемых параметрах функции языка C идентификатор можно рассматривать непосредственно как тип указателя. Просто идентификатор нужно распаковать в настоящий Go-указатель перед использованием указателя типа ID.
БытьБыть
Таким образом, указатели объектов Go могут передаваться по ссылке на любом языке.
8. Go обращается к объектам C++, а объекты Go экспортируются как объекты C++.
Из-за нехватки времени я не буду расширять доступ Go к объектам C++, которые являются расширениями предыдущей теории. Преобразование интерфейса C++ в интерфейс C и доступ к объектам C++ через Go. В свою очередь, объекты Go экспортируются как объекты C++ и расширяются в глобальные функции Go, экспортируются как функции языка C, а затем функции языка C упаковываются в объекты C++.
Позвольте мне показать вам более интересное использование C++ под названием «Верните мне мой бесплатный указатель C++»:
БытьБыть
Если вы используете язык Go, этот код покажется вам знакомым. Этот класс C++ не имеет членов. Я определяю обычное целое число x. При использовании x я привожу его к пользовательскому типу Int и вызываю метод Twice.
Основная хитрость здесь заключается в том, что мы вручную создаем этот параметр. Это сердце функций метода в Go: наш метод Twice также привязан к типу Int. Ограничение языка C++ состоит в том, что это фиксируется как тип указателя.Если размер исходного объекта отличается от размера указателя, он должен передаваться через тип указателя. В языке Go это извлекается как общий параметр, и пользователь может свободно выбирать тип этого параметра в соответствии со своими потребностями.
Если мы сделаем еще один шаг на основе ручного конструирования этого, то это будет динамическое создание виртуальной таблицы C++ во время выполнения, что также является способом интерфейса в языке Go.