Slide 1

Slide 1 text

Introduction to C Extensions Ryo Kajiwara (sylph01), 2025/3/9 @ kyoto.rb 1

Slide 2

Slide 2 text

RubyKaigi, more like CKaigi, huh? よく冗談めかして言われますね 2

Slide 3

Slide 3 text

C 拡張って何 C 言語でRuby の機能を書くこと gem の形で使うことが多い C やアセンブリで書いたほうが速い部分をC で書く 実はJIT のおかげで必ずしもC だから速いというわけでもない C 以外の言語の入り口になることもある そういえば今年Go gem の話がありますね? 3

Slide 4

Slide 4 text

現代RubyKaigi ではもはやC 拡張の話は 当たり前のこととして通過される RubyKaigi のトークには新規性が必要(なことが多い) Ruby Core としてはもはや当たり前のもの C で書かれたライブラリのラッパーだけでは新規性がない とはいえスルーするにはあまりにも不親切。なので今回できる限りの解 説を試みる。全部は 私もわからないので 解説しきれません。 4

Slide 5

Slide 5 text

見るべき2 大ドキュメント ruby/ruby のdoc/extension.rdoc 日本語がある(リンクは日本語のほう) The Definitive Guide to Ruby's C API 5

Slide 6

Slide 6 text

実際に自分が手を入れた/ ているコー ドがこちら https:/ /github.com/sylph01/openssl/blob/hpke/ext/openssl/ossl_hpke _ctx.c https:/ /github.com/sylph01/openssl/blob/hpke/ext/openssl/ossl_ hpke_ctx.h https:/ /github.com/sylph01/openssl/blob/hpke/ext/openssl/ossl.c から Init_ossl_hpke_ctx() が呼ばれるのがエントリーポイ ント 6

Slide 7

Slide 7 text

