├── .gitignore ├── Makefile ├── README.md ├── basic-class ├── Makefile ├── README.md ├── bridge.cpp ├── bridge.h ├── circle.hpp ├── cylinder.hpp └── main.go ├── goroutines ├── Makefile ├── fancy.cpp ├── fancy.go └── fancy.hpp ├── library-bridge.cpp ├── library-bridge.h ├── library.cpp ├── library.go └── library.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | liblibrary.so 2 | library 3 | goroutines/PIA17218.tif 4 | goroutines/fancy 5 | goroutines/libfancy.so -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | liblibrary.so: library.cpp library-bridge.cpp 2 | clang++ -o liblibrary.so library.cpp library-bridge.cpp \ 3 | -std=c++17 -O3 -Wall -Wextra -fPIC -shared 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Calling C++ from Go 2 | 3 | This repo contains a simple example calling C++ from Go. 4 | 5 | Sample run (tested on macOS High Sierra 10.13.3 running go version go1.10 darwin/amd64): 6 | 7 | ``` 8 | $ make 9 | clang++ -o liblibrary.so library.cpp library-bridge.cpp \ 10 | -std=c++17 -O3 -Wall -Wextra -fPIC -shared 11 | $ go run library.go 12 | [c++ bridge] LIB_NewFoo(42) 13 | [c++] Foo::Foo(42) 14 | [c++ bridge] LIB_NewFoo(42) will return pointer 0x42002e0 15 | [c++ bridge] LIB_FooValue(0x42002e0) 16 | [c++] Foo::value() is 42 17 | [go] 42 18 | [c++ bridge] LIB_DestroyFoo(0x42002e0) 19 | [c++] Foo::~Foo(42) 20 | ``` 21 | 22 | I can also build an executable 23 | 24 | ``` 25 | $ go build library.go 26 | $ ./library 27 | [c++ bridge] LIB_NewFoo(42) 28 | [c++] Foo::Foo(42) 29 | [c++ bridge] LIB_NewFoo(42) will return pointer 0x4500000 30 | [c++ bridge] LIB_FooValue(0x4500000) 31 | [c++] Foo::value() is 42 32 | [go] 42 33 | [c++ bridge] LIB_DestroyFoo(0x4500000) 34 | [c++] Foo::~Foo(42) 35 | ``` 36 | 37 | ## Calling C++ from goroutines 38 | 39 | In the `goroutines` folder I created a very simple example that dispatches 40 | multiple cpu-intensive tasks, and many io-intensive tasks. It is pleasant that 41 | Go can coordinate the calls relatively well. 42 | 43 | Sample run: 44 | 45 | ``` 46 | $ cd goroutines 47 | $ make fancy 48 | clang++ -o libfancy.so fancy.cpp \ 49 | -std=c++17 -O3 -Wall -Wextra -fPIC -shared 50 | go build fancy.go 51 | $ ./fancy 52 | [go] dispatched all tasks 53 | [go] dispatching io_intensive 54 | [go] dispatching cpu_intensive( 45 ) 55 | [go] dispatching cpu_intensive( 30 ) 56 | [c++] starting to download NASA image on thread 0x7fff94f7d340. 57 | [c++] starting fib(45) on thread 0x7000006a0000. 58 | [go] dispatching cpu_intensive( 45 ) 59 | [c++] starting fib(30) on thread 0x70000061d000. 60 | [go] dispatching cpu_intensive( 40 ) 61 | [c++] starting fib(45) on thread 0x70000059a000. 62 | [c++] starting fib(40) on thread 0x7000008ac000. 63 | [go] dispatching cpu_intensive( 35 ) 64 | [c++] starting fib(35) on thread 0x7000007a6000. 65 | [go] dispatching cpu_intensive( 35 ) 66 | [go] dispatching cpu_intensive( 30 ) 67 | [c++] starting fib(35) on thread 0x7000009b2000. 68 | [c++] starting fib(30) on thread 0x700000829000. 69 | [go] dispatching cpu_intensive( 30 ) 70 | [c++] starting fib(30) on thread 0x700000a35000. 71 | [c++] fib(30) on thread 0x700000829000 took 0.003607 seconds. 72 | [go] dispatching cpu_intensive( 45 ) 73 | [go] dispatching cpu_intensive( 45 ) 74 | [c++] fib(30) on thread 0x70000061d000 took 0.003796 seconds. 75 | [go] dispatching cpu_intensive( 40 ) 76 | [c++] starting fib(45) on thread 0x700000829000. 77 | [go] dispatching cpu_intensive( 40 ) 78 | [c++] fib(30) on thread 0x700000a35000 took 0.003653 seconds. 79 | [go] dispatching cpu_intensive( 50 ) 80 | [c++] starting fib(45) on thread 0x700000ab8000. 81 | [c++] starting fib(40) on thread 0x70000061d000. 82 | [c++] starting fib(40) on thread 0x70000092f000. 83 | [c++] starting fib(50) on thread 0x700000a35000. 84 | [go] dispatching cpu_intensive( 45 ) 85 | [c++] starting fib(45) on thread 0x700000b3b000. 86 | [go] dispatching io_intensive 87 | [c++] starting to download NASA image on thread 0x700000bbe000. 88 | [go] dispatching cpu_intensive( 50 ) 89 | [c++] starting fib(50) on thread 0x700000c41000. 90 | [go] dispatching cpu_intensive( 35 ) 91 | [c++] starting fib(35) on thread 0x700000cc4000. 92 | [go] dispatching io_intensive 93 | [c++] starting to download NASA image on thread 0x700000d47000. 94 | [c++] fib(35) on thread 0x7000009b2000 took 0.063846 seconds. 95 | [go] dispatching cpu_intensive( 50 ) 96 | [c++] starting fib(50) on thread 0x7000009b2000. 97 | [c++] fib(35) on thread 0x7000007a6000 took 0.067398 seconds. 98 | [go] dispatching cpu_intensive( 45 ) 99 | [c++] starting fib(45) on thread 0x7000007a6000. 100 | [c++] fib(35) on thread 0x700000cc4000 took 0.054202 seconds. 101 | [c++] fib(40) on thread 0x70000061d000 took 0.707855 seconds. 102 | [c++] fib(40) on thread 0x7000008ac000 took 0.716816 seconds. 103 | [c++] fib(40) on thread 0x70000092f000 took 0.716797 seconds. 104 | [c++] fib(45) on thread 0x70000059a000 took 6.23767 seconds. 105 | [c++] fib(45) on thread 0x700000829000 took 6.23499 seconds. 106 | [c++] fib(45) on thread 0x7000006a0000 took 6.23971 seconds. 107 | [c++] fib(45) on thread 0x700000ab8000 took 6.2396 seconds. 108 | [c++] fib(45) on thread 0x700000b3b000 took 6.23648 seconds. 109 | [c++] fib(45) on thread 0x7000007a6000 took 6.1945 seconds. 110 | [c++] downloaded NASA image 111 | [c++] io_intensive on thread 0x7fff94f7d340 took 12.1647 seconds. 112 | [c++] downloaded NASA image 113 | [c++] io_intensive on thread 0x700000bbe000 took 23.5905 seconds. 114 | [c++] downloaded NASA image 115 | [c++] io_intensive on thread 0x700000d47000 took 32.3157 seconds. 116 | [c++] fib(50) on thread 0x700000a35000 took 33.1472 seconds. 117 | [c++] fib(50) on thread 0x700000c41000 took 33.1787 seconds. 118 | [c++] fib(50) on thread 0x7000009b2000 took 33.2035 seconds. 119 | [go] done (elapsed: 33.268694231 seconds) 120 | ``` -------------------------------------------------------------------------------- /basic-class/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | 3 | all: main 4 | 5 | bridge.so: 6 | /usr/bin/clang++ -o libbridge.so *.cpp -std=c++20 -O3 -Wall -Wextra -fPIC -shared 7 | 8 | main: bridge.so 9 | go build main.go 10 | -------------------------------------------------------------------------------- /basic-class/README.md: -------------------------------------------------------------------------------- 1 | ## Basic Class Example 2 | 3 | Basic class example that shows the bare minimum to work with a couple of classes. 4 | 5 | ## Usage: 6 | ``` 7 | $ make 8 | $ ./main 9 | 2022/03/10 16:32:49 circ.Area() = 5.5417694409323949e+03 10 | 2022/03/10 16:32:49 cyl.Volume() = 1.1083538881864790e+05 11 | ``` 12 | -------------------------------------------------------------------------------- /basic-class/bridge.cpp: -------------------------------------------------------------------------------- 1 | #include "bridge.h" 2 | #include "circle.hpp" 3 | #include "cylinder.hpp" 4 | 5 | void* DEMO_CircleNew(double radius) { return new Circle(radius); } 6 | 7 | void DEMO_CircleDestroy(void* circle) { 8 | auto ptr = reinterpret_cast(circle); 9 | delete ptr; 10 | } 11 | 12 | double DEMO_CircleArea(const void* circle) { 13 | return reinterpret_cast(circle)->area(); 14 | }; 15 | 16 | void* DEMO_CylinderNew(double radius, double height) { 17 | return new Cylinder(radius, height); 18 | } 19 | 20 | void DEMO_CylinderDestroy(void* cylinder) { 21 | auto ptr = reinterpret_cast(cylinder); 22 | delete ptr; 23 | } 24 | 25 | double DEMO_CylinderVolume(const void* cylinder) { 26 | return reinterpret_cast(cylinder)->volume(); 27 | } 28 | -------------------------------------------------------------------------------- /basic-class/bridge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef __cplusplus 3 | extern "C" { 4 | #endif 5 | 6 | void* DEMO_CircleNew(double radius); 7 | void DEMO_CircleDestroy(void* circle); 8 | double DEMO_CircleArea(const void* circle); 9 | 10 | void* DEMO_CylinderNew(double radius, double height); 11 | void DEMO_CylinderDestroy(void* cylinder); 12 | double DEMO_CylinderVolume(const void* cylinder); 13 | 14 | #ifdef __cplusplus 15 | } // extern "C" 16 | #endif 17 | -------------------------------------------------------------------------------- /basic-class/circle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Circle { 6 | public: 7 | Circle(double radius) : m_radius{radius} {} 8 | 9 | double area() const { return M_PI * std::pow(m_radius, 2); } 10 | 11 | private: 12 | double m_radius = {}; 13 | }; 14 | -------------------------------------------------------------------------------- /basic-class/cylinder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "circle.hpp" 4 | 5 | class Cylinder { 6 | public: 7 | Cylinder(double radius, double height) : m_base{radius}, m_height{height} {} 8 | 9 | double volume() const { return m_base.area() * m_height; } 10 | 11 | private: 12 | Circle m_base = {1}; 13 | double m_height = {}; 14 | }; 15 | -------------------------------------------------------------------------------- /basic-class/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // #cgo LDFLAGS: -L. -lbridge 4 | // #include "bridge.h" 5 | import "C" 6 | 7 | import ( 8 | "log" 9 | "unsafe" 10 | ) 11 | 12 | type Circle struct { 13 | ptr unsafe.Pointer 14 | } 15 | 16 | func NewCircle(radius float64) Circle { 17 | return Circle{ 18 | ptr: C.DEMO_CircleNew(C.double(radius)), 19 | } 20 | } 21 | 22 | func (circle Circle) Destroy() { 23 | C.DEMO_CircleDestroy(circle.ptr) 24 | } 25 | 26 | func (circle Circle) Area() float64 { 27 | return float64(C.DEMO_CircleArea(circle.ptr)) 28 | } 29 | 30 | type Cylinder struct { 31 | ptr unsafe.Pointer 32 | } 33 | 34 | func NewCylinder(radius float64, height float64) Cylinder { 35 | return Cylinder{ 36 | ptr: C.DEMO_CylinderNew(C.double(radius), C.double(height)), 37 | } 38 | } 39 | 40 | func (cylinder Cylinder) Destroy() { 41 | C.DEMO_CylinderDestroy(cylinder.ptr) 42 | } 43 | 44 | func (cylinder Cylinder) Volume() float64 { 45 | return float64(C.DEMO_CylinderVolume(cylinder.ptr)) 46 | } 47 | 48 | func main() { 49 | circ := NewCircle(42.0) 50 | defer circ.Destroy() 51 | 52 | log.Printf("circ.Area() = %23.16e", circ.Area()) 53 | 54 | cyl := NewCylinder(42.0, 20) 55 | defer cyl.Destroy() 56 | 57 | log.Printf("cyl.Volume() = %23.16e", cyl.Volume()) 58 | } 59 | -------------------------------------------------------------------------------- /goroutines/Makefile: -------------------------------------------------------------------------------- 1 | liblibrary.so: fancy.cpp 2 | clang++ -o libfancy.so fancy.cpp \ 3 | -std=c++17 -O3 -Wall -Wextra -fPIC -shared 4 | 5 | fancy: fancy.go liblibrary.so 6 | go build fancy.go 7 | -------------------------------------------------------------------------------- /goroutines/fancy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "fancy.hpp" 8 | 9 | // Print to STDOUT without interleaving messages 10 | std::mutex gIO_MUTEX; 11 | #define LOG(x) \ 12 | do { \ 13 | { \ 14 | std::lock_guard _(gIO_MUTEX); \ 15 | std::cout << x << std::endl; \ 16 | } \ 17 | } while (false) 18 | 19 | // Sample "cpu-intensive" task 20 | int fib(int n) { 21 | switch (n) { 22 | case 0: 23 | return 1; 24 | case 1: 25 | return 1; 26 | default: 27 | return fib(n - 1) + fib(n - 2); 28 | } 29 | } 30 | 31 | // Sample "io-intensive" task 32 | static constexpr const char* const COMMAND = 33 | "" 34 | "curl -O " 35 | "https://photojournal.jpl.nasa.gov/tiff/PIA17218.tif " 36 | " 2>/dev/null"; 37 | int fetch_nasa_image() { return std::system(COMMAND); } 38 | 39 | using namespace std::chrono; 40 | int cpu_intensive(int n) { 41 | const auto tbeg = system_clock::now(); 42 | const auto id = std::this_thread::get_id(); 43 | LOG("[c++] starting fib(" << n << ") on thread " << id << "."); 44 | const auto res = n < 0 ? -1 : fib(n); 45 | const auto dur = nanoseconds(system_clock::now() - tbeg).count(); 46 | LOG("[c++] fib(" << n << ") on thread " << id << " took " << (dur / 1.0E9) 47 | << " seconds."); 48 | return res; 49 | } 50 | 51 | int io_intensive() { 52 | const auto tbeg = system_clock::now(); 53 | const auto id = std::this_thread::get_id(); 54 | LOG("[c++] starting to download NASA image on thread " << id << "."); 55 | const auto status = fetch_nasa_image(); 56 | if (status == 0) { 57 | LOG("[c++] downloaded NASA image"); 58 | } else { 59 | LOG("[c++] failed to download NASA image"); 60 | } 61 | const auto dur = nanoseconds(system_clock::now() - tbeg).count(); 62 | LOG("[c++] io_intensive on thread " << id << " took " << (dur / 1.0E9) 63 | << " seconds."); 64 | return status; 65 | } 66 | -------------------------------------------------------------------------------- /goroutines/fancy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // #cgo LDFLAGS: -L. -lfancy 4 | // #include "fancy.hpp" 5 | import "C" 6 | import "fmt" 7 | import "time" 8 | import "sync" 9 | 10 | func cpu_intensive(n int) int { 11 | fmt.Println("[go] dispatching cpu_intensive(", n, ")") 12 | return int(C.cpu_intensive(C.int(n))) 13 | } 14 | 15 | func io_intensive() int { 16 | fmt.Println("[go] dispatching io_intensive") 17 | return int(C.io_intensive()) 18 | } 19 | 20 | func dispatch_cpu_intensive(wg *sync.WaitGroup, n int) { 21 | wg.Add(1) 22 | go func() { 23 | defer wg.Done() 24 | cpu_intensive(n) 25 | }() 26 | } 27 | 28 | func dispatch_io_intensive(wg *sync.WaitGroup) { 29 | wg.Add(1) 30 | go func() { 31 | defer wg.Done() 32 | io_intensive() 33 | }() 34 | } 35 | 36 | func main() { 37 | // A dirty example launching a bunch of C++ calls 38 | tbeg := time.Now() 39 | var wg sync.WaitGroup 40 | dispatch_cpu_intensive(&wg, 30) 41 | dispatch_cpu_intensive(&wg, 35) 42 | dispatch_cpu_intensive(&wg, 40) 43 | dispatch_cpu_intensive(&wg, 45) 44 | dispatch_cpu_intensive(&wg, 45) 45 | dispatch_cpu_intensive(&wg, 50) 46 | dispatch_cpu_intensive(&wg, 30) 47 | dispatch_cpu_intensive(&wg, 35) 48 | dispatch_cpu_intensive(&wg, 40) 49 | dispatch_cpu_intensive(&wg, 45) 50 | dispatch_cpu_intensive(&wg, 45) 51 | dispatch_cpu_intensive(&wg, 50) 52 | dispatch_cpu_intensive(&wg, 30) 53 | dispatch_cpu_intensive(&wg, 35) 54 | dispatch_cpu_intensive(&wg, 40) 55 | dispatch_cpu_intensive(&wg, 45) 56 | dispatch_cpu_intensive(&wg, 45) 57 | dispatch_cpu_intensive(&wg, 50) 58 | dispatch_io_intensive(&wg) 59 | dispatch_io_intensive(&wg) 60 | dispatch_io_intensive(&wg) 61 | fmt.Println("[go] dispatched all tasks") 62 | wg.Wait() 63 | fmt.Println("[go] done (elapsed:", time.Since(tbeg).Seconds(), "seconds)") 64 | } 65 | -------------------------------------------------------------------------------- /goroutines/fancy.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef __cplusplus 3 | extern "C" { 4 | #endif 5 | 6 | int cpu_intensive(int n); 7 | int io_intensive(); 8 | 9 | #ifdef __cplusplus 10 | } // extern "C" 11 | #endif 12 | -------------------------------------------------------------------------------- /library-bridge.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "library-bridge.h" 4 | #include "library.hpp" 5 | 6 | void* LIB_NewFoo(int value) { 7 | std::cout << "[c++ bridge] LIB_NewFoo(" << value << ")" << std::endl; 8 | auto foo = new Foo(value); 9 | std::cout << "[c++ bridge] LIB_NewFoo(" << value << ") will return pointer " 10 | << foo << std::endl; 11 | return foo; 12 | } 13 | 14 | // Utility function local to the bridge's implementation 15 | Foo* AsFoo(void* foo) { return reinterpret_cast(foo); } 16 | 17 | void LIB_DestroyFoo(void* foo) { 18 | std::cout << "[c++ bridge] LIB_DestroyFoo(" << foo << ")" << std::endl; 19 | AsFoo(foo)->~Foo(); 20 | } 21 | 22 | int LIB_FooValue(void* foo) { 23 | std::cout << "[c++ bridge] LIB_FooValue(" << foo << ")" << std::endl; 24 | return AsFoo(foo)->value(); 25 | } 26 | -------------------------------------------------------------------------------- /library-bridge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef __cplusplus 3 | extern "C" { 4 | #endif 5 | 6 | void* LIB_NewFoo(int value); 7 | void LIB_DestroyFoo(void* foo); 8 | int LIB_FooValue(void* foo); 9 | 10 | #ifdef __cplusplus 11 | } // extern "C" 12 | #endif 13 | -------------------------------------------------------------------------------- /library.cpp: -------------------------------------------------------------------------------- 1 | #include "library.hpp" 2 | #include 3 | 4 | Foo::Foo(int value) : m_value(value) { 5 | std::cout << "[c++] Foo::Foo(" << m_value << ")" << std::endl; 6 | } 7 | 8 | Foo::~Foo() { std::cout << "[c++] Foo::~Foo(" << m_value << ")" << std::endl; } 9 | 10 | int Foo::value() const { 11 | std::cout << "[c++] Foo::value() is " << m_value << std::endl; 12 | return m_value; 13 | } 14 | -------------------------------------------------------------------------------- /library.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // #cgo LDFLAGS: -L. -llibrary 4 | // #include "library-bridge.h" 5 | import "C" 6 | import "unsafe" 7 | import "fmt" 8 | 9 | type Foo struct { 10 | ptr unsafe.Pointer 11 | } 12 | 13 | func NewFoo(value int) Foo { 14 | var foo Foo 15 | foo.ptr = C.LIB_NewFoo(C.int(value)) 16 | return foo 17 | } 18 | 19 | func (foo Foo) Free() { 20 | C.LIB_DestroyFoo(foo.ptr) 21 | } 22 | 23 | func (foo Foo) value() int { 24 | return int(C.LIB_FooValue(foo.ptr)) 25 | } 26 | 27 | func main() { 28 | foo := NewFoo(42) 29 | defer foo.Free() // The Go analog to C++'s RAII 30 | fmt.Println("[go]", foo.value()) 31 | } 32 | -------------------------------------------------------------------------------- /library.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Foo { 4 | public: 5 | Foo(int value); 6 | ~Foo(); 7 | int value() const; 8 | 9 | private: 10 | int m_value; 11 | }; 12 | --------------------------------------------------------------------------------