rice 4.0.4 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/CONTRIBUTORS.md +2 -0
  4. data/Rakefile +1 -1
  5. data/include/rice/rice.hpp +2851 -1955
  6. data/include/rice/stl.hpp +1654 -287
  7. data/lib/mkmf-rice.rb +5 -2
  8. data/lib/version.rb +1 -1
  9. data/rice/Arg.hpp +6 -6
  10. data/rice/Arg.ipp +8 -9
  11. data/rice/Constructor.hpp +2 -2
  12. data/rice/Data_Object.ipp +69 -15
  13. data/rice/Data_Object_defn.hpp +1 -15
  14. data/rice/Data_Type.ipp +56 -86
  15. data/rice/Data_Type_defn.hpp +14 -17
  16. data/rice/Director.hpp +0 -1
  17. data/rice/Enum.ipp +31 -22
  18. data/rice/Exception.ipp +2 -3
  19. data/rice/Exception_defn.hpp +5 -5
  20. data/rice/HandlerRegistration.hpp +15 -0
  21. data/rice/Return.hpp +5 -4
  22. data/rice/Return.ipp +8 -3
  23. data/rice/detail/ExceptionHandler.hpp +8 -0
  24. data/rice/detail/ExceptionHandler.ipp +28 -0
  25. data/rice/detail/{Exception_Handler_defn.hpp → ExceptionHandler_defn.hpp} +17 -21
  26. data/rice/detail/HandlerRegistry.hpp +51 -0
  27. data/rice/detail/HandlerRegistry.ipp +20 -0
  28. data/rice/detail/InstanceRegistry.hpp +34 -0
  29. data/rice/detail/InstanceRegistry.ipp +50 -0
  30. data/rice/detail/MethodInfo.ipp +1 -1
  31. data/rice/detail/NativeAttribute.hpp +26 -15
  32. data/rice/detail/NativeAttribute.ipp +76 -47
  33. data/rice/detail/NativeFunction.hpp +64 -14
  34. data/rice/detail/NativeFunction.ipp +138 -86
  35. data/rice/detail/NativeIterator.hpp +49 -0
  36. data/rice/detail/NativeIterator.ipp +102 -0
  37. data/rice/detail/NativeRegistry.hpp +31 -0
  38. data/rice/detail/{method_data.ipp → NativeRegistry.ipp} +20 -16
  39. data/rice/detail/Registries.hpp +26 -0
  40. data/rice/detail/Registries.ipp +23 -0
  41. data/rice/detail/RubyFunction.hpp +6 -11
  42. data/rice/detail/RubyFunction.ipp +10 -22
  43. data/rice/detail/Type.hpp +1 -1
  44. data/rice/detail/Type.ipp +2 -2
  45. data/rice/detail/TypeRegistry.hpp +8 -11
  46. data/rice/detail/TypeRegistry.ipp +3 -28
  47. data/rice/detail/Wrapper.hpp +0 -2
  48. data/rice/detail/Wrapper.ipp +74 -24
  49. data/rice/detail/cpp_protect.hpp +93 -0
  50. data/rice/detail/default_allocation_func.ipp +1 -1
  51. data/rice/detail/from_ruby.ipp +206 -2
  52. data/rice/detail/to_ruby.ipp +39 -5
  53. data/rice/detail/to_ruby_defn.hpp +1 -1
  54. data/rice/forward_declares.ipp +6 -0
  55. data/rice/global_function.hpp +0 -4
  56. data/rice/global_function.ipp +0 -6
  57. data/rice/rice.hpp +29 -24
  58. data/rice/stl.hpp +6 -1
  59. data/sample/callbacks/extconf.rb +0 -1
  60. data/sample/enum/extconf.rb +0 -1
  61. data/sample/inheritance/extconf.rb +0 -1
  62. data/sample/map/extconf.rb +0 -1
  63. data/test/embed_ruby.cpp +6 -15
  64. data/test/ext/t1/extconf.rb +0 -1
  65. data/test/ext/t2/extconf.rb +0 -1
  66. data/test/extconf.rb +0 -1
  67. data/test/test_Array.cpp +20 -24
  68. data/test/test_Attribute.cpp +6 -6
  69. data/test/test_Class.cpp +8 -47
  70. data/test/test_Constructor.cpp +0 -2
  71. data/test/test_Data_Object.cpp +25 -11
  72. data/test/test_Data_Type.cpp +124 -28
  73. data/test/test_Director.cpp +12 -13
  74. data/test/test_Enum.cpp +65 -26
  75. data/test/test_Inheritance.cpp +9 -9
  76. data/test/test_Iterator.cpp +134 -5
  77. data/test/test_Keep_Alive.cpp +7 -7
  78. data/test/test_Keep_Alive_No_Wrapper.cpp +80 -0
  79. data/test/test_Memory_Management.cpp +1 -1
  80. data/test/test_Module.cpp +25 -62
  81. data/test/test_Object.cpp +75 -3
  82. data/test/test_Ownership.cpp +12 -13
  83. data/test/test_Self.cpp +12 -13
  84. data/test/test_Stl_Map.cpp +696 -0
  85. data/test/test_Stl_Optional.cpp +3 -3
  86. data/test/test_Stl_Pair.cpp +38 -2
  87. data/test/test_Stl_Reference_Wrapper.cpp +102 -0
  88. data/test/test_Stl_SmartPointer.cpp +49 -9
  89. data/test/test_Stl_String.cpp +5 -2
  90. data/test/test_Stl_Unordered_Map.cpp +697 -0
  91. data/test/test_Stl_Variant.cpp +346 -0
  92. data/test/test_Stl_Vector.cpp +200 -41
  93. data/test/test_Struct.cpp +3 -3
  94. data/test/test_To_From_Ruby.cpp +8 -2
  95. data/test/test_Tracking.cpp +239 -0
  96. data/test/unittest.hpp +21 -4
  97. metadata +24 -13
  98. data/rice/detail/Exception_Handler.hpp +0 -8
  99. data/rice/detail/Exception_Handler.ipp +0 -28
  100. data/rice/detail/Iterator.hpp +0 -23
  101. data/rice/detail/Iterator.ipp +0 -47
  102. data/rice/detail/function_traits.hpp +0 -124
  103. data/rice/detail/method_data.hpp +0 -29
  104. data/rice/detail/rice_traits.hpp +0 -116
  105. data/rice/ruby_try_catch.hpp +0 -86
