6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

swig使い方メモ

Last updated at Posted at 2019-09-04

概要

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

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

サンプルコード

SwigTest.h
int divide(int x, int y);
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」で開いたコンソール上で以下を実行する

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 を指定するとクラッシュします。
これを、クラッシュではなく、ちゃんとエラーになるようにしてみます。

SwigTest.h
#include <stdexcept>

int divide(int x, int y) {
    if (y == 0) {
        throw std::runtime_error("Division by 0");
    }
    return x / y;
}
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 とか)だと非対応なので、まずは、自前の参照カウンタ実装で...

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_;
};
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 を使用する場合

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_;
};
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 で...

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

FloatArrayTest.h
void writeFloatArray(float* floatData, int size, float val);
float getSumOfFloatArray(const float* floatData, int size);
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"
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
6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?