Skip to content

Commit 799a1cb

Browse files
authored
[embind] Add return value policy option for function bindings. (#21692)
* [embind] Add return value policy option for function bindings. This enables C++ function bindings to explicitly control what happens to an object when returned to JS via BindingType::toWireType. The new polices are: default(no policy), take_ownership, and reference. The polices are further explained in the updated documentation. To support the polices BindingType::toWireType now uses tag dispatching to allow specialization for each of the return policies. Custom BindingTypes that do not want to specialize each option can use the return_value_policy::default_tag which will match all the policies. Note: this is the framework for further work on improving class property bindings and allowing more idiomatic JS.
1 parent 7e7c057 commit 799a1cb

File tree

9 files changed

+496
-84
lines changed

9 files changed

+496
-84
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ See docs/process.md for more on how version tagging works.
3030
https://github.com/llvm/llvm-project/pull/90792), multivalue feature is now
3131
enabled by default in Emscripten. This only enables the language features and
3232
does not turn on the multivalue ABI.
33+
- Embind now supports return value policies to better define object lifetimes.
34+
See https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#object-ownership for more information.
3335

3436
3.1.59 - 04/30/24
3537
-----------------

site/source/docs/porting/connecting_cpp_and_javascript/embind.rst

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,6 @@ to enable the closure compiler.
203203
Memory management
204204
=================
205205

206-
JavaScript only gained support for `finalizers`_ in ECMAScript 2021, or ECMA-262
207-
Edition 12. The new API is called `FinalizationRegistry`_ and it still does not
208-
offer any guarantees that the provided finalization callback will be called.
209-
Embind uses this for cleanup if available, but only for smart pointers,
210-
and only as a last resort.
211-
212-
.. warning:: It is strongly recommended that JavaScript code explicitly deletes
213-
any C++ object handles it has received.
214-
215206
The :js:func:`delete()` JavaScript method is provided to manually signal that
216207
a C++ object is no longer needed and can be deleted:
217208

@@ -226,7 +217,8 @@ a C++ object is no longer needed and can be deleted:
226217
y.delete();
227218
228219
.. note:: Both C++ objects constructed from the JavaScript side as well as
229-
those returned from C++ methods must be explicitly deleted.
220+
those returned from C++ methods must be explicitly deleted, unless a
221+
``reference`` return value policy is used (see below).
230222

231223

232224
.. tip:: The ``try`` … ``finally`` JavaScript construct can be used to guarantee
@@ -248,6 +240,19 @@ a C++ object is no longer needed and can be deleted:
248240
}
249241
}
250242
243+
Automatic memory management
244+
---------------------------
245+
246+
JavaScript only gained support for `finalizers`_ in ECMAScript 2021, or ECMA-262
247+
Edition 12. The new API is called `FinalizationRegistry`_ and it still does not
248+
offer any guarantees that the provided finalization callback will be called.
249+
Embind uses this for cleanup if available, but only for smart pointers,
250+
and only as a last resort.
251+
252+
.. warning:: It is strongly recommended that JavaScript code explicitly deletes
253+
any C++ object handles it has received.
254+
255+
251256
Cloning and Reference Counting
252257
------------------------------
253258

@@ -344,31 +349,96 @@ The JavaScript code does not need to worry about lifetime management.
344349
Advanced class concepts
345350
=======================
346351

