SWIGの使い方

概要

近年、C#を代表とした.NETFrameworkのアプリケーションが増えてきました。 C#は.NETFrameworkの豊富なライブラリによるC++並の柔軟性とVBライクなデザイナによる開発効率性を兼ね備えた言語です。 しかし、C++で作られた既存のライブラリやフレームワークを使う機能には制限があり、 DLLの場合、クラスを公開している拡張DLLは使えず、C言語の関数を公開したレギュラーDLLしか使えません。

SWIGは拡張DLLのヘッダファイルを読込み、拡張DLLをレギュラーDLLに変換するラッパと、 そのラッパを他の言語のクラスに変換するラッパを出力するものです。 本文書ではSWIGを使って拡張DLLであるIrrlichtをC#で使えるようにした際のTipsについてまとめています。

SWIGの基本的な機能・制限は以下のとおり

  • C++で定義されたクラスをC#から使える形にしてくれる
  • char*をstringに変換するなど、基本的な型変換は行ってくれる
  • Directorという機能によってC++側のクラスをC#で継承可能
  • C++側で多重継承されていた場合、C#側ではそれが反映されない(制限)
  • ネストされたクラス、構造体、共用体には対応できない(制限)

基本

サンプルコード:00_basic.zip

SWIGは専用の構文を使ってインターフェイスファイル(*.i)を作成して それをインプットとしてラッパを作成します。 最も基本的なインターフェイスファイルは以下になります。

// irrlicht.i
%module "IrrlichtNet"  //(1)

%{
#include "irrlicht.h"  //(2)
%}

%include "irrlicht.h"  //(3)

  1. モジュール名を指定する。モジュール名は生成される*.cppファイルや*.hファイルの名前、C#側での一番親のクラス名になる。
  2. %{、%}で囲まれた部分は生成される*.cppファイルにそのまま出力される。ここでは型の名前解決のためにヘッダファイルを指定している。
  3. %includeでSWIGが解析を行うファイルを指定する。

このようなインターフェイスファイルを作ったら、以下のようにSWIGに食わせるとラッパのIrrlichtNet.cppとIrrlichtNet.hとCSファイルが出力される。

swig.exe -csharp -c++ irrlicht.i

C#に独自のコードを追加したい

サンプルコード:03_AddCSCode.zip

以下のように%typemapを記述します。 ただし、設定できるのはクラスに対して1つのみで、多重定義の場合は上書きされます。

  1. %typemap(cscode) C++
  2. %{
  3. //お好きなC#コード
  4. %}

Warningを無視する

多くの警告が出ると重要な警告を見逃してしまうことがあります。 そんな時には警告を出さないように制御します。 全体に適用する方法とクラス単位で適用する方法の2種類があります。

  1. //全体に適用
  2. #pragma SWIG nowarn=312
  3. //クラス単位に適用
  4. %warnfilter(312) irr::string //C++型名を指定

メソッド・クラスの無効化

サンプルコード:04_ignoreMethod.zip

C#側でラッパを作って欲しくないメソッドやクラスを指定できます。

  1. //メソッド名で指定
  2. %ignore irr::core::array::operator==;
  3. //引数まで含めて完全一致で指定
  4. %ignore irr::io::IAttributes::addBinary(const char* attributeName, void* data, int dataSizeInBytes);
  5. //クラスを指定
  6. %ignore irr::SEvent::SGUIEvent;

メソッドの名前変更

C++のクラス・メソッドの名前を変更できます。 またC#の予約語で使えない識別子を変更ために使ったりします。

  1. //クラス名の変更
  2. %rename(Lock) lock;
  3. //メソッド名の変更
  4. %rename(equalTo) operator==;

C#ラッパクラスにpartialをつける

C#のクラスにpartialをつけるとclassを複数ファイルに書くことができます。 そのため、SWIGが生成するクラスに独自のメソッドを追加することができます。

  1. //全クラスにpartialをつける
  2. %typemap(csclassmodifiers) SWIGTYPE, SWIGTYPE *, SWIGTYPE &, SWIGTYPE [], SWIGTYPE (CLASS::*) "public partial class"
  1. //C#
  2. //SWIG生成csファイル
  3. public partial class CTest
  4. {
  5. ・・・
  6. }
  1. //C#
  2. //自作csファイル
  3. public partial class CTest
  4. {
  5. public void Add(int x)
  6. {
  7. ・・・
  8. }
  9. }

templateが使われている

サンプルコード:05_template.zip

テンプレートはC#へ変換されません。 テンプレート付きのクラスをC#で使うにはテンプレートに型を指定して別名のクラスとして使用します。 C#にはジェネリックというテンプレートライクな機能がありますが、それには変換しません。

  1. template <class T>
  2. class CArray
  3. {
  4. //・・・
  5. }
  6. //CArray<int>型をArrayInt型としてC#から使う
  7. %template(ArrayInt) CArray<int>;

wchar_tが使われている

