Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions site/source/docs/porting/connecting_cpp_and_javascript/embind.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,63 @@ The example can be compiled on the Linux/macOS terminal with::
emcc -O2 -Wall -Werror -lembind -o oscillator.html oscillator.cpp


Argument policies
-----------------

When calling JavaScript functions via ``val``, arguments are converted from C++ to JavaScript. By default, objects are copied.
To control this behavior, you can use argument policies.

The following policies are supported:

* :cpp:type:`policy::take_ownership` - Ownership is transferred to JavaScript. The C++ object is moved.
* :cpp:type:`policy::reference` - A reference is passed to JavaScript. The C++ object is not copied or moved.

Policies can be specified for specific arguments using the ``arg`` template parameter.

.. code-block:: cpp

#include <emscripten/val.h>
#include <emscripten/bind.h>

using namespace emscripten;

struct MyClass {
MyClass() {}
MyClass(const MyClass&) { printf("copy\n"); }
MyClass(MyClass&&) { printf("move\n"); }
};

EMSCRIPTEN_BINDINGS(my_module) {
class_<MyClass>("MyClass");
}

int main() {
MyClass obj;

// Default: copy
val::global("someFunction")(obj);

// Take ownership: move
val::global("someFunction")(obj, policy::take_ownership<arg<0>>());

// Reference: no copy/move
val::global("someFunction")(obj, policy::reference<arg<0>>());
}

Policies can also be used with ``val::call``, ``val::new_``, and ``val::set``.

.. code-block:: cpp

// Use policy with val::call
val::global().call<void>("someFunction", obj, policy::reference<arg<0>>());

// Use policy with val::new_
val v = val::global("MyClass").new_(obj, policy::take_ownership<arg<0>>());

// Use policy with val::set
val::global().set("prop", obj, policy::reference());


Built-in type conversions
=========================

Expand Down
40 changes: 30 additions & 10 deletions system/include/emscripten/val.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,20 +236,36 @@ void writeGenericWireType(GenericWireType*& cursor, T wt) {
++cursor;
}

template<typename TransformedType, typename OriginalType>
struct WireTypeSelector {
using Type = OriginalType;
static const OriginalType& convert(const OriginalType& v) { return v; }
};

template<typename T, typename Policy, typename OriginalType>
struct WireTypeSelector<ArgWithPolicy<T, Policy>, OriginalType> {
using Type = ArgWithPolicy<T, Policy>;
static Type convert(const OriginalType& v) { return Type(v); }
};

template<typename Policy, size_t Index>
inline void writeGenericWireTypes(GenericWireType*&) {
}

template<typename First, typename... Rest>
template<typename Policy, size_t Index, typename First, typename... Rest>
EMSCRIPTEN_ALWAYS_INLINE void writeGenericWireTypes(GenericWireType*& cursor, First&& first, Rest&&... rest) {
writeGenericWireType(cursor, BindingType<First>::toWireType(std::forward<First>(first), rvp::default_tag{}));
writeGenericWireTypes(cursor, std::forward<Rest>(rest)...);
using TransformedType = typename Policy::template MapWithPolicies<Index, First>::type;
using Selector = WireTypeSelector<TransformedType, First>;

writeGenericWireType(cursor, BindingType<typename Selector::Type>::toWireType(Selector::convert(std::forward<First>(first)), rvp::default_tag{}));
writeGenericWireTypes<Policy, Index + 1>(cursor, std::forward<Rest>(rest)...);
}

