วิธีใช้ C ++ ใน Go


173

ในภาษาGoใหม่ฉันจะเรียกรหัส C ++ ได้อย่างไร ในคำอื่น ๆ ฉันจะห่อคลาส C ++ ของฉันและใช้ใน Go ได้อย่างไร


1
ในการพูดคุยเรื่องเทคโนโลยี SWIG นั้นถูกกล่าวถึงอย่างสั้น ๆ บางอย่างเช่น "..
จนกว่า

1
@Matt: มีแนวโน้มว่าเขาต้องการใช้ไลบรารี C ++ ที่มีอยู่โดยไม่ต้องย้ายไปที่ C หรือ Go ฉันต้องการสิ่งเดียวกัน
แกรมเพอร์โรว์

ฉันไม่สามารถนึกถึงห้องสมุดดีๆที่มีให้สำหรับ C ++ และไม่ใช่สำหรับ C. ฉันชอบที่จะรู้ว่าคุณมีอะไรในใจ
Matt Joiner

13
@Matt: ตัวอย่างหนึ่งคือห้องสมุด Boost และมีห้องสมุด C ++ ที่มีประโยชน์อื่นอีกหลายพันรายการ แต่บางทีฉันแค่ให้อาหารโทรลล์ที่นี่ ...
แฟรงก์

@Matt: ในกรณีของฉันฉันต้องการสร้างอินเทอร์เฟซแบบไปยังไลบรารีไคลเอ็นต์ที่มีอยู่ของเรา แต่ไลบรารีส่วนใหญ่คือ C ++ การย้ายไปที่ C หรือ Go ไม่ใช่ตัวเลือก
แกรมเพอร์โรว์

คำตอบ:


154

อัปเดต:ฉันประสบความสำเร็จในการเชื่อมโยงคลาส C ++ ทดสอบขนาดเล็กกับ Go

หากคุณใส่รหัส C ++ ด้วยอินเตอร์เฟส C คุณควรจะสามารถเรียกไลบรารีของคุณด้วย cgo (ดูตัวอย่างของ gmp in $GOROOT/misc/cgo/gmp)

ฉันไม่แน่ใจว่าแนวคิดของคลาสใน C ++ นั้นชัดเจนใน Go หรือไม่เนื่องจากไม่มีการสืบทอด

นี่คือตัวอย่าง:

ฉันมีคลาส C ++ ที่กำหนดเป็น:

// foo.hpp
class cxxFoo {
public:
  int a;
  cxxFoo(int _a):a(_a){};
  ~cxxFoo(){};
  void Bar();
};

// foo.cpp
#include <iostream>
#include "foo.hpp"
void
cxxFoo::Bar(void){
  std::cout<<this->a<<std::endl;
}

ซึ่งฉันต้องการใช้ใน Go ฉันจะใช้ส่วนต่อประสาน C

// foo.h
#ifdef __cplusplus
extern "C" {
#endif
  typedef void* Foo;
  Foo FooInit(void);
  void FooFree(Foo);
  void FooBar(Foo);
#ifdef __cplusplus
}
#endif

(ฉันใช้void*แทน C โครงสร้างดังนั้นคอมไพเลอร์รู้ขนาดของ Foo)

การดำเนินการคือ:

//cfoo.cpp
#include "foo.hpp"
#include "foo.h"
Foo FooInit()
{
  cxxFoo * ret = new cxxFoo(1);
  return (void*)ret;
}
void FooFree(Foo f)
{
  cxxFoo * foo = (cxxFoo*)f;
  delete foo;
}
void FooBar(Foo f)
{
  cxxFoo * foo = (cxxFoo*)f;
  foo->Bar();
}

เมื่อทำเสร็จแล้วไฟล์ Go คือ:

// foo.go
package foo
// #include "foo.h"
import "C"
import "unsafe"
type GoFoo struct {
     foo C.Foo;
}
func New()(GoFoo){
     var ret GoFoo;
     ret.foo = C.FooInit();
     return ret;
}
func (f GoFoo)Free(){
     C.FooFree(unsafe.Pointer(f.foo));
}
func (f GoFoo)Bar(){
     C.FooBar(unsafe.Pointer(f.foo));
}

makefile ที่ฉันใช้รวบรวมนี่คือ:

// makefile
TARG=foo
CGOFILES=foo.go
include $(GOROOT)/src/Make.$(GOARCH)
include $(GOROOT)/src/Make.pkg
foo.o:foo.cpp
    g++ $(_CGO_CFLAGS_$(GOARCH)) -fPIC -O2 -o $@ -c $(CGO_CFLAGS) $<