サンプルコード:06_wchar_t.zip

SWIG標準では、char*の引数をstringに変換しますが、ワイド文字であるwchar_t*が使われている場合があります。 その場合でも以下のよう"wchar.i"を読み込むとstringへ変換されるようになります。

  1. %include "wchar.i"

応用

再帰的なinclude

SWIGでは、*.hファイルに書かれている#include文を再帰的に読み込んでくれません。 オプションで全てのincludeを読むようにもできますが、その場合標準ライブラリも読みに行ってしまうのであまり使えません。 宣言の順番もどうやら関係しているようなので、#includeのネストを解析して、順番どおりに並べる必要があります。 私の場合、次のようなツールを作ってヘッダファイルを解析し、%includeを並べました。ParseInclude.zip

C++のクラスをC#側で継承し、C++側に渡したい

サンプルコード:07_inheritance.zip

以下のようにインターフェイスファイルを書くと指定した型をC#で継承することができます。 特定の型を継承した引数を要求するC++ライブラリに有効です。

  1. %module(directors="1") ○○
  2. %feature("director") C++;

ただし、問題も多いです。

  • wchar_t*が正しく文字列に変換されない→対応可能
  • 引数の名前がC#の予約語にかぶっていても変更されない→ヘッダファイルを修正するしかない。
  • 無限ループになることがある。

以下にC#で継承する例を示します。

  1. //Test.h
  2. class CReceiver
  3. {
  4. public:
  5. virtual void OnReceiveMessage(int msgCode) = 0;
  6. };
  7. class CConnection
  8. {
  9. CReceiver* m_pReceiver;
  10. public:
  11. void SetReceiver(CReceiver* pReceiver){m_pReceiver=pReceiver;}
  12. void RaiseReceive(int msgCode)
  13. {
  14. m_pReceiver->OnReceiveMessage(msgCode);
  15. }
  16. };

  1. //Test.i
  2. %module(directors="1") "Test"
  3. %{
  4. #include "Test.h"
  5. %}
  6. %feature("director") CRecever;
  7. %include "Test.h"
  1. //Program.cs
  2. class CSharpRecever : CReceiver
  3. {
  4. override void OnReceiveMessage(int msgCode)
  5. {
  6. Console.WriteLine("Receive message:{0}", msgCode);
  7. }
  8. }
  9. class Program
  10. {
  11. static void Main(string[] args)
  12. {
  13. var connection = new CConnection();
  14. connection.SetReceiver(new CSharpRecever());
  15. connection.RaiseReceive(10);
  16. }
  17. }

C++の引数に配列を渡したい

C++へC#の配列を渡す方法です。C++で引数に配列を渡す際はポインタになりますが、 SWIGは、単なる参照渡しか、ポインタに値を格納するのか、配列渡しなのか判断できません。 そのため、引数が配列であることを指定する必要があります。 指定の方法ですが、まず"arrays_csharp.i"を%includeします。この中に配列の宣言をするための 定義が書かれています。その後、%apply文を以下のように書きます。

  1. %include "arrays_csharp.i"
  2. %apply C++型 INPUT[] {引数のパターン}
なお、この方法はintやdoubleなど基本型のみです。文字列配列やクラスの配列には対応していません。

以下に実際の例を示します。

  1. //Test.h
  2. class CTest
  3. {
  4. void SetArray(unsigned short* arr){}
  5. }
  1. //Test.i
  2. %module() "Test"
  3. %{
  4. #include "Test.h"
  5. %}
  6. %include "arrays_csharp.i"
  7. %apply unsigned short INPUT[] {unsigned short* arr}
  8. %include "Test.h"
  1. //Program.cs
  2. static void Main(string[] args)
  3. {
  4. var test = new CTest();
  5. test.SetArray(new ushort[]{0,1,2,3});
  6. }

C++へ引数で渡した配列へ値を格納させたい

上記と逆のパターンです。配列を渡してそこに値を格納するようなC++メソッドをC#から使えるようにします。 基本的には上記と同じですが、INPUTをOUTPUTにします。

以下で例を示します。

  1. //Test.h
  2. class CTest
  3. {
  4. void GetArray(float* arr)
  5. {
  6. arr[0] = 0f;
  7. arr[1] = 1f;
  8. }
  9. }
  1. //Test.i
  2. %module() "Test"
  3. %{
  4. #include "Test.h"
  5. %}
  6. %include "arrays_csharp.i"
  7. %apply float OUTPUT[] {float *arr_out}
  8. %include "Test.h"
  1. //Program.cs
  2. static void Main(string[] args)
  3. {
  4. var test = new CTest();
  5. var arr = new float[2];
  6. test.GetArray(arr);
  7. }

C++側に参照を渡したい

C++の引数が参照(○○&)でC#でrefを使った引数を使いたい場合、以下のように指定します。ただし、intやshortなど基本的な型のみです。enumも非対応です。

