關於 web service, unity, blogger 等軟體工程筆記

Go with Cgo 動態連結 (dynamic linking) 函式庫建置範例與筆記

Edit icon 沒有留言
Golang

上一篇文章中,已經知道如何在 Windows 中,利用 gcc/g++ 建置動態連結函式庫 (dynamic-link library),本篇文章筆記如何在 Go (Golang) 中,利用 cgo 來動態連結函式庫,呼叫使用其函式。

提示:測試環境 go1.10.1 windows/amd64 & gcc version 7.3.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)

建置環境

在 Windows 環境中,若要在 go 使用 cgo 來串接 C 的函式庫,必須先安裝 gcc compiler (例如 mingw-w64),且必須在環境變數 PATH 中,加入 gcc.exe 的路徑,才能確保 go build 能成功編譯 cgo 的程式碼。

其安裝方法可參考上篇文章的 Install MinGW-w64 章節

在 Linux 環境中,gcc 應該都有安裝為成預設的編譯器 (?),這問題可以不用煩惱。

簡易範例

函式庫標頭程式碼 example.h

#ifndef EXAMPLE_H__
#define EXAMPLE_H__

#ifdef WINDOWS
   #define API __declspec(dllexport)
#else
   #define API extern
#endif

#define int64 long long

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

   API int64 Sum(int64 a, int64 b);
   API int64 SumAll(const int64* nums, int n);

#ifdef __cplusplus
}
#endif // __cplusplus
#endif // EXAMPLE_H__

其實做程式碼:

#include "example.h"

API int64 Sum(int64 a, int64 b){
   return a + b;
}

API int64 SumAll(const int64* nums, int n){
   int64 r = 0;
   for (int i = 0; i < n; i++){
      r += nums[i];
   }
   return r;
}

以上 C++ 程式碼使用 gcc/g++ 在各平台建置成動態連結函式庫 (dynamic-link library)。在此範例中,Windows 平台建置為 example.x64.dll,而 Linux 平台則是為 libexample.so,關於建置方式可參考上一篇文章

其 go 程式碼 main.go

package main

/*
#cgo windows LDFLAGS: -lexample.x64
#cgo windows CFLAGS: -DWINDOWS
#cgo linux LDFLAGS: -lexample

#cgo LDFLAGS: -L"${SRCDIR}/libs"
#cgo CFLAGS: -I"${SRCDIR}/includes"

#include "example.h"
*/
import "C"

func Sum(a, b int) int {
   return int(C.Sum(C.longlong(a), C.longlong(b)))
}

func SumAll(nums ...int) int {
   n := len(nums)
   v := make([]C.longlong, n)
   for i, _ := range nums {
      v[i] = C.longlong(nums[i])
   }
   return int(C.SumAll(&v[0], C.int(n)))
}
  • Line 4: Windows 環境中,連結函式庫 example.x64.dll
  • Line 5: Windows 環境中,編譯定義符號 WINDOWS
  • Line 6: Linux 環境中,連結函式庫 libexample.so
  • Line 8: 函式庫所在目錄位置,${SRCDIR} 表示 main.go 檔案相對位置
  • Line 9: include 程式碼所在目錄位置
  • Line 21-24: 考慮 Go 型別 int size,在 32-bit systems 中通常是 32 bits,而在 64-bit systems 中通常為 64 bits,因此採用此方式準備資料,而不是直接傳入 nums slice 內部的 pointer

此專案的檔案架構:

  • example
    • main.go
    • libs/example.x64.dll
    • libs/libexample.so
    • includes/example.h

如此一來,其所建置的執行檔 (executable file) 來會採用動態連結 (dynamic linking) 該 C 函式庫。

執行環境

執行時,應該注意函式庫能被 linker 找到,避免出現找不該函式庫的錯誤。

在 Windows 中,可以設定環境變數 PATH 加入該函式庫的所在資料夾位置,或是將函式庫與執行檔放置在同一個資料夾下。

在 Linux 中,修改 /etc/ld.so.conf 加入函式庫的所在資料夾位置,或是執行前設定環境變數 LD_LIBRARY_PATH 指向函數庫位置。

$ LD_LIBRARY_PATH={LIBRARY_PATH} ./example

例如指向當前工作目錄:

$ LD_LIBRARY_PATH=$(pwd) ./example

如果建置失敗…

若遇到未預期的編譯錯誤,加入參數印出建置所執行的指令,藉此來檢查可能是哪裡出錯:

$ go build -x -ldflags '-v'

使用 cgo 需要檢查 gcc/g++ 的指令錯誤,可能是 compilation error,也有可能是 linking error...。

系列筆記

Reference

沒有留言: