calling c++ from golang with swig--windows dll(一)


calling c++ from golang with swig--windows dll

 

之前項目組開發的項目核心代碼全部使用C++語言,新項目可能會引入golang,花了一天多時間研究了windows環境下golang調用C++動態鏈接庫的方法。

谷歌加百度之后,很快發現官方推薦的方法,在官方FAQ頁面可以找到答案:

https://golang.org/doc/faq

 

Do Go programs link with C/C++ programs?

There are two Go compiler implementations, gc and gccgo. Gc uses a different calling convention and linker and can therefore only be linked with C programs using the same convention. There is such a C compiler but no C++ compiler. Gccgo is a GCC front-end that can, with care, be linked with GCC-compiled C or C++ programs.

The cgo program provides the mechanism for a “foreign function interface” to allow safe calling of C libraries from Go code. SWIG extends this capability to C++ libraries.

 

    上文的大意是:golang有兩個編譯器實現,gcgccgo。gc編譯器使用一個不同的調用約定和鏈接器,因此只能鏈接使用相同約定的C程序;gccgo是一個GCC的前端,可以鏈接GCC編譯的CC++程序。cgo程序提供了一個“外部函數接口”機制,允許在Golang中安全地調用C庫。SWIG擴展了這個能力,使得Golang可以調用C++庫。

    由官方的問答可以知道,如果Golang要調用C++庫,需要借助gccgoswig

    SWIG 是一個非常優秀的開源工具,支持您將 C/C++ 代碼與任何主流腳本語言相集成。SWIG的一個入門介紹可以參考《開發人員 SWIG 快速入門

http://www.ibm.com/developerworks/cn/aix/library/au-swig/

 

  

SWIG官方網址 http://www.swig.org/

Github上的開源代碼倉庫: https://github.com/swig/swig

github上看介紹,swig支持很多種語言:

SWIG is a compiler that integrates C and C++ with languages

including Perl, Python, Tcl, Ruby, PHP, Java, C#, D, Go, Lua,Octave, R, Scheme (Guile, MzScheme/Racket, CHICKEN), Scilab, Ocaml, Modula-3, Common Lisp (CLISP, Allegro CL, CFFI, UFFI) and Pike. SWIG can also export its parse tree into XML and Lisp s-expressions.

 

 

Swig最新的發布版本是2017128日發布的rel-3.0.12

Github上下載的發布包沒有預編譯的swig.exe;自己編譯比較麻煩,所以直接從http://www.swig.org/download.html 頁面下載最新發布包,

 

根據提示,從第二個鏈接下載,包含了預編譯的可執行程序。

 

解壓縮后,文件夾下有swig.exe,后面會看到需要借助這個工具生成代碼。Golang借助swig調用C++還是非常簡單的,看下Examples/go目錄下的示例就可以入門了。

用瀏覽器打開Examples\go\index.html看下文字說明,

 

 

  • When using the gccgo compiler, the steps look like this:

% swig -go -cgo interface.i

% mkdir -p gopath/src/interface

% cp interface_wrap.c interface_wrap.h interface.go gopath/src/interface

% GOPATH=`pwd`/gopath

% export GOPATH

% cd gopath/src/interface

% go build

% gccgo -c $(SRCDIR)/runme.go

% gccgo -o runme runme.o interface.a

這個文檔介紹的使用說明與軟件版本不太相符,文檔更新落后軟件太多。實際上只要一條命令就可以了。下面來介紹下go借助swig調用C++的方法,以Examples\go\class為例:

 

(我的電腦是windows 7 X64golang也是64位)

 

將下載的swigwin-3.0.12.zip包解壓到D盤,然后將D:\swigwin-3.0.12\swig.exe加入環境變量。

 

打開cmd,工作目錄切換到D:\swigwin-3.0.12\Examples\go\class

執行命令 swig -c++ -cgo -go -intgosize 64  example.i

成功執行后在D:\swigwin-3.0.12\Examples\go\class生成兩個文件,example.goexample_wrap.cxx,這兩個文件便是是在go中調用的包,runme.go屬於main包,golang只運行一個文件夾對應一個包,所以需要在D:\swigwin-3.0.12\Examples\go\class下創建example子文件夾,將example.goexample_wrap.cxxclass.cxxexample.h四個文件移動到D:\swigwin-3.0.12\Examples\go\class\example路徑下。

 

