1. 程式人生 > 實用技巧 >linux下用CMake + SWIG為GO語言寫動態庫

linux下用CMake + SWIG為GO語言寫動態庫

網上沒有完整的例子,這裡是本人蔘考各個程式碼repo總結的結果。遺憾的是,我沒有實現Pure Go的庫。呼叫該lib的GO程式需要和wrap.cxx,還有.h檔案一起build產生可執行檔案。本文采用go 1.15版本, SWIG 4.0.1 cmake 3.17.1

本文的程式碼參考https://github.com/zacg/simplelib. 不同的是,原例子並沒有使用CMake,也沒有產生lib檔案,而是手動輸入swig和go命令,將所有c++程式碼和go程式碼混在一起完成了go對c++的呼叫。這在實際工程應用中是很不方便的。我們希望C++寫的部分編譯成一個lib,GO呼叫時只需要呼叫這個lib就可以,而不需要連lib一起重新編譯。因此我對這個例子做了改動,加入了CMake,並且將執行程式和動態庫分開編譯。

下面是我修改後的程式碼目錄結構,本文使用預設GOPATH = ~/go:

xian@chaos:~/go/src/simplelib$ tree
.
├── CMakeLists.txt
├── include
│   └── simpleclass.h
├── lib.go
├── simplelib.i
└── src
    └── simpleclass.cpp

2 directories, 5 files

此外還有個呼叫程式碼main.go,他可以在任何地方。

首先看一下c++/go程式碼

simpleclass.h

#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

#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');
}

lib.go

package simplelib
/*
#cgo LDFLAGS: -L. -lExample
*/
import "C"

simplelib.i

%module simplelib
%{
#include "simpleclass.h"
%}

%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"

main.go

package main

import (
        "fmt"
        "simplelib/build/go/Example"
        //這是swig 產生wrapper檔案的目錄
)

func main() {

        simpleClass := simplelib.NewSimpleClass()
        result := simpleClass.Hello()
        fmt.Println(result)
}

CMakeLists.txt

分三個部分:第一部分與go無關,只需要.h .cpp生成動態庫libExample.so。第二部分使用swig_add_library,根據.i檔案以及.i檔案中涉及到的.h檔案生成wrap.cxx檔案。第三部分則是複製一些檔案,包括libExample.so到特定目錄以解決go build時的路徑問題。

CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0 FATAL_ERROR)

find_package(SWIG REQUIRED)
include(UseSWIG)
find_program(GO_EXECUTABLE go REQUIRED)

if(POLICY CMP0078)
  cmake_policy(SET CMP0078 NEW)
endif()
project(
  Example
  LANGUAGES CXX
)


#########################################################
# 第一步產生Example.so,這裡只需要.cpp .h檔案
#########################################################
add_library(Example SHARED "")

target_sources(Example
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/simpleclass.h>
    $<INSTALL_INTERFACE:include/simpleclass.h>
  PRIVATE
    src/simpleclass.cpp
)

target_include_directories(Example
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

target_compile_features(Example PUBLIC cxx_std_11)

add_library(${PROJECT_NAME}::Example ALIAS Example)





############################################################
## 第二步產生wrap.cxx檔案,這裡只需要*.i及其include的標頭檔案 
###########################################################
set(CMAKE_SWIG_FLAGS -cgo -intgosize 64)

set_property(SOURCE simplelib.i PROPERTY CPLUSPLUS ON) #沒有這一條會生成wrap.c
set_property(SOURCE simplelib.i PROPERTY COMPILE_OPTIONS
  -package simplelib)

swig_add_library(exampleForGo
  LANGUAGE go
  TYPE OBJECT
  OUTPUT_DIR ${PROJECT_BINARY_DIR}/go/${PROJECT_NAME}
  SOURCES simplelib.i)


target_include_directories(exampleForGo PRIVATE ${PROJECT_SOURCE_DIR}/include)
#simplelib.i中有include “*.h”, 這裡為這些標頭檔案提供路徑

set_target_properties(exampleForGo PROPERTIES
  SWIG_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR}/include #SWIG也新增include路徑
  SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON
  POSITION_INDEPENDENT_CODE ON)
target_link_libraries(exampleForGo PRIVATE ${PROJECT_NAME}::Example)

#############################################################################
# 第三步複製一些檔案到wrapper所在目錄,供go build使用
#############################################################################
file(COPY "${CMAKE_SOURCE_DIR}/include/simpleclass.h" DESTINATION "${PROJECT_BINARY_DIR}/go/${PROJECT_NAME}")
file(COPY "${CMAKE_SOURCE_DIR}/lib.go" DESTINATION "${PROJECT_BINARY_DIR}/go/${PROJECT_NAME}")

#############################################################################
# 複製動態連結庫libExample.so到系統目錄 /usr/local/lib
#############################################################################

install(TARGETS Example DESTINATION ${CMAKE_INSTALL_LIBDIR})

好了。接下來進入到CMakeLists.txt開始build

cmake -Bbuild
sudo cmake --build build --target install  #這裡需要sudo許可權訪問/usr/local/lib

這樣就算是安裝好庫了。接下來看怎麼呼叫它。進入main.go所在目錄,

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib   #把庫目錄加入到環境變數
go build main.go                                         #build
./main                    #run!
world                     #<==you should read this

最後,我好像沒找到go的打包程式,看來只能使用CPack了。