From d728fb2db291781cec40dd4ce49b595d60b9df5f Mon Sep 17 00:00:00 2001 From: zekexiao Date: Sat, 18 Oct 2025 19:46:41 +0800 Subject: [PATCH] init --- .gitignore | 4 + CMakeLists.txt | 36 ++++++ example/Point.cpp | 5 + example/Point.h | 58 ++++++++++ main.cpp | 60 ++++++++++ mrubypp.h | 52 +++++++++ mrubypp_arena_guard.h | 26 +++++ mrubypp_bind_class.h | 260 ++++++++++++++++++++++++++++++++++++++++++ mrubypp_converters.h | 87 ++++++++++++++ 9 files changed, 588 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 example/Point.cpp create mode 100644 example/Point.h create mode 100644 main.cpp create mode 100644 mrubypp.h create mode 100644 mrubypp_arena_guard.h create mode 100644 mrubypp_bind_class.h create mode 100644 mrubypp_converters.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc15d0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +cmake-*/ +build/ +.idea/ + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c460007 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.16) + +project(mrubypp LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (MSVC) + add_compile_options("$<$:/utf-8>") + add_compile_options("$<$:/utf-8>") + add_compile_definitions(NOMINMAX) +endif () + +add_executable(mrubypp main.cpp + mrubypp.h + mrubypp_converters.h + mrubypp_arena_guard.h + mrubypp_bind_class.h + example/Point.cpp + example/Point.h) + + +set(mruby_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/deps/mruby") +target_include_directories(mrubypp PUBLIC "${mruby_ROOT}/include") +target_link_directories(mrubypp PUBLIC "${mruby_ROOT}/lib") +target_link_libraries(mrubypp PUBLIC + libmruby + ws2_32.lib wsock32.lib ws2_32.lib) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +include(GNUInstallDirs) +install(TARGETS mrubypp + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/example/Point.cpp b/example/Point.cpp new file mode 100644 index 0000000..075643d --- /dev/null +++ b/example/Point.cpp @@ -0,0 +1,5 @@ +// +// Created by ZekeXiao on 2025/10/18. +// + +#include "Point.h" diff --git a/example/Point.h b/example/Point.h new file mode 100644 index 0000000..26cd191 --- /dev/null +++ b/example/Point.h @@ -0,0 +1,58 @@ +// +// Created by ZekeXiao on 2025/10/18. +// + +#ifndef MRUBYPP_POINT_H +#define MRUBYPP_POINT_H + +#include "mrubypp_converters.h" + +#include "mruby/data.h" + +#include "mrubypp_bind_class.h" + +class Point { +public: + Point(int x, int y) : x_(x), y_(y) {} + + void set_values(int x, int y) { + x_ = x; + y_ = y; + } + + int get_x() const { return x_; } + void set_x(int x) { x_ = x; } + + int get_y() const { return y_; } + void set_y(int y) { y_ = y; } + + void add(const Point &other) { + this->x_ += other.x_; + this->y_ += other.y_; + } + static int none() { return 1; } + +private: + int x_; + int y_; +}; + +template <> struct mrubypp_converter { + static mrb_value to_mrb(mrb_state *mrb, const Point &var) { + // 创建一个新的 Point 对象数据结构 + mrb_value obj = mrb_obj_value( + mrb_data_object_alloc(mrb, mrb->object_class, new Point(var), + &mrubypp_class_builder::data_type)); + return obj; + } + + static Point from_mrb(mrb_state *mrb, mrb_value value) { + // 从 mrb_value 中提取 Point 对象 + if (mrb_type(value) == MRB_TT_DATA) { + Point *point = static_cast(DATA_PTR(value)); + return *point; + } + return Point(0, 0); // 默认构造 + } +}; +#endif // MRUBYPP_POINT_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..5e822d6 --- /dev/null +++ b/main.cpp @@ -0,0 +1,60 @@ +#include + +#include + +#include "example/Point.h" +#include "mrubypp.h" +#include "mrubypp_bind_class.h" + +#ifdef _WIN32 +#include +#include +#endif +int main() { +#ifdef _WIN32 + SetConsoleOutputCP(CP_UTF8); +#endif + mrubypp engine; + engine.load(R"( +def add(a) + a.sort! + a[0] +end)"); + + std::vector a{3, 1, 2}; + static volatile int b = 0; + std::chrono::steady_clock::time_point start = + std::chrono::steady_clock::now(); + for (auto i = 0; i < 10000; ++i) { + b = engine.call("add", a); + assert(b == 1); + } + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + auto duration = + std::chrono::duration_cast(end - start) + .count(); + std::cout << "10000 ruby call duration: " << duration << "ms" << std::endl; + + engine.class_builder("Point") + .def_constructor() + .def_method("add", &Point::add) + .def_class_method("none", &Point::none) + .def_property("x", &Point::get_x, &Point::set_x); + + engine.load(R"( + def test_point() + p = Point.new(3, 4) + p.x = 10 + p.add(p) + p.x += Point::none() + return p + end + )"); + + // 调用test_point函数 + auto point_result = engine.call("test_point"); + std::cout << "returned point: [" << point_result.get_x() << ", " + << point_result.get_y() << "]" << std::endl; + + return 0; +} diff --git a/mrubypp.h b/mrubypp.h new file mode 100644 index 0000000..02f8b07 --- /dev/null +++ b/mrubypp.h @@ -0,0 +1,52 @@ +#ifndef MRUBYPP_H +#define MRUBYPP_H + +#include "mrubypp_arena_guard.h" +#include "mrubypp_bind_class.h" +#include "mrubypp_converters.h" + +#include +#include +#include + +#include + +// 支持自定义类型注册 +class mrubypp { +public: + mrubypp() { mrb = mrb_open(); } + ~mrubypp() { mrb_close(mrb); } + void load(const std::string &str) { + if (!mrb) + return; + mrb_load_string(mrb, str.c_str()); + } + + template T call(const std::string &funcName) { + mrubypp_arena_guard guard(mrb); + mrb_value result = mrb_funcall(mrb, mrb_top_self(mrb), funcName.data(), 0); + return mrubypp_converter::from_mrb(mrb, result); + } + + template + T call(const std::string &funcName, Args... args) { + mrubypp_arena_guard guard(mrb); + mrb_value argv[] = {mrubypp_converter::to_mrb(mrb, args)...}; + mrb_sym sym = mrb_intern_cstr(mrb, funcName.data()); + mrb_value result = + mrb_funcall_argv(mrb, mrb_top_self(mrb), sym, sizeof...(Args), argv); + return mrubypp_converter::from_mrb(mrb, result); + } + + template + mrubypp_class_builder class_builder(const std::string &class_name) { + return mrubypp_class_builder(mrb, class_name); + } + + [[nodiscard]] mrb_state *get_mrb() const { return mrb; } + +private: + mrb_state *mrb; +}; + +#endif // MRUBYPP_H diff --git a/mrubypp_arena_guard.h b/mrubypp_arena_guard.h new file mode 100644 index 0000000..68fd490 --- /dev/null +++ b/mrubypp_arena_guard.h @@ -0,0 +1,26 @@ +// +// Created by ZekeXiao on 2025/10/17. +// + +#ifndef MRUBYPP_MRUBYPP_UTILS_H +#define MRUBYPP_MRUBYPP_UTILS_H + +#include + +class mrubypp_arena_guard { +public: + explicit mrubypp_arena_guard(mrb_state *mrb) : mrb(mrb) { + ai = mrb_gc_arena_save(mrb); + } + mrubypp_arena_guard(mrubypp_arena_guard &&other) = delete; + mrubypp_arena_guard(const mrubypp_arena_guard &other) = delete; + ~mrubypp_arena_guard() { mrb_gc_arena_restore(mrb, ai); } + + // arena_idx + [[nodiscard]] int get_ai() const { return ai; } + +private: + mrb_state *mrb; + int ai; +}; +#endif // MRUBYPP_MRUBYPP_UTILS_H diff --git a/mrubypp_bind_class.h b/mrubypp_bind_class.h new file mode 100644 index 0000000..41d4987 --- /dev/null +++ b/mrubypp_bind_class.h @@ -0,0 +1,260 @@ +#ifndef MRUBYPP_BIND_CLASS +#define MRUBYPP_BIND_CLASS + +#include "mrubypp_arena_guard.h" +#include "mrubypp_converters.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +template +std::tuple mrubypp_get_args_helper_impl(mrb_state *mrb, + const mrb_value *argv, + std::index_sequence) { + return std::make_tuple( + mrubypp_converter::type>::from_mrb( + mrb, argv[Is])...); +} + +template +std::tuple::type...> +mrubypp_get_args_helper(mrb_state *mrb) { + mrubypp_arena_guard guard(mrb); + auto argc = mrb_get_argc(mrb); + auto argv = mrb_get_argv(mrb); + + if (argc != sizeof...(Args)) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "argument count mismatch"); + } + + return mrubypp_get_args_helper_impl::type...>( + mrb, argv, std::index_sequence_for{}); +} + +template +struct mrubypp_method_wrapper { + static Ret (T::*method)(MethodArgs...); + + static mrb_value wrapper(mrb_state *mrb, mrb_value self) { + auto args = mrubypp_get_args_helper(mrb); + T *instance = static_cast(DATA_PTR(self)); + if (!instance) { + mrb_raise(mrb, E_RUNTIME_ERROR, "Invalid instance"); + } + + if constexpr (std::is_same_v) { + std::apply(method, std::tuple_cat(std::make_tuple(instance), args)); + return mrb_nil_value(); + } else { + Ret result = + std::apply(method, std::tuple_cat(std::make_tuple(instance), args)); + return mrubypp_converter::to_mrb(mrb, result); + } + } +}; + +template +struct mrubypp_const_method_wrapper { + static Ret (T::*method)(MethodArgs...) const; + + static mrb_value wrapper(mrb_state *mrb, mrb_value self) { + auto args = mrubypp_get_args_helper(mrb); + T *instance = static_cast(DATA_PTR(self)); + if (!instance) { + mrb_raise(mrb, E_RUNTIME_ERROR, "Invalid instance"); + } + + if constexpr (std::is_same_v) { + std::apply(method, std::tuple_cat(std::make_tuple(instance), args)); + return mrb_nil_value(); + } else { + Ret result = + std::apply(method, std::tuple_cat(std::make_tuple(instance), args)); + return mrubypp_converter::to_mrb(mrb, result); + } + } +}; + +template struct mrubypp_function_wrapper { + static Ret (*func)(FuncArgs...); + + static mrb_value wrapper(mrb_state *mrb, mrb_value self) { + auto args = mrubypp_get_args_helper(mrb); + if constexpr (std::is_same_v) { + std::apply(func, args); + return mrb_nil_value(); + } else { + Ret result = std::apply(func, args); + return mrubypp_converter::to_mrb(mrb, result); + } + } +}; + +template struct mrubypp_getter_wrapper { + static Ret (T::*getter)() const; + + static mrb_value wrapper(mrb_state *mrb, mrb_value self) { + T *instance = static_cast(DATA_PTR(self)); + if (!instance) { + mrb_raise(mrb, E_RUNTIME_ERROR, "Invalid instance"); + } + + Ret result = (instance->*getter)(); + return mrubypp_converter::to_mrb(mrb, result); + } +}; + +template struct mrubypp_setter_wrapper { + static void (T::*setter)(Ret); + + static mrb_value wrapper(mrb_state *mrb, mrb_value self) { + auto args = mrubypp_get_args_helper(mrb); + T *instance = static_cast(DATA_PTR(self)); + if (!instance) { + mrb_raise(mrb, E_RUNTIME_ERROR, "Invalid instance"); + } + + std::apply(setter, std::tuple_cat(std::make_tuple(instance), args)); + return self; + } +}; +template class mrubypp_class_builder; + +template struct mrubypp_constructor_wrapper { + static mrb_value wrapper(mrb_state *mrb, mrb_value self) { + auto args = mrubypp_get_args_helper(mrb); + T *instance = std::apply([](Args... args) { return new T(args...); }, args); + + DATA_PTR(self) = instance; + DATA_TYPE(self) = &mrubypp_class_builder::data_type; + return self; + } +}; + +template class mrubypp_class_builder { +private: + mrb_state *mrb_; + struct RClass *rclass_; + std::string name_; + + static void free_instance(mrb_state *mrb, void *ptr) { + if (ptr) { + T *instance = static_cast(ptr); + delete instance; + } + } + +public: + static const struct mrb_data_type data_type; + + mrubypp_class_builder(mrb_state *mrb, std::string name) + : mrb_(mrb), name_(std::move(name)) { + rclass_ = mrb_define_class(mrb_, name_.c_str(), mrb_->object_class); + MRB_SET_INSTANCE_TT(rclass_, MRB_TT_DATA); + + mrb_define_method( + mrb_, rclass_, "initialize", + [](mrb_state *mrb, mrb_value self) -> mrb_value { + mrb_raise(mrb, E_NOTIMP_ERROR, + "Initialize must be defined explicitly"); + return self; + }, + MRB_ARGS_ANY()); + } + + template mrubypp_class_builder &def_constructor() { + mrb_define_method(mrb_, rclass_, "initialize", + &mrubypp_constructor_wrapper::wrapper, + MRB_ARGS_REQ(sizeof...(Args))); + + return *this; + } + + template + mrubypp_class_builder &def_method(const std::string &name, + Ret (T::*method)(MethodArgs...)) { + mrubypp_method_wrapper::method = method; + + mrb_define_method(mrb_, rclass_, name.c_str(), + &mrubypp_method_wrapper::wrapper, + MRB_ARGS_REQ(sizeof...(MethodArgs))); + + return *this; + } + + template + mrubypp_class_builder &def_method(const std::string &name, + Ret (T::*method)(MethodArgs...) const) { + mrubypp_const_method_wrapper::method = method; + + mrb_define_method( + mrb_, rclass_, name.c_str(), + &mrubypp_const_method_wrapper::wrapper, + MRB_ARGS_REQ(sizeof...(MethodArgs))); + + return *this; + } + + template + mrubypp_class_builder &def_class_method(const std::string &name, + Ret (*func)(FuncArgs...)) { + mrubypp_function_wrapper::func = func; + + mrb_define_class_method( + mrb_, rclass_, name.c_str(), + &mrubypp_function_wrapper::wrapper, + MRB_ARGS_REQ(sizeof...(FuncArgs))); + + return *this; + } + + template + mrubypp_class_builder &def_property(const std::string &name, + Ret (T::*getter)() const, + void (T::*setter)(Ret)) { + mrubypp_getter_wrapper::getter = getter; + mrb_define_method(mrb_, rclass_, name.c_str(), + &mrubypp_getter_wrapper::wrapper, + MRB_ARGS_NONE()); + + std::string setter_name = name + "="; + mrubypp_setter_wrapper::setter = setter; + mrb_define_method(mrb_, rclass_, setter_name.c_str(), + &mrubypp_setter_wrapper::wrapper, + MRB_ARGS_REQ(1)); + + return *this; + } + + [[nodiscard]] struct RClass *get_rclass() const { return rclass_; } +}; + +template +const struct mrb_data_type mrubypp_class_builder::data_type = { + typeid(T).name(), mrubypp_class_builder::free_instance +}; + +template +Ret (T::*mrubypp_method_wrapper::method)(MethodArgs...); + +template +Ret (T::*mrubypp_const_method_wrapper::method)( + MethodArgs...) const; + +template +Ret (*mrubypp_function_wrapper::func)(FuncArgs...); + +template +Ret (T::*mrubypp_getter_wrapper::getter)() const; + +template +void (T::*mrubypp_setter_wrapper::setter)(Ret); + +#endif diff --git a/mrubypp_converters.h b/mrubypp_converters.h new file mode 100644 index 0000000..2f417eb --- /dev/null +++ b/mrubypp_converters.h @@ -0,0 +1,87 @@ +#ifndef MRUBYPP_STD_CONVERTERS_H +#define MRUBYPP_STD_CONVERTERS_H + +#include "mrubypp_arena_guard.h" + +#include +#include +#include +#include +#include + +template struct mrubypp_converter { + static mrb_value to_mrb(mrb_state *mrb, const T &var) { + static_assert(sizeof(T) == 0, "Specialization required"); + return mrb_nil_value(); + } + static T from_mrb(mrb_state *mrb, mrb_value value) { + static_assert(sizeof(T) == 0, "Specialization required"); + return T(); + } +}; + +template <> struct mrubypp_converter { + static mrb_value to_mrb(mrb_state *mrb, int var) { + return mrb_fixnum_value(var); + } + static int from_mrb(mrb_state *mrb, mrb_value value) { + return mrb_fixnum(value); + } +}; + +template <> struct mrubypp_converter { + static mrb_value to_mrb(mrb_state *mrb, float var) { + return mrb_float_value(mrb, var); + } + static double from_mrb(mrb_state *mrb, mrb_value value) { + return mrb_float(value); + } +}; + +template <> struct mrubypp_converter { + static mrb_value to_mrb(mrb_state *mrb, double var) { + return mrb_float_value(mrb, var); + } + static double from_mrb(mrb_state *mrb, mrb_value value) { + return mrb_float(value); + } +}; + +template <> struct mrubypp_converter { + static mrb_value to_mrb(mrb_state *mrb, const std::string &var) { + return mrb_str_new(mrb, var.data(), (mrb_int)var.size()); + } + + static std::string from_mrb(mrb_state *mrb, mrb_value value) { + if (mrb_string_p(value)) { + return std::string(RSTRING_PTR(value), RSTRING_LEN(value)); + } + return {}; + } +}; + +template struct mrubypp_converter> { + static mrb_value to_mrb(mrb_state *mrb, const std::vector &var) { + mrb_value ary = mrb_ary_new_capa(mrb, static_cast(var.size())); + for (const T &el : var) { + mrubypp_arena_guard guard(mrb); + mrb_ary_push(mrb, ary, mrubypp_converter::to_mrb(mrb, el)); + } + return ary; + } + + static std::vector from_mrb(mrb_state *mrb, mrb_value value) { + mrubypp_arena_guard guard(mrb); + std::vector result; + if (mrb_array_p(value)) { + int len = RARRAY_LEN(value); + for (int i = 0; i < len; ++i) { + result.append( + mrubypp_converter::from_mrb(mrb, mrb_ary_ref(mrb, value, i))); + } + } + return result; + } +}; + +#endif // MRUBYPP_STD_CONVERTERS_H