C++/Go Dynamic Library

In the previous post, I wrote about a wrapper that could be compiled and linked into a Go program which accessed a shared/dynamic library. I’m going to follow up with two other examples today: a static library which exposes overloaded functions to Go and a way to expose overloaded functions in a Dynamic library directly to Go.

File structure:

  • src/
    • sums_dyn.cpp
    • sums_dyn.h
    • wrapper.cpp
    • wrapper.h
  • lib/
    • sums_dyn.so
    • wrapper.a
  • main.go
  • go.mod

Here is something interesting. __cplusplus will only be defined when compiling through g++. Go will only see int sum2(int i, int j) as a possible function call when including sums_dyn.h

// sums_dyn.h
#pragma once
#ifdef __cplusplus
int sum(int i, int j);

int sum(int i, int j, int k);

extern "C" int sum2(int i, int j);

#else

int sum2(int i, int j);

#endif
// sums_dyn.cpp
#include "sums_dyn.h"

int sum(int i, int j)
{
    return i + j;
}

int sum(int i, int j, int k)
{
    return i + j + k;
}

int sum2(int i, int j)
{
    return sum(i, j);
}

Just as before, we want to tell g++ to use extern “C” when compiling, but Go just gets confused when it sees extern “C”, so we only want to have the directive included when C++ is defined.

// wrapper.h
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif

int sum3(int i, int j, int k);

#ifdef __cplusplus
}
#endif
// wrapper.cpp
#include "wrapper.h"
#include "sums_dyn.h"

int sum3(int i, int j, int k)
{
    return sum(i, j, k);
}

The dynamic library’s header only exposes sum2 to Go, but the static library exposes sum3 to go. We’ll need to link both and include their headers.

// main.go
package main
/*
#cgo LDFLAGS: -L. -l:./lib/sums_dyn.so -lstdc++
#include "./src/sums_dyn.h"
#cgo LDFLAGS: -L. -l:./lib/wrapper.a -lstdc++
#include "./src/wrapper.h"
*/
import "C"

import "fmt"

func main() {
    fmt.Println("5 + 100 =", C.sum2(5, 100))

    fmt.Println("7 + 33 + 432 =", C.sum3(7, 33, 432))
}

Let’s compile and run (from the directory in which main.go resides):

g++ -fPIC -shared src/sums_dyn.cpp -o lib/sums_dyn.so
g++ -c src/wrapper.cpp -o src/wrapper.o
ar rcs lib/wrapper.a src/wrapper.o
go build -a
./main
5 + 100 = 105
7 + 33 + 432 = 472

To recap, the dynamic library exposed sum2(int i, int j); directly through the clever use of #include. We did have to declare sum2 twice: first with an extern “C” in front of it to let C++ know to export it as it is written.

Go and other languages don’t need to know about the overloaded function sum or the extern “C” version of sum2, so we also left a C-style sum2 between the #else and #endif directives. Go is happy with that. For the wrapper function, we wanted to expose sum3 as a C-style function, so it was wrapped in an extern “C” block. We could have written it as extern “C” int sum3(int i, int j, int k);, but it’s good practice to use #ifdef.