Building shared libraries in Go: Part 2
Summary
In part 1 we called a very simple Go shared library from Python. Let’s do a more complex example, passing string
and []byte
, from C++, and getting back a []byte
.
Calling Go from C++
Save the following as concat/main.go
:
package main
import "C"
//export Concat
func Concat(sIn string, bIn []byte, bOut []byte) {
n := copy(bOut, sIn)
copy(bOut[n:], bIn)
}
func main() {}
We add the import "C"
so that cgo
gives us a header file to #include
. Build the shared library and header:
go build -buildmode=c-shared -o libconcat.so concat
In libconcat.h
we have this signature:
extern void Concat(GoString p0, GoSlice p1, GoSlice p2)
GoString
and GoSlice
are defined further up in the header file like this:
typedef struct { char *p; GoInt n; } GoString;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
Copy libconcat.so
to /usr/lib/
(or wherever your libraries live). Now let’s call Concat
from C++:
#include <vector>
#include <string>
#include <iostream>
#include "libconcat.h"
int main() {
std::string s_in {"Hello "};
std::vector<char> v_in {'W', 'o', 'r', 'l', 'd'};
std::vector<char> v_out(11);
GoString go_s_in{&s_in[0], static_cast<GoInt>(s_in.size())};
GoSlice go_v_in{
v_in.data(),
static_cast<GoInt>(v_in.size()),
static_cast<GoInt>(v_in.size()),
};
GoSlice go_v_out{
v_out.data(),
static_cast<GoInt>(v_out.size()),
static_cast<GoInt>(v_out.size()),
};
Concat(go_s_in, go_v_in, go_v_out);
for(auto& c : v_out) {
std::cout << c;
}
std::cout << '\n';
}
Save that as concat.cpp
. Copy libconcat.h
into the same directory. Build and run:
g++ --std=c++14 concat.cpp -o concat -lconcat
./concat
I need the static_cast<GoInt>
because a GoInt
is a signed type (long long
on my machine), but size()
returns unsigned type size_t
. Apart from wrapping things in Go[String|Slice|etc]
, this is exactly like calling an ordinary shared libary, because that’s what we built, an ordinary shared library.
The one very important caveat is that you should not pass pointers to Go allocated memory back to the C++ side; that’s why we use an output parameter (v_out
). Even if you maintain a reference to it on the Go side so that the garbage collector doesn’t reclaim it, the Go runtime reserves the right to move that memory if they build a copying garbage collector. Lots of details in issue #8310.
Passing C++ allocated memory to Go is fine. Go will not garbage collect memory it did not allocate. There rules here are the same as for cgo.