cfoo.o:cfoo.cpp
    g++ $(_CGO_CFLAGS_$(GOARCH)) -fPIC -O2 -o $@ -c $(CGO_CFLAGS) $<
CGO_LDFLAGS+=-lstdc++
$(elem)_foo.so: foo.cgo4.o foo.o cfoo.o
    gcc $(_CGO_CFLAGS_$(GOARCH)) $(_CGO_LDFLAGS_$(GOOS)) -o $@ $^ $(CGO_LDFLAGS)

ลองทดสอบด้วย:

// foo_test.go
package foo
import "testing"
func TestFoo(t *testing.T){
    foo := New();
    foo.Bar();
    foo.Free();
}

คุณจะต้องติดตั้งไลบรารี่ที่มีการติดตั้งแล้วทำการทดสอบ ผลลัพธ์ที่คาดหวังคือ:

gotest
rm -f _test/foo.a _gotest_.6
6g -o _gotest_.6 foo.cgo1.go foo.cgo2.go foo_test.go
rm -f _test/foo.a
gopack grc _test/foo.a _gotest_.6  foo.cgo3.6
1
PASS

1
ระวังสิ่งนี้ฉันไม่รู้ว่าจะเกิดอะไรขึ้นกับหน่วยความจำหากคุณส่งระหว่างสองภาษา
สกอตต์เวลส์

11
ฉันต้องบอกว่าตัวอย่างนี้ทำให้ฉันนึกถึงว่าทำไมฉันถึงต้องการเขียน Pure Go ดูว่า C ++ ใหญ่กว่าและน่าเกลียดกว่าเท่าไหร่ ick
Jeff Allen

@ScottWales โอกาสใด ๆ ที่คุณอาจนำสิ่งนี้มาไว้ในธุรกรรมซื้อคืนบน Github หรืออะไรก็ตาม? ฉันชอบที่จะเห็นตัวอย่างการทำงาน
netpoetica

7
@Arne: คุณไม่ลงคะแนนตอบเพราะมันไม่ได้ดีที่สุด คุณลงคะแนนคำตอบเพราะมันไม่เป็นประโยชน์ ตราบใดที่ยังใช้งานได้คำตอบนี้ยังคงมีประโยชน์แม้ว่าจะมีวิธีแก้ปัญหาที่ดีกว่า
แกรมเพอร์โรว์

ข่าวดี Go จะรวบรวม cpp ในขณะนี้ดังนั้นจึงไม่จำเป็นต้องใช้ makefile อีกต่อไป ผู้ห่อหุ้มตัวที่ไม่ปลอดภัยไม่ทำงานสำหรับฉัน การปรับเปลี่ยนเล็กน้อยที่รวบรวมสำหรับฉัน: play.golang.org/p/hKuKV51cRp go testควรทำงานโดยไม่ต้องใช้ makefile
Drew

47

ดูเหมือนว่า SWIG ในปัจจุบันเป็นทางออกที่ดีที่สุดสำหรับสิ่งนี้:

http://www.swig.org/Doc2.0/Go.html

สนับสนุนการสืบทอดและยังอนุญาตให้คลาส C ++ คลาสย่อยพร้อมโครงสร้าง Go ดังนั้นเมื่อเมธอด overridden ถูกเรียกใช้ในรหัส C ++ โค้ด Go จะถูกใช้

มาตราเกี่ยวกับ C ++ ไปในคำถามที่พบบ่อยมีการปรับปรุงและตอนนี้กล่าวถึง SWIG และไม่ได้บอกว่า " เพราะไปเป็นขยะที่เก็บรวบรวมมันจะฉลาดที่จะทำเช่นนั้นอย่างน้อยไร้เดียงสา "


9
ฉันหวังว่าจะมีวิธีการชนนี้ คำตอบอื่น ๆ จะล้าสมัย Plus SWIG ได้อัปเดตรุ่น swig.org/Doc3.0/Go.html แล้ว
dragonx

34

คุณยังไม่สามารถอ่านสิ่งที่ฉันได้จากคำถามที่พบบ่อย :

โปรแกรม Go เชื่อมโยงกับโปรแกรม C / C ++ หรือไม่

มีการใช้งานคอมไพเลอร์สองรายการคือ gc (โปรแกรม 6g และเพื่อน ๆ ) และ gccgo Gc ใช้ระเบียบการประชุมและการเรียกที่แตกต่างกันดังนั้นจึงสามารถเชื่อมโยงกับโปรแกรม C โดยใช้การประชุมเดียวกัน มีคอมไพเลอร์ C แต่ไม่มีคอมไพเลอร์ C ++ Gccgo เป็นส่วนหน้าของ GCC ที่สามารถเชื่อมโยงกับโปรแกรม C หรือ C ++ ที่คอมไพล์ด้วย GCC