%include"typemap.i"
%apply int &INOUT { int& outBegin };

以下で例を示します。

  1. //Test.h
  2. class CTest
  3. {
  4. public:
  5. void GetRange(int& outBegin, int& outEnd)
  6. {
  7. outBegin = 10;
  8. outEnd = 14;
  9. }
  10. }
  1. //Test.i
  2. %module() "Test"
  3. %{
  4. #include "Test.h"
  5. %}
  6. %include"typemap.i"
  7. %apply int &INOUT { int& outBegin };
  8. %apply int &INOUT { int& outEnd };
  9. %include "Test.h"
  1. //Program.cs
  2. static void Main(string[] args)
  3. {
  4. var test = new CTest();
  5. int begin = 0;
  6. int end = 0;
  7. test.GetRange(ref begin, ref end);
  8. }

enumの引数でref、out

サンプルコード:02_enum_ref.zip

SWIGではintやdoubleなど基本的な型でのref、outに対応していますが、enumではできません。 以下のような書き方をするとenumでのref、outが使えます。

  1. //Test.h
  2. enum E_TYPE
  3. {
  4. TYPE_A = 0,
  5. TYPE_B,
  6. TYPE_COUNT
  7. };
  8. class CTest
  9. {
  10. E_TYPE m_type;
  11. public:
  12. void SetType(E_TYPE type)
  13. {
  14. m_type = type;
  15. }
  16. void GetType(E_TYPE& type)
  17. {
  18. type = m_type;
  19. }
  20. void GetType2(E_TYPE* type)
  21. {
  22. *type = m_type;
  23. }
  24. void Print()
  25. {
  26. switch(m_type)
  27. {
  28. case TYPE_A:
  29. printf("m_type=TYPE_A\r\n");
  30. break;
  31. case TYPE_B:
  32. printf("m_type=TYPE_B\r\n");
  33. break;
  34. default:
  35. printf("m_type=Unknown\r\n");
  36. break;
  37. }
  38. }
  39. };

  1. //Test.i
  2. %module "Test"
  3. %{
  4. #include "..\Test.h"
  5. %}
  6. %include"typemaps.i"
  7. //■enumのref対応
  8. %apply int &INOUT {E_TYPE& type};
  9. %typemap(cstype) E_TYPE& type "ref E_TYPE" //C#側の引数の型
  10. %typemap(csin) E_TYPE& type "ref $csinput" //C#側から中間ラッパへ渡す際の引数
  11. %typemap(imtype) E_TYPE& type "ref E_TYPE" //中間ラッパの型
  12. %typemap(ctype) E_TYPE& type "int*" //C++側の引数の型
  13. //■enumのout対応
  14. %apply int &OUTPUT {E_TYPE* type};
  15. %typemap(cstype) E_TYPE* type "out E_TYPE" //C#側の引数の型
  16. %typemap(csin) E_TYPE* type "out $csinput" //C#側から中間ラッパへ渡す際の引数
  17. %typemap(imtype) E_TYPE* type "out E_TYPE" //中間ラッパの型
  18. %typemap(ctype) E_TYPE* type "int*" //C++側の引数の型
  19. %include "test.h"
  1. //Program.cs
  2. static void Main(string[] args)
  3. {
  4. CTest test = new CTest();
  5. //enumのref
  6. E_TYPE type = E_TYPE.TYPE_B;
  7. test.GetType(ref type);
  8. //enumのout
  9. test.GetType2(out type);
  10. }

Windows独自の構文

サンプルコード:11_dllimport.zip

"declspec(dllimport)"のようなWindows独自の構文が使われているとSWIGでエラーになることがあります。 そんな時、インターフェイスファイルでwindows.iを読み込むことで、これらの構文を無視することができます。

  1. //Test.h
  2. __declspec(dllimport) class CTest
  3. {
  4. };
  1. //Test.i
  2. %module "Test"
  3. %{
  4. #include "..\Test.h"
  5. %}
  6. %include <windows.i>
  7. %include "Test.h"

クラス・構造体のネストが使われている

サンプルコード:12_nestedClass.zip

近日公開

C++から戻り値で配列を受け取りたい(サイズ固定、コピー渡し)

近日公開

C++の変なコードのせいでエラー

近日公開

C#側でキャストしたい

近日公開

C#側でコピーコンストラクタを使いたい

近日公開

C#側でインデクサを使いたい

近日公開

void*引数をIntPtrに変換

近日公開

publicメンバに配列

近日公開

directorを使うとin,outが変換されない

サンプルコード:09_inheritance_conflictName.zip

近日公開

directorでwchar_t*がstringに変換されない

サンプルコード:08_inheritance_wchar_t.zip

近日公開

Dispose処理の変更

近日公開

新たな関数を追加する

近日公開

ポインタ配列を引数で渡す

近日公開

配列の戻り値を受け取る

近日公開

FILE*を扱えるようにする

近日公開

引数のoutでポインタを返す場合

近日公開