rice 4.3.0 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,300 +1,300 @@
1
- #include <array>
2
- #include <algorithm>
3
- #include <stdexcept>
4
- #include <sstream>
5
-
6
- #include "cpp_protect.hpp"
7
- #include "to_ruby_defn.hpp"
8
- #include "NativeRegistry.hpp"
9
-
10
- namespace Rice::detail
11
- {
12
- template<typename Class_T, typename Function_T, bool IsMethod>
13
- void NativeFunction<Class_T, Function_T, IsMethod>::define(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo)
14
- {
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 Class_T, typename Function_T, bool IsMethod>
26
- VALUE NativeFunction<Class_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
- });
36
- }
37
-
38
- template<typename Class_T, typename Function_T, bool IsMethod>
39
- NativeFunction<Class_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)
41
- {
42
- // Create a tuple of NativeArgs that will convert the Ruby values to native values. For
43
- // builtin types NativeArgs will keep a copy of the native value so that it
44
- // can be passed by reference or pointer to the native function. For non-builtin types
45
- // it will just pass the value through.
46
- auto indices = std::make_index_sequence<std::tuple_size_v<Arg_Ts>>{};
47
- this->fromRubys_ = this->createFromRuby(indices);
48
-
49
- this->toRuby_ = this->createToRuby();
50
- }
51
-
52
- template<typename Class_T, typename Function_T, bool IsMethod>
53
- template<typename T, std::size_t I>
54
- From_Ruby<T> NativeFunction<Class_T, Function_T, IsMethod>::createFromRuby()
55
- {
56
- // Does the From_Ruby instantiation work with Arg?
57
- if constexpr (std::is_constructible_v<From_Ruby<T>, Arg*>)
58
- {
59
- return From_Ruby<T>(&this->methodInfo_->arg(I));
60
- }
61
- else
62
- {
63
- return From_Ruby<T>();
64
- }
65
- }
66
-
67
- template<typename Class_T, typename Function_T, bool IsMethod>
68
- To_Ruby<typename NativeFunction<Class_T, Function_T, IsMethod>::Return_T> NativeFunction<Class_T, Function_T, IsMethod>::createToRuby()
69
- {
70
- // Does the From_Ruby instantiation work with ReturnInfo?
71
- if constexpr (std::is_constructible_v<To_Ruby<Return_T>, Return*>)
72
- {
73
- return To_Ruby<Return_T>(&this->methodInfo_->returnInfo);
74
- }
75
- else
76
- {
77
- return To_Ruby<Return_T>();
78
- }
79
- }
80
-
81
- template<typename Class_T, typename Function_T, bool IsMethod>
82
- template<std::size_t... I>
83
- typename NativeFunction<Class_T, Function_T, IsMethod>::From_Ruby_Args_Ts NativeFunction<Class_T, Function_T, IsMethod>::createFromRuby(std::index_sequence<I...>& indices)
84
- {
85
- return std::make_tuple(createFromRuby<remove_cv_recursive_t<typename std::tuple_element<I, Arg_Ts>::type>, I>()...);
86
- }
87
-
88
- template<typename Class_T, typename Function_T, bool IsMethod>
89
- std::vector<VALUE> NativeFunction<Class_T, Function_T, IsMethod>::getRubyValues(int argc, VALUE* argv)
90
- {
91
- // Setup a tuple for the leading rb_scan_args arguments
92
- std::string scanFormat = this->methodInfo_->formatString();
93
- std::tuple<int, VALUE*, const char*> rbScanArgs = std::forward_as_tuple(argc, argv, scanFormat.c_str());
94
-
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);
97
-
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(),
102
- [](VALUE& value)
103
- {
104
- return &value;
105
- });
106
-
107
- // Combine the tuples and call rb_scan_args
108
- std::apply(rb_scan_args, std::tuple_cat(rbScanArgs, rbScanValuePointers));
109
-
110
- return rbScanValues;
111
- }
112
-
113
- template<typename Class_T, typename Function_T, bool IsMethod>
114
- template<std::size_t... I>
115
- typename NativeFunction<Class_T, Function_T, IsMethod>::Arg_Ts NativeFunction<Class_T, Function_T, IsMethod>::getNativeValues(std::vector<VALUE>& values,
116
- std::index_sequence<I...>& indices)
117
- {
118
- // Convert each Ruby value to its native value by calling the appropriate fromRuby instance.
119
- // Note that for fundamental types From_Ruby<Arg_Ts> will keep a copy of the native value
120
- // so it can be passed by reference or pointer to a native function.
121
- return std::forward_as_tuple(std::get<I>(this->fromRubys_).convert(values[I])...);
122
- }
123
-
124
- template<typename Class_T, typename Function_T, bool IsMethod>
125
- typename NativeFunction<Class_T, Function_T, IsMethod>::Receiver_T NativeFunction<Class_T, Function_T, IsMethod>::getReceiver(VALUE self)
126
- {
127
- // There is no self parameter
128
- if constexpr (std::is_same_v<Receiver_T, std::nullptr_t>)
129
- {
130
- return nullptr;
131
- }
132
- // Self parameter is a Ruby VALUE so no conversion is needed
133
- else if constexpr (std::is_same_v<Receiver_T, VALUE>)
134
- {
135
- return self;
136
- }
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<Receiver_T>, Class_T> &&
143
- std::is_base_of_v<intrinsic_type<Receiver_T>, Class_T>)
144
- {
145
- Class_T* instance = From_Ruby<Class_T*>().convert(self);
146
- return dynamic_cast<Receiver_T>(instance);
147
- }
148
- // Self parameter could be derived from Object or it is an C++ instance and
149
- // needs to be unwrapped from Ruby
150
- else
151
- {
152
- return From_Ruby<Receiver_T>().convert(self);
153
- }
154
- }
155
-
156
- template<typename Class_T, typename Function_T, bool IsMethod>
157
- VALUE NativeFunction<Class_T, Function_T, IsMethod>::invokeNativeFunction(const Arg_Ts& nativeArgs)
158
- {
159
- if constexpr (std::is_void_v<Return_T>)
160
- {
161
- std::apply(this->function_, nativeArgs);
162
- return Qnil;
163
- }
164
- else
165
- {
166
- // Call the native method and get the result
167
- Return_T nativeResult = std::apply(this->function_, nativeArgs);
168
-
169
- // Return the result
170
- return this->toRuby_.convert(nativeResult);
171
- }
172
- }
173
-
174
- template<typename Class_T, typename Function_T, bool IsMethod>
175
- VALUE NativeFunction<Class_T, Function_T, IsMethod>::invokeNativeMethod(VALUE self, const Arg_Ts& nativeArgs)
176
- {
177
- Receiver_T receiver = this->getReceiver(self);
178
- auto selfAndNativeArgs = std::tuple_cat(std::forward_as_tuple(receiver), nativeArgs);
179
-
180
- if constexpr (std::is_void_v<Return_T>)
181
- {
182
- std::apply(this->function_, selfAndNativeArgs);
183
- return Qnil;
184
- }
185
- else
186
- {
187
- Return_T nativeResult = (Return_T)std::apply(this->function_, selfAndNativeArgs);
188
-
189
- // Special handling if the method returns self. If so we do not want
190
- // to create a new Ruby wrapper object and instead return self.
191
- if constexpr (std::is_same_v<intrinsic_type<Return_T>, intrinsic_type<Receiver_T>>)
192
- {
193
- if constexpr (std::is_pointer_v<Return_T> && std::is_pointer_v<Receiver_T>)
194
- {
195
- if (nativeResult == receiver)
196
- return self;
197
- }
198
- else if constexpr (std::is_pointer_v<Return_T> && std::is_reference_v<Receiver_T>)
199
- {
200
- if (nativeResult == &receiver)
201
- return self;
202
- }
203
- else if constexpr (std::is_reference_v<Return_T> && std::is_pointer_v<Receiver_T>)
204
- {
205
- if (&nativeResult == receiver)
206
- return self;
207
- }
208
- else if constexpr (std::is_reference_v<Return_T> && std::is_reference_v<Receiver_T>)
209
- {
210
- if (&nativeResult == &receiver)
211
- return self;
212
- }
213
- }
214
-
215
- return this->toRuby_.convert(nativeResult);
216
- }
217
- }
218
-
219
- template<typename Class_T, typename Function_T, bool IsMethod>
220
- void NativeFunction<Class_T, Function_T, IsMethod>::noWrapper(const VALUE klass, const std::string& wrapper)
221
- {
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 Class_T, typename Function_T, bool IsMethod>
236
- void NativeFunction<Class_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
240
- Wrapper* selfWrapper = getWrapper(self);
241
-
242
- // Check function arguments
243
- for (const Arg& arg : (*this->methodInfo_))
244
- {
245
- if (arg.isKeepAlive())
246
- {
247
- if (selfWrapper == nullptr)
248
- {
249
- noWrapper(self, "self");
250
- }
251
- selfWrapper->addKeepAlive(rubyValues[arg.position]);
252
- }
253
- }
254
-
255
- // Check return value
256
- if (this->methodInfo_->returnInfo.isKeepAlive())
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
264
- Wrapper* returnWrapper = getWrapper(returnValue);
265
- if (returnWrapper == nullptr)
266
- {
267
- noWrapper(returnValue, "return");
268
- }
269
- returnWrapper->addKeepAlive(self);
270
- }
271
- }
272
-
273
- template<typename Class_T, typename Function_T, bool IsMethod>
274
- VALUE NativeFunction<Class_T, Function_T, IsMethod>::operator()(int argc, VALUE* argv, VALUE self)
275
- {
276
- // Get the ruby values
277
- std::vector<VALUE> rubyValues = this->getRubyValues(argc, argv);
278
-
279
- auto indices = std::make_index_sequence<std::tuple_size_v<Arg_Ts>>{};
280
-
281
- // Convert the Ruby values to native values
282
- Arg_Ts nativeValues = this->getNativeValues(rubyValues, indices);
283
-
284
- // Now call the native method
285
- VALUE result = Qnil;
286
- if constexpr (std::is_same_v<Receiver_T, std::nullptr_t>)
287
- {
288
- result = this->invokeNativeFunction(nativeValues);
289
- }
290
- else
291
- {
292
- result = this->invokeNativeMethod(self, nativeValues);
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;
299
- }
300
- }
1
+ #include <array>
2
+ #include <algorithm>
3
+ #include <stdexcept>
4
+ #include <sstream>
5
+
6
+ #include "cpp_protect.hpp"
7
+ #include "to_ruby_defn.hpp"
8
+ #include "NativeRegistry.hpp"
9
+
10
+ namespace Rice::detail
11
+ {
12
+ template<typename Class_T, typename Function_T, bool IsMethod>
13
+ void NativeFunction<Class_T, Function_T, IsMethod>::define(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo)
14
+ {
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 Class_T, typename Function_T, bool IsMethod>
26
+ VALUE NativeFunction<Class_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
+ });
36
+ }
37
+
38
+ template<typename Class_T, typename Function_T, bool IsMethod>
39
+ NativeFunction<Class_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)
41
+ {
42
+ // Create a tuple of NativeArgs that will convert the Ruby values to native values. For
43
+ // builtin types NativeArgs will keep a copy of the native value so that it
44
+ // can be passed by reference or pointer to the native function. For non-builtin types
45
+ // it will just pass the value through.
46
+ auto indices = std::make_index_sequence<std::tuple_size_v<Arg_Ts>>{};
47
+ this->fromRubys_ = this->createFromRuby(indices);
48
+
49
+ this->toRuby_ = this->createToRuby();
50
+ }
51
+
52
+ template<typename Class_T, typename Function_T, bool IsMethod>
53
+ template<typename T, std::size_t I>
54
+ From_Ruby<T> NativeFunction<Class_T, Function_T, IsMethod>::createFromRuby()
55
+ {
56
+ // Does the From_Ruby instantiation work with Arg?
57
+ if constexpr (std::is_constructible_v<From_Ruby<T>, Arg*>)
58
+ {
59
+ return From_Ruby<T>(&this->methodInfo_->arg(I));
60
+ }
61
+ else
62
+ {
63
+ return From_Ruby<T>();
64
+ }
65
+ }
66
+
67
+ template<typename Class_T, typename Function_T, bool IsMethod>
68
+ To_Ruby<typename NativeFunction<Class_T, Function_T, IsMethod>::Return_T> NativeFunction<Class_T, Function_T, IsMethod>::createToRuby()
69
+ {
70
+ // Does the From_Ruby instantiation work with ReturnInfo?
71
+ if constexpr (std::is_constructible_v<To_Ruby<Return_T>, Return*>)
72
+ {
73
+ return To_Ruby<Return_T>(&this->methodInfo_->returnInfo);
74
+ }
75
+ else
76
+ {
77
+ return To_Ruby<Return_T>();
78
+ }
79
+ }
80
+
81
+ template<typename Class_T, typename Function_T, bool IsMethod>
82
+ template<std::size_t... I>
83
+ typename NativeFunction<Class_T, Function_T, IsMethod>::From_Ruby_Args_Ts NativeFunction<Class_T, Function_T, IsMethod>::createFromRuby(std::index_sequence<I...>& indices)
84
+ {
85
+ return std::make_tuple(createFromRuby<remove_cv_recursive_t<typename std::tuple_element<I, Arg_Ts>::type>, I>()...);
86
+ }
87
+
88
+ template<typename Class_T, typename Function_T, bool IsMethod>
89
+ std::vector<VALUE> NativeFunction<Class_T, Function_T, IsMethod>::getRubyValues(int argc, VALUE* argv)
90
+ {
91
+ // Setup a tuple for the leading rb_scan_args arguments
92
+ std::string scanFormat = this->methodInfo_->formatString();
93
+ std::tuple<int, VALUE*, const char*> rbScanArgs = std::forward_as_tuple(argc, argv, scanFormat.c_str());
94
+
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);
97
+
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(),
102
+ [](VALUE& value)
103
+ {
104
+ return &value;
105
+ });
106
+
107
+ // Combine the tuples and call rb_scan_args
108
+ std::apply(rb_scan_args, std::tuple_cat(rbScanArgs, rbScanValuePointers));
109
+
110
+ return rbScanValues;
111
+ }
112
+
113
+ template<typename Class_T, typename Function_T, bool IsMethod>
114
+ template<std::size_t... I>
115
+ typename NativeFunction<Class_T, Function_T, IsMethod>::Arg_Ts NativeFunction<Class_T, Function_T, IsMethod>::getNativeValues(std::vector<VALUE>& values,
116
+ std::index_sequence<I...>& indices)
117
+ {
118
+ // Convert each Ruby value to its native value by calling the appropriate fromRuby instance.
119
+ // Note that for fundamental types From_Ruby<Arg_Ts> will keep a copy of the native value
120
+ // so it can be passed by reference or pointer to a native function.
121
+ return std::forward_as_tuple(std::get<I>(this->fromRubys_).convert(values[I])...);
122
+ }
123
+
124
+ template<typename Class_T, typename Function_T, bool IsMethod>
125
+ typename NativeFunction<Class_T, Function_T, IsMethod>::Receiver_T NativeFunction<Class_T, Function_T, IsMethod>::getReceiver(VALUE self)
126
+ {
127
+ // There is no self parameter
128
+ if constexpr (std::is_same_v<Receiver_T, std::nullptr_t>)
129
+ {
130
+ return nullptr;
131
+ }
132
+ // Self parameter is a Ruby VALUE so no conversion is needed
133
+ else if constexpr (std::is_same_v<Receiver_T, VALUE>)
134
+ {
135
+ return self;
136
+ }
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<Receiver_T>, Class_T> &&
143
+ std::is_base_of_v<intrinsic_type<Receiver_T>, Class_T>)
144
+ {
145
+ Class_T* instance = From_Ruby<Class_T*>().convert(self);
146
+ return dynamic_cast<Receiver_T>(instance);
147
+ }
148
+ // Self parameter could be derived from Object or it is an C++ instance and
149
+ // needs to be unwrapped from Ruby
150
+ else
151
+ {
152
+ return From_Ruby<Receiver_T>().convert(self);
153
+ }
154
+ }
155
+
156
+ template<typename Class_T, typename Function_T, bool IsMethod>
157
+ VALUE NativeFunction<Class_T, Function_T, IsMethod>::invokeNativeFunction(const Arg_Ts& nativeArgs)
158
+ {
159
+ if constexpr (std::is_void_v<Return_T>)
160
+ {
161
+ std::apply(this->function_, nativeArgs);
162
+ return Qnil;
163
+ }
164
+ else
165
+ {
166
+ // Call the native method and get the result
167
+ Return_T nativeResult = std::apply(this->function_, nativeArgs);
168
+
169
+ // Return the result
170
+ return this->toRuby_.convert(nativeResult);
171
+ }
172
+ }
173
+
174
+ template<typename Class_T, typename Function_T, bool IsMethod>
175
+ VALUE NativeFunction<Class_T, Function_T, IsMethod>::invokeNativeMethod(VALUE self, const Arg_Ts& nativeArgs)
176
+ {
177
+ Receiver_T receiver = this->getReceiver(self);
178
+ auto selfAndNativeArgs = std::tuple_cat(std::forward_as_tuple(receiver), nativeArgs);
179
+
180
+ if constexpr (std::is_void_v<Return_T>)
181
+ {
182
+ std::apply(this->function_, selfAndNativeArgs);
183
+ return Qnil;
184
+ }
185
+ else
186
+ {
187
+ Return_T nativeResult = (Return_T)std::apply(this->function_, selfAndNativeArgs);
188
+
189
+ // Special handling if the method returns self. If so we do not want
190
+ // to create a new Ruby wrapper object and instead return self.
191
+ if constexpr (std::is_same_v<intrinsic_type<Return_T>, intrinsic_type<Receiver_T>>)
192
+ {
193
+ if constexpr (std::is_pointer_v<Return_T> && std::is_pointer_v<Receiver_T>)
194
+ {
195
+ if (nativeResult == receiver)
196
+ return self;
197
+ }
198
+ else if constexpr (std::is_pointer_v<Return_T> && std::is_reference_v<Receiver_T>)
199
+ {
200
+ if (nativeResult == &receiver)
201
+ return self;
202
+ }
203
+ else if constexpr (std::is_reference_v<Return_T> && std::is_pointer_v<Receiver_T>)
204
+ {
205
+ if (&nativeResult == receiver)
206
+ return self;
207
+ }
208
+ else if constexpr (std::is_reference_v<Return_T> && std::is_reference_v<Receiver_T>)
209
+ {
210
+ if (&nativeResult == &receiver)
211
+ return self;
212
+ }
213
+ }
214
+
215
+ return this->toRuby_.convert(nativeResult);
216
+ }
217
+ }
218
+
219
+ template<typename Class_T, typename Function_T, bool IsMethod>
220
+ void NativeFunction<Class_T, Function_T, IsMethod>::noWrapper(const VALUE klass, const std::string& wrapper)
221
+ {
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 Class_T, typename Function_T, bool IsMethod>
236
+ void NativeFunction<Class_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
240
+ Wrapper* selfWrapper = getWrapper(self);
241
+
242
+ // Check function arguments
243
+ for (const Arg& arg : (*this->methodInfo_))
244
+ {
245
+ if (arg.isKeepAlive())
246
+ {
247
+ if (selfWrapper == nullptr)
248
+ {
249
+ noWrapper(self, "self");
250
+ }
251
+ selfWrapper->addKeepAlive(rubyValues[arg.position]);
252
+ }
253
+ }
254
+
255
+ // Check return value
256
+ if (this->methodInfo_->returnInfo.isKeepAlive())
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
264
+ Wrapper* returnWrapper = getWrapper(returnValue);
265
+ if (returnWrapper == nullptr)
266
+ {
267
+ noWrapper(returnValue, "return");
268
+ }
269
+ returnWrapper->addKeepAlive(self);
270
+ }
271
+ }
272
+
273
+ template<typename Class_T, typename Function_T, bool IsMethod>
274
+ VALUE NativeFunction<Class_T, Function_T, IsMethod>::operator()(int argc, VALUE* argv, VALUE self)
275
+ {
276
+ // Get the ruby values
277
+ std::vector<VALUE> rubyValues = this->getRubyValues(argc, argv);
278
+
279
+ auto indices = std::make_index_sequence<std::tuple_size_v<Arg_Ts>>{};
280
+
281
+ // Convert the Ruby values to native values
282
+ Arg_Ts nativeValues = this->getNativeValues(rubyValues, indices);
283
+
284
+ // Now call the native method
285
+ VALUE result = Qnil;
286
+ if constexpr (std::is_same_v<Receiver_T, std::nullptr_t>)
287
+ {
288
+ result = this->invokeNativeFunction(nativeValues);
289
+ }
290
+ else
291
+ {
292
+ result = this->invokeNativeMethod(self, nativeValues);
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;
299
+ }
300
+ }