2018/03/17(土) [n年前の日記]
#1 [ruby][mruby] Cで書いたプログラムからmrubyを呼び出せるか実験その2
Windows10 x64 + MSYS2 上で、Cで書いたプログラムに mruby 1.4.0 を組み込んで動かせるか実験。
昨日は、ただ単に組み込んでビルドできるか試したけれど、今回はCで書いたクラスを mrubyスクリプトから呼び出せるか試してみた。
以下の記事を参考にして作業。
_mruby で C 言語の構造体をラップしたオブジェクトを作る正しい方法 - Qiita
_手探りでおぼえるmruby その1:クラスを定義する、メソッドを定義する - エンジニアのソフトウェア的愛情
昨日は、ただ単に組み込んでビルドできるか試したけれど、今回はCで書いたクラスを mrubyスクリプトから呼び出せるか試してみた。
以下の記事を参考にして作業。
_mruby で C 言語の構造体をラップしたオブジェクトを作る正しい方法 - Qiita
_手探りでおぼえるmruby その1:クラスを定義する、メソッドを定義する - エンジニアのソフトウェア的愛情
◎ ディレクトリ構成。 :
以下のような構成で作業。
~/prg
|
+---- mruby
|
+---- classmruby
|
+---- mruby
◎ 名前が変わっている部分があるらしい。 :
mruby 1.4.0 では、タイプや名前が変わってるものがいくつかある模様。
- RClass → struct RClass
- ARGS_NONE() → MRB_ARGS_NONE()
- ARGS_REQ(1) → MRB_ARGS_REQ(1)
◎ 実験に使ったソース。 :
以下のような .c、.rb、Makefile になった。前述の解説ページの写経状態だけど…。
_classmruby.c
_main.rb
_Makefile
make でビルド。./classmruby.exe で実行。
mrubyスクリプト側でクラスを生成して、値を読んだり、値を設定したり等ができることが確認できた。
_classmruby.c
#include <stdio.h>
#include <errno.h>
#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/proc.h>
#include <mruby/class.h>
#include <mruby/data.h>
#include <mruby/variable.h>
#include <mruby/string.h>
struct my_class
{
int num;
FILE *fp;
};
static void mrb_my_class_free(mrb_state *mrb, void *ptr);
// const static struct mrb_data_type mrb_my_class_type = {"MyClass", mrb_free};
const static struct mrb_data_type mrb_my_class_type = {"MyClass", mrb_my_class_free};
// class method
mrb_value mrb_my_class_initialize(mrb_state *mrb, mrb_value self)
{
FILE *fp;
struct my_class *h;
mrb_value path;
char *cpath;
h = (struct my_class *)mrb_malloc(mrb, sizeof(struct my_class));
h->fp = NULL;
DATA_TYPE(self) = &mrb_my_class_type;
DATA_PTR(self) = h;
mrb_get_args(mrb, "S", &path);
cpath = mrb_str_to_cstr(mrb, path);
fp = fopen(cpath, "r");
if (fp == NULL)
{
if (errno == EMFILE || errno == ENFILE)
{
mrb_full_gc(mrb);
fp = fopen(cpath, "r");
}
if (fp == NULL)
{
mrb_raisef(mrb, E_ARGUMENT_ERROR, "Cannot open file: %s", path);
}
}
h->num = 1234;
h->fp = fp;
return self;
}
mrb_value mrb_my_class_get(mrb_state *mrb, mrb_value self)
{
struct my_class *h = DATA_PTR(self);
return mrb_fixnum_value(h->num);
}
mrb_value mrb_my_class_put(mrb_state *mrb, mrb_value self)
{
struct my_class *h = DATA_PTR(self);
mrb_int num;
mrb_get_args(mrb, "i", &num);
h->num = num;
return mrb_fixnum_value(num);
}
mrb_value mrb_my_class_read(mrb_state *mrb, mrb_value self)
{
struct my_class *h;
size_t n;
char buf[1024];
h = DATA_PTR(self);
n = fread(buf, 1, sizeof(buf), h->fp);
if (n == 0)
{
mrb_raise(mrb, E_ARGUMENT_ERROR, "fread(3) returns 0");
}
return mrb_str_new(mrb, buf, n);
}
static void mrb_my_class_free(mrb_state *mrb, void *ptr)
{
struct my_class *h = ptr;
if (h->fp != NULL)
{
fclose(h->fp);
h->fp = NULL;
}
mrb_free(mrb, h);
}
int main(int argc, char *argv[])
{
// open mruby state
mrb_state *mrb = mrb_open();
if (mrb == 0)
{
printf("cannot open mruby state.\n");
return -2;
}
// define mruby class
struct RClass *my_class;
my_class = mrb_define_class(mrb, "MyClass", mrb->object_class);
MRB_SET_INSTANCE_TT(my_class, MRB_TT_DATA);
// define method
mrb_define_method(mrb, my_class, "initialize",
mrb_my_class_initialize, MRB_ARGS_NONE());
mrb_define_method(mrb, my_class, "get",
mrb_my_class_get, MRB_ARGS_NONE());
mrb_define_method(mrb, my_class, "put",
mrb_my_class_put, MRB_ARGS_REQ(1));
mrb_define_method(mrb, my_class, "read",
mrb_my_class_read, MRB_ARGS_NONE());
// file open
FILE *fp = fopen("main.rb", "r");
// run mruby script
mrb_load_file(mrb, fp);
// file close
fclose(fp);
// close mruby state
mrb_close(mrb);
return 0;
}
関係がありそうな関数をメモすると…。
- mrb_define_class() でクラスを定義。struct RClass * が返ってくる。
- mrb_define_method() でクラスメソッドを定義。
- MRB_ARGS_NONE() は引数が無いことを指定。
- MRB_ARGS_REQ(n) で引数の個数を指定。
- const static struct mrb_data_type mrb_my_class_type = {"MyClass", mrb_my_class_free}; で、クラスのインスタンスを解放する時に呼ばれる関数を指定。
_main.rb
a = MyClass.new("main.rb")
p a.get
a.put(3)
p a.get
puts
p a.object_id
p a.nil?
puts
p a.read
やってることは…。
- C側で書いたクラスを、パラメータを渡しながら生成。
- クラスが持ってる変数の値を読む。
- クラスが持ってる変数に値を設定。
- クラスのオブジェクトIDを取得。
- クラスが nil かどうか調べる。
- ファイルを読んで返すメソッドを呼ぶ。
_Makefile
TARGETS = classmruby all: $(TARGETS) SDL_PREFIX = /mingw32 # SDL_PREFIX = /mingw MRB_HEAD = ../mruby/include MRB_LIBS = ../mruby/build/host/lib # CG_LIBS = CROSS_COMPILE = $(SDL_PREFIX)/bin/ CC = $(CROSS_COMPILE)gcc CXX = $(CROSS_COMPILE)g++ CFLAGS = -g -Wall -I$(MRB_HEAD) CXXFLAGS = -g -Wall -I$(MRB_HEAD) LIBS = -L$(MRB_LIBS) -lmruby -lpthread -lm LDFLAGS = -Wl,-rpath -static -static-libgcc -static-libstdc++ clean: rm -f *.o *.a *~ $(TARGETS) $(TARGETS): $(TARGETS).o $(CXX) -o $@ $^ $(LIBS) $(LDFLAGS)
- -static をつけて、スタティックリンクをしてる。
make でビルド。./classmruby.exe で実行。
$ make /mingw32/bin/gcc -g -Wall -I../mruby/include -c -o classmruby.o classmruby.c /mingw32/bin/g++ -o classmruby classmruby.o -L../mruby/build/host/lib -lmruby -lpthread -lm -Wl,-rpath -static -static-libgcc -static-libstdc++ $ ./classmruby.exe 1234 3 48457229 false "a = MyClass.new(\"main.rb\")\np a.get\na.put(3)\np a.get\nputs\np a.object_id\np a.nil?\nputs\np a.read\n"
mrubyスクリプト側でクラスを生成して、値を読んだり、値を設定したり等ができることが確認できた。
◎ Makefileについて。 :
Makefile内で使われている特殊変数についてよく分からなかったのでググったり。
_Makefile の特殊変数の一覧
_Makefile の書き方 (C 言語) ・ WTOPIA v1.0 documentation
_Makefile の特殊変数の一覧
_Makefile の書き方 (C 言語) ・ WTOPIA v1.0 documentation
- $@ ... ターゲット名
- $^ ... 依存ファイルのリスト
[ ツッコむ ]
以上です。