Cách sử dụng C ++ trong Go


173

Trong ngôn ngữ Go mới , làm cách nào để gọi mã C ++? Nói cách khác, làm thế nào tôi có thể bọc các lớp C ++ của mình và sử dụng chúng trong Go?


1
Trong cuộc nói chuyện về công nghệ, SWIG đã được đề cập rất ngắn gọn, một cái gì đó như ".. cho đến khi chúng ta hoàn thành công việc .."
StackedCrooking

1
@Matt: Có khả năng anh ta muốn sử dụng thư viện C ++ hiện có mà không cần phải chuyển nó sang C hoặc Go. Tôi muốn điều tương tự.
Graeme Perrow

Tôi không thể nghĩ ra một thư viện phong nha duy nhất có sẵn cho C ++ và không dành cho C. Tôi rất muốn biết bạn đang nghĩ gì.
Matt Joiner

13
@Matt: Một ví dụ là thư viện Boost và có hàng ngàn thư viện C ++ hữu ích khác. Nhưng có lẽ tôi chỉ đang cho một con troll ở đây ...
Frank

@Matt: trong trường hợp của tôi, tôi muốn tạo giao diện Go cho thư viện máy khách hiện tại của chúng tôi nhưng thư viện chủ yếu là C ++. Chuyển nó sang C hoặc Go đơn giản không phải là một lựa chọn.
Graeme Perrow

Câu trả lời:


154

Cập nhật: Tôi đã thành công trong việc liên kết một lớp C ++ thử nghiệm nhỏ với Go

Nếu bạn bọc mã C ++ của mình bằng giao diện C, bạn sẽ có thể gọi thư viện của mình bằng cgo (xem ví dụ về gmp trong $GOROOT/misc/cgo/gmp ).

Tôi không chắc ý tưởng về một lớp trong C ++ có thực sự rõ ràng trong Go hay không, vì nó không có tính kế thừa.

Đây là một ví dụ:

Tôi có một lớp C ++ được định nghĩa là:

// 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;
}

mà tôi muốn sử dụng trong Go. Tôi sẽ sử dụng giao diện C

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

(Tôi sử dụng void*thay vì cấu trúc C để trình biên dịch biết kích thước của Foo)

Việc thực hiện là:

//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();
}

với tất cả những gì đã làm, tệp Go là:

// 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 tôi đã sử dụng để biên dịch này là:

// 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)

Hãy thử kiểm tra nó với:

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

Bạn sẽ cần cài đặt thư viện chia sẻ với make install, sau đó chạy make test. Sản lượng dự kiến ​​là:

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
Hãy cẩn thận với điều này, tôi không biết điều gì có thể xảy ra với bộ nhớ nếu bạn gửi nó giữa hai ngôn ngữ.
Scott Wales

11
Tôi phải nói rằng, ví dụ này nhắc tôi tại sao tôi muốn viết Go thuần túy. Nhìn bên C ++ lớn hơn và xấu hơn bao nhiêu. Ick.
Jeff Allen

@ScottWales bất kỳ cơ hội nào bạn có thể đã đưa điều này vào repo trên Github hay bất cứ điều gì? Tôi rất thích xem một ví dụ hoạt động
netpoetica

7
@Arne: Bạn không downvote một câu trả lời vì nó không phải là tốt nhất. Bạn downvote một câu trả lời vì nó không hữu ích. Miễn là nó hoạt động, câu trả lời này vẫn hữu ích ngay cả khi có giải pháp tốt hơn.
Graeme Perrow

Tin tốt, Go sẽ biên dịch cpp ngay bây giờ để makefile không còn cần thiết nữa. Các trình bao bọc không an toàn. Con trỏ không hoạt động đối với tôi. Một sửa đổi chút ít biên soạn cho tôi: play.golang.org/p/hKuKV51cRp go test nên làm việc mà không có makefile
Drew

47

Có vẻ như hiện tại SWIG là giải pháp tốt nhất cho việc này:

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

Nó hỗ trợ kế thừa và thậm chí cho phép phân lớp lớp C ++ với cấu trúc Go để khi các phương thức được ghi đè được gọi trong mã C ++, mã Go được kích hoạt.

Phần về C ++ trong Go FAQ được cập nhật và bây giờ đề cập đến SWIG và không còn nói " bởi vì Go là rác được thu thập nên sẽ không khôn ngoan khi làm như vậy, ít nhất là ngây thơ ".


9
Tôi ước có một cách để tăng điều này. Các câu trả lời khác đã lỗi thời. Plus SWIG đã được phiên bản lên swig.org/Doc3.0/Go.html
dragonx

34

Bạn chưa thể hoàn toàn từ những gì tôi đọc trong Câu hỏi thường gặp :

