swig使い方メモ

# 概要

swig とは、C/C++ のソース（ヘッダ）から、いろいろな言語のラッパーを作成するものです。
ここでは、

* 開発環境は Windows(msys2+mingw 使用)
* 対象言語は Python3.7 (Anaconda), Perl (ActivePerl, Msys perl), C#

とします。

## 環境作成

msys2 をインストール後、以下をインストール

* swig
    `pacman -S swig`
* mingw64 の gcc
    `pacman -S mingw-w64-x86_64-gcc`
* msys2 の gcc
    `pacman -S gcc`

# 簡単な例で各種ラッパーを作ってみる

## サンプルコード

```cpp:SwigTest.h
int divide(int x, int y);
```

```cpp:SwigTest.cpp
int divide(int x, int y) {
    return x / y;
}
```

```:SwigTest.i
%module swigtest

%{
#include "SwigTest.h"
%}

%include "SwigTest.h"
```

## ビルド方法

### Python (Windows の Python)

「MSYS2 MinGW 64-bit」で開いたコンソール上で以下を実行する

```sh
PYTHON_PATH=/c/ProgramData/Anaconda3
PYTHON_DLL=$PYTHON_PATH/python37.dll

# swig で wrappar を作成
# ・SwigTestWrapPython.cpp (-o で指定) 
# ・swigtest.py (SwigTest.i の %module で指定)
# が作成される
swig -c++ -python -o SwigTestWrapPython.cpp SwigTest.i

# コンパイル
# -I で 「<Pythonインストール先>/include」を指定する
g++ -c SwigTestWrapPython.cpp -I $PYTHON_PATH/include
g++ -c SwigTest.cpp

# リンク
# python インストール先にある pythonXX.dll もリンクする。
# ※ ここで Pythonのマイナーバージョンに依存するようになる(linux の場合、これは不要なので、python3系全部で OK なものが作れるんだけど....)
# 出力ファイル名は 「_<module名>.pyd」にする
# (<module名> は、.i ファイルの先頭で指定する)
g++ -shared -static \
    -o _swigtest.pyd \
    $PYTHON_DLL \
    SwigTestWrapPython.o \
    SwigTest.o
```

できあがったものを試すには

```python
import swigtest

print(swigtest.divide(6, 2))
```

### Perl (ActivePerl)

「MSYS2 MinGW 64-bit」で開いたコンソール上で以下を実行する

```sh
# swig で wrappar を作成
# ・SwigTestWrapPerl.cpp (-o で指定) 
# ・swigtest.pm (SwigTest.i の %module で指定)
# が作成される
swig -c++ -perl -o SwigTestWrapPerl.cpp SwigTest.i

# Perl 関連の情報抽出
# 対象となる Perl の実行ファイルのフルパス
Perl=/c/Perl64/bin/perl.exe
# Perl のライブラリパス (上記 Perl の場合 C:\Perl64\lib になるはず)
PerlArcLib=`$Perl -e 'use Config; print $Config{archlib};'`
# Perl ライブラリファイル名 (ActivePerl5.28だと「libperl528.a」になる)
PerlLib=`$Perl -e 'use Config; print $Config{libperl};'`

# コンパイル
g++ -c SwigTestWrapPerl.cpp -I $PerlArcLib/CORE
g++ -c SwigTest.cpp

# リンク
# 出力ファイル名は 「<module名>.dll」にする
g++ -shared -static \
    -o swigtest.dll \
    SwigTestWrapPerl.o \
    SwigTest.o \
    $PerlArcLib/CORE/$PerlLib
```

できあがったものを試すには、

```perl
use lib '.';
use swigtest;

print swigtest::divide(6, 2);
```

### Perl (Msys)

「MSYS2 MSYS」で開いたコンソール上で以下を実行する

```sh
# swig で wrappar を作成
# ・SwigTestWrapPerl.cpp (-o で指定) 
# ・swigtest.pm (SwigTest.i の %module で指定)
# が作成される
swig -c++ -perl -o SwigTestWrapPerl.cpp SwigTest.i

# Perl 関連の情報抽出
# Perl のライブラリパス (Msysだと「/usr/lib/perl5/core_perl」)
PerlArcLib=`perl -e 'use Config; print $Config{archlib};'`
# Perl ライブラリファイル名 (自分の環境だと「msys-perl5_28.dll」になった)
PerlLib=`perl -e 'use Config; print $Config{libperl};'`

# コンパイル
g++ -c SwigTestWrapPerl.cpp -I $PerlArclib/CORE
g++ -c SwigTest.cpp

# リンク
# 出力ファイル名は 「<module名>.dll」にする
# コンパイラ、実行環境ともに Msys なので -static はつけなくてもOK (→ dll が小さくなる)
g++ -shared \
    -o swigtest.dll \
    SwigTestWrapPerl.o \
    SwigTest.o \
    $PerlArcLib/CORE/$PerlLib
```

