概要
過去に inline の基本的な使い方を紹介しました
今回は少し複雑な処理として Ruby と C言語間で Struct のやり取りを行ってみました
ポイントは NativeExtension 用の関数を使う点です
環境
- macOS 10.14.3
- Ruby 2.5.1p57
- RubyInline 3.12.4
サンプルコード
Ruby <-> C 間で struct のやり取りをするサンプルコードの全体です
require 'inline'
class MyTest
inline do |builder|
# builder.add_compile_flags '-x c++', '-std=c++11'
builder.prefix '
typedef struct {
char *name;
int age;
} Person;
'
builder.c_singleton '
VALUE allocate() {
Person *p = ALLOC(Person);
return Data_Wrap_Struct(self, NULL, free, p);
}
'
builder.struct_name = 'Person'
builder.accessor 'name', 'char *'
builder.accessor 'age', 'int'
builder.c '
void set() {
Person *p;
Data_Get_Struct(self, Person, p);
p->name = (char*)"hawk";
p->age = 10;
}
'
builder.c '
void show() {
Person *p;
Data_Get_Struct(self, Person, p);
printf("%s\n", p->name);
printf("%d\n", p->age);
}
'
end
end
p = MyTest.new
p.set
p.show
p.name = "snowlog"
p.age = 20
p.show
少し長いですがこんな感じです
実行すると以下のようになります
hawk
10
snowlog
20
解説
簡単に上記サンプルコードについて説明します
自信がないので間違っていれば訂正をお願いしたいです
構造体の定義
まず C 側で使用する構造体を定義します
構造体の定義は builder.prefix
で定義します (#define
などもここです)
builder.prefix
の定義だけでは C 側からしか参照できません
Ruby 側から構造体を参照する準備
Ruby 側から参照するために builder.struct_name
, builder.accessor
を定義します
struct_name
は C 側で定義した構造体を指定します
この構造体を Ruby 側のクラスで使いますという定義になります
accessor
は構造体のメンバーに Ruby 側からどうやってアクセスするかの定義をします
例えば builder.accessor 'name', 'char *'
は name という char *
型のメンバーに対して name というアクセサ名でアクセスできるようにする定義になります
構造体に含まれるメンバー分定義しましょう
C 側に構造体のメモリ領域を確保する
そして 1 つ目のポイントの builder.c_singleton
です
これは Ruby 側で構造体を作成した際に自動的にコールされます
自動的に構造体を格納するためのメモリ領域を確保してくれます
今回であれば、MyTest.new
のタイミングで呼ばれています (たぶん)
定義しないと次の C 側で構造体を参照する際にエラーになります
C 側で構造体を参照する
set
および show
関数でメモリ領域を確保した構造体にアクセスします
まず set
ですが構造体のメンバーに値をセットしています
ポイントは Data_Get_Struct(self, Person, p);
です
これでメモリ上に確保した構造体のポインタをセットします
最後の p
に構造体のポインタが代入されるのであとは p
を参照すれば OK です
p
はポインタなのでアローで参照しましょう
同じように show
をメモリ領域からポイントを取り出して参照しています
show
は printf
しているだけです
動作確認
今回はちゃんと Ruby <-> C 間のやり取りがうまくいっているか確認するために set
-> show
したあとに Ruby 側でメンバを参照して値を代入し直し再度 show
しています
結果を見るとわかりますがちゃんと Ruby 側の反映がされて C 側で表示されていることがわかると思います
おまけ: struct in struct
構造体の中に更に構造体を入れた場合はあると思います
Ruby からの参照はできませんが、C 内だけであれば使えました
builder.prefix '
typedef struct {
char *type;
} Job;
typedef struct {
char *name;
int age;
Job job;
} Person;
'
という感じで構造体を定義して
builder.c '
void set() {
Person *p;
Data_Get_Struct(self, Person, p);
p->name = (char*)"hawk";
p->age = 10;
p->job.type = (char*)"se";
}
'
こんな感じで参照すればメモリ上に格納されます
ただ jot.type
を Ruby 側から参照する方法がわかりませんでした
builder.accessor 'job', 'Job'
という感じで定義しても Job
というタイプは存在しないと言われてエラーになります
VALUE
を使えば何とかなりそうですがわかりませんでした、、
最後に
RubyInline で Ruby <-> C 言語間の構造体データのやり取りを行ってみました
int や char だけなら特に気にすることなく返り値と引数でやり取りすればいいのですが、構造体になるといきなりハードルが上がる感じがします
今回使った Data_Wrap_Struct
や Data_Get_Struct
, VALUE
は Ruby で NativeExtension を開発する際に必要になる関数です
RubyInline も内部的には NativeExtension 用の関数をコールしています
おそらくですが RubyInline はそのような関数を使わなくても Ruby から C 言語を簡単に参照できるライブラリかなと思います
なので、今回のように NativeExtension の関数を直接呼ぶような実装をするのであれば初めから NativeExtension として書いたほうが良いかもしれません
その辺りの感覚やベストプラクティスは開発経験を積まないと何とも言えないかなと思います
0 件のコメント:
コメントを投稿