概要
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
簡単な例で各種ラッパーを作ってみる
サンプルコード
int divide(int x, int y);
int divide(int x, int y) {
return x / y;
}
%module swigtest
%{
#include "SwigTest.h"
%}
%include "SwigTest.h"
ビルド方法
Python (Windows の Python)
「MSYS2 MinGW 64-bit」で開いたコンソール上で以下を実行する
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
できあがったものを試すには
import swigtest
print(swigtest.divide(6, 2))
Perl (ActivePerl)
「MSYS2 MinGW 64-bit」で開いたコンソール上で以下を実行する
# 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
できあがったものを試すには、
use lib '.';
use swigtest;
print swigtest::divide(6, 2);
Perl (Msys)
「MSYS2 MSYS」で開いたコンソール上で以下を実行する
# 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」で開いたコンソール上で以下を実行する
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 関数で、第2引数に 0 を指定するとクラッシュします。
これを、クラッシュではなく、ちゃんとエラーになるようにしてみます。
#include <stdexcept>
int divide(int x, int y) {
if (y == 0) {
throw std::runtime_error("Division by 0");
}
return x / y;
}
%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 とか)だと非対応なので、まずは、自前の参照カウンタ実装で...
#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_;
};
%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 を使用する場合
#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_;
};
%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 で...
とりあえず、1次元のベクトルのやりとりの例。
numpy.i が必要。
自分は、github の numpy のソースの tools/swig からとってきました。
(https://github.com/numpy/numpy/tree/main/tools/swig)
void writeFloatArray(float* floatData, int size, float val);
float getSumOfFloatArray(const float* floatData, int size);
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;
}
%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"
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 の再定義の警告がでるけど無視(出ないようにする方法は、現時点、わからない。。。)
とりあえず、これで、以下のような感じで使えるようになります。
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