352+
.. _embind-object-ownership:
353+
354+
Object Ownership
355+
----------------
356+
357+
JavaScript and C++ have very different memory models which can lead to it being
358+
unclear which language owns and is responsible for deleting an object when it
359+
moves between languages. To make object ownership more explicit, *embind*
360+
supports smart pointers and return value policies. Return value
361+
polices dictate what happens to a C++ object when it is returned to JavaScript.
362+
363+
To use a return value policy, pass the desired policy into function or method
364+
bindings. For example:
365+
366+
.. code:: cpp
367+
368+
EMSCRIPTEN_BINDINGS(module) {
369+
function("createData", &createData, return_value_policy::take_ownership());
370+
}
371+
372+
Embind supports three return value policies that behave differently depending
373+
on the return type of the function. The policies work as follows:
374+
375+
* *default (no argument)* - For return by value and reference a new object will be allocated using the
376+
object's copy constructor. JS then owns the object and is responsible for deleting it. Returning a
377+
pointer is not allowed by default (use an explicit policy below).
378+
* :cpp:type:`return_value_policy::take_ownership` - Ownership is transferred to JS.
379+
* :cpp:type:`return_value_policy::reference` - Reference an existing object but do not take
380+
ownership. Care must be taken to not delete the object while it is still in use in JS.
381+
382+
More details below:
383+
384+
+--------------------+-------------+---------------------------------------------------------------+
385+
| Return Type | Constructor | Cleanup |
386+
+====================+=============+===============================================================+
387+
| **default** |
388+
+--------------------+-------------+---------------------------------------------------------------+
389+
| Value (``T``) | copy | JS must delete the copied object. |
390+
+--------------------+-------------+---------------------------------------------------------------+
391+
| Reference (``T&``) | copy | JS must delete the copied object. |
392+
+--------------------+-------------+---------------------------------------------------------------+
393+
| Pointer (``T*``) | n/a | Pointers must explicitly use a return policy. |
394+
+--------------------+-------------+---------------------------------------------------------------+
395+
| **take_ownership** |
396+
+--------------------+-------------+---------------------------------------------------------------+
397+
| Value (``T``) | move | JS must delete the moved object. |
398+
+--------------------+-------------+---------------------------------------------------------------+
399+
| Reference (``T&``) | move | JS must delete the moved object. |
400+
+--------------------+-------------+---------------------------------------------------------------+
401+
| Pointer (``T*``) | none | JS must delete the object. |
402+
+--------------------+-------------+---------------------------------------------------------------+
403+
| **reference** |
404+
+--------------------+-------------+---------------------------------------------------------------+
405+
| Value (``T``) | n/a | Reference to a value is not allowed. |
406+
+--------------------+-------------+---------------------------------------------------------------+
407+
| Reference (``T&``) | none | C++ must delete the object. |
408+
+--------------------+-------------+---------------------------------------------------------------+
409+
| Pointer (``T*``) | none | C++ must delete the object. |
410+
+--------------------+-------------+---------------------------------------------------------------+
411+
347412
.. _embind-raw-pointers:
348413

349414
Raw pointers
350415
------------
351416

352417
Because raw pointers have unclear lifetime semantics, *embind* requires
353-
their use to be marked with :cpp:type:`allow_raw_pointers`.
418+
their use to be marked with either :cpp:type:`allow_raw_pointers` or with a
419+
:cpp:type:`return_value_policy`. If the function returns a pointer it is
420+
recommended to use a :cpp:type:`return_value_policy` instead of the general
421+
:cpp:type:`allow_raw_pointers`.
354422

355423
For example:
356424

357425
.. code:: cpp
358426
359427
class C {};
360428
C* passThrough(C* ptr) { return ptr; }
429+
C* createC() { return new C(); }
361430
EMSCRIPTEN_BINDINGS(raw_pointers) {
362431
class_<C>("C");
363432
function("passThrough", &passThrough, allow_raw_pointers());
433+
function("createC", &createC, return_value_policy::take_ownership());
364434
}
365435
366436
.. note::
367437

368-
Currently the markup serves only to allow raw pointer use, and
369-
show that you've thought about the use of the raw pointers. Eventually
370-
we hope to implement `Boost.Python-like raw pointer policies`_ for
371-
managing object ownership.
438+
Currently allow_raw_pointers for pointer arguments only serves to allow raw
439+
pointer use, and show that you've thought about the use of the raw pointers.
440+
Eventually we hope to implement `Boost.Python-like raw pointer policies`_ for
441+
managing object ownership of arguments as well.
372442

373443
.. _embind-external-constructors:
374444

0 commit comments

Comments
 (0)