Perl入門ゼミ

テキスト処理、Linuxサーバー管理、Web開発ならPerl
  1. Perl
  2. XS
  3. here

C++のクラスをXSから利用する方法

XSでC++のクラスを呼び出す方法を学びましょう。これができるようになれば、どんなC++のライブラリでも、Perlにバインディングができるようになると思います。

h2xsでモジュールを作成

最初にh2xsでXS用のモジュールを作成します。

h2xs -A -n MyClass

こうすると「MyClass」というディレクトリが作成されます。次のようなファイルとディレクトリが作成されます。

Changes
lib/
Makefile.PL
MANIFEST
ppport.h
README
MyClass.xs
t/

C++でクラスの作成

C++でクラスを作成しましょう。ヘッダファイルとソースファイルを作成します。

MyClass.h

ヘッダファイルです。コンストラクタ、メソッド、クラスメソッドを宣言しています。通常のメソッドとクラスメソッドの呼び出し方の違いを理解するために、このような構成にしています。ファイル名は「MyClass.h」です。

#ifndef MYCLASS_INCLUDE
#define MYCLASS_INCLUDE
class MyClass {
  
  public:
  
  // コンストラクタ
  MyClass();
  
  // メソッド
  void print();
  
  // クラスメソッド
  static void print_static();
};
#endif

「#ifndef」で始まっているのは、インクルードガードと呼ばれるものです。ヘッダファイルは、XSファイルと、ソースファイルの2箇所から読み込まれる必要があります。すると、そのままでは、ヘッダファイルが二重に取り込まれて、コンパイルエラーになってしまいます。それを防ぐために、インクルードを行っています。

MyClass_src.cpp

C++のソースファイルです。コンストラクタ、メソッド、クラスメソッドの実装を行っています。ソースファイルでは、MyClassというシンボルを解決するために、ヘッダファイルを読み込む必要があります。ファイル名は「MyClass_src.cpp」にしてあります。これは「MyClass.cpp」としてしまうと、コンパイル後の名前は「MyClass.o」となりますが、これはXSファイル「MyClass.xs」のコンパイル後の名前「MyClass.o」とかぶってしまうためです。

#include <iostream>
#include "MyClass.h"

// コンストラクタ
MyClass::MyClass() {}

// メソッド
void MyClass::print() {
  std::cout << "MyClass::print\n";
}

// クラスメソッド
void MyClass::print_static() {
  std::cout << "MyClass::print_static\n";
}

ヘッダファイルとソースファイルは保存して、XSファイルが存在するディレクトリと同じディレクトリに配置してください。

XSファイルの記述

XSファイルを記述しましょう。

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"
#include "MyClass.h"

#define XS_OBJ_TO_PTR(x, type)     (INT2PTR(type, SvROK(x) ? SvIV(SvRV(x)) : SvIV(x)))
#define XS_PTR_TO_OBJ(x, class) \
  sv_bless( \
    sv_2mortal( \
      newRV_inc( \
        sv_2mortal( \
          newSViv(PTR2IV(x)) \
        ) \
      ) \
    ), \
    gv_stashpv(class, 1) \
  );

MODULE = MyClass		PACKAGE = MyClass		

void
new(...)
  PPCODE:
{
  MyClass* self = new MyClass();
  SV* self_sv = XS_PTR_TO_OBJ(self, "MyClass");
  
  XPUSHs(self_sv);
  XSRETURN(1);
}

void
print(...)
  PPCODE:
{
  MyClass* self = XS_OBJ_TO_PTR(ST(0), MyClass*);
  
  self->print();
  XSRETURN(0);
}

void
print_static(...)
  PPCODE:
{
  MyClass::print_static();
  XSRETURN(0);
}

void DESTORY(...)
  PPCODE:
{
  MyClass* self = XS_OBJ_TO_PTR(ST(0), MyClass*);
  delete self;
}

型の変換を行うためのマクロ

少し解説をしておきます。以下は、Perlのオブジェクト(SV*型)をC(あるいはC++)のポインタに変換するマクロと、C(あるいはC++)のポインタをPerlのオブジェクト(SV*型)に変換するマクロになっています。

