Home > Net >  Native C extension not finding code used by Ruby core
Native C extension not finding code used by Ruby core

Time:07-01

Based on browsing the Ruby API sources for Array#length and Range#begin I know that macros RARRAY_LEN and RANGE_BEG exist and are used to implement the corresponding methods:

Array#length

static VALUE
rb_ary_length(VALUE ary)
{
    long len = RARRAY_LEN(ary);
    return LONG2NUM(len);
}

Range#begin

static VALUE
range_begin(VALUE range)
{
    return RANGE_BEG(range);
}

However, while the code below has no problems with RARRAY_LEN I have been unable to get RANGE_BEG to work:

test.c

#include "test.h"

VALUE rb_mTest = Qnil;
VALUE rb_cTest = Qnil;

VALUE super_initialize(VALUE self) {
  return self;
}

VALUE array_len(VALUE self, VALUE ary) {
  Check_Type(ary, T_ARRAY);
  return LONG2NUM(RARRAY_LEN(ary));       // this works
}

VALUE range_begin(VALUE self, VALUE rng) {
  if (rb_obj_is_kind_of(rng, rb_cRange)) {
    // return RANGE_BEG(rng);                          // doesn't work
    return rb_funcall(rng, rb_intern("begin"), 0);     // this works
  }
  return Qnil;
}

void Init_test(void) {
  rb_mTest = rb_define_module("RangeTest");
  rb_cTest = rb_define_class_under(rb_mTest, "Tester", rb_cObject);

  rb_define_method(rb_cTest, "initialize", super_initialize, 0);
  rb_define_method(rb_cTest, "array_len", array_len, 1);
  rb_define_method(rb_cTest, "begin_value", range_begin, 1);
}

test.h

#ifndef TEST_H
#define TEST_H 1

#include "ruby.h"

VALUE super_initialize(VALUE self);
void Init_test(void);
VALUE range_begin(VALUE self, VALUE rng);
VALUE array_len(VALUE self, VALUE ary);

#endif /* TEST_H */

When the commented line is made active, the compiler generates this error message:

error: implicit declaration of function 'RANGE_BEG' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
    return RANGE_BEG(rng);
           ^
1 error generated.

This sounds to me like I'm missing an include, but 1) no additional include is required for RARRAY_LEN; 2) including range.h didn't change anything; and 3) I've failed to find where RANGE_BEG is defined when I tried searching with grep.

As you can see, I have a work-around using rb_funcall but would like to know why one macro found in the Ruby source works while another doesn't. The extension docs I've looked at have also implied that the macro versions are more efficient than function call versions for data access, so using the macro would be preferable.

To sum up, I'm hoping anybody can tell me a suitable incantation to get the compiler to access RANGE_BEG. This is happening on MacOS 12.4 (Monterey) using ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin21] installed by HomeBrew, with CPPFLAGS=-I/opt/homebrew/opt/ruby/include and LDFLAGS=-L/opt/homebrew/opt/ruby/lib.

CodePudding user response:

For whatever reason the RANGE_XXX macros are not included in the API headers. They are only defined and used in the internal Ruby implementation itself. It seems they wanted to keep these methods private, probably to ensure backwards and forward compatibility in case the internal implementation changes over time.

However, you get almost identical access through the API by using the provided method rb_range_values:

int rb_range_values(VALUE range, VALUE *begp, VALUE *endp, int *exclp);

You can peek at its implementation here, where you can see it actually makes use of those internal macros.

To use it you simply have to define the returned variables and then call:

 VALUE beg;
 VALUE end;
 int excl;
 rb_range_values(range, &beg, &end, &excl);

Although this method does a little bit more than you need, it seeems likely it is still faster than a version using funcall, but you would need to benchmark to make sure.

  • Related