@@ -1,83 +1,112 @@
1
1
  #include <array>
2
2
  #include <algorithm>
3
3
 
4
- #include "rice_traits.hpp"
5
- #include "method_data.hpp"
4
+ #include "../traits/rice_traits.hpp"
5
+ #include "NativeRegistry.hpp"
6
6
  #include "to_ruby_defn.hpp"
7
- #include "../ruby_try_catch.hpp"
7
+ #include "cpp_protect.hpp"
8
8
 
9
9
  namespace Rice::detail
10
10
  {
11
- template<typename Return_T, typename Attr_T, typename Self_T>
12
- inline VALUE NativeAttribute<Return_T, Attr_T, Self_T>::get(VALUE self)
11
+ template<typename Attribute_T>
12
+ void NativeAttribute<Attribute_T>::define(VALUE klass, std::string name, Attribute_T attribute, AttrAccess access)
13
13
  {
14
- RUBY_TRY
14
+ // Create a NativeAttribute that Ruby will call to read/write C++ variables
15
+ NativeAttribute_T* native = new NativeAttribute_T(klass, name, std::forward<Attribute_T>(attribute), access);
16
+
17
+ if (access == AttrAccess::ReadWrite || access == AttrAccess::Read)
15
18
  {
16
- using Native_Attr_T = NativeAttribute<Return_T, Attr_T, Self_T>;
17
- Native_Attr_T* attr = detail::MethodData::data<Native_Attr_T*>();
18
- return attr->read(self);
19
+ // Tell Ruby to invoke the static method read to get the attribute value
20
+ detail::protect(rb_define_method, klass, name.c_str(), (RUBY_METHOD_FUNC)&NativeAttribute_T::get, 0);
21
+
22
+ // Add to native registry
23
+ detail::Registries::instance.natives.add(klass, Identifier(name).id(), native);
19
24
  }
20
- RUBY_CATCH
25
+
26
+ if (access == AttrAccess::ReadWrite || access == AttrAccess::Write)
27
+ {
28
+ if (std::is_const_v<std::remove_pointer_t<T>>)
29
+ {
30
+ throw std::runtime_error(name + " is readonly");
31
+ }
32
+
33
+ // Define the write method name
34
+ std::string setter = name + "=";
35
+
36
+ // Tell Ruby to invoke the static method write to get the attribute value
37
+ detail::protect(rb_define_method, klass, setter.c_str(), (RUBY_METHOD_FUNC)&NativeAttribute_T::set, 1);
38
+
39
+ // Add to native registry
40
+ detail::Registries::instance.natives.add(klass, Identifier(setter).id(), native);
41
+ }
42
+ }
43
+
44
+ template<typename Attribute_T>
45
+ inline VALUE NativeAttribute<Attribute_T>::get(VALUE self)
46
+ {
47
+ return cpp_protect([&]
48
+ {
49
+ using Native_Attr_T = NativeAttribute<Attribute_T>;
50
+ Native_Attr_T* attr = detail::Registries::instance.natives.lookup<Native_Attr_T*>();
51
+ return attr->read(self);
52
+ });
21
53
  }
22
54
 
23
- template<typename Return_T, typename Attr_T, typename Self_T>
24
- inline VALUE NativeAttribute<Return_T, Attr_T, Self_T>::set(VALUE self, VALUE value)
55
+ template<typename Attribute_T>
56
+ inline VALUE NativeAttribute<Attribute_T>::set(VALUE self, VALUE value)
25
57
  {
26
- RUBY_TRY
58
+ return cpp_protect([&]
27
59
  {
28
- using Native_Attr_T = NativeAttribute<Return_T, Attr_T, Self_T>;
29
- Native_Attr_T* attr = detail::MethodData::data<Native_Attr_T*>();
60
+ using Native_Attr_T = NativeAttribute<Attribute_T>;
61
+ Native_Attr_T* attr = detail::Registries::instance.natives.lookup<Native_Attr_T*>();
30
62
  return attr->write(self, value);
31
- }
32
- RUBY_CATCH
63
+ });
33
64
  }
