Calling C++ Code From Go With SWIG

http://zacg.github.io/blog/2013/06/06/calling-c-plus-plus-code-from-go-with-swig/

 

Recently while working on a Go based project I needed to use some functionality from another large C++ library. The library’s size and complexity made re-writing it in Go unfeasible. After some research I decided to use the popular SWIG (Simplified Wrapper and Interface Generator) framework to enable interop between my two projects.

The following is a brief tutorial to guide you through wrapping a C++ class with a Go package which can be called from any Go program.

Start by installing GO and SWIG if not already installed

1
2
sudo apt-get install golang
sudo apt-get install swig

Update: The debian package is out of date and lacking many go related fixes, it is best to install current SWIG release from SWIG website: http://swig.org

I also recommend installing golang from source as some of the following commands only work with go 1.1 and up. http://golang.org/doc/install/source

Once everything in installed the first step is to define a module file which will tell the SWIG tool what code in the C++ project to expose in the resulting GO package. We’ll assume your project is object oriented with cpp/header files for each class, when this is the case we can just include the desired header files in our SWIG module.

 

We will pretend our C++ project is a dynamically linked shared library called “simplelib” and contains the following files:

 

 

1
2
3
simpleclass.h
simpleclass.cpp


 

 

simpleclass.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef SIMPLECLASS_H
#define SIMPLECLASS_H

#include <iostream>
#include <vector>

class SimpleClass
{
public:
    SimpleClass(){};
    std::string hello();
    void helloString(std::vector<std::string> results);
    void helloBytes(std::vector<char> results);
};

#endif



 

 

simpleclass.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "simpleclass.h"

std::string SimpleClass::hello(){
  return "world";
}

void SimpleClass::helloString(std::vector<std::string> results){
  results->push_back("world");
}

void SimpleClass::helloBytes(std::vector<char> results){
  results->push_back('w');
  results->push_back('o');
  results->push_back('r');
  results->push_back('l');
  results->push_back('d');
}



 

 

We will add a module file called simplelib.swig. Inside we include the simpleclass.h header, this will instruct the SWIG tool to generate wrapping code for this class allowing us to use it in GO.

 

 

Minimum Swig Template
1
2
3
%module simplelib  //name of the resulting GO package

%include "simpleclass.h"

 

 

If your wrapped class(es) are simple and use primitive types the above swig file should suffice, SWIG will translate the following primitive types to the specified Go types

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

C/C++ type Go type
bool bool
char byte
signed char int8
unsigned char byte
short int16
unsigned short uint16
int int
unsigned int uint
long int32 or int64, depending on -long-type-size
unsigned long uint32 or uint64, depending on -long-type-size
long long int64
unsigned long long uint64
float float32
double float64
char *
char []
string

 

 

 

If your target code contains non-primitive types you have a bit more work to do. SWIG includes headers to help with common non primitive types like string and vector from the standard library. Vectors bring up another issue because they use templates, template types have to be explicitly defined in your SWIG mapping file. A class that uses std::string and std::vector might look like the following:

 

 

simplelib.swig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
%module simplelib

%include <typemaps.i>
%include "std_string.i"
%include "std_vector.i"

// This will create 2 wrapped types in Go called
// "StringVector" and "ByteVector" for their respective
// types.
namespace std {
   %template(StringVector) vector<string>;
   %template(ByteVector) vector<char>;
}

%include "simpleclass.h"

 



 

The following definitions are included with the SWIG library, go here for full reference

 

 

 

 

 

 

C++ class C++ Library file SWIG Interface library file
std::deque deque std_deque.i
std::list list std_list.i
std::map map std_map.i
std::pair utility std_pair.i
std::set set std_set.i
std::string string std_string.i
std::vector vector std_vector.i

 



 

Next we need to generate the necessary C++ wrapper code to allow Go to bind to it.

 

 

Update: With the official release of Go 1.2, the go build tool now recognizes SWIG files. The following steps are not necessary but may still be useful for users with more complicated build systems.

If you are using Go 1.2 simply include the .swig module file in your package directory along with the relevant c++ code files and run “go build” or “go install”

 

 

1
2
3
cd /project/src/
SWIG -go -c++ simplelib.swig


 

 

Common optional parameters you may need to use:

  • “-soname” for specifying the name of your compiled shared library which is dynamically linked at runtime. e.g. -soname libSimpleLib.so.1
  • “-intgosize” Depending on which version of go you are using 1/1.1 and which platform you are targeting you may need to explicitly set the Go int size (note the documentation for this is currently out of date). e.g. -intgosize 64

 

 

 