※ Linux では、リンクの「-o swigtest.dll」が不要

### C＃

「MSYS2 MinGW 64-bit」で開いたコンソール上で以下を実行する

```sh
CSC=/c/Windows/Microsoft.NET/Framework/v4.0.30319/csc 

# swig で wrappar を作成
# ・SwigTestWrapCSharp.cpp (-o で指定) 
# ・swigtest.cs (SwigTest.i の %module で指定)
# が作成される
swig -c++ -csharp -o SwigTestWrapCSharp.cpp SwigTest.i

# コンパイル
g++ -c SwigTestWrapCSharp.cpp -o SwigTestWrapCSharp.o
g++ -c SwigTest.cpp

# リンク
# 出力ファイル名は 「<module名>.dll」にする。 
g++ -shared -static \
    -o swigtest.dll \
    SwigTestWrapCSharp.o \
    SwigTest.o

# swigtest.cs をコンパイルして dll を作成する。
# dll名は、なんでもかまわないが、ここでは「swigtestcs.dll」にする。
$CSC -target:library -platform:x64 -nologo -warn:0 -utf8output \
     -out:swigtestcs.dll *.cs
```

# 例外を出すようにしてみる

前述の例の divide 関数で、第２引数に 0 を指定するとクラッシュします。
これを、クラッシュではなく、ちゃんとエラーになるようにしてみます。

```cpp:SwigTest.h
#include <stdexcept>

int divide(int x, int y) {
    if (y == 0) {
        throw std::runtime_error("Division by 0");
    }
    return x / y;
}
```


```cpp:SwigTest.i
%module swigtest

// 例外処理
%include "exception.i"
%exception {
  try { $action } catch (std::exception& e) {
    SWIG_exception(SWIG_RuntimeError, const_cast<char *>(e.what()));
  }
}

%{
#include "SwigTest.h"
%}

%include "SwigTest.h"
```

# 参照カウンター管理のオブジェクトの例

## 独自実装の参照カウンター

swig の shared_ptr サポートを使用するのが簡単なのですが、ちょっとマイナーな言語(Perl とか)だと非対応なので、まずは、自前の参照カウンタ実装で...

```cpp:SwigTest.h
#include <atomic>
#include <string>
#include <iostream>

// 参照カウンター管理のクラス
class RefObj {
public:
    RefObj() : refCount_(0) {}
    virtual ~RefObj() {}
    int releaseRef() {
        int refCount = --refCount_;
        if (refCount <= 0) { delete this; }
        return refCount;
    }
    int addRef() {
        refCount_++;
        return refCount_;
       }
private:
    std::atomic_int refCount_;
};

class TestObj : public RefObj {
public:
    TestObj(const std::string& name) :
        RefObj(), obj_(nullptr)
    {
        name_ = name;
    }
    virtual ~TestObj() {
        if (obj_ != nullptr) { obj_->releaseRef(); }
        std::cout << name_ << " was deleted\n";
    }
    
    std::string getName() { return name_; }
    
    // オブジェクトを作成して返す例
    TestObj* createChild() {
        return new TestObj("Child of " + name_);
    }
    
    // 参照しているオブジェクトを返す例
    TestObj* getObj() { return obj_; }
    
    // 参照をセットする例
    void setObj(TestObj* obj) {
        if (obj != nullptr) { obj->addRef(); }
        if (obj_ != nullptr) { obj_->releaseRef(); }
        obj_ = obj;
    }
private:
    std::string name_;
    TestObj* obj_;
};
```

```cpp:SwigTest.i
%module swigtest

// 参照カウンターを増減させる方法を swig に教える
// (これで、 RefObj クラスから継承しているものも対象となる)
%feature("ref")   RefObj "if ($this) { $this->addRef(); }"
%feature("unref") RefObj "$this->releaseRef();"
// オブジェクトを返す場合は、常に参照カウンターを増やすように
%newobject;
// wrapper クラスに参照カウンターへのアクセスメソッドを見せないようにする
%ignore addRef;
%ignore releaseRef;

// std::string が wrapper 側で文字列として扱われるようにする
%include "std_string.i"

%{
#include "SwigTest.h"
%}

%include "SwigTest.h"
```