34
65
 
35
- template<typename Return_T, typename Attr_T, typename Self_T>
36
- NativeAttribute<Return_T, Attr_T, Self_T>::NativeAttribute(Attr_T attr, AttrAccess access)
37
- : attr_(attr), access_(access)
66
+ template<typename Attribute_T>
67
+ NativeAttribute<Attribute_T>::NativeAttribute(VALUE klass, std::string name,
68
+ Attribute_T attribute, AttrAccess access)
69
+ : klass_(klass), name_(name), attribute_(attribute), access_(access)
38
70
  {
39
71
  }
40
72
 
41
- template<typename Return_T, typename Attr_T, typename Self_T>
42
- inline VALUE NativeAttribute<Return_T, Attr_T, Self_T>::read(VALUE self)
73
+ template<typename Attribute_T>
74
+ inline VALUE NativeAttribute<Attribute_T>::read(VALUE self)
43
75
  {
44
- using Unqualified_T = remove_cv_recursive_t<Return_T>;
45
- if constexpr (std::is_member_object_pointer_v<Attr_T>)
76
+ using T_Unqualified = remove_cv_recursive_t<T>;
77
+ if constexpr (std::is_member_object_pointer_v<Attribute_T>)
46
78
  {
47
- Self_T* nativeSelf = From_Ruby<Self_T*>().convert(self);
48
- return To_Ruby<Unqualified_T>().convert(nativeSelf->*attr_);
79
+ Receiver_T* nativeSelf = From_Ruby<Receiver_T*>().convert(self);
80
+ return To_Ruby<T_Unqualified>().convert(nativeSelf->*attribute_);
49
81
  }
50
82
  else
51
83
  {
52
- return To_Ruby<Unqualified_T>().convert(*attr_);
84
+ return To_Ruby<T_Unqualified>().convert(*attribute_);
53
85
  }
54
86
  }
55
87
 
56
- template<typename Return_T, typename Attr_T, typename Self_T>
57
- inline VALUE NativeAttribute<Return_T, Attr_T, Self_T>::write(VALUE self, VALUE value)
88
+ template<typename Attribute_T>
89
+ inline VALUE NativeAttribute<Attribute_T>::write(VALUE self, VALUE value)
58
90
  {
59
- if constexpr (!std::is_const_v<std::remove_pointer_t<Attr_T>> && std::is_member_object_pointer_v<Attr_T>)
91
+ if constexpr (std::is_fundamental_v<intrinsic_type<T>> && std::is_pointer_v<T>)
60
92
  {
61
- Self_T* nativeSelf = From_Ruby<Self_T*>().convert(self);
62
- nativeSelf->*attr_ = From_Ruby<Return_T>().convert(value);
93
+ static_assert(true, "An fundamental value, such as an integer, cannot be assigned to an attribute that is a pointer");
63
94
  }
64
- else if constexpr (!std::is_const_v<std::remove_pointer_t<Attr_T>>)
95
+ else if constexpr (std::is_same_v<intrinsic_type<T>, std::string> && std::is_pointer_v<T>)
65
96
  {
66
- *attr_ = From_Ruby<Return_T>().convert(value);
97
+ static_assert(true, "An string cannot be assigned to an attribute that is a pointer");
98
+ }
99
+
100
+ if constexpr (!std::is_null_pointer_v<Receiver_T>)
101
+ {
102
+ Receiver_T* nativeSelf = From_Ruby<Receiver_T*>().convert(self);
103
+ nativeSelf->*attribute_ = From_Ruby<T_Unqualified>().convert(value);
104
+ }
105
+ else if constexpr (!std::is_const_v<std::remove_pointer_t<T>>)
106
+ {
107
+ *attribute_ = From_Ruby<T_Unqualified>().convert(value);
67
108
  }
68
- return value;
69
- }
70
-
71
- template<typename T>
72
- auto* Make_Native_Attribute(T* attr, AttrAccess access)
73
- {
74
- return new NativeAttribute<T, T*>(attr, access);
75
- }
76
109
 
77
- template<typename Class_T, typename T>
78
- auto* Make_Native_Attribute(T Class_T::* attr, AttrAccess access)
79
- {
80
- using Attr_T = T Class_T::*;
81
- return new NativeAttribute<T, Attr_T, Class_T>(attr, access);
110
+ return value;
82
111
  }
83
112
  } // Rice
@@ -2,40 +2,86 @@
2
2
  #define Rice__detail__Native_Function__hpp_
3
3
 
4
4
  #include "ruby.hpp"
5
- #include "Exception_Handler_defn.hpp"
5
+ #include "ExceptionHandler_defn.hpp"
6
6
  #include "MethodInfo.hpp"
7
- #include "function_traits.hpp"
7
+ #include "../traits/function_traits.hpp"
8
+ #include "../traits/method_traits.hpp"
8
9
  #include "from_ruby.hpp"
9
10
 
