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.