## shared_ptr を使用する場合

```cpp:SwigTest.h
#include <memory>
#include <string>
#include <iostream>

class TestObj {
public:
    TestObj(const std::string& name) : obj_(nullptr) {
        name_ = name;
    }
    virtual ~TestObj() {
        std::cout << name_ << " was deleted\n";
    }
    
    std::string getName() { return name_; }
    
    // オブジェクトを作成して返す例
    std::shared_ptr<TestObj> createChild() {
        return std::make_shared<TestObj>("Child of " + name_);
    }
    
    // 参照しているオブジェクトを返す例
    std::shared_ptr<TestObj> getObj() { return obj_; }
    
    // 参照をセットする例
    void setObj(const std::shared_ptr<TestObj>& obj) {
        obj_ = obj;
    }
private:
    std::string name_;
    std::shared_ptr<TestObj> obj_;
};
```

```cpp:SwigTest.i
%module swigtest

// shared_ptr サポートを使用する
%include "std_shared_ptr.i"
// shared_ptr を使用するクラスを指定
%shared_ptr(TestObj)

// std::string が wrapper 側で文字列として扱われるようにする
%include "std_string.i"

%{
#include "SwigTest.h"
%}

%include "SwigTest.h"
```

# 配列のやりとり

## Python の numpy で...

とりあえず、１次元のベクトルのやりとりの例。
numpy.i が必要。
自分は、github の numpy のソースの tools/swig からとってきました。
(https://github.com/numpy/numpy/tree/main/tools/swig)

```cpp:FloatArrayTest.h
void writeFloatArray(float* floatData, int size, float val);
float getSumOfFloatArray(const float* floatData, int size);
```

```cpp:FloatArrayTest.cpp
void writeFloatArray(float* floatData, int size, float val)
{
    for (int i = 0; i < size; i++) { floatData[i] = val; }
}

float getSumOfFloatArray(const float* floatData, int size)
{
    float sumVal = 0;
    for (int i = 0; i < size; i++) { sumVal += floatData[i]; }
    return sumVal;
}
```

```:FloatArrayTest.i
%module float_array_test

// -------------------------------------------------------------------------
// numpy でやりとりするための記述
// -------------------------------------------------------------------------
%{
#define SWIG_FILE_WITH_INIT
%}
%include "numpy.i"
// numpy のデータに書き込む関数のパラメータパターン
// これで、cpp側の関数のパラメータが float* floatData, int size の場合、
// Python 側では 1次元の np.float32 のベクトルで扱えるようになる。
%apply (float* INPLACE_ARRAY1, int DIM1) { (float* floatData, int size) };
// numpy のデータを読む関数のパラメータパターン
%apply (float* IN_ARRAY1, int DIM1) { (const float* floatData, int size) };
%init %{
import_array();
%}
// -------------------------------------------------------------------------

%{
#include "FloatArrayTest.h"
%}

%include "FloatArrayTest.h"
```

```sh
swig -c++ -python -o FloatArrayTestWrapPython.cpp FloatArrayTest.i

PYTHON_PATH=/c/ProgramData/Anaconda3
PYTHON_DLL=$PYTHON_PATH/python37.dll

# コンパイル
g++ -c FloatArrayTestWrapPython.cpp \
    -I $PYTHON_PATH/include \
    -I $PYTHON_PATH/Lib/site-packages/numpy/core/include
g++ -c FloatArrayTest.cpp

# リンク
# 出力ファイル名は 「_<module名>.pyd」にする
g++ -shared -static \
    -o _float_array_test.pyd \
    $PYTHON_DLL \
    FloatArrayTestWrapPython.o \
    FloatArrayTest.o
```

コンパイル時に _fseeki64 の再定義の警告がでるけど無視（出ないようにする方法は、現時点、わからない。。。）

とりあえず、これで、以下のような感じで使えるようになります。

```python
import numpy as np
import float_array_test

data = np.zeros(4, dtype=np.float32)
float_array_test.writeFloatArray(data, 3)
print(data) # -> [3. 3. 3. 3.]
sum = float_array_test.getSumOfFloatArray(data)
print(sum) # -> 12.0
```