如何从 C 扩展打印值?
How do I print values from C extensions?
每个 Ruby 对象在 C 中都是 VALUE
类型。如何以可读的方式打印它?
欢迎提供有关调试 Ruby C 扩展的任何其他提示。
这是我想出的:
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
文件中,您可以像这样输出 VALUE
s:
VALUE v;
d(v);
我借鉴了this article的想法。
我在 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为例:
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(如果您有兴趣,请关注该帐户。)
您可以使用 C 函数 rb_p
在 Ruby 个对象上调用 p
。例如:
VALUE empty_array = rb_ary_new();
rb_p(empty_array); // prints out "[]"
每个 Ruby 对象在 C 中都是 VALUE
类型。如何以可读的方式打印它?
欢迎提供有关调试 Ruby C 扩展的任何其他提示。
这是我想出的:
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
文件中,您可以像这样输出 VALUE
s:
VALUE v;
d(v);
我借鉴了this article的想法。
我在 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为例:
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(如果您有兴趣,请关注该帐户。)
您可以使用 C 函数 rb_p
在 Ruby 个对象上调用 p
。例如:
VALUE empty_array = rb_ary_new();
rb_p(empty_array); // prints out "[]"