0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

mruby/cのクラスやモジュールを、C言語で記述する

Posted at

しまねソフト研究開発センター(略称 ITOC)にいます、東です。
mruby/c 向けのC言語を使った開発資料の充実のために、数回にわたり解説記事を書いています。今回は、ユーザー独自のクラス、モジュールの作成方法について説明します。

Abstruct

mruby/cのクラスを、C言語で記述するには、

① 単純なクラスと、そのメソッド

static void c_method1(mrbc_vm *vm, mrbc_value v[], int argc)
{
}

// 以下 mrbc_init() の後で
mrbc_class *cls = mrbc_define_class(0, "MyClass", 0);
mrbc_define_method( 0, cls, "method1", c_method1 );

② 継承 (MySubClass < MyClass)

mrbc_class *cls    = mrbc_define_class(0, "MyClass", 0);
mrbc_class *subcls = mrbc_define_class(0, "MySubClass", cls);

③ ネストクラス (MyClass::MyNestClass)

mrbc_class *cls     = mrbc_define_class(0, "MyClass", 0);
mrbc_class *nestcls = mrbc_define_class_under(0, cls, "MyNestClass", 0);

mruby/cのモジュールを、C言語で記述するには、

① モジュール

mrbc_class *module = mrbc_define_module(0, "MyModule");

② ネストモジュール (MyModule::MyNestModule)

mrbc_class *module     = mrbc_define_module(0, "MyModule");
mrbc_class *nestmodule = mrbc_define_module_under(0, module, "MyNestModule");

目標・方針

  • クラスの記述方法を説明する
  • モジュールの記述方法を説明する

事前準備・環境

先日公開した記事、mruby/c をPCで動かす の動作環境での動作テストを想定しています。今回も、sample_c/sample_concurrent.c に追記する形で説明します。

クラスを作成する

独自のクラス、クラス名を、MyClass として作成します。

以下のRubyコードと同等の事を、Cで記述します。

class MyClass
  def method1
    puts "method1 called."
  end
end

C言語による独自クラスの定義

クラスの定義は、mrbc_define_class 関数を使います。

// クラスの定義
mrbc_class *cls = mrbc_define_class(0, "MyClass", 0);

第2引数に文字列でクラス名を指定します。その他の引数は、とりあえず無視してください。

メソッドの定義

定義したクラスにメソッドを追加するには、以下のようにします。

// メソッドの定義
static void c_method1(mrbc_vm *vm, mrbc_value v[], int argc)
{
  mrbc_printf("method1 called.\n");
}

メソッドの動作を記述する関数本体は、前回の記事「mruby/c の拡張関数を、C言語で記述する」 で説明した内容と同じフォーマットで記述します。

この関数を MyClassのメソッドとして登録するには、以下のようにします。

  // メソッドの登録
  mrbc_define_method( 0, cls, "method1", c_method1 );

この mrbc_define_method() も、前回の記事と同じ関数です。違いは、第2引数に mrbc_define_class() 関数の戻り値を指定することだけです。これによって、method1 は、MyClass のメソッドとして登録されます。

sample_concurrent.c 全体
sample_concurrent.c
/*
 * This sample program executes multiple mruby/c programs concurrently.
 */

#include <stdio.h>
#include <stdlib.h>
#include "mrubyc.h"

#if !defined(MRBC_MEMORY_SIZE)
#define MRBC_MEMORY_SIZE (1024*60)
#endif
static uint8_t memory_pool[MRBC_MEMORY_SIZE];

uint8_t * load_mrb_file(const char *filename)
{
  FILE *fp = fopen(filename, "rb");

  if( fp == NULL ) {
    fprintf(stderr, "File not found (%s)\n", filename);
    return NULL;
  }

  // get filesize
  fseek(fp, 0, SEEK_END);
  size_t size = ftell(fp);
  fseek(fp, 0, SEEK_SET);

  // allocate memory
  uint8_t *p = malloc(size);
  if( p != NULL ) {
    fread(p, sizeof(uint8_t), size, fp);
  } else {
    fprintf(stderr, "Memory allocate error.\n");
  }
  fclose(fp);

  return p;
}


// メソッドの定義
static void c_method1(mrbc_vm *vm, mrbc_value v[], int argc)
{
  mrbc_printf("method1 called.\n");
}


int main(int argc, char *argv[])
{
  int vm_cnt = argc-1;
  if( vm_cnt < 1 || vm_cnt > MAX_VM_COUNT ) {
    printf("Usage: %s <xxxx.mrb> <xxxx.mrb> ... \n", argv[0]);
    printf("  Maximum number of mrb file: %d\n", MAX_VM_COUNT );
    return 1;
  }

  /*
    start mruby/c with rrt0 scheduler.
  */
  mrbc_init(memory_pool, MRBC_MEMORY_SIZE);

  // クラスの定義
  mrbc_class *cls = mrbc_define_class(0, "MyClass", 0);

  // メソッドの登録
  mrbc_define_method( 0, cls, "method1", c_method1 );

  // create each task.
  for( int i = 0; i < vm_cnt; i++ ) {
    fprintf( stderr, "Loading: '%s'\n", argv[i+1] );
    uint8_t *mrbbuf = load_mrb_file( argv[i+1] );
    if( mrbbuf == 0 ) return 1;
    if( !mrbc_create_task( mrbbuf, NULL ) ) return 1;
  }

  // and execute all.
  int ret = mrbc_run();

  return ret == 1 ? 0 : ret;
}

動作テスト

ファイルが用意できたら、ビルドして実行してみます。

[~/work/mrubyc%] make
cd mrblib   ; /Applications/Xcode.app/Contents/Developer/usr/bin/make all
make[1]: Nothing to be done for `all'.
cd src      ; /Applications/Xcode.app/Contents/Developer/usr/bin/make all
make[1]: Nothing to be done for `all'.
cd sample_c ; /Applications/Xcode.app/Contents/Developer/usr/bin/make all
cc -I../hal/posix -I../src -Wall -g   -o sample_concurrent sample_concurrent.c ../build/libmrubyc.a

実行テスト用の Ruby ファイルを用意します。

tst.rb
obj = MyClass.new
obj.method1

では、準備ができたので実行してみます。

[~/work/mrubyc%] mrubyc tst.rb
Loading: './tst.mrb'
method1 called.

意図通り c_method1 が呼ばれて、method1 called. と表示されました。

クラスの継承

継承したクラスを定義するには、mrbc_define_class() の第3引数に親クラスを指定します。

// 継承したクラスの定義
mrbc_class *subcls = mrbc_define_class(0, "MySubClass", cls);

動作テスト

実行テスト用の Ruby ファイルを用意します。

tst.rb
obj = MySubClass.new
obj.method1

ファイルが用意できたら、ビルドして実行してみます。

[~/work/mrubyc%] make
   :
   :
[~/work/mrubyc%] mrubyc tst.rb
Loading: './tst.mrb'
method1 called.

Rubyのテストコードでは、MySubClass のインスタンスを作って method1 をコールしています。MySubClass には該当メソッドは定義されていませんので、親クラスの method1 メソッドが呼ばれていることが確認できました。

クラスのネスト

Rubyは、クラスのネストを行う事が可能です。

class MyClass
end

class MyClass::MyNestClass
end

C言語でこれを定義するには、先ほどとは別の API mrbc_define_class_under() を使います。

// ネストしたクラスの定義
mrbc_class *cls     = mrbc_define_class(0, "MyClass", 0);
mrbc_class *nestcls = mrbc_define_class_under(0, cls, "MyNestClass", 0);

外側のクラスを先に mrbc_define_class() を使って定義しておきます。
そして、第2引数に外側のクラスを、第3引数に内側のクラス名を指定します。

動作テスト

実行テスト用の Ruby ファイルを用意します。

tst.rb
obj = MyClass::MyNestClass.new
p obj.class

ファイルが用意できたら、ビルドして実行してみます。

[~/work/mrubyc%] make
   :
   :
[~/work/mrubyc%] mrubyc tst.rb
Loading: './tst.mrb'
MyClass::MyNestClass

ネストしたクラスを定義できたことが確認できます。

モジュールを作成する

mruby/c は、3.4からモジュールをサポートしています。
モジュールを作成するには、mrbc_define_module() を使います。

mrbc_class *module = mrbc_define_module(0, "MyModule");

2番目の引数に、モジュール名を指定します。戻り値が mrbc_class ですが、クラスとモジュールをネストする場合に都合が良いという理由と、内部的にも同じ構造体を使っているからという理由から、そうなっています。

動作テスト

C言語でモジュールを作って Rubyコードで include する例を試してみます。

// メソッドの定義
static void c_method1(mrbc_vm *vm, mrbc_value v[], int argc)
{
  mrbc_printf("method1 called.\n");
}

int main(int argc, char *argv[])
{
  ...

  // モジュールの定義
  mrbc_class *module = mrbc_define_module(0, "MyModule");

  // メソッドの登録
  mrbc_define_method( 0, module, "method1", c_method1 );

Rubyのテストコードです。

class MyClass
  include MyModule
end

obj = MyClass.new
obj.method1

ビルドして実行してみます。

[~/work/mrubyc%] make
   :
   :
[~/work/mrubyc%] mrubyc tst.rb
Loading: './tst.mrb'
method1 called.

意図通り実行できました。

クラスのネストの項で説明したように、モジュールもネストできます。ネストしたモジュールを作るには、以下の関数を使います。

mrbc_define_module_under(0, mrbc_class *outer, char *name)

制限事項

mruby/c では 2025年10月 現在、Ruby, mruby と比較してできない事があります。

  1. クラス変数、モジュール変数 (@@var) は使えない
  2. クラスメソッドとインスタンスメソッドの区別が無い
  3. C言語レベルで、moduleを includeする方法が無い

どれも大きなデメリットでは無いと考えていますが、今後クラス変数はサポートしても良いかなとは考えています。

おわりに

C言語を使って、Rubyのクラス、モジュールを作成する方法を説明しました。
次回は、クラスのインスタンスに関わる、もっと踏み込んだ内容について記事にしようと思います。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?