โปรแกรม cgo จัดเตรียมกลไกสำหรับ "อินเตอร์เฟสฟังก์ชั่นต่างประเทศ" เพื่ออนุญาตการโทรที่ปลอดภัยของไลบรารี C จากโค้ด Go SWIG ขยายขีดความสามารถนี้ไปยังไลบรารี C ++



13

ฉันได้สร้างตัวอย่างต่อไปนี้ขึ้นอยู่กับคำตอบของสกอตต์ของเวลส์ ฉันทดสอบใน macOS High Sierra 10.13.3 goเวอร์ชันที่ใช้งานgo1.10 darwin/amd64แล้ว

(1) รหัสสำหรับlibrary.hppC ++ API ที่เราตั้งใจจะโทร

#pragma once
class Foo {
 public:
  Foo(int value);
  ~Foo();
  int value() const;    
 private:
  int m_value;
};

(2) รหัสสำหรับlibrary.cppการติดตั้ง C ++

#include "library.hpp"
#include <iostream>

Foo::Foo(int value) : m_value(value) {
  std::cout << "[c++] Foo::Foo(" << m_value << ")" << std::endl;
}

Foo::~Foo() { std::cout << "[c++] Foo::~Foo(" << m_value << ")" << std::endl; }

int Foo::value() const {
  std::cout << "[c++] Foo::value() is " << m_value << std::endl;
  return m_value;
}

(3) รหัสสำหรับlibrary-bridge.hบริดจ์จำเป็นต้องเปิดเผยCAPI ที่ถูกนำไปใช้C++เพื่อให้goสามารถใช้งานได้

#pragma once
#ifdef __cplusplus
extern "C" {
#endif

void* LIB_NewFoo(int value);
void LIB_DestroyFoo(void* foo);
int LIB_FooValue(void* foo);

#ifdef __cplusplus
}  // extern "C"
#endif

(4) รหัสสำหรับlibrary-bridge.cppการดำเนินการของสะพาน

#include <iostream>

#include "library-bridge.h"
#include "library.hpp"

void* LIB_NewFoo(int value) {
  std::cout << "[c++ bridge] LIB_NewFoo(" << value << ")" << std::endl;
  auto foo = new Foo(value);
  std::cout << "[c++ bridge] LIB_NewFoo(" << value << ") will return pointer "
            << foo << std::endl;
  return foo;
}

// Utility function local to the bridge's implementation
Foo* AsFoo(void* foo) { return reinterpret_cast<Foo*>(foo); }

void LIB_DestroyFoo(void* foo) {
  std::cout << "[c++ bridge] LIB_DestroyFoo(" << foo << ")" << std::endl;
  AsFoo(foo)->~Foo();
}

int LIB_FooValue(void* foo) {
  std::cout << "[c++ bridge] LIB_FooValue(" << foo << ")" << std::endl;
  return AsFoo(foo)->value();
}

(5) ในที่สุดlibrary.goโปรแกรมไปเรียก C ++ API

package main

// #cgo LDFLAGS: -L. -llibrary
// #include "library-bridge.h"
import "C"
import "unsafe"
import "fmt"

type Foo struct {
    ptr unsafe.Pointer
}

func NewFoo(value int) Foo {
    var foo Foo
    foo.ptr = C.LIB_NewFoo(C.int(value))
    return foo
}

func (foo Foo) Free() {
    C.LIB_DestroyFoo(foo.ptr)
}

func (foo Foo) value() int {
    return int(C.LIB_FooValue(foo.ptr))
}

func main() {
    foo := NewFoo(42)
    defer foo.Free() // The Go analog to C++'s RAII
    fmt.Println("[go]", foo.value())
}

การใช้ Makefile ต่อไปนี้

liblibrary.so: library.cpp library-bridge.cpp
    clang++ -o liblibrary.so library.cpp library-bridge.cpp \
    -std=c++17 -O3 -Wall -Wextra -fPIC -shared

ฉันสามารถรันโปรแกรมตัวอย่างได้ดังนี้:

$ make
clang++ -o liblibrary.so library.cpp library-bridge.cpp \
    -std=c++17 -O3 -Wall -Wextra -fPIC -shared
$ go run library.go
[c++ bridge] LIB_NewFoo(42)
[c++] Foo::Foo(42)
[c++ bridge] LIB_NewFoo(42) will return pointer 0x42002e0
[c++ bridge] LIB_FooValue(0x42002e0)
[c++] Foo::value() is 42
[go] 42
[c++ bridge] LIB_DestroyFoo(0x42002e0)
[c++] Foo::~Foo(42)

