Ruby Language
C拡張
サーチ…
あなたの最初の内線番号
C拡張は2つの一般的な部分で構成されます:
- Cコードそのもの。
- 拡張構成ファイル。
最初の拡張機能を使い始めるには、 extconf.rb
というファイルに次のextconf.rb
:
require 'mkmf'
create_makefile('hello_c')
いくつかのことを指摘しておきます。
最初に、 hello_c
という名前は、コンパイルされた拡張の出力に名前をhello_c
ものです。それはあなたがrequire
と関連して使用するものになります。
次に、 extconf.rb
ファイルは実際に何かに名前をextconf.rb
ことができます。これは伝統的にネイティブコードを持つ宝石を作るために使われています。実際には拡張子をコンパイルするファイルはruby extconf.rb
実行するときに生成されるMakefile ruby extconf.rb
。生成されるデフォルトのMakefileは、現在のディレクトリ内のすべての.c
ファイルをコンパイルします。
hello.c
という名前のファイルに以下を入れ、 ruby extconf.rb && make
実行します。
#include <stdio.h>
#include "ruby.h"
VALUE world(VALUE self) {
printf("Hello World!\n");
return Qnil;
}
// The initialization method for this module
void Init_hello_c() {
VALUE HelloC = rb_define_module("HelloC");
rb_define_singleton_method(HelloC, "world", world, 0);
}
コードの内訳:
Init_hello_c
という名前は、 extconf.rb
ファイルで定義されている名前と一致していなければなりません。そうしないと、拡張機能を動的にロードすると、拡張機能を起動するためのシンボルが見つかりません。
rb_define_module
への呼び出しはrb_define_module
という名前のRubyモジュールを作成しています。 HelloC
では、C関数の名前空間を作成します。
最後に、への呼び出しrb_define_singleton_method
に直接接続モジュールレベルのメソッドになりHelloC
我々がルビーから呼び出すことができるモジュールHelloC.world
。
make
を呼び出して拡張をコンパイルした後で、C拡張でコードを実行できます。
コンソールを起動してください!
irb(main):001:0> require './hello_c'
=> true
irb(main):002:0> HelloC.world
Hello World!
=> nil
C構造体の操作
C構造体をRubyオブジェクトとしてData_Wrap_Struct
ようにするには、 Data_Wrap_Struct
とData_Get_Struct
呼び出しでそれらをラップする必要があります。
Data_Wrap_Struct
、Cのデータ構造をRubyオブジェクトにラップします。コールバック関数へのポインタとともに、データ構造体へのポインタをとり、VALUEを返します。 Data_Get_Struct
マクロはそのVALUEを受け取り、Cのデータ構造体へのポインタを返します。
ここに簡単な例があります:
#include <stdio.h>
#include <ruby.h>
typedef struct example_struct {
char *name;
} example_struct;
void example_struct_free(example_struct * self) {
if (self->name != NULL) {
free(self->name);
}
ruby_xfree(self);
}
static VALUE rb_example_struct_alloc(VALUE klass) {
return Data_Wrap_Struct(klass, NULL, example_struct_free, ruby_xmalloc(sizeof(example_struct)));
}
static VALUE rb_example_struct_init(VALUE self, VALUE name) {
example_struct* p;
Check_Type(name, T_STRING);
Data_Get_Struct(self, example_struct, p);
p->name = (char *)malloc(RSTRING_LEN(name) + 1);
memcpy(p->name, StringValuePtr(name), RSTRING_LEN(name) + 1);
return self;
}
static VALUE rb_example_struct_name(VALUE self) {
example_struct* p;
Data_Get_Struct(self, example_struct, p);
printf("%s\n", p->name);
return Qnil;
}
void Init_example()
{
VALUE mExample = rb_define_module("Example");
VALUE cStruct = rb_define_class_under(mExample, "Struct", rb_cObject);
rb_define_alloc_func(cStruct, rb_example_struct_alloc);
rb_define_method(cStruct, "initialize", rb_example_struct_init, 1);
rb_define_method(cStruct, "name", rb_example_struct_name, 0);
}
また、 extconf.rb
:
require 'mkmf'
create_makefile('example')
拡張機能をコンパイルした後:
irb(main):001:0> require './example'
=> true
irb(main):002:0> test_struct = Example::Struct.new("Test Struct")
=> #<Example::Struct:0x007fc741965068>
irb(main):003:0> test_struct.name
Test Struct
=> nil
インラインCを書く - RubyInLine
RubyInlineはRubyコードの中に他の言語を埋め込むためのフレームワークです。 Module#inlineメソッドを定義し、ビルダーオブジェクトを返します。あなたは、Ruby以外の言語で書かれたコードを含む文字列をBuilderに渡し、Rubyから呼び出せるものにBuilderを変換します。
CまたはC ++コード(デフォルトのRubyInlineインストールでサポートされている2つの言語)が与えられると、Builderオブジェクトは小さな拡張をディスクに書き込み、コンパイルしてロードします。自分でコンパイルする必要はありませんが、ホームディレクトリの.ruby_inlineサブディレクトリに生成されたコードとコンパイルされた拡張子が表示されます。
あなたのRubyプログラムにCコードを埋め込む:
- RubyInline( rubyinline gemとして利用可能)は自動的に拡張子を作成します
RubyInlineはirb内では動作しません
#!/usr/bin/ruby -w
# copy.rb
require 'rubygems'
require 'inline'
class Copier
inline do |builder|
builder.c <<END
void copy_file(const char *source, const char *dest)
{
FILE *source_f = fopen(source, "r");
if (!source_f)
{
rb_raise(rb_eIOError, "Could not open source : '%s'", source);
}
FILE *dest_f = fopen(dest, "w+");
if (!dest_f)
{
rb_raise(rb_eIOError, "Could not open destination : '%s'", dest);
}
char buffer[1024];
int nread = fread(buffer, 1, 1024, source_f);
while (nread > 0)
{
fwrite(buffer, 1, nread, dest_f);
nread = fread(buffer, 1, 1024, source_f);
}
}
END
end
end
C関数copy_file
がCopier
インスタンスメソッドとして存在するようになりました。
open('source.txt', 'w') { |f| f << 'Some text.' }
Copier.new.copy_file('source.txt', 'dest.txt')
puts open('dest.txt') { |f| f.read }