template<typename... Args>
template<typename Policy, typename... Args>
struct WireTypePack {
WireTypePack(Args&&... args) {
GenericWireType* cursor = elements.data();
writeGenericWireTypes(cursor, std::forward<Args>(args)...);
writeGenericWireTypes<Policy, 1>(cursor, std::forward<Args>(args)...);
}

operator EM_VAR_ARGS() const {
Expand Down Expand Up @@ -483,8 +499,8 @@ class EMBIND_VISIBILITY_DEFAULT val {
}

template<typename K, typename V, typename... Policies>
void set(const K& key, const V& value, Policies... policies) {
internal::_emval_set_property(as_handle(), val_ref(key).as_handle(), val_ref(value, policies...).as_handle());
void set(K&& key, V&& value, Policies... policies) {
internal::_emval_set_property(as_handle(), val_ref(std::forward<K>(key)).as_handle(), val_ref(std::forward<V>(value), policies...).as_handle());
}

template<typename T>
Expand Down Expand Up @@ -599,7 +615,7 @@ class EMBIND_VISIBILITY_DEFAULT val {
static constexpr typename Policy::template ArgTypeList<Ret, Args...> argTypes;
thread_local EM_INVOKER mc = _emval_create_invoker(argTypes.getCount(), argTypes.getTypes(), Kind);

WireTypePack<Args...> argv(std::forward<Args>(args)...);
WireTypePack<Policy, Args...> argv(std::forward<Args>(args)...);
EM_DESTRUCTORS destructors = nullptr;

RetWire result;
Expand All @@ -624,14 +640,18 @@ class EMBIND_VISIBILITY_DEFAULT val {
}

template<typename T, typename... Policies>
val val_ref(const T& v, Policies... policies) const {
return val(v, policies...);
val val_ref(T&& v, Policies... policies) const {
return val(std::forward<T>(v), policies...);
}

const val& val_ref(const val& v) const {
return v;
}

val& val_ref(val& v) const {
return v;
}

pthread_t thread;
EM_VAL handle;

Expand Down
80 changes: 80 additions & 0 deletions system/include/emscripten/wire.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ namespace internal {

typedef const void* TYPEID;

template<typename T, typename Policy>
struct ArgWithPolicy {
const T& value;
ArgWithPolicy(const T& v) : value(v) {}
};

// We don't need the full std::type_info implementation. We
// just need a unique identifier per type and polymorphic type
// identification.
Expand Down Expand Up @@ -155,6 +161,34 @@ template<typename T>
struct TypeID<T&&> : TypeID<T> {
};

template<typename T, typename Policy>
struct TypeID<ArgWithPolicy<T, Policy>> {
static constexpr TYPEID get() {
return TypeID<T>::get();
}
};

template<typename T, typename Policy>
struct TypeID<ArgWithPolicy<T&, Policy>> {
static constexpr TYPEID get() {
return TypeID<ArgWithPolicy<T, Policy>>::get();
}
};

template<typename T, typename Policy>
struct TypeID<ArgWithPolicy<T&&, Policy>> {
static constexpr TYPEID get() {
return TypeID<ArgWithPolicy<T, Policy>>::get();
}
};

template<typename T, typename Policy>
struct TypeID<ArgWithPolicy<T*, Policy>> {
static constexpr TYPEID get() {
return LightTypeID<T*>::get();
}
};

// ExecutePolicies<>

template<typename... Policies>
Expand Down Expand Up @@ -381,6 +415,14 @@ struct BindingType<T*> {
}
};

template<typename T, typename Policy>
struct BindingType<ArgWithPolicy<T, Policy>> {
using WireType = typename BindingType<T>::WireType;
static WireType toWireType(const ArgWithPolicy<T, Policy>& v, rvp::default_tag) {
return BindingType<T>::toWireType(v.value, Policy{});
}
};

template<typename T>
struct GenericBindingType {
typedef typename std::remove_reference<T>::type ActualT;
Expand Down Expand Up @@ -580,6 +622,34 @@ enum class enum_value_type {
string = 2
};

namespace policy {

template<typename Slot = arg<0>>
struct take_ownership {
template<typename InputType, int Index>
struct Transform {
using type = typename std::conditional<
Index == Slot::index,
internal::ArgWithPolicy<InputType, internal::rvp::take_ownership>,
InputType
>::type;
};
};

template<typename Slot = arg<0>>
struct reference {
template<typename InputType, int Index>
struct Transform {
using type = typename std::conditional<
Index == Slot::index,
internal::ArgWithPolicy<InputType, internal::rvp::reference>,
InputType
>::type;
};
};

} // end namespace policy

namespace internal {

template<typename... Policies>
Expand Down Expand Up @@ -650,6 +720,16 @@ struct GetReturnValuePolicy<ReturnType, return_value_policy::reference, Rest...>
using tag = rvp::reference;
};

template<typename Slot, typename... Rest>
struct isPolicy<policy::take_ownership<Slot>, Rest...> {
static constexpr bool value = true;
};

template<typename Slot, typename... Rest>
struct isPolicy<policy::reference<Slot>, Rest...> {
static constexpr bool value = true;
};

template<typename ReturnType, typename T, typename... Rest>
struct GetReturnValuePolicy<ReturnType, T, Rest...> {
using tag = GetReturnValuePolicy<ReturnType, Rest...>::tag;
Expand Down
117 changes: 117 additions & 0 deletions test/embind/test_val.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,22 @@ Dummy * makeDummy() {
return new Dummy();
}

struct MoveTester {
static int moves;
static int copies;
static void reset() { moves = 0; copies = 0; }
MoveTester() {}
MoveTester(const MoveTester&) { copies++; }
MoveTester(MoveTester&&) { moves++; }
};
int MoveTester::moves = 0;
int MoveTester::copies = 0;

EMSCRIPTEN_BINDINGS(test_bindings) {
emscripten::class_<Dummy>("Dummy");
emscripten::function("throw_js_error", &throw_js_error);
emscripten::function("makeDummy", &makeDummy, emscripten::allow_raw_pointers());
emscripten::class_<MoveTester>("MoveTester");
}

int main() {
Expand Down Expand Up @@ -721,6 +733,111 @@ int main() {
globalThis.staticDummy.delete();
);

test("val::operator() with policies");
EM_ASM(
globalThis.assertAndDelete = function(arg0) {
assert(arg0);
arg0.delete();
};
globalThis.assertArg0 = function(arg0) {
assert(arg0);
};
globalThis.assertArg1 = function(arg0, arg1) {
assert(arg1);
};
);
MoveTester t;
MoveTester::reset();
val::global("assertAndDelete")(t); // default is copy
ensure(MoveTester::copies == 1);
ensure(MoveTester::moves == 0);

MoveTester::reset();
val::global("assertAndDelete")(t, policy::take_ownership<arg<0>>());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 1);

MoveTester::reset();
val::global("assertAndDelete")(new MoveTester(), policy::take_ownership<arg<0>>());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 0);

MoveTester::reset();
MoveTester *ptr = new MoveTester();
val::global("assertArg0")(ptr, policy::reference<arg<0>>());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 0);
delete ptr;

MoveTester::reset();
val::global("assertArg0")(t, policy::reference<arg<0>>());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 0);

MoveTester::reset();
val::global("assertArg1")(val::null(), t, policy::reference<arg<1>>());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 0);

test("val::call with policies");
MoveTester::reset();
val::global().call<void>("assertAndDelete", t, val::null(), policy::take_ownership<arg<0>>());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 1);

MoveTester::reset();
val::global().call<void>("assertArg0", t, val::null(), policy::reference<arg<0>>());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 0);

test("val constructor with policies");
MoveTester::reset();
val v_owned(t, policy::take_ownership());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 1);
v_owned.call<void>("delete");

MoveTester::reset();
val v_ref(t, policy::reference());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 0);

test("val::set with policies");
MoveTester::reset();
val::global().set("temp", t, policy::take_ownership());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 1);
val::global("temp").call<void>("delete");
val::global().delete_("temp");

MoveTester::reset();
val::global().set("temp", t, policy::reference());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 0);
val::global().delete_("temp");

test("val::new_ with policies");
EM_ASM(
globalThis.TestClass = class {
constructor(arg0) {
this.arg0 = arg0;
}
delete() {
this.arg0.delete();
}
};
);
MoveTester::reset();
val testClass_owned = val::global("TestClass").new_(t, policy::take_ownership<arg<0>>());
testClass_owned.call<void>("delete");
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 1);

MoveTester::reset();
val testClass_ref = val::global("TestClass").new_(t, policy::reference<arg<0>>());
ensure(MoveTester::copies == 0);
ensure(MoveTester::moves == 0);

printf("end\n");
return 0;
}
Loading