Как использовать C++ код в Go: вызов функций и классов из C++ через C
Правильный подход: C-обёртка для C++ кода
Проблема в том, что cgo работает только с C-совместимыми функциями (extern "C"). C++ имеет искажение имён (name mangling), свои ABI и исключения, которые cgo не поддерживает. Решение — создать прослойку на C, которая инкапсулирует C++ код. Вот пошаговая инструкция с рабочим примером.
Шаг 1: Создаём C-обёртку для C++ класса
Сначала напишем C++ код, который мы хотим использовать. Файл image_processor.cpp:
// image_processor.cpp
#include "image_processor.h"
#include <vector>
int ImageProcessor::process(unsigned char* data, int size) {
std::vector<unsigned char> buffer(data, data + size);
// Имитация обработки: возвращаем сумму байтов
int sum = 0;
for (unsigned char b : buffer) {
sum += b;
}
return sum;
}
Теперь создаём C-обёртку. Файл wrapper.h:
// wrapper.h
#ifdef __cplusplus
extern "C" {
#endif
// Opaque указатель на C++ объект
typedef void* ImageProcessorHandle;
// Функции C-обёртки
ImageProcessorHandle image_processor_new();
void image_processor_delete(ImageProcessorHandle handle);
int image_processor_process(ImageProcessorHandle handle, unsigned char* data, int size);
#ifdef __cplusplus
}
#endif
И реализация обёртки — файл wrapper.cpp:
// wrapper.cpp
#include "wrapper.h"
#include "image_processor.h"
ImageProcessorHandle image_processor_new() {
return new ImageProcessor();
}
void image_processor_delete(ImageProcessorHandle handle) {
delete static_cast<ImageProcessor*>(handle);
}
int image_processor_process(ImageProcessorHandle handle, unsigned char* data, int size) {
ImageProcessor* proc = static_cast<ImageProcessor*>(handle);
return proc->process(data, size);
}
Шаг 2: Сборка C++ кода в статическую библиотеку
Скомпилируем C++ файлы в статическую библиотеку .a. Команды для Linux/macOS:
g++ -c -fPIC image_processor.cpp -o image_processor.o
g++ -c -fPIC wrapper.cpp -o wrapper.o
ar rcs libimageprocessor.a image_processor.o wrapper.o
Шаг 3: Использование в Go через cgo
Теперь в Go-файле подключаем библиотеку через cgo:
// main.go
package main
/*
#cgo LDFLAGS: -L. -limageprocessor -lstdc++
#include "wrapper.h"
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
// Создаём экземпляр C++ класса через обёртку
handle := C.image_processor_new()
if handle == nil {
fmt.Println("Failed to create ImageProcessor")
return
}
defer C.image_processor_delete(handle)
// Подготавливаем данные
data := []byte{1, 2, 3, 4, 5}
cData := (*C.uchar)(unsafe.Pointer(&data[0]))
cSize := C.int(len(data))
// Вызываем метод
result := C.image_processor_process(handle, cData, cSize)
fmt.Printf("Result: %d\n", int(result))
}
Важные моменты:
- Управление памятью: C++ объекты создаются через
newи удаляются черезdeleteв C-обёртке. В Go используйтеdeferдля гарантированного освобождения. - Обработка исключений: C++ исключения не должны пересекать границу C. В обёртке ловите все исключения и возвращайте код ошибки через C-функцию.
- Передача строк: Для строк используйте
const char*и конвертируйте в Go черезC.GoString(). - Сборка: Укажите
-lstdc++для линковки с C++ стандартной библиотекой.
Полный пример с обработкой ошибок
Дополним обёртку поддержкой ошибок. В wrapper.h добавим:
typedef struct {
int result;
const char* error;
} ProcessResult;
ProcessResult image_processor_process_safe(ImageProcessorHandle handle, unsigned char* data, int size);
В wrapper.cpp:
ProcessResult image_processor_process_safe(ImageProcessorHandle handle, unsigned char* data, int size) {
ProcessResult res = {0, nullptr};
try {
ImageProcessor* proc = static_cast<ImageProcessor*>(handle);
res.result = proc->process(data, size);
} catch (const std::exception& e) {
res.error = strdup(e.what());
} catch (...) {
res.error = strdup("Unknown C++ exception");
}
return res;
}
В Go обрабатываем результат и освобождаем строку ошибки.
Этот подход позволяет безопасно и эффективно использовать любой C++ код в Go-проектах. Главное — чётко разделить границу через C и аккуратно управлять памятью.
<<Альтернатива: использование SWIG для автоматической генерации обёрток
Если ваш C++ проект большой и вы не хотите писать C-обёртку вручную, используйте SWIG (Simplified Wrapper and Interface Generator). SWIG умеет генерировать cgo-совместимые обёртки для C++ классов.
Пример файла интерфейса SWIG (image_processor.i):
%module imageprocessor
%{
#include "image_processor.h"
%}
%include "image_processor.h"
Генерация обёртки:
swig -go -cgo -intgosize 64 image_processor.i
g++ -c -fPIC image_processor.cpp image_processor_wrap.cxx -o image_processor_wrap.o
ar rcs libimageprocessor.a image_processor_wrap.o
После этого в Go можно писать:
package main
import "example.com/imageprocessor"
func main() {
proc := imageprocessor.NewImageProcessor()
result := proc.Process([]byte{1, 2, 3})
proc.Delete()
}
Плюсы: автоматическая генерация, поддержка сложных типов (шаблоны, STL).
Минусы: SWIG генерирует много кода, требует настройки, может быть избыточен для простых случаев.
Для небольших проектов проще написать C-обёртку вручную, как в первом ответе.
Ответов пока нет.
Войдите чтобы оставить ответ