さて、前回ではRubyの BasicSocket#setsocopt メソッドの optval になぜ文字列を渡せばいいのか、という疑問が生じるまでの背景を記した。今回は、その疑問の深堀りをさらに進めていく。
Rubyは実装はCで書かれているため、 BasicSocket#setsocopt メソッドも最終的にはCのsetsockopt 関数が呼ばれるはずだ(実際には、これはCの関数というより、システムコールの1つのようでCはそのラップをしているだけのようだ)。
まず、疑問があるのはCレベルでのsetsocopt
の optval に渡される値が途中でRubyの文字列から途中で変換されて、 packet_mreq
構造体が渡されるのか、文字列が渡されるのか、ということだ。ドキュメント的には文字列と記載されているのだが、Cの方のドキュメントには構造体を渡すものとしか記載されておらず、本当に文字列が渡されるのか疑問だったので、ここをコードレベルで確認することにした。
Ruby の setsockopt メソッドは
class BasicSocket - Documentation for Ruby 3.0.0
の click to toggle source
をみてソースを確認すると、 ext/socket/basicsocket.c
の bsock_setsockopt
関数が実体のようだ(Ruby 3.0.0 のソースで確認)。GitHubでは
https://github.com/ruby/ruby/blob/v3_0_0/ext/socket/basicsocket.c#L198 あたりに該当する。
ここのソースを見ると、241行目に setsockopt(fptr->fd, level, option, v, vlen)
があり、ここで setsockopt
が実行され、この時の v
が optval のデータに該当するようだ。さてこの v
は何かというと、少し上の方に以下のような記述があり、 RSTRING_PTR マクロは何かというと、文字列の先頭ポインタを返すマクロのようだ。
StringValue(val); v = RSTRING_PTR(val); vlen = RSTRING_SOCKLEN(val);
ということは、 setsockopt の optval には文字列を直接渡しているということだろうか…?
実際に v
の中身を展開して確認してみることとした。
泥臭いが、RubyのCコードを編集し、v を printf で出力するように setsocopt
関数を改変して、Rubyから setsocopt メソッドを実行してみる(いまいちgdbを使う方法が分からなかった)。
すると、以下の通り、 v.mr_type
で参照しようとしても、コンパイルエラーとなり、中身は文字列で間違いないようだ。
内容としても、Rubyから指定した内容と合致する。
rb_io_check_closed(fptr); // printf("mreq.mr_type: %d\n", v.mr_type); コンパイルエラーになる printf("%d\n", v[1]); // 3 or 4 printf("%d\n", v[1]); // 0 printf("%d\n", v[2]); // 0 printf("%d\n", v[3]); // 0 printf("%d\n", v[4]); // 1 printf("%d\n", v[5]); // 0 printf("%d\n", v[6]); // 0 printf("%d\n", v[7]); // 0 printf("%d\n", v[8]); // 0 printf("%d\n", v[9]); // 0 printf("%d\n", v[10]); // 0 printf("%d\n", v[11]); // 0 printf("%d\n", v[12]); // 0 printf("%d\n", v[13]); // 0 printf("%d\n", v[14]); // 0 printf("%d\n", v[15]); // 0 printf("%d\n", vlen); // 16 if (setsockopt(fptr->fd, level, option, v, vlen) < 0)
つまり、 setsockopt
関数のoptval には文字列を渡しているということになる。
なぜ指定の構造体では無いのに、問題なく動作しているのだろうか?
長くなってきたので、続きは次回に。