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?
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?
Câu trả lời:
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
go test
nên làm việc mà không có makefile
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ơ ".
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 ++.
Kể từ go1.2 +, cgo tự động kết hợp và biên dịch mã C ++:
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 go
phiê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.h
cây cầu cần thiết để hiển thị C
API được triển khai C++
để go
có 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.go
chươ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 go
chươ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ị để cgo
biế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"
Có vẻ đó là một trong những câu hỏi đầu tiên về Golang. Và đồng thời trả lời để không bao giờ cập nhật. Trong ba đến bốn năm này, quá nhiều thư viện và bài đăng blog mới đã được đưa ra. Dưới đây là một vài liên kết những gì tôi cảm thấy hữu ích.
Đã 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).
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ó.
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).
Đ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"