diff --git a/test/test_fast_api.cpp b/test/test_fast_api.cpp index 02e7ad7..66e2266 100644 --- a/test/test_fast_api.cpp +++ b/test/test_fast_api.cpp @@ -158,6 +158,28 @@ void test_fast_api() run_script(context, "p.fast_xy = 77; p.x"), 77); } + // --- Class property with fast getter + slow setter --- + { + struct Gauge + { + int32_t val = 0; + int32_t get_val() const { return val; } + void set_val(v8::Isolate*, int32_t v) { val = v; } + }; + + v8pp::class_ gauge_class(isolate); + gauge_class + .ctor<>() + .property("mixed", v8pp::fast_fn<&Gauge::get_val>, &Gauge::set_val); + context.class_("Gauge", gauge_class); + + check_eq("fast_api: mixed property get", + run_script(context, "var g = new Gauge(); g.mixed"), 0); + + check_eq("fast_api: mixed property set+get", + run_script(context, "g.mixed = 42; g.mixed"), 42); + } + // --- Module property with fast API --- { static int32_t mod_value = 0; @@ -188,4 +210,24 @@ void test_fast_api() check_eq("fast_api: module read-only property is readonly", run_script(context, "fmod.fast_const = 999; fmod.fast_const"), 123); } + + // --- Module property with fast getter + slow setter --- + { + static int32_t mixed_val = 0; + + struct MixedMod + { + static int32_t get_val() { return mixed_val; } + static void set_val(int32_t v) { mixed_val = v; } + }; + + v8pp::module m(isolate); + m.property("mixed", v8pp::fast_fn<&MixedMod::get_val>, &MixedMod::set_val); + context.module("mmod", m); + + mixed_val = 0; + check_eq("fast_api: module mixed property set+get", + run_script(context, "mmod.mixed = 33; mmod.mixed"), 33); + check_eq("fast_api: module mixed property updated C++ side", mixed_val, 33); + } } diff --git a/v8pp/CMakeLists.txt b/v8pp/CMakeLists.txt index f8ea84a..fb454b6 100644 --- a/v8pp/CMakeLists.txt +++ b/v8pp/CMakeLists.txt @@ -60,6 +60,7 @@ set(V8PP_HEADERS promise.hpp property.hpp ptr_traits.hpp + string_utils.hpp throw_ex.hpp type_info.hpp utility.hpp @@ -71,6 +72,7 @@ if(V8PP_HEADER_ONLY) class.ipp context_store.ipp json.ipp + string_utils.ipp throw_ex.ipp version.ipp ) @@ -81,6 +83,7 @@ else() context.cpp context_store.cpp convert.cpp + string_utils.cpp json.cpp throw_ex.cpp version.cpp diff --git a/v8pp/class.hpp b/v8pp/class.hpp index 341b5e0..10f079b 100644 --- a/v8pp/class.hpp +++ b/v8pp/class.hpp @@ -741,6 +741,34 @@ class class_ return *this; } + /// Set read/write class property with V8 Fast API getter and slow setter + template + requires(!is_fast_function>::value) + class_& property(std::string_view name, fast_function, SetFunction&& set) + { + using Setter = std::decay_t; + static_assert(std::is_member_function_pointer_v || detail::is_callable::value, "SetFunction must be callable"); + + v8::HandleScope scope(isolate()); + + v8::Local v8_name = v8pp::to_v8_name(isolate(), name); + auto getter_template = wrap_function_template( + isolate(), fast_function{}, + v8::SideEffectType::kHasNoSideEffect); + auto setter_template = v8::FunctionTemplate::New(isolate(), + &detail::forward_function, + detail::external_data::set(isolate(), Setter(std::forward(set))), + v8::Local(), 0, + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasSideEffectToReceiver); + + class_info_.js_function_template()->PrototypeTemplate()->SetAccessorProperty( + v8_name, getter_template, setter_template, + v8::PropertyAttribute(v8::DontDelete)); + + return *this; + } + /// Set read/write class property with V8 Fast API getter and setter template class_& property(std::string_view name, fast_function, fast_function) diff --git a/v8pp/convert.hpp b/v8pp/convert.hpp index be7a437..b604b96 100644 --- a/v8pp/convert.hpp +++ b/v8pp/convert.hpp @@ -25,6 +25,7 @@ #include #include "v8pp/ptr_traits.hpp" +#include "v8pp/string_utils.hpp" #include "v8pp/utility.hpp" namespace v8pp { @@ -1093,10 +1094,7 @@ struct convert { std::string str = convert::from_v8(isolate, value); #ifdef WIN32 - int sz = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), nullptr, 0); - std::wstring w(sz, 0); - MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), w.data(), sz); - return std::filesystem::path(w); + return std::filesystem::path(utf8_to_wide(str)); #else return std::filesystem::path(str); #endif @@ -1106,14 +1104,10 @@ struct convert { if (auto str = convert::try_from_v8(isolate, value)) { - std::string s = *std::move(str); #ifdef WIN32 - int sz = MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast(s.size()), nullptr, 0); - std::wstring w(sz, 0); - MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast(s.size()), w.data(), sz); - return std::filesystem::path(w); + return std::filesystem::path(utf8_to_wide(*std::move(str))); #else - return std::filesystem::path(s); + return std::filesystem::path(*std::move(str)); #endif } return std::nullopt; @@ -1122,11 +1116,7 @@ struct convert static to_type to_v8(v8::Isolate* isolate, from_type const& value) { #ifdef WIN32 - std::wstring w = value.generic_wstring(); - int sz = WideCharToMultiByte(CP_UTF8, 0, w.data(), static_cast(w.size()), nullptr, 0, nullptr, nullptr); - std::string utf8(sz, 0); - WideCharToMultiByte(CP_UTF8, 0, w.data(), static_cast(w.size()), utf8.data(), sz, nullptr, nullptr); - return convert::to_v8(isolate, utf8); + return convert::to_v8(isolate, wide_to_utf8(value.generic_wstring())); #else return convert::to_v8(isolate, value.string()); #endif diff --git a/v8pp/module.hpp b/v8pp/module.hpp index e8cdeda..6b94e59 100644 --- a/v8pp/module.hpp +++ b/v8pp/module.hpp @@ -178,6 +178,33 @@ class module return *this; } + /// Set read/write module property with V8 Fast API getter and slow setter + template + requires(!is_fast_function>::value) + module& property(char const* name, fast_function, SetFunction&& set) + { + using Setter = std::decay_t; + static_assert(detail::is_callable::value, "SetFunction must be callable"); + + v8::HandleScope scope(isolate_); + + v8::Local v8_name = v8pp::to_v8_name(isolate_, name); + auto getter_template = wrap_function_template( + isolate_, fast_function{}, + v8::SideEffectType::kHasNoSideEffect); + auto setter_template = v8::FunctionTemplate::New(isolate_, + &detail::forward_function, + detail::external_data::set(isolate_, Setter(std::forward(set))), + v8::Local(), 0, + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasSideEffectToReceiver); + + obj_->SetAccessorProperty(v8_name, getter_template, setter_template, + v8::PropertyAttribute(v8::DontDelete)); + + return *this; + } + /// Set read/write module property with V8 Fast API getter and setter template module& property(char const* name, fast_function, fast_function) diff --git a/v8pp/string_utils.cpp b/v8pp/string_utils.cpp new file mode 100644 index 0000000..27c38bb --- /dev/null +++ b/v8pp/string_utils.cpp @@ -0,0 +1,5 @@ +#include "v8pp/config.hpp" + +#if !V8PP_HEADER_ONLY +#include "v8pp/string_utils.ipp" +#endif diff --git a/v8pp/string_utils.hpp b/v8pp/string_utils.hpp new file mode 100644 index 0000000..aca7558 --- /dev/null +++ b/v8pp/string_utils.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include "v8pp/config.hpp" + +namespace v8pp { + +#ifdef WIN32 +std::wstring utf8_to_wide(std::string_view utf8); +std::string wide_to_utf8(std::wstring_view wide); +#endif + +} // namespace v8pp + +#if V8PP_HEADER_ONLY +#include "v8pp/string_utils.ipp" +#endif diff --git a/v8pp/string_utils.ipp b/v8pp/string_utils.ipp new file mode 100644 index 0000000..eb22da8 --- /dev/null +++ b/v8pp/string_utils.ipp @@ -0,0 +1,33 @@ +#include "v8pp/string_utils.hpp" + +#ifdef WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +namespace v8pp { + +#ifdef WIN32 +V8PP_IMPL std::wstring utf8_to_wide(std::string_view utf8) +{ + int sz = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), nullptr, 0); + std::wstring w(sz, 0); + MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), w.data(), sz); + return w; +} + +V8PP_IMPL std::string wide_to_utf8(std::wstring_view wide) +{ + int sz = WideCharToMultiByte(CP_UTF8, 0, wide.data(), static_cast(wide.size()), nullptr, 0, nullptr, nullptr); + std::string utf8(sz, 0); + WideCharToMultiByte(CP_UTF8, 0, wide.data(), static_cast(wide.size()), utf8.data(), sz, nullptr, nullptr); + return utf8; +} +#endif + +} // namespace v8pp