10
11
  namespace Rice::detail
11
12
  {
12
- template<typename Function_T, bool IsMethod>
13
+ //! The NativeFunction class calls C++ functions/methods/lambdas on behalf of Ruby
14
+ /*! The NativeFunction class is an intermediate between Ruby and C++. Every method
15
+ * defined in Rice is associated with a NativeFuntion instance that is stored in
16
+ * a unordered_map maintained by the MethodData class. The key is the Ruby class
17
+ * and method.
18
+ *
19
+ * When Ruby calls into C++ it invokes the static NativeFunction.call method. This
20
+ * method then looks up the NativeFunction instance and calls its ->() operator.
21
+ *
22
+ * The instance then converts each of the arguments passed from Ruby into their
23
+ * C++ equivalents. It then retrieves the C++ object (if there is one, Ruby could
24
+ * be calling a free standing method or lambda). Then it calls the C++ method
25
+ * and gets back the result. If there is a result (so not void), it is converted
26
+ * from a C++ object to a Ruby object and returned back to Ruby.
27
+ *
28
+ * This class make heavy use of C++ Template metaprogramming to determine
29
+ * the types and parameters a method takes. It then uses that information
30
+ * to perform type conversion Ruby to C++.
31
+ *
32
+ * @tparam From_Ruby_T - The type of C++ class wrapped by Ruby. Note
33
+ * this may be different than the Class of Function_T. For example,
34
+ * std::map has a size() method but that is actually implemented on
35
+ * an ancestor class _Tree. Thus From_Ruby_T is std::map but
36
+ * Function_T::Class_T is _Tree. This typename must be specified
37
+ * by the calling code.
38
+ * @tparam Function_T - A template that represents the C++ function
39
+ * to call. This typename is automatically deduced by the compiler.
40
+ * @tparam IsMethod - A boolean specifying whether the function has
41
+ * a self parameter or not. Rice differentiates these two cases by
42
+ * calling them methods (self) or functions (no self).
43
+ */
44
+
45
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
13
46
  class NativeFunction
14
47
  {
15
48
  public:
49
+ using NativeFunction_T = NativeFunction<From_Ruby_T, Function_T, IsMethod>;
50
+
16
51
  // We remove const to avoid an explosion of To_Ruby specializations and Ruby doesn't
17
52
  // have the concept of constants anyways
18
53
  using Return_T = remove_cv_recursive_t<typename function_traits<Function_T>::return_type>;
19
- using Self_T = typename method_traits<Function_T, IsMethod>::Self_T;
54
+ using Class_T = typename method_traits<Function_T, IsMethod>::Class_T;
20
55
  using Arg_Ts = typename method_traits<Function_T, IsMethod>::Arg_Ts;
21
- using From_Ruby_Ts = typename tuple_map<From_Ruby, Arg_Ts>::type;
56
+ using From_Ruby_Args_Ts = typename tuple_map<From_Ruby, Arg_Ts>::type;
57
+
58
+ // Register function with Ruby
59
+ static void define(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo);
22
60
 
23
61
  // Static member function that Ruby calls
24
62
  static VALUE call(int argc, VALUE* argv, VALUE self);
25
63
 
26
64
  public:
27
- NativeFunction(Function_T func, std::shared_ptr<Exception_Handler> handler, MethodInfo* methodInfo);
65
+ // Disallow creating/copying/moving
66
+ NativeFunction() = delete;
67
+ NativeFunction(const NativeFunction_T&) = delete;
68
+ NativeFunction(NativeFunction_T&&) = delete;
69
+ void operator=(const NativeFunction_T&) = delete;
70
+ void operator=(NativeFunction_T&&) = delete;
28
71
 
29
72
  // Invokes the wrapped function
30
73
  VALUE operator()(int argc, VALUE* argv, VALUE self);
31
74
 
75
+ protected:
76
+ NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo);
77
+
32
78
  private:
33
79
  template<typename T, std::size_t I>
34
80
  From_Ruby<T> createFromRuby();
35
81
 
36
82
  // Create NativeArgs which are used to convert values from Ruby to C++
37
83
  template<std::size_t...I>
38
- From_Ruby_Ts createFromRuby(std::index_sequence<I...>& indices);
84
+ From_Ruby_Args_Ts createFromRuby(std::index_sequence<I...>& indices);
39
85
 
40
86
  To_Ruby<Return_T> createToRuby();
41
87
 
@@ -47,23 +93,27 @@ namespace Rice::detail
47
93
  Arg_Ts getNativeValues(std::vector<VALUE>& values, std::index_sequence<I...>& indices);
48
94
 
49
95
  // Figure out what self is
50
- Self_T getSelf(VALUE self);
96
+ Class_T getReceiver(VALUE self);
97
+
98
+ // Throw an exception when wrapper cannot be extracted
99
+ [[noreturn]] void noWrapper(const VALUE klass, const std::string& wrapper);
51
100
 
52
101
  // Do we need to keep alive any arguments?
53
102
  void checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues);
54
103
 
55
104
  // Call the underlying C++ function