クラス/ モジュールの定義 void Init_ossl_hpke_ctx(void) { mHPKE = rb_define_module_under(mOSSL, "HPKE"); cContext = rb_define_class_under(mHPKE, "Context", rb_cObject); cSenderContext = rb_define_class_under(cContext, "Sender", cContext); cReceiverContext = rb_define_class_under(cContext, "Receiver", cContext); eHPKEError = rb_define_class_under(mHPKE, "HPKEError", eOSSLError); ... class OpenSSL::HPKE::Context class OpenSSL::HPKE::Context::Sender class OpenSSL::HPKE::Context::Receiver class OpenSSL::HPKE::Error 7

Slide 8

Slide 8 text

メソッドの定義 rb_define_method(cSenderContext, "initialize", ossl_hpke_ctx_new_sender, 2); (1) 定義したいクラス、(2) メソッド名、(3) 実装を示す関数ポインタ、(4) 引数の個数 以下に相当 class OpenSSL::HPKE::Context::Sender def initialize(arg1, arg2) end 8

Slide 9

Slide 9 text

メソッドの定義 VALUE ossl_hpke_ctx_new_sender(VALUE self, VALUE mode, VALUE suite) { ... C の世界ではRuby のオブジェクトは全部 VALUE 自身がどんな型であるかを知っているデータ(へのポインタ) (1) self 、(2) 以降は rb_define_method で指定した引数の数だけ VALUE が続く return で返す VALUE がRuby の世界で返る値 9

Slide 10

Slide 10 text

メソッドの定義 おまけ: モジュール関数の場合は rb_define_module_function rb_define_module_function(mHPKE, "keygen", ossl_hpke_keygen, 3); おまけ2: mruby/c ではどうするの?→るびま0064 号のRubyKaigi 2024 の トーク解説記事 10

Slide 11

Slide 11 text

attributes // attr_readers for suite values rb_define_attr(cContext, "kem_id", 1, 0); rb_define_attr(cContext, "kdf_id", 1, 0); rb_define_attr(cContext, "aead_id", 1, 0); https:/ /docs.ruby-lang.org/ja/latest/function/rb_define_attr.html 第3 引数はread 、第4 引数はwrite 11

Slide 12

Slide 12 text

インスタンス変数 rb_iv_set(self, "@kem_id", kem_id); kem_id = rb_iv_get(suite, "@kem_id"); get するときは VALUE が返ってくるので、Ruby の世界のinteger をC で使 う場合は NUM2INT(kem_id) みたいな形で変換する。 12

Slide 13

Slide 13 text

定数の呼び出し https:/ /docs.ruby-lang.org/ja/latest/function/rb_const_get_at.html による と VALUE rb_const_get_at(VALUE klass, ID name) 。 ID → rb_intern で名前との対応関係が得られる。 mode_table = rb_const_get_at(cContext, rb_intern("MODES")); 13

Slide 14

Slide 14 text

Ruby の世界の関数を呼び出す mode_id = rb_funcall(mode_table, rb_intern("[]"), 1, mode); ↓ mode_id = MODES[mode] rb_p(rb_funcall(rbstr, rb_intern("unpack1"), 1, rb_str_new_cstr("H*"))); ↓ p(str.unpack1("H*")) 14

Slide 15

Slide 15 text

C の構造体をRuby で包む static void ossl_hpke_ctx_free(void *ptr) { OSSL_HPKE_CTX_free(ptr); } const rb_data_type_t ossl_hpke_ctx_type = { "OpenSSL/HPKE_CTX", { 0, ossl_hpke_ctx_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY }; 15

Slide 16

Slide 16 text

C の構造体をRuby で包む static VALUE hpke_ctx_new0(VALUE arg){ OSSL_HPKE_CTX *ctx = (OSSL_HPKE_CTX *)arg; VALUE obj; obj = rb_obj_alloc(cContext); RTYPEDDATA_DATA(obj) = ctx; return obj; } VALUE ossl_hpke_ctx_new(OSSL_HPKE_CTX *ctx){ VALUE obj; int status; obj = rb_protect(hpke_ctx_new0, (VALUE)ctx, &status); if (status) { OSSL_HPKE_CTX_free(ctx); rb_jump_tag(status); } return obj; 16

Slide 17

Slide 17 text

C の構造体をRuby で包む static VALUE ossl_hpke_ctx_alloc(VALUE klass) { return TypedData_Wrap_Struct(klass, &ossl_hpke_ctx_type, NULL); } void Init_ossl_hpke_ctx(void) { mHPKE = rb_define_module_under(mOSSL, "HPKE"); cContext = rb_define_class_under(mHPKE, "Context", rb_cObject); ... rb_define_alloc_func(cContext, ossl_hpke_ctx_alloc); } 17

Slide 18

Slide 18 text

C 拡張ってどうやってビルドするの? https:/ /docs.ruby-lang.org/ja/latest/library/mkmf.html Makefile を生成するためのライブラリ。 extconf.rb からmkmf をrequire してMakefile が作られてshared object が作ら れる。 皆さんももしかしたら apt-get し足りない何かがあったときに extconf.rb がfailed になってるのを見たことあるかもしれない。 18

Slide 19

Slide 19 text

おまけ: Copilot に作らせてみた だいたい https:/ /github.com/tilo/gem_with_c_extension と近い内容のも のが出てきたが、Mac でビルドできなかった。shared object ( .so ) ファ イルが作られなかった 19

Slide 20

Slide 20 text

ext/my_c_extension/extconf.rb require 'mkmf' create_makefile('my_c_extension/my_c_extension') 20

Slide 21

Slide 21 text

ext/my_c_extension/my_c_extension.c #include "ruby.h" VALUE rb_mMyCExtension; VALUE hello_world(VALUE self) { return rb_str_new_cstr("Hello, world!"); } void Init_my_c_extension() { rb_mMyCExtension = rb_define_module("MyCExtension"); rb_define_method(rb_mMyCExtension, "hello_world", hello_world, 0); } 21

Slide 22

Slide 22 text

gemspec Gem::Specification.new do |spec| # ... other configurations ... spec.extensions = ['ext/my_c_extension/extconf.rb'] spec.files = Dir.chdir(File.expand_path(__dir__)) do `git ls-files -z`.split("\x0").select do |f| f.match(%r{^(ext|lib)/}) || f == 'my_c_extension.gemspec' end end # ... other configurations ... end 22

Slide 23

Slide 23 text

Ruby ラッパー require 'my_c_extension/my_c_extension' module MyCExtension def self.hello MyCExtension.hello_world end end 23