【问题标题】:How do I print values from C extensions?如何从 C 扩展中打印值?
【发布时间】:2015-04-20 21:35:03
【问题描述】:

每个 Ruby 对象都是 C 中的 VALUE 类型。如何以可读的方式打印它?

欢迎任何其他有关调试 Ruby C 扩展的提示。

【问题讨论】:

    标签: c ruby debugging ruby-c-extension


    【解决方案1】:

    您可以使用 C 函数 rb_p 对 Ruby 对象调用 p。例如:

    VALUE empty_array = rb_ary_new();
    rb_p(empty_array); // prints out "[]"
    

    【讨论】:

    • 谢谢,这值得更多的支持!调试 c 扩展的任何其他提示或资源?
    【解决方案2】:

    我发现了一种在 Visual Studio 中使用 Natvis 文件的有趣方式。

    我已经在 Ruby C API 上创建了 C++ 包装对象 - 这给了我更多的类型安全性,并且语法变得更类似于编写实际的 Ruby。

    我不会发布整个代码 - 太长了,我计划最终将其开源。

    但它的要点是:

    class Object
    {
    public:
      Object(VALUE value) : value_(value)
      {
        assert(NIL_P(value_) || kind_of(rb_cObject));
      }
    
      operator VALUE() const
      {
        return value_;
      }
      // [More code] ...
    }
    

    那么我们以String 类为例:

    class String : public Object
    {
    public:
      String() : Object(GetVALUE("")) {}
      String(VALUE value) : Object(value)
      {
        CheckTypeOfOrNil(value_, String::klass());
      }
      String(std::string value) : Object( GetVALUE(value.c_str()) ) {}
      String(const char* value) : Object( GetVALUE(value) ) {}
    
    
      operator std::string()
      {
        return StringValueCStr(value_);
      }
    
      operator std::string() const
      {
        return operator std::string();
      }
    
      static VALUE klass()
      {
        return rb_cString;
      }
    
      // String.empty?
      bool empty()
      {
        return length() == 0;
      }
    
      size_t length() const
      {
        return static_cast<size_t>(RSTRING_LEN(value_));
      }
    
      size_t size() const
      {
        return length();
      };
    
    };
    

    所以 - 我的包装器确保检查它们包装的 VALUE 是预期类型还是 Nil

    然后,我为 Visual Studio 编写了一些 natvis 文件,当我逐步执行代码时,这些文件将为我的包装器对象提供一些实时调试信息:

    <?xml version="1.0" encoding="utf-8"?>
    <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
    
    <Type Name="SUbD::ruby::String">
      <DisplayString Condition="value_ == RUBY_Qnil">Ruby String: Nil</DisplayString>
      <DisplayString Condition="value_ != RUBY_Qnil">Ruby String: {((struct RString*)value_)->as.heap.ptr,s}</DisplayString>
      <StringView Condition="value_ != RUBY_Qnil">((struct RString*)value_)->as.heap.ptr,s</StringView>
      <Expand>
        <Item Name="[VALUE]">value_</Item>
        <Item Name="[size]" Condition="value_ != RUBY_Qnil">((struct RString*)value_)->as.heap.len</Item>
        <Item Name="[string]" Condition="value_ != RUBY_Qnil">((struct RString*)value_)->as.heap.ptr</Item>
        <Item Name="[capacity]" Condition="value_ != RUBY_Qnil">((struct RString*)value_)->as.heap.aux.capa</Item>
      </Expand>
    </Type>
    
    </AutoVisualizer>
    

    请注意,这一切都被硬编码为 Ruby 2.0 的确切内部结构。这在 Ruby 1.8 或 1.9 中不起作用 - 尚未尝试使用 2.1 或 2.2。此外,我还没有添加 String 的存储方式可能会发生变化。 (短字符串可以存储为立即值。) (事实上​​ - 上面发布的 natvis 仅适用于 32 位 - 目前不适用于 64 位。)

    但是一旦设置完成,我就可以单步执行代码并检查 Ruby 字符串,就像它们是 std::string:

    让这一切正常工作并非易事。如果您在我的 natvis 中注意到我的 RUBY_Qnil 引用 - 除非我将这段调试代码添加到我的项目中,否则它们将无法工作:

    // Required in order to make them available to natvis files in Visual Studio.
    #ifdef _DEBUG
    const auto DEBUG_RUBY_Qnil              = RUBY_Qnil;
    const auto DEBUG_RUBY_FIXNUM_FLAG       = RUBY_FIXNUM_FLAG;
    const auto DEBUG_RUBY_T_MASK            = RUBY_T_MASK;
    const auto DEBUG_RUBY_T_FLOAT           = RUBY_T_FLOAT;
    const auto DEBUG_RARRAY_EMBED_FLAG      = RARRAY_EMBED_FLAG;
    const auto DEBUG_RARRAY_EMBED_LEN_SHIFT = RARRAY_EMBED_LEN_SHIFT;
    const auto DEBUG_RARRAY_EMBED_LEN_MASK  = RARRAY_EMBED_LEN_MASK;
    #endif
    

    不幸的是,您不能在 natvis 定义中使用宏,这就是为什么我必须通过检查 Ruby 源代码本身将其中的许多宏手动扩展为 natvis 文件。 (Ruby 交叉参考在这里有很大帮助:http://rxr.whitequark.org/mri/ident?v=2.0.0-p247

    它仍然是 WIP,但它已经为我省去了很多麻烦。最终我想在 GitHub 上提取调试设置:https://github.com/thomthom(如果您有兴趣,请关注该帐户。)

    【讨论】:

      【解决方案3】:

      这是我想出的:

      static void d(VALUE v) {
          ID sym_puts = rb_intern("puts");
          ID sym_inspect = rb_intern("inspect");
          rb_funcall(rb_mKernel, sym_puts, 1,
              rb_funcall(v, sym_inspect, 0));
      }
      

      将它放在C 文件中,您可以像这样输出VALUEs:

      VALUE v;
      d(v);
      

      我借鉴了this article 的想法。

      【讨论】:

        猜你喜欢
        • 2014-04-11
        • 2021-07-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多