#define XS_OBJ_TO_PTR(x, type)     (INT2PTR(type, SvROK(x) ? SvIV(SvRV(x)) : SvIV(x)))
#define XS_PTR_TO_OBJ(x, class) \
  sv_bless( \
    sv_2mortal( \
      newRV_inc( \
        sv_2mortal( \
          newSViv(PTR2IV(x)) \
        ) \
      ) \
    ), \
    gv_stashpv(class, 1) \
  );

コンストラクタ

コンストラクタです。newを使ってMyClassをインスタンス化してMyClass*型に代入します。そして、このポンイタを、Perlのオブジェクト変換して、返します。

void
new(...)
  PPCODE:
{
  MyClass* self = new MyClass();
  SV* self_sv = XS_PTR_TO_OBJ(self, "MyClass");
  
  XPUSHs(self_sv);
  XSRETURN(1);
}

メソッド呼び出し

メソッドの呼び出しです。

void
print(...)
  PPCODE:
{
  MyClass* self = XS_OBJ_TO_PTR(ST(0), MyClass*);
  
  self->print();
  XSRETURN(0);
}

メソッド呼び出しでは、第一引数にはPerlのオブジェクトが渡ってくるので、これをポインタ「MyClass*」に変換します。そして「self->print」としてメソッドを呼び出します。

クラスメソッドの呼び出し

クラスメソッドの呼び出しです。

void
print_static(...)
  PPCODE:
{
  MyClass::print_static();
  XSRETURN(0);
}

「MyClass::print_static()」と完全修飾名で呼び出しているだけです。

デストラクタ

デストラクタです。deleteを使って、メモリの開放を行う必要があります。

void DESTORY(...)
  PPCODE:
{
  MyClass* self = XS_OBJ_TO_PTR(ST(0), MyClass*);
  delete self;
}

Makefile.PLの修正

次にMakefile.PLを少し修正しましょう。一番下の「OBJECT」オプションがデフォルトではコメントアウトされているので、コメントを取り除きます。「$(O_FILES)」という設定をすれば、カレントディレクトリのすべてのC言語のソースファイルとC++のソースファイルがコンパイルの対象になります。

そしてコンパイラとリンカを「g++」に変更します。コンパイラは「CC」オプション、リンカは「LD」で設定できます。

use ExtUtils::MakeMaker;
use strict;
use warnings;

WriteMakefile(
    NAME              => 'MyClass',
    VERSION_FROM      => 'lib/MyClass.pm', # finds $VERSION
    PREREQ_PM         => {}, # e.g., Module::Name => 1.1
    ($] >= 5.005 ?     ## Add these new keywords supported since 5.005
      (ABSTRACT_FROM  => 'lib/MyClass.pm', # retrieve abstract from module
       AUTHOR         => 'A. U. Thor <kimoto@sakura.ne.jp>') : ()),
    LIBS              => [''], # e.g., '-lm'
    DEFINE            => '', # e.g., '-DHAVE_SOMETHING'
    INC               => '-I.', # e.g., '-I. -I/usr/include/other'
    OBJECT            => '$(O_FILES)', # link all the C files too
    CC =>'g++',
    LD => 'g++',
);

テストスクリプト

テストスクリプトを作成します。これは、XSファイルがあるディレクトリと同じディレクトリにおいてください。

use strict;
use warnings;

use MyClass;

my $obj = MyClass->new;
$obj->print;
MyClass->print_static;

コンパイルして実行

コンパイルして実行してみましょう。

perl Makefile.PL
make
perl -Mblib test.pl

次のように出力されれば成功です。

MyClass::print
MyClass::print_static

これで、C++のクラスをXSファイルから利用することができるようになりました。

Qiitaで
「3分間Perlテキストクッキング」
という連載を始めました。
テキスト処理を題材にして、3分くらいで読める分量で、書いていきます。
文字コード、テキストデータ、コンピュータにおけるテキストの扱いなど、ソフトウェアの基礎の話題も
3分間Perlテキストクッキング