命令行中執行go build, 編譯成功后,在D:\swigwin-3.0.12\Examples\go\class文件夾中生成class.exe,執行class.exe輸出如下內容:

 

 

回過頭來看下詳細的開發步驟。

首先編寫供golang調用的c++源文件,

  example.hclass.cxx

 

 

/* File : example.h */

 

class Shape {

public:

  Shape() {

    nshapes++;

  }

  virtual ~Shape() {

    nshapes--;

  }

  double  x, y;

  void    move(double dx, double dy);

  virtual double area() = 0;

  virtual double perimeter() = 0;

  static  int nshapes;

};

 

class Circle : public Shape {

private:

  double radius;

public:

  Circle(double r) : radius(r) { }

  virtual double area();

  virtual double perimeter();

};

 

class Square : public Shape {

private:

  double width;

public:

  Square(double w) : width(w) { }

  virtual double area();

  virtual double perimeter();

};

 

 

該頭文件包含了一個基類和兩個派生類的聲明。

 class.cxx包含了對應的源碼實現:

/* File : class.cxx */

 

#include "example.h"

#define M_PI 3.14159265358979323846

 

/* Move the shape to a new location */

void Shape::move(double dx, double dy) {

  x += dx;

  y += dy;

}

 

int Shape::nshapes = 0;

 

double Circle::area() {

  return M_PI*radius*radius;

}

 

double Circle::perimeter() {

  return 2*M_PI*radius;

}

 

double Square::area() {

  return width*width;

}

 

double Square::perimeter() {

  return 4*width;

}

 

接下來需要定義swig工具生成代碼需要的輸入文件example.i,只需指定包名和包含的c++頭文件,相當簡單。

/* File : example.i */

%module example

 

%{

#include "example.h"

%}

 

/* Let's just grab the original header file here */

%include "example.h"

 

接下來就是在命令行中輸入swig -c++ -cgo -go -intgosize 64  example.i 生成golang調用的包裝代碼:example.goexample_wrap.cxx;example_wrap.cxx文件將c++語法隱藏在C函數內部,對外暴露c接口,example.go調用example_wrap.cxx中的c函數,聲明並實現golang版的接口。

 

runme.go中引入example包  . "./example"

 

c := NewCircle(10) 簡短變量聲明並定義了SwigcptrCircle類型的變量,SwigcptrCircle實現了Circle 接口。SwigcptrCircle的基礎類型是uintptrNewCircle返回的值實際上就是C++對象的this指針,通過SwigcptrCircle類型的值調用Circle 接口定義的方法,實質上是通過example_wrap.cxx暴露的C函數及“this指針”在函數內部調用將指針轉換成c++對象指針然后調用相應的方法。

 

type Circle interface {

Swigcptr() uintptr

SwigIsCircle()

Area() (_swig_ret float64)

Perimeter() (_swig_ret float64)

SetX(arg1 float64)

GetX() (_swig_ret float64)

SetY(arg1 float64)

GetY() (_swig_ret float64)

Move(arg1 float64, arg2 float64)

SwigIsShape()

SwigGetShape() Shape

}

 

Golang的角度看,借助swig生成的包裝類型,調用c++類方法就像調用golang內部的接口一樣,非常簡單易用。

   在實際的項目開發中,包含了大量的C++頭文件和源碼文件,通常還會引入大量的第三方庫文件。我們的項目中,開發人員編寫的代碼超過五十萬行,還有大量的第三方庫,例如boostthriftredis等,所有C++代碼量超過幾百萬行,編譯后的輸出包含大量的動態鏈接庫及少量的可執行程序;在服務端,進行跨語言混合開發無外乎涉及兩方面,實現庫時提供不同語言的實現,例如使用thrift框架可以生成C++JavaGolang等語言的客戶端、服務端版本,不同的組件可實現跨語言跨平台調用;另外一種主要涉及跨語言庫調用。Examples\go目錄下的示例沒有講述如何實現golang調用C++ dll,並且在官方文檔中也沒有找到如何實現。(通過D:\swigwin-3.0.12\Doc\Manual\index.html 可以查看最新版的3.0文檔)。Google加百度沒有找到一篇文檔能夠詳細地指出windows環境下golang調用c++ dll的方法,在不斷地編碼嘗試下,最終自己搞定。具體細節在后兩篇文檔中指出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM