しまねソフト研究開発センター(略称 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 全体
/*
* 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 ファイルを用意します。
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 ファイルを用意します。
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 ファイルを用意します。
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 と比較してできない事があります。
- クラス変数、モジュール変数 (
@@var) は使えない - クラスメソッドとインスタンスメソッドの区別が無い
- C言語レベルで、moduleを includeする方法が無い
どれも大きなデメリットでは無いと考えていますが、今後クラス変数はサポートしても良いかなとは考えています。
おわりに
C言語を使って、Rubyのクラス、モジュールを作成する方法を説明しました。
次回は、クラスのインスタンスに関わる、もっと踏み込んだ内容について記事にしようと思います。