Go에서 C ++를 사용하는 방법


173

새로운 Go 언어에서는 C ++ 코드를 어떻게 호출합니까? 즉, C ++ 클래스를 어떻게 래핑하여 Go에서 사용할 수 있습니까?


1
테크 토크에서 꿀꺽 꿀꺽은 아주 간단하게, 같은 언급 한 "우리가 꿀꺽 꿀꺽 완수 ..until을 ..."
StackedCrooked

1
@Matt : 기존 C ++ 라이브러리를 C 또는 Go로 이식하지 않고 사용하려고합니다. 나는 같은 것을 원했다.
Graeme Perrow

C ++이 아닌 C ++에서 사용할 수있는 단일 라이브러리를 생각할 수 없습니다. 나는 당신이 생각하고있는 것을 알고 싶습니다.
Matt Joiner

13
@Matt : Boost 라이브러리 중 하나는 수천 가지 유용한 C ++ 라이브러리가 있습니다. 하지만 어쩌면 난 그냥 ... 여기 트롤을 공급하고 있습니다
프랭크

@ 매트 : 제 경우에는 기존 클라이언트 라이브러리에 Go 인터페이스를 만들고 싶었지만 라이브러리는 주로 C ++입니다. C 또는 Go로 포팅하는 것은 단순히 옵션이 아닙니다.
Graeme Perrow

답변:


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*컴파일러가 Foo의 크기를 알 수 있도록 C 구조체 대신 a 를 사용합니다 )

구현은 다음과 같습니다.

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

make install로 공유 라이브러리를 설치 한 다음 make test를 실행해야합니다. 예상되는 결과는 다음과 같습니다.

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
이 점에주의하십시오. 두 언어 사이에 메모리를 보내면 메모리에 어떤 일이 일어날 지 전혀 모릅니다.
Scott Wales

11
이 예제는 왜 순수 Go를 작성하고 싶은지를 상기시켜줍니다. C ++ 쪽이 얼마나 크고 더 못생긴 지보십시오. Ick.
Jeff Allen

@ScottWales Github의 레포지토리에 넣었을 가능성이 있습니까? 나는 실례를보고
싶다

7
@ Arne : 당신은 대답이 최고가 아니기 때문에 downvote하지 않습니다. 도움이되지 않기 때문에 답을 공감합니다. 작동하는 한 더 나은 솔루션이 있어도이 답변은 여전히 ​​유용합니다.
Graeme Perrow 13:29에

희소식, Go는 이제 cpp를 컴파일하므로 makefile이 더 이상 필요하지 않습니다. 안전하지 않은 포인터 래퍼가 작동하지 않았습니다. 나를 위해 컴파일 된 약간의 수정 : play.golang.org/p/hKuKV51cRp go test 는 makefile없이 작동해야합니다
Drew

47

현재 SWIG가 가장 적합한 솔루션 인 것 같습니다.

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

상속을 지원하고 심지어 Go 구조체를 사용하여 C ++ 클래스를 서브 클래 싱 할 수 있으므로 C ++ 코드에서 재정의 된 메서드를 호출하면 Go 코드가 시작됩니다.

Go FAQ의 C ++에 대한 섹션 이 업데이트되었으며 이제 SWIG를 언급하고 더 이상 " Go는 가비지 수집되므로 적어도 순진하게 그렇게하는 것이 현명 하지 않습니다"라고 말하지 않습니다 .


9
나는 이것을 부술 수있는 방법이 있었으면 좋겠다. 다른 답변은 구식입니다. 또한 SWIG의 버전은 swig.org/Doc3.0/Go.html
dragonx

34

FAQ에서 읽은 내용을 아직 읽을 수 없습니다 .

Go 프로그램은 C / C ++ 프로그램과 연결됩니까?

gc (6g 프로그램 및 친구)와 gccgo의 두 가지 Go 컴파일러 구현이 있습니다. Gc는 다른 호출 규칙과 링커를 사용하므로 동일한 규칙을 사용하여 C 프로그램과 만 연결할 수 있습니다. 이러한 C 컴파일러는 있지만 C ++ 컴파일러는 없습니다. Gccgo는주의해서 GCC 컴파일 C 또는 C ++ 프로그램과 연결될 수있는 GCC 프론트 엔드입니다.

cgo 프로그램은 Go 코드에서 C 라이브러리를 안전하게 호출 할 수 있도록 "외부 기능 인터페이스"메커니즘을 제공합니다. SWIG는이 기능을 C ++ 라이브러리로 확장합니다.



13

Scott Wales의 답변을 기반으로 다음 예제를 만들었습니다 . macOS High Sierra 10.13.3 running goversion 에서 테스트했습니다 go1.10 darwin/amd64.

(1) library.hpp우리가 호출하고자하는 C ++ API를 위한 코드 .

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

(2) library.cppC ++ 구현을 위한 코드 .

#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++있도록 구현 된 API 를 공개 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.goC ++ API를 호출하는 go 프로그램입니다.

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 리포지토리에 연결하십시오 .


감사합니다-이것은 매우 도움이되었습니다!
Robert Cowham


3

gcc Go 컴파일러 인 gccgo를 사용할 때 C와 Go의 상호 운용성에 대해 이야기 합니다. 그러나 gccgo를 사용할 때 상호 운용성 및 구현 된 Go 기능 세트에는 제한이 있습니다 (예 : 제한된 고 루틴, 가비지 콜렉션 없음).


2
1. 수동 메모리 관리 기능이없는 언어를 만듭니다. 2. 가비지 수집을 제거 하시겠습니까? 나는 이것에 나의 머리를 긁는 유일한 사람인가?
György Andrasek

2

미지의 영역을 걷고 있습니다. 다음 은 C 코드 호출에 대한 Go 예제입니다. 아마도 C ++ 이름 맹 글링 및 호출 규칙과 많은 시행 착오를 읽은 후 이와 같은 작업을 수행 할 수 있습니다 .

여전히 시도해보고 싶다면 행운을 빈다.


1

여기서 문제는 호환 구현이 클래스를 컴파일 .cpp 파일에 넣을 필요가 없다는 것입니다. 컴파일러가 클래스의 존재를 최적화 할 수 있으면 프로그램이 클래스없이 동일한 방식으로 작동하는 한 출력 실행 파일에서 생략 할 수 있습니다.

C에는 표준화 된 이진 인터페이스가 있습니다. 따라서 함수가 내보내 졌음을 알 수 있습니다. 그러나 C ++에는 그 뒤에 표준이 없습니다.


1

당신은 추가해야 할 수 있습니다 -lc++받는 LDFlagsGolang / 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.