#include #include #include #include #include "cpp_protect.hpp" #include "to_ruby_defn.hpp" #include "NativeRegistry.hpp" namespace Rice::detail { template void NativeFunction::define(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo) { // Tell Ruby to invoke the static method call on this class detail::protect(rb_define_method, klass, method_name.c_str(), (RUBY_METHOD_FUNC)&NativeFunction_T::call, -1); // Now create a NativeFunction instance and save it to the natives registry keyed on // Ruby klass and method id. There may be multiple NativeFunction instances // because the same C++ method could be mapped to multiple Ruby methods. NativeFunction_T* native = new NativeFunction_T(klass, method_name, std::forward(function), methodInfo); detail::Registries::instance.natives.add(klass, Identifier(method_name).id(), native); } template VALUE NativeFunction::call(int argc, VALUE* argv, VALUE self) { // Look up the native function based on the Ruby klass and method id NativeFunction_T* nativeFunction = detail::Registries::instance.natives.lookup(); // Execute the function but make sure to catch any C++ exceptions! return cpp_protect([&] { return nativeFunction->operator()(argc, argv, self); }); } template NativeFunction::NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo) : klass_(klass), method_name_(method_name), function_(function), methodInfo_(methodInfo) { // Create a tuple of NativeArgs that will convert the Ruby values to native values. For // builtin types NativeArgs will keep a copy of the native value so that it // can be passed by reference or pointer to the native function. For non-builtin types // it will just pass the value through. auto indices = std::make_index_sequence>{}; this->fromRubys_ = this->createFromRuby(indices); this->toRuby_ = this->createToRuby(); } template template From_Ruby NativeFunction::createFromRuby() { // Does the From_Ruby instantiation work with Arg? if constexpr (std::is_constructible_v, Arg*>) { return From_Ruby(&this->methodInfo_->arg(I)); } else { return From_Ruby(); } } template To_Ruby::Return_T> NativeFunction::createToRuby() { // Does the From_Ruby instantiation work with ReturnInfo? if constexpr (std::is_constructible_v, Return*>) { return To_Ruby(&this->methodInfo_->returnInfo); } else { return To_Ruby(); } } template template typename NativeFunction::From_Ruby_Args_Ts NativeFunction::createFromRuby(std::index_sequence& indices) { return std::make_tuple(createFromRuby::type>, I>()...); } template std::vector NativeFunction::getRubyValues(int argc, VALUE* argv) { // Setup a tuple for the leading rb_scan_args arguments std::string scanFormat = this->methodInfo_->formatString(); std::tuple rbScanArgs = std::forward_as_tuple(argc, argv, scanFormat.c_str()); // Create a vector to store the VALUEs that will be returned by rb_scan_args std::vector rbScanValues(std::tuple_size_v, Qnil); // Convert the vector to an array so it can be concatenated to a tuple. As importantly // fill it with pointers to rbScanValues std::array> rbScanValuePointers; std::transform(rbScanValues.begin(), rbScanValues.end(), rbScanValuePointers.begin(), [](VALUE& value) { return &value; }); // Combine the tuples and call rb_scan_args std::apply(rb_scan_args, std::tuple_cat(rbScanArgs, rbScanValuePointers)); return rbScanValues; } template template typename NativeFunction::Arg_Ts NativeFunction::getNativeValues(std::vector& values, std::index_sequence& indices) { // Convert each Ruby value to its native value by calling the appropriate fromRuby instance. // Note that for fundamental types From_Ruby will keep a copy of the native value // so it can be passed by reference or pointer to a native function. return std::forward_as_tuple(std::get(this->fromRubys_).convert(values[I])...); } template typename NativeFunction::Receiver_T NativeFunction::getReceiver(VALUE self) { // There is no self parameter if constexpr (std::is_same_v) { return nullptr; } // Self parameter is a Ruby VALUE so no conversion is needed else if constexpr (std::is_same_v) { return self; } /* This case happens when a class wrapped by Rice is calling a method defined on an ancestor class. For example, the std::map size method is defined on _Tree not map. Rice needs to know the actual type that was wrapped so it can correctly extract the C++ object from the Ruby object. */ else if constexpr (!std::is_same_v, Class_T> && std::is_base_of_v, Class_T>) { Class_T* instance = From_Ruby().convert(self); return dynamic_cast(instance); } // Self parameter could be derived from Object or it is an C++ instance and // needs to be unwrapped from Ruby else { return From_Ruby().convert(self); } } template VALUE NativeFunction::invokeNativeFunction(const Arg_Ts& nativeArgs) { if constexpr (std::is_void_v) { std::apply(this->function_, nativeArgs); return Qnil; } else { // Call the native method and get the result Return_T nativeResult = std::apply(this->function_, nativeArgs); // Return the result return this->toRuby_.convert(nativeResult); } } template VALUE NativeFunction::invokeNativeMethod(VALUE self, const Arg_Ts& nativeArgs) { Receiver_T receiver = this->getReceiver(self); auto selfAndNativeArgs = std::tuple_cat(std::forward_as_tuple(receiver), nativeArgs); if constexpr (std::is_void_v) { std::apply(this->function_, selfAndNativeArgs); return Qnil; } else { Return_T nativeResult = (Return_T)std::apply(this->function_, selfAndNativeArgs); // Special handling if the method returns self. If so we do not want // to create a new Ruby wrapper object and instead return self. if constexpr (std::is_same_v, intrinsic_type>) { if constexpr (std::is_pointer_v && std::is_pointer_v) { if (nativeResult == receiver) return self; } else if constexpr (std::is_pointer_v && std::is_reference_v) { if (nativeResult == &receiver) return self; } else if constexpr (std::is_reference_v && std::is_pointer_v) { if (&nativeResult == receiver) return self; } else if constexpr (std::is_reference_v && std::is_reference_v) { if (&nativeResult == &receiver) return self; } } return this->toRuby_.convert(nativeResult); } } template void NativeFunction::noWrapper(const VALUE klass, const std::string& wrapper) { std::stringstream message; message << "When calling the method `"; message << this->method_name_; message << "' we could not find the wrapper for the '"; message << rb_obj_classname(klass); message << "' "; message << wrapper; message << " type. You should not use keepAlive() on a Return or Arg that is a builtin Rice type."; throw std::runtime_error(message.str()); } template void NativeFunction::checkKeepAlive(VALUE self, VALUE returnValue, std::vector& rubyValues) { // selfWrapper will be nullptr if this(self) is a builtin type and not an external(wrapped) type // it is highly unlikely that keepAlive is used in this case but we check anyway Wrapper* selfWrapper = getWrapper(self); // Check function arguments for (const Arg& arg : (*this->methodInfo_)) { if (arg.isKeepAlive()) { if (selfWrapper == nullptr) { noWrapper(self, "self"); } selfWrapper->addKeepAlive(rubyValues[arg.position]); } } // Check return value if (this->methodInfo_->returnInfo.isKeepAlive()) { if (selfWrapper == nullptr) { noWrapper(self, "self"); } // returnWrapper will be nullptr if returnValue is a built-in type and not an external(wrapped) type Wrapper* returnWrapper = getWrapper(returnValue); if (returnWrapper == nullptr) { noWrapper(returnValue, "return"); } returnWrapper->addKeepAlive(self); } } template VALUE NativeFunction::operator()(int argc, VALUE* argv, VALUE self) { // Get the ruby values std::vector rubyValues = this->getRubyValues(argc, argv); auto indices = std::make_index_sequence>{}; // Convert the Ruby values to native values Arg_Ts nativeValues = this->getNativeValues(rubyValues, indices); // Now call the native method VALUE result = Qnil; if constexpr (std::is_same_v) { result = this->invokeNativeFunction(nativeValues); } else { result = this->invokeNativeMethod(self, nativeValues); } // Check if any function arguments or return values need to have their lifetimes tied to the receiver this->checkKeepAlive(self, result, rubyValues); return result; } }