56
- VALUE invokeNativeFunction(Arg_Ts& nativeArgs);
57
- VALUE invokeNativeMethod(VALUE self, Arg_Ts& nativeArgs);
105
+ VALUE invokeNativeFunction(const Arg_Ts& nativeArgs);
106
+ VALUE invokeNativeMethod(VALUE self, const Arg_Ts& nativeArgs);
58
107
 
59
108
  private:
60
- Function_T func_;
61
- From_Ruby_Ts fromRubys_;
109
+ VALUE klass_;
110
+ std::string method_name_;
111
+ Function_T function_;
112
+ From_Ruby_Args_Ts fromRubys_;
62
113
  To_Ruby<Return_T> toRuby_;
63
- std::shared_ptr<Exception_Handler> handler_;
64
114
  std::unique_ptr<MethodInfo> methodInfo_;
65
115
  };
66
116
  }
67
117
  #include "NativeFunction.ipp"
68
118
 
69
- #endif // Rice__detail__Native_Function__hpp_
119
+ #endif // Rice__detail__Native_Function__hpp_
@@ -1,24 +1,43 @@
1
1
  #include <array>
2
2
  #include <algorithm>
3
3
  #include <stdexcept>
4
+ #include <sstream>
4
5
 
5
- #include "method_data.hpp"
6
+ #include "cpp_protect.hpp"
6
7
  #include "to_ruby_defn.hpp"
7
- #include "../ruby_try_catch.hpp"
8
+ #include "NativeRegistry.hpp"
8
9
 
9
10
  namespace Rice::detail
10
11
  {
11
- template<typename Function_T, bool IsMethod>
12
- VALUE NativeFunction<Function_T, IsMethod>::call(int argc, VALUE* argv, VALUE self)
12
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
13
+ void NativeFunction<From_Ruby_T, Function_T, IsMethod>::define(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo)
13
14
  {
14
- using Wrapper_T = NativeFunction<Function_T, IsMethod>;
15
- Wrapper_T* wrapper = detail::MethodData::data<Wrapper_T*>();
16
- return wrapper->operator()(argc, argv, self);
15
+ // Tell Ruby to invoke the static method call on this class
16
+ detail::protect(rb_define_method, klass, method_name.c_str(), (RUBY_METHOD_FUNC)&NativeFunction_T::call, -1);
17
+
18
+ // Now create a NativeFunction instance and save it to the natives registry keyed on
19
+ // Ruby klass and method id. There may be multiple NativeFunction instances
20
+ // because the same C++ method could be mapped to multiple Ruby methods.
21
+ NativeFunction_T* native = new NativeFunction_T(klass, method_name, std::forward<Function_T>(function), methodInfo);
22
+ detail::Registries::instance.natives.add(klass, Identifier(method_name).id(), native);
23
+ }
24
+
25
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
26
+ VALUE NativeFunction<From_Ruby_T, Function_T, IsMethod>::call(int argc, VALUE* argv, VALUE self)
27
+ {
28
+ // Look up the native function based on the Ruby klass and method id
29
+ NativeFunction_T* nativeFunction = detail::Registries::instance.natives.lookup<NativeFunction_T*>();
30
+
31
+ // Execute the function but make sure to catch any C++ exceptions!
32
+ return cpp_protect([&]
33
+ {
34
+ return nativeFunction->operator()(argc, argv, self);
35
+ });
17
36
  }
18
37
 
19
- template<typename Function_T, bool IsMethod>
20
- NativeFunction<Function_T, IsMethod>::NativeFunction(Function_T func, std::shared_ptr<Exception_Handler> handler, MethodInfo* methodInfo)
21
- : func_(func), handler_(handler), methodInfo_(methodInfo)
38
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
39
+ NativeFunction<From_Ruby_T, Function_T, IsMethod>::NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo)
40
+ : klass_(klass), method_name_(method_name), function_(function), methodInfo_(methodInfo)
22
41
  {
23
42
  // Create a tuple of NativeArgs that will convert the Ruby values to native values. For
24
43
  // builtin types NativeArgs will keep a copy of the native value so that it
@@ -30,9 +49,9 @@ namespace Rice::detail
30
49
  this->toRuby_ = this->createToRuby();
31
50
  }
32
51
 
33
- template<typename Function_T, bool IsMethod>
52
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
34
53
  template<typename T, std::size_t I>
35
- From_Ruby<T> NativeFunction<Function_T, IsMethod>::createFromRuby()
54
+ From_Ruby<T> NativeFunction<From_Ruby_T, Function_T, IsMethod>::createFromRuby()
36
55
  {
37
56
  // Does the From_Ruby instantiation work with Arg?
38
57
  if constexpr (std::is_constructible_v<From_Ruby<T>, Arg*>)
@@ -45,8 +64,8 @@ namespace Rice::detail
45
64
  }
46
65
  }
47
66
 
48
- template<typename Function_T, bool IsMethod>
49
- To_Ruby<typename NativeFunction<Function_T, IsMethod>::Return_T> NativeFunction<Function_T, IsMethod>::createToRuby()
67
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
68
+ To_Ruby<typename NativeFunction<From_Ruby_T, Function_T, IsMethod>::Return_T> NativeFunction<From_Ruby_T, Function_T, IsMethod>::createToRuby()
50
69
  {
51
70
  // Does the From_Ruby instantiation work with ReturnInfo?
52
71
  if constexpr (std::is_constructible_v<To_Ruby<Return_T>, Return*>)
@@ -59,41 +78,41 @@ namespace Rice::detail
59
78
  }
