Calling Go Functions from c++

· 1087 words · 6 minute read

As programmers we sometimes need to do weird stuff or make two systems work together that really should not interoperate. As is the case with c++ and go in the way of calling functions written in go from c++. But I didnt ask if i should, i just asked if i could and the result is documented in the form of this blog post and the repo here.

Project setup ##

The flow of our project will be to compile the go file to an archive with the go toolchain. We will then use the gnu c++ compiler to link our c++ code calling functions from the go library with the compiled archive.

Compiling a go file to an archive:

1go build -buildmode=c-archive -o lib.a lib.go

Compiling a c++ file and linking it against the afore compiled archive:

1g++ -Wall main.cpp lib.a -o main

To make our iteration loop faster we will use make and insert the invocation of the resulting binary to our example target:

1example:
2	go build -buildmode=c-archive -o lib.a lib.go
3	g++ -Wall main.cpp lib.a -o main
4	./main

Simple example ##

Tip

If you are interested in the code without any explanations, take a look here. For documentation around the way one can interact with the go runtime from c or c++, check out Go Wiki: cgo and cgo command.

We will start with some easy stuff and wrap the go standard library methods for the square root and the n-th power, export them and call them from c++.

The first step is to create both lib.go and main.cpp and fill them with the following boilerplate:

1// lib.go
2package main
3
4import "C"
5import ()
6
7func main() {}
1// main.cpp
2#include "lib.h"
3int main() {}

We can now check if our project setup works:

1$ make
2go build -buildmode=c-archive -o lib.a lib.go
3g++ -Wall main.cpp lib.a -o main
4./main

Lets now wrap both math.Sqrt and math.Pow in go functions, export them and invoke them from c++ to get a feel for the interaction logic. Starting of with the definition in lib.go:

 1package main
 2
 3import "C"
 4import (
 5	"math"
 6)
 7
 8//export Sqrt
 9func Sqrt(n float64) float64 {
10	return math.Sqrt(n)
11}
12
13//export Pow
14func Pow(x float64, y float64) float64 {
15	return math.Pow(x, y)
16}
17
18func main() {}

All exported functions need to start with an uppercase letter (as defined in the go spec) and require a comment above them with the name of the function it should be invoked by from the c++ context.

We can now modify our c++ and create a small function to call the functions we defined above:

 1#include "lib.h"
 2#include <iostream>
 3
 4void maths() {
 5  GoFloat64 sqrt = Sqrt(25.0);
 6  std::cout << "sqrt(25.0)=" << sqrt << std::endl;
 7  GoFloat64 pow = Pow(5, 3);
 8  std::cout << "power(5, 3)=" << pow << std::endl;
 9}
10
11int main() {
12  maths();
13}

Go types in c

We use GoFloat64 to interact with the result of the invoked go function. GoFloat64 is defined as:

1typedef double GoFloat64;

Other types are:

 1// ...
 2typedef signed char GoInt8;
 3typedef unsigned char GoUint8;
 4typedef short GoInt16;
 5typedef unsigned short GoUint16;
 6typedef int GoInt32;
 7typedef unsigned int GoUint32;
 8typedef long long GoInt64;
 9typedef unsigned long long GoUint64;
10typedef GoInt64 GoInt;
11typedef GoUint64 GoUint;
12typedef size_t GoUintptr;
13typedef float GoFloat32;
14// ...

These definitions are generated by the go toolchain for the archive build in the lib.h file, upon first compiling the archive. After 70 lines of boilerplate we can see the output generated for our lib.go file:

 1#ifdef __cplusplus
 2extern "C" {
 3#endif
 4
 5extern GoFloat64 Sqrt(GoFloat64 n);
 6extern GoFloat64 Pow(GoFloat64 x, GoFloat64 y);
 7
 8#ifdef __cplusplus
 9}
10#endif

Running our example via make:

1$ make
2go build -buildmode=c-archive -o lib.a lib.go
3g++ -Wall main.cpp lib.a -o main
4./main
5sqrt(25.0)=5
6power(5, 3)=125

Web requests and passing strings to go ##

Lets take a look at the possibility of making a get request to an URL. For that we implement the Request method in lib.go:

 1package main
 2
 3import "C"
 4import (
 5	"math"
 6	"net/http"
 7	"time"
 8)
 9
10// ...
11
12//export Request
13func Request(str string) int {
14	client := http.Client{
15		Timeout: time.Millisecond * 200,
16	}
17	resp, _ := client.Get(str)
18	return resp.StatusCode
19}
20
21// ...

However the conventional string used in the go runtime differs from the string used in c++. To help us with this, the toolchain generated a type definition in lib.h:

1#ifndef GO_CGO_GOSTRING_TYPEDEF
2typedef struct { const char *p; ptrdiff_t n; } _GoString_;
3#endif
4// ...
5#ifndef GO_CGO_GOSTRING_TYPEDEF
6typedef _GoString_ GoString;
7#endif

We can create an instance of this structure and pass it to our Request function:

 1#include "lib.h"
 2#include <cstring>
 3
 4// ...
 5
 6void request(const char *s) {
 7  GoString str;
 8  str.p = s;
 9  str.n = strlen(str.p);
10  int r = Request(str);
11  std::cout << "http.Get(" << str.p << ")=" << r << std::endl;
12}
13
14int main() {
15  // ...
16  request("https://xnacly.me/5");
17  request("https://xnacly.me/about");
18}

GoString.n is the length of the string and GoString.p refers to the pointer to the string. Lets test the request to an invalid page and the request to a valid page:

1$ make
2go build -buildmode=c-archive -o lib.a lib.go
3g++ -Wall main.cpp lib.a -o main
4./main
5http.Get(https://xnacly.me/5)=404
6http.Get(https://xnacly.me/about)=200

Go routines ##

Probably the most unique feature of go in comparison to other largely used programming languages is its concurrency model. Therefore lets add the ability to spawn go routines to c++.

 1package main
 2
 3import "C"
 4import (
 5	"fmt"
 6	"math"
 7	"net/http"
 8	"sync"
 9	"time"
10)
11
12// ...
13
14//export Routines
15func Routines(n int) {
16	wg := sync.WaitGroup{}
17	for i := range n {
18		wg.Add(1)
19		fmt.Printf("Spawning go routine %d\n", i)
20		go func() {
21			defer wg.Done()
22			time.Sleep(time.Millisecond * 10)
23		}()
24	}
25	wg.Wait()
26}
27
28// ...

Lets just add the Routines call to the c++ main function and invoke 1024 go routines:

1// ...
2int main() {
3  // ...
4  Routines(1024);
5}

After building and executing the binary we get:

1$ make
2go build -buildmode=c-archive -o lib.a lib.go
3g++ -Wall main.cpp lib.a -o main
4./main
5Spawning go routine 0
6...
7Spawning go routine 1023

Very many go routines :^).

There you have it, a nice and short explanation for calling go functions from c++ (if you need to). The same should work for c, but I didnt bother to test this one.