mieki256's diary



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:クラスを定義する、メソッドを定義する - エンジニアのソフトウェア的愛情

ディレクトリ構成。 :

以下のような構成で作業。
~/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
#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

  • $@ ... ターゲット名
  • $^ ... 依存ファイルのリスト

以上です。

過去ログ表示

Prev - 2018/03 -
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project