Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to C Extensions

sylph01
March 09, 2025

Introduction to C Extensions

@ kyoto.rb 2025/3/9

sylph01

March 09, 2025
Tweet

More Decks by sylph01

Other Decks in Programming

Transcript

  1. C 拡張って何 C 言語でRuby の機能を書くこと gem の形で使うことが多い C やアセンブリで書いたほうが速い部分をC で書く

    実はJIT のおかげで必ずしもC だから速いというわけでもない C 以外の言語の入り口になることもある そういえば今年Go gem の話がありますね? 3
  2. 現代RubyKaigi ではもはやC 拡張の話は 当たり前のこととして通過される RubyKaigi のトークには新規性が必要(なことが多い) Ruby Core としてはもはや当たり前のもの C

    で書かれたライブラリのラッパーだけでは新規性がない とはいえスルーするにはあまりにも不親切。なので今回できる限りの解 説を試みる。全部は 私もわからないので 解説しきれません。 4
  3. クラス/ モジュールの定義 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
  4. メソッドの定義 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
  5. 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
  6. インスタンス変数 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
  7. 定数の呼び出し 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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