สำคัญ

ความเห็นดังกล่าวข้างต้นimport "C"ในgoโปรแกรมมีไม่บังคับ คุณต้องวางอย่างถูกต้องตามที่แสดงเพื่อให้cgoทราบว่าส่วนหัวและไลบรารีใดที่จะโหลดในกรณีนี้:

// #cgo LDFLAGS: -L. -llibrary
// #include "library-bridge.h"
import "C"

เชื่อมโยงไปยัง GitHub repo กับตัวอย่างเต็มรูปแบบ


ขอบคุณ - มันมีประโยชน์มาก!
Robert Cowham

11

ดูเหมือนว่าเป็นหนึ่งในคำถามที่ถามมาก่อนเกี่ยวกับ Golang และในเวลาเดียวกันก็ไม่ต้องอัพเดท ในช่วงสามถึงสี่ปีที่ผ่านมาห้องสมุดและโพสต์บล็อกจำนวนมากเกินไปได้หมดไปแล้ว ลิงค์ด้านล่างมีบางสิ่งที่ฉันรู้สึกว่ามีประโยชน์

SWIG และไป

เรียกรหัส C ++ จากการเดินทางด้วย SWIG

เมื่อเปรียบเทียบภาษา C ++ และ Go

GoForCPPProgrammers


3

มีการพูดคุยเกี่ยวกับการทำงานร่วมกันระหว่าง C และ Goเมื่อใช้คอมไพเลอร์ gcc Go, gccgo มีข้อ จำกัด ทั้งต่อการทำงานร่วมกันและชุดคุณลักษณะที่นำไปใช้ของ Go เมื่อใช้ gccgo อย่างไรก็ตาม (เช่น goroutines ที่ จำกัด ไม่มีการรวบรวมขยะ)


2
1. สร้างภาษาที่ไม่มีระบบอำนวยความสะดวกสำหรับการจัดการหน่วยความจำแบบแมนนวล 2. ลบการรวบรวมขยะ ฉันเป็นคนเดียวที่เกาหัวของฉันที่นี้
György Andrasek

2

คุณกำลังเดินบนดินแดนที่ไม่ได้จดที่นี่ นี่คือตัวอย่าง Go สำหรับการเรียกรหัส C บางทีคุณสามารถทำสิ่งนี้ได้หลังจากอ่านชื่อ C ++ ชื่อ manglingและการเรียกการประชุมและการลองผิดลองถูกมากมาย

หากคุณยังคงรู้สึกอยากลองมันโชคดี


1

ปัญหาที่นี่คือการใช้งานตามมาตรฐานไม่จำเป็นต้องใส่ชั้นเรียนของคุณในไฟล์. cpp รวบรวม หากคอมไพเลอร์สามารถออปติไมซ์การมีอยู่ของคลาสได้ตราบใดที่โปรแกรมทำงานในลักษณะเดียวกันหากไม่มีมันก็จะถูกตัดออกจากเอาต์พุตที่รันได้

C มีอินเตอร์เฟสไบนารีมาตรฐาน ดังนั้นคุณจะสามารถทราบได้ว่าฟังก์ชั่นของคุณถูกส่งออก แต่ C ++ ไม่มีมาตรฐานดังกล่าวอยู่ข้างหลัง


1

คุณอาจต้องเพิ่มลง-lc++ในLDFlagsสำหรับ Golang / CGo เพื่อรับรู้ถึงความต้องการห้องสมุดมาตรฐาน


0

ตลกว่ามีการปล่อยข่าวในวงกว้างออกมามากมาย Dan Lyke มีการพูดคุยอย่างสนุกสนานและครุ่นคิดบนเว็บไซต์ของเขา Flutterby เกี่ยวกับการพัฒนามาตรฐานการประมวลผลเป็นวิธีการเริ่มต้นใหม่ของภาษา (และเครือข่ายอื่น ๆ


0

สามารถทำได้โดยใช้คำสั่ง cgo

ในสาระสำคัญ 'หากการนำเข้า "C" นำหน้าด้วยความคิดเห็นทันทีความคิดเห็นนั้นเรียกว่าคำนำจะถูกใช้เป็นส่วนหัวเมื่อรวบรวมชิ้นส่วน C ของแพ็คเกจ ตัวอย่างเช่น: '
source: https://golang.org/cmd/cgo/

// #include <stdio.h>
// #include <errno.h>
import "C"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.