The above SWIG command should generate 3 new files in your project directory. Your project directory should now look something like the following:

 

 

1
2
3
4
5
6
simpleclass.h
simpleclass.cpp
simplelib.swig
simplelib.go
simplelib.cxx
simplelib_gc.c

 

 

Now we need to include these 3 new files in our projects. Simplelib.cxx contains the C++ wrapper code allowing your C++ project to interop with CGO. simplelib_gc.c contains the C code designed to be called from CGO. simplelib.go contains the GO code stubs for the resulting GO package, it uses cgo to call into the simplelib_gc.c interfaces.

Add the simplelib.cxx file to the C++ project and build with the projects C++ compiler (I have only tested this process with GCC). Simply add it to your makefile or build script.

 

 

simplelib_gc.c and simplelib.go need to be included in the go package using the following 5/6/8c and 5/6/8g commands.

 

 

1
2
3
go tool 6c -I <Go Installation Path here>/pkg/linux_amd64/ -D _64BIT simplelib_gc.c
go tool 6g simplelib.go
go tool pack grc simplelib.a simplelib.6 simplelib_gc.6

 

 

The last step is installation: first install your compiled C++ shared library, then run go install on the package created in the last step.

 

 

1
2
cd /go/src/simplelib/
go install

 

 

If the installation was successful you should see simplelib.a file in /go/pkg//

 

 

That’s it! you should now be able to import “simplelib” in your go projects and call the wrapped C++ code.

 

 

Setting up Build Scripts

 

 

To recap the steps required are:

 

 

  1. Run SWIG tool (generate wrapper code)
  2. Compile C++ project (including new wrapper code)
  3. Copy the built C++ library and files generated in step 1 to a directory in your go path
  4. Link and package the generated files into a Go Package
  5. Run Go install to make the new package available in your applications

 

 

And here is an example makefile:

 

 

simplelib.make
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
CC=g++

ifeq ($(DEBUG),yes)
  CXXFLAGS=-Wall -g
  LDFLAGS=-Wall -g
else
  CXXFLAGS=-Wall
  LDFLAGS=-Wall
endif

INCPATH=inc
SRCPATH=src
OBJPATH=obj
LIBPATH=lib
BINPATH=bin

OBJS=$(OBJPATH)/simpleclass.o $(OBJPATH)/simplelib_wrap.o
OUT=$(LIBPATH)/libSimpleLib.so

INCLUDES=-I ./$(INCPATH)

#Set this to your go installation directory
EXE=$$HOME/dev/goinstallation/go/bin/
export PATH := bin:$(PATH)

default: $(OUT)

$(OUT): $(OBJS)
  $(CC) $(LDFLAGS) -shared -o $@ $^

obj/simplelib_wrap.o: simplelib_wrap.cxx inc/simpleclass.h
  $(CC) $(CXXFLAGS) $(INCLUDES) -fpic -c $< -o $@

obj/simpleclass.o: src/simpleclass.cpp inc/simpleclass.h
  $(CC) $(CXXFLAGS) $(INCLUDES) -fpic -c $< -o $@


simplelib_wrap.cxx:
  swig -go -c++ -intgosize 64 -soname libSimpleLib.so simplelib.swig

.PHONY: clean cleanall

clean:
  rm -f $(OBJPATH)/.o

cleanall: clean
  rm -f $(OUT)
  rm -f .6
  rm -f .a
  rm -f .so
  rm -f .cxx
  rm -f .c

build:
  @echo "Building bindings..."
  $(EXE)go tool 6c -I $$HOME/dev/goinstallation/go/pkg/linux_amd64/ -D _64BIT simplelib_gc.c
  $(EXE)go tool 6g simplelib.go
  $(EXE)go tool pack grc simplelib.a simplelib.6 simplelib_gc.6




install:
  @echo "Installing go package..."
  #Rename swig file so go install command does not try to reprocess it
  mv simplelib.swig simplelib.notswig
  export GOPATH=$$HOME/dev/go/; </span>
  $(EXE)go install
  mv simplelib.notswig simplelib.swig

  @echo "Installing go shared lib..."
  sudo cp -f lib/libSimpleLib.so /usr/local/lib/
  sudo ldconfig

 

 

 

Get example project on github: https://github.com/zacg/simplelib

 

 

 

 

Feel free to leave your comments below.

 

posted @ 2023-02-13 13:58  jiftle  阅读(23)  评论(0编辑  收藏  举报