60
79
  }
61
80
 
62
- template<typename Function_T, bool IsMethod>
81
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
63
82
  template<std::size_t... I>
64
- typename NativeFunction<Function_T, IsMethod>::From_Ruby_Ts NativeFunction<Function_T, IsMethod>::createFromRuby(std::index_sequence<I...>& indices)
83
+ typename NativeFunction<From_Ruby_T, Function_T, IsMethod>::From_Ruby_Args_Ts NativeFunction<From_Ruby_T, Function_T, IsMethod>::createFromRuby(std::index_sequence<I...>& indices)
65
84
  {
66
85
  return std::make_tuple(createFromRuby<remove_cv_recursive_t<typename std::tuple_element<I, Arg_Ts>::type>, I>()...);
67
86
  }
68
87
 
69
- template<typename Function_T, bool IsMethod>
70
- std::vector<VALUE> NativeFunction<Function_T, IsMethod>::getRubyValues(int argc, VALUE* argv)
88
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
89
+ std::vector<VALUE> NativeFunction<From_Ruby_T, Function_T, IsMethod>::getRubyValues(int argc, VALUE* argv)
71
90
  {
72
- // Setup a tuple to contain required methodInfo to rb_scan_args
91
+ // Setup a tuple for the leading rb_scan_args arguments
73
92
  std::string scanFormat = this->methodInfo_->formatString();
74
- std::tuple<int, VALUE*, const char*> rbScanMandatory = std::forward_as_tuple(argc, argv, scanFormat.c_str());
93
+ std::tuple<int, VALUE*, const char*> rbScanArgs = std::forward_as_tuple(argc, argv, scanFormat.c_str());
75
94
 
76
- // Create a vector to store the variable number of Ruby Values
77
- std::vector<VALUE> rbScanArgsOptional(std::tuple_size_v<Arg_Ts>, Qnil);
95
+ // Create a vector to store the VALUEs that will be returned by rb_scan_args
96
+ std::vector<VALUE> rbScanValues(std::tuple_size_v<Arg_Ts>, Qnil);
78
97
 
79
- // Convert the vector to an array so it can then be concatenated to a tuple
80
- std::array<VALUE*, std::tuple_size_v<Arg_Ts>> rbScanArgsOptionalPointers;
81
- std::transform(rbScanArgsOptional.begin(), rbScanArgsOptional.end(), rbScanArgsOptionalPointers.begin(),
98
+ // Convert the vector to an array so it can be concatenated to a tuple. As importantly
99
+ // fill it with pointers to rbScanValues
100
+ std::array<VALUE*, std::tuple_size_v<Arg_Ts>> rbScanValuePointers;
101
+ std::transform(rbScanValues.begin(), rbScanValues.end(), rbScanValuePointers.begin(),
82
102
  [](VALUE& value)
83
103
  {
84
104
  return &value;
85
105
  });
86
106
 
87
107
  // Combine the tuples and call rb_scan_args
88
- auto rbScanArgs = std::tuple_cat(rbScanMandatory, rbScanArgsOptionalPointers);
89
- std::apply(rb_scan_args, rbScanArgs);
108
+ std::apply(rb_scan_args, std::tuple_cat(rbScanArgs, rbScanValuePointers));
90
109
 
91
- return rbScanArgsOptional;
110
+ return rbScanValues;
92
111
  }
93
112
 
94
- template<typename Function_T, bool IsMethod>
113
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
95
114
  template<std::size_t... I>
96
- typename NativeFunction<Function_T, IsMethod>::Arg_Ts NativeFunction<Function_T, IsMethod>::getNativeValues(std::vector<VALUE>& values,
115
+ typename NativeFunction<From_Ruby_T, Function_T, IsMethod>::Arg_Ts NativeFunction<From_Ruby_T, Function_T, IsMethod>::getNativeValues(std::vector<VALUE>& values,
97
116
  std::index_sequence<I...>& indices)
98
117
  {
99
118
  // Convert each Ruby value to its native value by calling the appropriate fromRuby instance.
@@ -102,80 +121,91 @@ namespace Rice::detail
102
121
  return std::forward_as_tuple(std::get<I>(this->fromRubys_).convert(values[I])...);
103
122
  }
104
123
 
105
- template<typename Function_T, bool IsMethod>
106
- typename NativeFunction<Function_T, IsMethod>::Self_T NativeFunction<Function_T, IsMethod>::getSelf(VALUE self)
124
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
125
+ typename NativeFunction<From_Ruby_T, Function_T, IsMethod>::Class_T NativeFunction<From_Ruby_T, Function_T, IsMethod>::getReceiver(VALUE self)
107
126
  {
108
127
  // There is no self parameter
109
- if constexpr (std::is_same_v<Self_T, std::nullptr_t>)
128
+ if constexpr (std::is_same_v<Class_T, std::nullptr_t>)
110
129
  {
111
130
  return nullptr;
112
131
  }
113
132
  // Self parameter is a Ruby VALUE so no conversion is needed
114
- else if constexpr (std::is_same_v<Self_T, VALUE>)
133
+ else if constexpr (std::is_same_v<Class_T, VALUE>)
115
134
  {
116
135
  return self;
117
136
  }
118
- // Self parameter could be derived from Object or it is an C++ instdance and
137
+ /* This case happens when a class wrapped by Rice is calling a method
138
+ defined on an ancestor class. For example, the std::map size method
139
+ is defined on _Tree not map. Rice needs to know the actual type
140
+ that was wrapped so it can correctly extract the C++ object from
141
+ the Ruby object. */
142
+ else if constexpr (!std::is_same_v<intrinsic_type<Class_T>, From_Ruby_T> &&
143
+ std::is_base_of_v<intrinsic_type<Class_T>, From_Ruby_T>)
144
+ {
145
+ From_Ruby_T* instance = From_Ruby<From_Ruby_T*>().convert(self);
146
+ return dynamic_cast<Class_T>(instance);
147
+ }
148
+ // Self parameter could be derived from Object or it is an C++ instance and
119
149
  // needs to be unwrapped from Ruby
120
150
  else
121
151
  {
122
- return From_Ruby<Self_T>().convert(self);
152
+ return From_Ruby<Class_T>().convert(self);
123
153
  }
124
154
  }
125
155
 
126
- template<typename Function_T, bool IsMethod>
127
- VALUE NativeFunction<Function_T, IsMethod>::invokeNativeFunction(Arg_Ts& nativeArgs)
156
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
157
+ VALUE NativeFunction<From_Ruby_T, Function_T, IsMethod>::invokeNativeFunction(const Arg_Ts& nativeArgs)
128
158
  {
129
159
  if constexpr (std::is_void_v<Return_T>)
130
160
  {
131
- std::apply(this->func_, nativeArgs);
161
+ std::apply(this->function_, nativeArgs);
132
162
  return Qnil;
133
163
  }
134
164
  else
135
165
  {
136
166
  // Call the native method and get the result
137
- Return_T nativeResult = std::apply(this->func_, nativeArgs);
138
-
167
+ Return_T nativeResult = std::apply(this->function_, nativeArgs);
168
+
139
169
  // Return the result
140
170
  return this->toRuby_.convert(nativeResult);
141
171
  }
142
172
  }
143
173
 
144
- template<typename Function_T, bool IsMethod>
145
- VALUE NativeFunction<Function_T, IsMethod>::invokeNativeMethod(VALUE self, Arg_Ts& nativeArgs)
174
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
175
+ VALUE NativeFunction<From_Ruby_T, Function_T, IsMethod>::invokeNativeMethod(VALUE self, const Arg_Ts& nativeArgs)
146
176
  {
147
- Self_T receiver = this->getSelf(self);
177
+ Class_T receiver = this->getReceiver(self);
148
178
  auto selfAndNativeArgs = std::tuple_cat(std::forward_as_tuple(receiver), nativeArgs);
149
179
 
150
180
  if constexpr (std::is_void_v<Return_T>)
151
181
  {
152
- std::apply(this->func_, selfAndNativeArgs);
182
+ std::apply(this->function_, selfAndNativeArgs);
153
183
  return Qnil;
154
184
  }
155
185
  else
156
186
  {
157
- Return_T nativeResult = (Return_T)std::apply(this->func_, selfAndNativeArgs);
187
+ Return_T nativeResult = (Return_T)std::apply(this->function_, selfAndNativeArgs);
158
188
 
159
189
  // Special handling if the method returns self. If so we do not want
160
190
  // to create a new Ruby wrapper object and instead return self.
161
- if constexpr (std::is_same_v<intrinsic_type<Return_T>, intrinsic_type<Self_T>>)
191
+ if constexpr (std::is_same_v<intrinsic_type<Return_T>, intrinsic_type<Class_T>>)
162
192
  {
163
- if constexpr (std::is_pointer_v<Return_T> && std::is_pointer_v<Self_T>)
193
+ if constexpr (std::is_pointer_v<Return_T> && std::is_pointer_v<Class_T>)
164
194
  {
165
195
  if (nativeResult == receiver)
166
196
  return self;
167
197
  }
168
- else if constexpr (std::is_pointer_v<Return_T> && std::is_reference_v<Self_T>)
198
+ else if constexpr (std::is_pointer_v<Return_T> && std::is_reference_v<Class_T>)
169
199
  {
170
200
  if (nativeResult == &receiver)
171
201
  return self;
172
202
  }
173
- else if constexpr (std::is_reference_v<Return_T> && std::is_pointer_v<Self_T>)
203
+ else if constexpr (std::is_reference_v<Return_T> && std::is_pointer_v<Class_T>)
174
204
  {
175
205
  if (&nativeResult == receiver)
176
206
  return self;
177
207
  }
178
- else if constexpr (std::is_reference_v<Return_T> && std::is_reference_v<Self_T>)
208
+ else if constexpr (std::is_reference_v<Return_T> && std::is_reference_v<Class_T>)
179
209
  {
180
210
  if (&nativeResult == &receiver)
181
211
  return self;
@@ -186,63 +216,85 @@ namespace Rice::detail
186
216
  }
187
217
  }
188
218
 
189
- template<typename Function_T, bool IsMethod>
190
- void NativeFunction<Function_T, IsMethod>::checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues)
219
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
220
+ void NativeFunction<From_Ruby_T, Function_T, IsMethod>::noWrapper(const VALUE klass, const std::string& wrapper)
191
221
  {
192
- // Check function arguments
222
+ std::stringstream message;
223
+
224
+ message << "When calling the method `";
225
+ message << this->method_name_;
226
+ message << "' we could not find the wrapper for the '";
227
+ message << rb_obj_classname(klass);
228
+ message << "' ";
229
+ message << wrapper;
230
+ message << " type. You should not use keepAlive() on a Return or Arg that is a builtin Rice type.";
231
+
232
+ throw std::runtime_error(message.str());
233
+ }
234
+
235
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
236
+ void NativeFunction<From_Ruby_T, Function_T, IsMethod>::checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues)
237
+ {
238
+ // selfWrapper will be nullptr if this(self) is a builtin type and not an external(wrapped) type
239
+ // it is highly unlikely that keepAlive is used in this case but we check anyway
193
240
  Wrapper* selfWrapper = getWrapper(self);
241
+
242
+ // Check function arguments
194
243
  for (const Arg& arg : (*this->methodInfo_))
195
244
  {
196
- if (arg.isKeepAlive)
245
+ if (arg.isKeepAlive())
197
246
  {
247
+ if (selfWrapper == nullptr)
248
+ {
249
+ noWrapper(self, "self");
250
+ }
198
251
  selfWrapper->addKeepAlive(rubyValues[arg.position]);
199
252
  }
200
253
  }
201
254
 
202
255
  // Check return value
203
- if (this->methodInfo_->returnInfo.isKeepAlive)
256
+ if (this->methodInfo_->returnInfo.isKeepAlive())
204
257
  {
258
+ if (selfWrapper == nullptr)
259
+ {
260
+ noWrapper(self, "self");
261
+ }
262
+
263
+ // returnWrapper will be nullptr if returnValue is a built-in type and not an external(wrapped) type
205
264
  Wrapper* returnWrapper = getWrapper(returnValue);
265
+ if (returnWrapper == nullptr)
266
+ {
267
+ noWrapper(returnValue, "return");
268
+ }
206
269
  returnWrapper->addKeepAlive(self);
207
270
  }
208
271
  }
209
272
 
210
- template<typename Function_T, bool IsMethod>
211
- VALUE NativeFunction<Function_T, IsMethod>::operator()(int argc, VALUE* argv, VALUE self)
273
+ template<typename From_Ruby_T, typename Function_T, bool IsMethod>
274
+ VALUE NativeFunction<From_Ruby_T, Function_T, IsMethod>::operator()(int argc, VALUE* argv, VALUE self)
212
275
  {
213
- try
214
- {
215
- // Get the ruby values
216
- std::vector<VALUE> rubyValues = this->getRubyValues(argc, argv);
217
-
218
- auto indices = std::make_index_sequence<std::tuple_size_v<Arg_Ts>>{};
219
-
220
- // Convert the Ruby values to native values
221
- Arg_Ts nativeValues = this->getNativeValues(rubyValues, indices);
276
+ // Get the ruby values
277
+ std::vector<VALUE> rubyValues = this->getRubyValues(argc, argv);
222
278
 
223
- // Now call the native method
224
- VALUE result = Qnil;
225
- if constexpr (std::is_same_v<Self_T, std::nullptr_t>)
226
- {
227
- result = this->invokeNativeFunction(nativeValues);
228
- }
229
- else
230
- {
231
- result = this->invokeNativeMethod(self, nativeValues);
232
- }
279
+ auto indices = std::make_index_sequence<std::tuple_size_v<Arg_Ts>>{};
233
280
 
234
- // Check if any function arguments or return values need to have their lifetimes tied to the receiver
235
- this->checkKeepAlive(self, result, rubyValues);
281
+ // Convert the Ruby values to native values
282
+ Arg_Ts nativeValues = this->getNativeValues(rubyValues, indices);
236
283
 
237
- return result;
284
+ // Now call the native method
285
+ VALUE result = Qnil;
286
+ if constexpr (std::is_same_v<Class_T, std::nullptr_t>)
287
+ {
288
+ result = this->invokeNativeFunction(nativeValues);
238
289
  }
239
- catch (...)
290
+ else
240
291
  {
241
- RUBY_TRY
242
- {
243
- return this->handler_->handle_exception();
244
- }
245
- RUBY_CATCH
292
+ result = this->invokeNativeMethod(self, nativeValues);
246
293
  }
294
+
295
+ // Check if any function arguments or return values need to have their lifetimes tied to the receiver
296
+ this->checkKeepAlive(self, result, rubyValues);
297
+
298
+ return result;
247
299
  }
248
300
  }