Các chương trình Go có liên kết với các chương trình C / C ++ không?

Có hai triển khai trình biên dịch Go, gc (chương trình 6g và bạn bè) và gccgo. Gc sử dụng một quy ước gọi và liên kết khác và do đó chỉ có thể được liên kết với các chương trình C sử dụng cùng một quy ước. Có một trình biên dịch C như vậy nhưng không có trình biên dịch C ++. Gccgo là một giao diện người dùng GCC, có thể, được chăm sóc, được liên kết với các chương trình C hoặc C ++ do GCC biên dịch.

Chương trình cgo cung cấp cơ chế cho giao diện chức năng nước ngoài của Cameron, cho phép gọi thư viện C an toàn từ mã Go. SWIG mở rộng khả năng này cho các thư viện C ++.



13

Tôi đã tạo ra ví dụ sau dựa trên câu trả lời của Scott Wales . Tôi đã thử nghiệm nó trong gophiên bản chạy macOS High Sierra 10.13.3go1.10 darwin/amd64 .

(1) Mã cho library.hpp, API C ++ mà chúng tôi nhắm đến.

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

(2) Mã cho library.cpp, việc thực hiện 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) Mã cho library-bridge.hcây cầu cần thiết để hiển thị CAPI được triển khai C++để gocó thể sử dụng nó.

#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) Mã cho library-bridge.cpp, việc thực hiện cầu.

#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) Cuối cùng, library.gochương trình go gọi API C ++.

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())
}

Sử dụng Makefile sau

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

Tôi có thể chạy chương trình ví dụ như sau:

$ 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)

Quan trọng

Các ý kiến ​​trên import "C"trong gochương trình KHÔNG TÙY CHỌN . Bạn phải đặt chúng chính xác như được hiển thị để cgobiết tiêu đề và thư viện nào sẽ tải, trong trường hợp này:

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

Liên kết với repo GitHub với ví dụ đầy đủ .


Cảm ơn bạn - điều này rất hữu ích!
Robert Cowham


3

Đã nói về khả năng tương tác giữa C và Go khi sử dụng trình biên dịch gcc Go, gccgo. Tuy nhiên, có những hạn chế về khả năng tương tác và bộ tính năng được triển khai của Go khi sử dụng gccgo, tuy nhiên (ví dụ, các con khỉ đột giới hạn, không có bộ sưu tập rác).


2
1. Tạo ngôn ngữ không có phương tiện để quản lý bộ nhớ thủ công, 2. Xóa bộ sưu tập rác? Tôi có phải là người duy nhất gãi đầu lúc này không?
Gyorgy Andottok

2

Bạn đang đi trên lãnh thổ chưa được khám phá ở đây. Dưới đây là ví dụ Go để gọi mã C, có lẽ bạn có thể làm một cái gì đó tương tự sau khi đọc các quy ước và gọi tên C ++ , và rất nhiều thử nghiệm và lỗi.

Nếu bạn vẫn cảm thấy muốn thử nó, chúc may mắn.


1

Vấn đề ở đây là việc triển khai tuân thủ không cần phải đặt các lớp của bạn vào tệp .cpp biên dịch. Nếu trình biên dịch có thể tối ưu hóa sự tồn tại của một lớp, miễn là chương trình hoạt động theo cùng một cách mà không có nó, thì nó có thể được bỏ qua từ đầu ra thực thi.

C có giao diện nhị phân chuẩn hóa. Do đó, bạn sẽ có thể biết rằng các chức năng của bạn đã được xuất. Nhưng C ++ không có tiêu chuẩn như vậy đằng sau nó.


1

Bạn có thể cần thêm -lc++vào LDFlagsGolang / CGo để nhận ra sự cần thiết của thư viện chuẩn.


0

Thật buồn cười là có bao nhiêu vấn đề rộng lớn hơn mà thông báo này đã được nạo vét. Dan Lyke đã có một cuộc thảo luận rất thú vị và chu đáo trên trang web của mình, Flutterby, về việc phát triển Tiêu chuẩn liên quy trình như một cách để khởi động các ngôn ngữ mới (và các phân nhánh khác, nhưng đó là ngôn ngữ chính ở đây).


0

Điều này có thể đạt được bằng cách sử dụng lệnh cgo.

Về bản chất 'Nếu việc nhập "C" ngay lập tức đi trước một nhận xét, thì nhận xét đó, được gọi là phần mở đầu, được sử dụng làm tiêu đề khi biên dịch các phần C của gói. Ví dụ: '
nguồn: https://golang.org/cmd/cgo/

// #include <stdio.h>
// #include <errno.h>
import "C"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.