cpp
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <cassert>
#include <cstdint>
#include <cstring>
#include <exception>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#ifndef JSI_EXPORT
#ifdef _MSC_VER
#ifdef CREATE_SHARED_LIBRARY
#define JSI_EXPORT __declspec(dllexport)
#else
#define JSI_EXPORT
#endif // CREATE_SHARED_LIBRARY
#else // _MSC_VER
#define JSI_EXPORT __attribute__((visibility("default")))
#endif // _MSC_VER
#endif // !defined(JSI_EXPORT)
class FBJSRuntime;
namespace facebook {
namespace jsi {
/// UUID version 1 implementation. This should be constructed with constant
/// arguments to identify fixed UUIDs.
class JSI_EXPORT UUID {
public:
// Construct from raw parts
constexpr UUID(
uint32_t timeLow,
uint16_t timeMid,
uint16_t timeHighAndVersion,
uint16_t variantAndClockSeq,
uint64_t node)
: high(
((uint64_t)(timeLow) << 32) | ((uint64_t)(timeMid) << 16) |
((uint64_t)(timeHighAndVersion))),
low(((uint64_t)(variantAndClockSeq) << 48) | node) {}
// Default constructor (zero UUID)
constexpr UUID() : high(0), low(0) {}
constexpr UUID(const UUID&) = default;
constexpr UUID& operator=(const UUID&) = default;
constexpr bool operator==(const UUID& other) const {
return high == other.high && low == other.low;
}
constexpr bool operator!=(const UUID& other) const {
return !(*this == other);
}
// Ordering (for std::map, sorting, etc.)
constexpr bool operator<(const UUID& other) const {
return (high < other.high) || (high == other.high && low < other.low);
}
// Hash support for UUID (for unordered_map compatibility)
struct Hash {
std::size_t operator()(const UUID& uuid) const noexcept {
return std::hash<uint64_t>{}(uuid.high) ^
(std::hash<uint64_t>{}(uuid.low) << 1);
}
};
// UUID format: 8-4-4-4-12
std::string toString() const {
std::string buffer(36, ' ');
std::snprintf(
buffer.data(),
buffer.size() + 1,
"%08x-%04x-%04x-%04x-%012llx",
getTimeLow(),
getTimeMid(),
getTimeHighAndVersion(),
getVariantAndClockSeq(),
(unsigned long long)getNode());
return buffer;
}
constexpr uint32_t getTimeLow() const {
return (uint32_t)(high >> 32);
}
constexpr uint16_t getTimeMid() const {
return (uint16_t)(high >> 16);
}
constexpr uint16_t getTimeHighAndVersion() const {
return (uint16_t)high;
}
constexpr uint16_t getVariantAndClockSeq() const {
return (uint16_t)(low >> 48);
}
constexpr uint64_t getNode() const {
return low & 0xFFFFFFFFFFFF;
}
private:
uint64_t high;
uint64_t low;
};
/// Base interface that all JSI interfaces inherit from. Users should not try to
/// manipulate this base type directly, and should use castInterface to get the
/// appropriate subtype.
struct JSI_EXPORT ICast {
/// If the current object can be cast into the interface specified by \p
/// interfaceUUID, return a pointer to the object. Otherwise, return a null
/// pointer.
/// The returned interface has the same lifetime as the underlying object. It
/// does not need to be released when not needed.
virtual ICast* castInterface(const UUID& interfaceUUID) = 0;
protected:
/// Interfaces are not destructible, thus the destructor is intentionally
/// protected to prevent delete calls on the interface.
/// Additionally, the destructor is non-virtual to reduce the vtable
/// complexity from inheritance.
~ICast() = default;
};
/// Base class for buffers of data or bytecode that need to be passed to the
/// runtime. The buffer is expected to be fully immutable, so the result of
/// size(), data(), and the contents of the pointer returned by data() must not
/// change after construction.
class JSI_EXPORT Buffer {
public:
virtual ~Buffer();
virtual size_t size() const = 0;
virtual const uint8_t* data() const = 0;
};
class JSI_EXPORT StringBuffer : public Buffer {
public:
StringBuffer(std::string s) : s_(std::move(s)) {}
size_t size() const override {
return s_.size();
}
const uint8_t* data() const override {
return reinterpret_cast<const uint8_t*>(s_.data());
}
private:
std::string s_;
};
/// Base class for buffers of data that need to be passed to the runtime. The
/// result of size() and data() must not change after construction. However, the
/// region pointed to by data() may be modified by the user or the runtime. The
/// user must ensure that access to the contents of the buffer is properly
/// synchronised.
class JSI_EXPORT MutableBuffer {
public:
virtual ~MutableBuffer();
virtual size_t size() const = 0;
virtual uint8_t* data() = 0;
};
/// PreparedJavaScript is a base class representing JavaScript which is in a
/// form optimized for execution, in a runtime-specific way. Construct one via
/// jsi::Runtime::prepareJavaScript().
/// ** This is an experimental API that is subject to change. **
class JSI_EXPORT PreparedJavaScript {
protected:
PreparedJavaScript() = default;
public:
virtual ~PreparedJavaScript() = 0;
};
class Runtime;
class Pointer;
class PropNameID;
class Symbol;
class BigInt;
class String;
class Object;
class WeakObject;
class Array;
class ArrayBuffer;
class Function;
class Value;
class Instrumentation;
class Scope;
class JSIException;
class JSError;
/// A function which has this type can be registered as a function
/// callable from JavaScript using Function::createFromHostFunction().
/// When the function is called, args will point to the arguments, and
/// count will indicate how many arguments are passed. The function
/// can return a Value to the caller, or throw an exception. If a C++
/// exception is thrown, a JS Error will be created and thrown into
/// JS; if the C++ exception extends std::exception, the Error's
/// message will be whatever what() returns. Note that it is undefined whether
/// HostFunctions may or may not be called in strict mode; that is `thisVal`
/// can be any value - it will not necessarily be coerced to an object or
/// or set to the global object.
using HostFunctionType = std::function<
Value(Runtime& rt, const Value& thisVal, const Value* args, size_t count)>;
/// An object which implements this interface can be registered as an
/// Object with the JS runtime.
class JSI_EXPORT HostObject {
public:
// The C++ object's dtor will be called when the GC finalizes this
// object. (This may be as late as when the Runtime is shut down.)
// You have no control over which thread it is called on. This will
// be called from inside the GC, so it is unsafe to do any VM
// operations which require a Runtime&. Derived classes' dtors
// should also avoid doing anything expensive. Calling the dtor on
// a jsi object is explicitly ok. If you want to do JS operations,
// or any nontrivial work, you should add it to a work queue, and
// manage it externally.
virtual ~HostObject();
// When JS wants a property with a given name from the HostObject,
// it will call this method. If it throws an exception, the call
// will throw a JS \c Error object. By default this returns undefined.
// \return the value for the property.
virtual Value get(Runtime&, const PropNameID& name);
// When JS wants to set a property with a given name on the HostObject,
// it will call this method. If it throws an exception, the call will
// throw a JS \c Error object. By default this throws a type error exception
// mimicking the behavior of a frozen object in strict mode.
virtual void set(Runtime&, const PropNameID& name, const Value& value);
// When JS wants a list of property names for the HostObject, it will
// call this method. If it throws an exception, the call will throw a
// JS \c Error object. The default implementation returns empty vector.
virtual std::vector<PropNameID> getPropertyNames(Runtime& rt);
};
/// Native state (and destructor) that can be attached to any JS object
/// using setNativeState.
class JSI_EXPORT NativeState {
public:
virtual ~NativeState();
};
// JSI_UNSTABLE gates features that will be released with a Hermes version in
// the future. Until released, these features may be subject to change. After
// release, these features will be moved out of JSI_UNSTABLE and become frozen.
#ifdef JSI_UNSTABLE
/// Opaque class that is used to store serialized object from a runtime. The
/// lifetime of this object is orthogonal to the original runtime object, and
/// may outlive the original object.
class JSI_EXPORT Serialized {
public:
/// Uses \p secretAddr to validate if the Serialized data is supported. If so,
/// return the pointer to the underlying serialized data. Otherwise, return a
/// nullptr. This should be used by the runtime to deserialize the data.
virtual void* getPrivate(const void* secretAddr) = 0;
virtual ~Serialized();
};
/// Provides a set of APIs that allows copying objects between different
/// runtime instances. The runtimes instances must be of the same type. As an
/// example, a serialized object from Hermes runtime may only be deserialized by
/// another Hermes runtime.
class JSI_EXPORT ISerialization : public ICast {
public:
static constexpr jsi::UUID uuid{
0xd40fe0ec,
0xa47c,
0x42c9,
0x8c09,
0x661aeab832d8};
/// Serializes the given Value \p value using the structured clone algorithm.
/// It returns a shared pointer of an opaque Serialized object that can be
/// deserialized multiple times. The lifetime of the Serialized object is not
/// tied to the lifetime of the original object.
virtual std::shared_ptr<Serialized> serialize(Value& value) = 0;
/// Given a Serialized object provided by \p serialized, deserialize it using
/// the structured clone algorithm into a JS value in the current runtime.
/// Returns the deserialized JS value.
virtual Value deserialize(const std::shared_ptr<Serialized>& serialized) = 0;
/// Serializes the given jsi::Value \p value using the structured clone
/// algorithm. \p transferList must be a JS Array. Given the length property
/// of \p transferList, this API will transfer everything at index [0, length
/// - 1] to the serialized object. The transferred values will no longer be
/// usable in the original runtime. It returns a unique pointer of an opaque
/// Serialized object that can be deserialized once only by
/// deserializeWithTransfer. The lifetime of the Serialized object is not tied
/// to the lifetime of the original object.
virtual std::unique_ptr<Serialized> serializeWithTransfer(
Value& value,
const Array& transferList) = 0;
/// Using the structure clone algorithm, deserialize the object provided by \p
/// serialized into a JS value in the current runtime. \p serialized must be
/// created by serializeWithTransfer. If the current runtime does not support
/// the serialization scheme in \p serialized, then this method will throw and
/// \p serialized will remain unmodified. Otherwise, this will consume the
/// serialized data entirely and make the serialized objects in the current
/// runtime. Any transferred values in the serialized object will be owned by
/// the current runtime.
// This method returns an Array containing the deserialized values, where the
// first element is the value passed into serializeWithTransfer,
/// followed by all transferred values.
virtual Array deserializeWithTransfer(
std::unique_ptr<Serialized>& serialized) = 0;
protected:
~ISerialization() = default;
};
#endif // JSI_UNSTABLE
/// Represents a JS runtime. Movable, but not copyable. Note that
/// this object may not be thread-aware, but cannot be used safely from
/// multiple threads at once. The application is responsible for
/// ensuring that it is used safely. This could mean using the
/// Runtime from a single thread, using a mutex, doing all work on a
/// serial queue, etc. This restriction applies to the methods of
/// this class, and any method in the API which take a Runtime& as an
/// argument. Destructors (all but ~Scope), operators, or other methods
/// which do not take Runtime& as an argument are safe to call from any
/// thread, but it is still forbidden to make write operations on a single
/// instance of any class from more than one thread. In addition, to
/// make shutdown safe, destruction of objects associated with the Runtime
/// must be destroyed before the Runtime is destroyed, or from the
/// destructor of a managed HostObject or HostFunction. Informally, this
/// means that the main source of unsafe behavior is to hold a jsi object
/// in a non-Runtime-managed object, and not clean it up before the Runtime
/// is shut down. If your lifecycle is such that avoiding this is hard,
/// you will probably need to do use your own locks.
class JSI_EXPORT Runtime : public ICast {
public:
virtual ~Runtime();
ICast* castInterface(const UUID& interfaceUUID) override;
/// Evaluates the given JavaScript \c buffer. \c sourceURL is used
/// to annotate the stack trace if there is an exception. The
/// contents may be utf8-encoded JS source code, or binary bytecode
/// whose format is specific to the implementation. If the input
/// format is unknown, or evaluation causes an error, a JSIException
/// will be thrown.
/// Note this function should ONLY be used when there isn't another means
/// through the JSI API. For example, it will be much slower to use this to
/// call a global function than using the JSI APIs to read the function
/// property from the global object and then calling it explicitly.
virtual Value evaluateJavaScript(
const std::shared_ptr<const Buffer>& buffer,
const std::string& sourceURL) = 0;
/// Prepares to evaluate the given JavaScript \c buffer by processing it into
/// a form optimized for execution. This may include pre-parsing, compiling,
/// etc. If the input is invalid (for example, cannot be parsed), a
/// JSIException will be thrown. The resulting object is tied to the
/// particular concrete type of Runtime from which it was created. It may be
/// used (via evaluatePreparedJavaScript) in any Runtime of the same concrete
/// type.
/// The PreparedJavaScript object may be passed to multiple VM instances, so
/// they can all share and benefit from the prepared script.
/// As with evaluateJavaScript(), using JavaScript code should be avoided
/// when the JSI API is sufficient.
virtual std::shared_ptr<const PreparedJavaScript> prepareJavaScript(
const std::shared_ptr<const Buffer>& buffer,
std::string sourceURL) = 0;
/// Evaluates a PreparedJavaScript. If evaluation causes an error, a
/// JSIException will be thrown.
/// As with evaluateJavaScript(), using JavaScript code should be avoided
/// when the JSI API is sufficient.
virtual Value evaluatePreparedJavaScript(
const std::shared_ptr<const PreparedJavaScript>& js) = 0;
/// Queues a microtask in the JavaScript VM internal Microtask (a.k.a. Job in
/// ECMA262) queue, to be executed when the host drains microtasks in
/// its event loop implementation.
///
/// \param callback a function to be executed as a microtask.
virtual void queueMicrotask(const jsi::Function& callback) = 0;
/// Drain the JavaScript VM internal Microtask (a.k.a. Job in ECMA262) queue.
///
/// \param maxMicrotasksHint a hint to tell an implementation that it should
/// make a best effort not execute more than the given number. It's default
/// to -1 for infinity (unbounded execution).
/// \return true if the queue is drained or false if there is more work to do.
///
/// When there were exceptions thrown from the execution of microtasks,
/// implementations shall discard the exceptional jobs. An implementation may
/// \throw a \c JSError object to signal the hosts to handle. In that case, an
/// implementation may or may not suspend the draining.
///
/// Hosts may call this function again to resume the draining if it was
/// suspended due to either exceptions or the \p maxMicrotasksHint bound.
/// E.g. a host may repetitively invoke this function until the queue is
/// drained to implement the "microtask checkpoint" defined in WHATWG HTML
/// event loop: https://html.spec.whatwg.org/C#perform-a-microtask-checkpoint.
///
/// Note that error propagation is only a concern if a host needs to implement
/// `queueMicrotask`, a recent API that allows enqueueing arbitrary functions
/// (hence may throw) as microtasks. Exceptions from ECMA-262 Promise Jobs are
/// handled internally to VMs and are never propagated to hosts.
///
/// This API offers some queue management to hosts at its best effort due to
/// different behaviors and limitations imposed by different VMs and APIs. By
/// the time this is written, An implementation may swallow exceptions (JSC),
/// may not pause (V8), and may not support bounded executions.
virtual bool drainMicrotasks(int maxMicrotasksHint = -1) = 0;
/// \return the global object
virtual Object global() = 0;
/// \return a short printable description of the instance. It should
/// at least include some human-readable indication of the runtime
/// implementation. This should only be used by logging, debugging,
/// and other developer-facing callers.
virtual std::string description() = 0;
/// \return whether or not the underlying runtime supports debugging via the
/// Chrome remote debugging protocol.
///
/// NOTE: the API for determining whether a runtime is debuggable and
/// registering a runtime with the debugger is still in flux, so please don't
/// use this API unless you know what you're doing.
virtual bool isInspectable() = 0;
/// \return an interface to extract metrics from this \c Runtime. The default
/// implementation of this function returns an \c Instrumentation instance
/// which returns no metrics.
virtual Instrumentation& instrumentation();
/// Stores the pointer \p data with the \p uuid in the runtime. This can be
/// used to store some custom data within the runtime. When the runtime is
/// destroyed, or if an entry at an existing key is overwritten, the runtime
/// will release its ownership of the held object.
void setRuntimeData(const UUID& uuid, const std::shared_ptr<void>& data);
/// Returns the data associated with the \p uuid in the runtime. If there's no
/// data associated with the uuid, return a null pointer.
std::shared_ptr<void> getRuntimeData(const UUID& uuid);
protected:
friend class Pointer;
friend class PropNameID;
friend class Symbol;
friend class BigInt;
friend class String;
friend class Object;
friend class WeakObject;
friend class Array;
friend class ArrayBuffer;
friend class Function;
friend class Value;
friend class Scope;
friend class JSError;
/// Stores the pointer \p data with the \p uuid in the runtime. This can be
/// used to store some custom data within the runtime. When the runtime is
/// destroyed, or if an entry at an existing key is overwritten, the runtime
/// will release its ownership by calling \p deleter.
virtual void setRuntimeDataImpl(
const UUID& uuid,
const void* data,
void (*deleter)(const void* data));
/// Returns the data associated with the \p uuid in the runtime. If there's no
/// data associated with the uuid, return a null pointer.
virtual const void* getRuntimeDataImpl(const UUID& uuid);
// Potential optimization: avoid the cloneFoo() virtual dispatch,
// and instead just fix the number of fields, and copy them, since
// in practice they are trivially copyable. Sufficient use of
// rvalue arguments/methods would also reduce the number of clones.
struct PointerValue {
virtual void invalidate() noexcept = 0;
protected:
virtual ~PointerValue() = default;
};
virtual PointerValue* cloneSymbol(const Runtime::PointerValue* pv) = 0;
virtual PointerValue* cloneBigInt(const Runtime::PointerValue* pv) = 0;
virtual PointerValue* cloneString(const Runtime::PointerValue* pv) = 0;
virtual PointerValue* cloneObject(const Runtime::PointerValue* pv) = 0;
virtual PointerValue* clonePropNameID(const Runtime::PointerValue* pv) = 0;
virtual PropNameID createPropNameIDFromAscii(
const char* str,
size_t length) = 0;
virtual PropNameID createPropNameIDFromUtf8(
const uint8_t* utf8,
size_t length) = 0;
virtual PropNameID createPropNameIDFromUtf16(
const char16_t* utf16,
size_t length);
virtual PropNameID createPropNameIDFromString(const String& str) = 0;
virtual PropNameID createPropNameIDFromSymbol(const Symbol& sym) = 0;
virtual std::string utf8(const PropNameID&) = 0;
virtual bool compare(const PropNameID&, const PropNameID&) = 0;
virtual std::string symbolToString(const Symbol&) = 0;
virtual BigInt createBigIntFromInt64(int64_t) = 0;
virtual BigInt createBigIntFromUint64(uint64_t) = 0;
virtual bool bigintIsInt64(const BigInt&) = 0;
virtual bool bigintIsUint64(const BigInt&) = 0;
virtual uint64_t truncate(const BigInt&) = 0;
virtual String bigintToString(const BigInt&, int) = 0;
virtual String createStringFromAscii(const char* str, size_t length) = 0;
virtual String createStringFromUtf8(const uint8_t* utf8, size_t length) = 0;
virtual String createStringFromUtf16(const char16_t* utf16, size_t length);
virtual std::string utf8(const String&) = 0;
// \return a \c Value created from a utf8-encoded JSON string. The default
// implementation creates a \c String and invokes JSON.parse.
virtual Value createValueFromJsonUtf8(const uint8_t* json, size_t length);
virtual Object createObject() = 0;
virtual Object createObject(std::shared_ptr<HostObject> ho) = 0;
virtual std::shared_ptr<HostObject> getHostObject(const jsi::Object&) = 0;
virtual HostFunctionType& getHostFunction(const jsi::Function&) = 0;
// Creates a new Object with the custom prototype
virtual Object createObjectWithPrototype(const Value& prototype);
virtual bool hasNativeState(const jsi::Object&) = 0;
virtual std::shared_ptr<NativeState> getNativeState(const jsi::Object&) = 0;
virtual void setNativeState(
const jsi::Object&,
std::shared_ptr<NativeState> state) = 0;
virtual void setPrototypeOf(const Object& object, const Value& prototype);
virtual Value getPrototypeOf(const Object& object);
virtual Value getProperty(const Object&, const PropNameID& name) = 0;
virtual Value getProperty(const Object&, const String& name) = 0;
virtual Value getProperty(const Object&, const Value& name);
virtual bool hasProperty(const Object&, const PropNameID& name) = 0;
virtual bool hasProperty(const Object&, const String& name) = 0;
virtual bool hasProperty(const Object&, const Value& name);
virtual void setPropertyValue(
const Object&,
const PropNameID& name,
const Value& value) = 0;
virtual void
setPropertyValue(const Object&, const String& name, const Value& value) = 0;
virtual void
setPropertyValue(const Object&, const Value& name, const Value& value);
virtual void deleteProperty(const Object&, const PropNameID& name);
virtual void deleteProperty(const Object&, const String& name);
virtual void deleteProperty(const Object&, const Value& name);
virtual bool isArray(const Object&) const = 0;
virtual bool isArrayBuffer(const Object&) const = 0;
virtual bool isFunction(const Object&) const = 0;
virtual bool isHostObject(const jsi::Object&) const = 0;
virtual bool isHostFunction(const jsi::Function&) const = 0;
virtual Array getPropertyNames(const Object&) = 0;
virtual WeakObject createWeakObject(const Object&) = 0;
virtual Value lockWeakObject(const WeakObject&) = 0;
virtual Array createArray(size_t length) = 0;
virtual ArrayBuffer createArrayBuffer(
std::shared_ptr<MutableBuffer> buffer) = 0;
virtual size_t size(const Array&) = 0;
virtual size_t size(const ArrayBuffer&) = 0;
virtual uint8_t* data(const ArrayBuffer&) = 0;
virtual Value getValueAtIndex(const Array&, size_t i) = 0;
virtual void
setValueAtIndexImpl(const Array&, size_t i, const Value& value) = 0;
virtual Function createFunctionFromHostFunction(
const PropNameID& name,
unsigned int paramCount,
HostFunctionType func) = 0;
virtual Value call(
const Function&,
const Value& jsThis,
const Value* args,
size_t count) = 0;
virtual Value
callAsConstructor(const Function&, const Value* args, size_t count) = 0;
// Private data for managing scopes.
struct ScopeState;
virtual ScopeState* pushScope();
virtual void popScope(ScopeState*);
virtual bool strictEquals(const Symbol& a, const Symbol& b) const = 0;
virtual bool strictEquals(const BigInt& a, const BigInt& b) const = 0;
virtual bool strictEquals(const String& a, const String& b) const = 0;
virtual bool strictEquals(const Object& a, const Object& b) const = 0;
virtual bool instanceOf(const Object& o, const Function& f) = 0;
/// See Object::setExternalMemoryPressure.
virtual void setExternalMemoryPressure(
const jsi::Object& obj,
size_t amount) = 0;
virtual std::u16string utf16(const String& str);
virtual std::u16string utf16(const PropNameID& sym);
/// Invokes the provided callback \p cb with the String content in \p str.
/// The callback must take in three arguments: bool ascii, const void* data,
/// and size_t num, respectively. \p ascii indicates whether the \p data
/// passed to the callback should be interpreted as a pointer to a sequence of
/// \p num ASCII characters or UTF16 characters. Depending on the internal
/// representation of the string, the function may invoke the callback
/// multiple times, with a different format on each invocation. The callback
/// must not access runtime functionality, as any operation on the runtime may
/// invalidate the data pointers.
virtual void getStringData(
const jsi::String& str,
void* ctx,
void (*cb)(void* ctx, bool ascii, const void* data, size_t num));
/// Invokes the provided callback \p cb with the PropNameID content in \p sym.
/// The callback must take in three arguments: bool ascii, const void* data,
/// and size_t num, respectively. \p ascii indicates whether the \p data
/// passed to the callback should be interpreted as a pointer to a sequence of
/// \p num ASCII characters or UTF16 characters. Depending on the internal
/// representation of the string, the function may invoke the callback
/// multiple times, with a different format on each invocation. The callback
/// must not access runtime functionality, as any operation on the runtime may
/// invalidate the data pointers.
virtual void getPropNameIdData(
const jsi::PropNameID& sym,
void* ctx,
void (*cb)(void* ctx, bool ascii, const void* data, size_t num));
// These exist so derived classes can access the private parts of
// Value, Symbol, String, and Object, which are all friends of Runtime.
template <typename T>
static T make(PointerValue* pv);
static PointerValue* getPointerValue(Pointer& pointer);
static const PointerValue* getPointerValue(const Pointer& pointer);
static const PointerValue* getPointerValue(const Value& value);
friend class ::FBJSRuntime;
template <typename Plain, typename Base>
friend class RuntimeDecorator;
};
// Base class for pointer-storing types.
class JSI_EXPORT Pointer {
protected:
explicit Pointer(Pointer&& other) noexcept : ptr_(other.ptr_) {
other.ptr_ = nullptr;
}
~Pointer() {
if (ptr_) {
ptr_->invalidate();
}
}
Pointer& operator=(Pointer&& other) noexcept;
friend class Runtime;
friend class Value;
explicit Pointer(Runtime::PointerValue* ptr) : ptr_(ptr) {}
typename Runtime::PointerValue* ptr_;
};
/// Represents something that can be a JS property key. Movable, not copyable.
class JSI_EXPORT PropNameID : public Pointer {
public:
using Pointer::Pointer;
PropNameID(Runtime& runtime, const PropNameID& other)
: Pointer(runtime.clonePropNameID(other.ptr_)) {}
PropNameID(PropNameID&& other) = default;
PropNameID& operator=(PropNameID&& other) = default;
/// Create a JS property name id from ascii values. The data is
/// copied.
static PropNameID forAscii(Runtime& runtime, const char* str, size_t length) {
return runtime.createPropNameIDFromAscii(str, length);
}
/// Create a property name id from a nul-terminated C ascii name. The data is
/// copied.
static PropNameID forAscii(Runtime& runtime, const char* str) {
return forAscii(runtime, str, strlen(str));
}
/// Create a PropNameID from a C++ string. The string is copied.
static PropNameID forAscii(Runtime& runtime, const std::string& str) {
return forAscii(runtime, str.c_str(), str.size());
}
/// Create a PropNameID from utf8 values. The data is copied.
/// Results are undefined if \p utf8 contains invalid code points.
static PropNameID
forUtf8(Runtime& runtime, const uint8_t* utf8, size_t length) {
return runtime.createPropNameIDFromUtf8(utf8, length);
}
/// Create a PropNameID from utf8-encoded octets stored in a
/// std::string. The string data is transformed and copied.
/// Results are undefined if \p utf8 contains invalid code points.
static PropNameID forUtf8(Runtime& runtime, const std::string& utf8) {
return runtime.createPropNameIDFromUtf8(
reinterpret_cast<const uint8_t*>(utf8.data()), utf8.size());
}
/// Given a series of UTF-16 encoded code units, create a PropNameId. The
/// input may contain unpaired surrogates, which will be interpreted as a code
/// point of the same value.
static PropNameID
forUtf16(Runtime& runtime, const char16_t* utf16, size_t length) {
return runtime.createPropNameIDFromUtf16(utf16, length);
}
/// Given a series of UTF-16 encoded code units stored inside std::u16string,
/// create a PropNameId. The input may contain unpaired surrogates, which
/// will be interpreted as a code point of the same value.
static PropNameID forUtf16(Runtime& runtime, const std::u16string& str) {
return runtime.createPropNameIDFromUtf16(str.data(), str.size());
}
/// Create a PropNameID from a JS string.
static PropNameID forString(Runtime& runtime, const jsi::String& str) {
return runtime.createPropNameIDFromString(str);
}
/// Create a PropNameID from a JS symbol.
static PropNameID forSymbol(Runtime& runtime, const jsi::Symbol& sym) {
return runtime.createPropNameIDFromSymbol(sym);
}
// Creates a vector of PropNameIDs constructed from given arguments.
template <typename... Args>
static std::vector<PropNameID> names(Runtime& runtime, Args&&... args);
// Creates a vector of given PropNameIDs.
template <size_t N>
static std::vector<PropNameID> names(PropNameID (&&propertyNames)[N]);
/// Copies the data in a PropNameID as utf8 into a C++ string.
std::string utf8(Runtime& runtime) const {
return runtime.utf8(*this);
}
/// Copies the data in a PropNameID as utf16 into a C++ string.
std::u16string utf16(Runtime& runtime) const {
return runtime.utf16(*this);
}
/// Invokes the user provided callback to process the content in PropNameId.
/// The callback must take in three arguments: bool ascii, const void* data,
/// and size_t num, respectively. \p ascii indicates whether the \p data
/// passed to the callback should be interpreted as a pointer to a sequence of
/// \p num ASCII characters or UTF16 characters. The function may invoke the
/// callback multiple times, with a different format on each invocation. The
/// callback must not access runtime functionality, as any operation on the
/// runtime may invalidate the data pointers.
template <typename CB>
void getPropNameIdData(Runtime& runtime, CB& cb) const {
runtime.getPropNameIdData(
*this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) {
(*((CB*)ctx))(ascii, data, num);
});
}
static bool compare(
Runtime& runtime,
const jsi::PropNameID& a,
const jsi::PropNameID& b) {
return runtime.compare(a, b);
}
friend class Runtime;
friend class Value;
};
/// Represents a JS Symbol (es6). Movable, not copyable.
/// TODO T40778724: this is a limited implementation sufficient for
/// the debugger not to crash when a Symbol is a property in an Object
/// or element in an array. Complete support for creating will come
/// later.
class JSI_EXPORT Symbol : public Pointer {
public:
using Pointer::Pointer;
Symbol(Symbol&& other) = default;
Symbol& operator=(Symbol&& other) = default;
/// \return whether a and b refer to the same symbol.
static bool strictEquals(Runtime& runtime, const Symbol& a, const Symbol& b) {
return runtime.strictEquals(a, b);
}
/// Converts a Symbol into a C++ string as JS .toString would. The output
/// will look like \c Symbol(description) .
std::string toString(Runtime& runtime) const {
return runtime.symbolToString(*this);
}
friend class Runtime;
friend class Value;
};
/// Represents a JS BigInt. Movable, not copyable.
class JSI_EXPORT BigInt : public Pointer {
public:
using Pointer::Pointer;
BigInt(BigInt&& other) = default;
BigInt& operator=(BigInt&& other) = default;
/// Create a BigInt representing the signed 64-bit \p value.
static BigInt fromInt64(Runtime& runtime, int64_t value) {
return runtime.createBigIntFromInt64(value);
}
/// Create a BigInt representing the unsigned 64-bit \p value.
static BigInt fromUint64(Runtime& runtime, uint64_t value) {
return runtime.createBigIntFromUint64(value);
}
/// \return whether a === b.
static bool strictEquals(Runtime& runtime, const BigInt& a, const BigInt& b) {
return runtime.strictEquals(a, b);
}
/// \returns This bigint truncated to a signed 64-bit integer.
int64_t getInt64(Runtime& runtime) const {
return runtime.truncate(*this);
}
/// \returns Whether this bigint can be losslessly converted to int64_t.
bool isInt64(Runtime& runtime) const {
return runtime.bigintIsInt64(*this);
}
/// \returns This bigint truncated to a signed 64-bit integer. Throws a
/// JSIException if the truncation is lossy.
int64_t asInt64(Runtime& runtime) const;
/// \returns This bigint truncated to an unsigned 64-bit integer.
uint64_t getUint64(Runtime& runtime) const {
return runtime.truncate(*this);
}
/// \returns Whether this bigint can be losslessly converted to uint64_t.
bool isUint64(Runtime& runtime) const {
return runtime.bigintIsUint64(*this);
}
/// \returns This bigint truncated to an unsigned 64-bit integer. Throws a
/// JSIException if the truncation is lossy.
uint64_t asUint64(Runtime& runtime) const;
/// \returns this BigInt converted to a String in base \p radix. Throws a
/// JSIException if radix is not in the [2, 36] range.
inline String toString(Runtime& runtime, int radix = 10) const;
friend class Runtime;
friend class Value;
};
/// Represents a JS String. Movable, not copyable.
class JSI_EXPORT String : public Pointer {
public:
using Pointer::Pointer;
String(String&& other) = default;
String& operator=(String&& other) = default;
/// Create a JS string from ascii values. The string data is
/// copied.
static String
createFromAscii(Runtime& runtime, const char* str, size_t length) {
return runtime.createStringFromAscii(str, length);
}
/// Create a JS string from a nul-terminated C ascii string. The
/// string data is copied.
static String createFromAscii(Runtime& runtime, const char* str) {
return createFromAscii(runtime, str, strlen(str));
}
/// Create a JS string from a C++ string. The string data is
/// copied.
static String createFromAscii(Runtime& runtime, const std::string& str) {
return createFromAscii(runtime, str.c_str(), str.size());
}
/// Create a JS string from utf8-encoded octets. The string data is
/// transformed and copied. Results are undefined if \p utf8 contains invalid
/// code points.
static String
createFromUtf8(Runtime& runtime, const uint8_t* utf8, size_t length) {
return runtime.createStringFromUtf8(utf8, length);
}
/// Create a JS string from utf8-encoded octets stored in a
/// std::string. The string data is transformed and copied. Results are
/// undefined if \p utf8 contains invalid code points.
static String createFromUtf8(Runtime& runtime, const std::string& utf8) {
return runtime.createStringFromUtf8(
reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
}
/// Given a series of UTF-16 encoded code units, create a JS String. The input
/// may contain unpaired surrogates, which will be interpreted as a code point
/// of the same value.
static String
createFromUtf16(Runtime& runtime, const char16_t* utf16, size_t length) {
return runtime.createStringFromUtf16(utf16, length);
}
/// Given a series of UTF-16 encoded code units stored inside std::u16string,
/// create a JS String. The input may contain unpaired surrogates, which will
/// be interpreted as a code point of the same value.
static String createFromUtf16(Runtime& runtime, const std::u16string& utf16) {
return runtime.createStringFromUtf16(utf16.data(), utf16.length());
}
/// \return whether a and b contain the same characters.
static bool strictEquals(Runtime& runtime, const String& a, const String& b) {
return runtime.strictEquals(a, b);
}
/// Copies the data in a JS string as utf8 into a C++ string.
std::string utf8(Runtime& runtime) const {
return runtime.utf8(*this);
}
/// Copies the data in a JS string as utf16 into a C++ string.
std::u16string utf16(Runtime& runtime) const {
return runtime.utf16(*this);
}
/// Invokes the user provided callback to process content in String. The
/// callback must take in three arguments: bool ascii, const void* data, and
/// size_t num, respectively. \p ascii indicates whether the \p data passed to
/// the callback should be interpreted as a pointer to a sequence of \p num
/// ASCII characters or UTF16 characters. The function may invoke the callback
/// multiple times, with a different format on each invocation. The callback
/// must not access runtime functionality, as any operation on the runtime may
/// invalidate the data pointers.
template <typename CB>
void getStringData(Runtime& runtime, CB& cb) const {
runtime.getStringData(
*this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) {
(*((CB*)ctx))(ascii, data, num);
});
}
friend class Runtime;
friend class Value;
};
class Array;
class Function;
/// Represents a JS Object. Movable, not copyable.
class JSI_EXPORT Object : public Pointer {
public:
using Pointer::Pointer;
Object(Object&& other) = default;
Object& operator=(Object&& other) = default;
/// Creates a new Object instance, like '{}' in JS.
explicit Object(Runtime& runtime) : Object(runtime.createObject()) {}
static Object createFromHostObject(
Runtime& runtime,
std::shared_ptr<HostObject> ho) {
return runtime.createObject(ho);
}
/// Creates a new Object with the custom prototype
static Object create(Runtime& runtime, const Value& prototype) {
return runtime.createObjectWithPrototype(prototype);
}
/// \return whether this and \c obj are the same JSObject or not.
static bool strictEquals(Runtime& runtime, const Object& a, const Object& b) {
return runtime.strictEquals(a, b);
}
/// \return the result of `this instanceOf ctor` in JS.
bool instanceOf(Runtime& rt, const Function& ctor) const {
return rt.instanceOf(*this, ctor);
}
/// Sets \p prototype as the prototype of the object. The prototype must be
/// either an Object or null. If the prototype was not set successfully, this
/// method will throw.
void setPrototype(Runtime& runtime, const Value& prototype) const {
return runtime.setPrototypeOf(*this, prototype);
}
/// \return the prototype of the object
inline Value getPrototype(Runtime& runtime) const;
/// \return the property of the object with the given ascii name.
/// If the name isn't a property on the object, returns the
/// undefined value.
Value getProperty(Runtime& runtime, const char* name) const;
/// \return the property of the object with the String name.
/// If the name isn't a property on the object, returns the
/// undefined value.
Value getProperty(Runtime& runtime, const String& name) const;
/// \return the property of the object with the given JS PropNameID
/// name. If the name isn't a property on the object, returns the
/// undefined value.
Value getProperty(Runtime& runtime, const PropNameID& name) const;
/// \return the Property of the object with the given JS Value name. If the
/// name isn't a property on the object, returns the undefined value.This
/// attempts to convert the JS Value to convert to a property key. If the
/// conversion fails, this method may throw.
Value getProperty(Runtime& runtime, const Value& name) const;
/// \return true if and only if the object has a property with the
/// given ascii name.
bool hasProperty(Runtime& runtime, const char* name) const;
/// \return true if and only if the object has a property with the
/// given String name.
bool hasProperty(Runtime& runtime, const String& name) const;
/// \return true if and only if the object has a property with the
/// given PropNameID name.
bool hasProperty(Runtime& runtime, const PropNameID& name) const;
/// \return true if and only if the object has a property with the given
/// JS Value name. This attempts to convert the JS Value to convert to a
/// property key. If the conversion fails, this method may throw.
bool hasProperty(Runtime& runtime, const Value& name) const;
/// Sets the property value from a Value or anything which can be
/// used to make one: nullptr_t, bool, double, int, const char*,
/// String, or Object.
template <typename T>
void setProperty(Runtime& runtime, const char* name, T&& value) const;
/// Sets the property value from a Value or anything which can be
/// used to make one: nullptr_t, bool, double, int, const char*,
/// String, or Object.
template <typename T>
void setProperty(Runtime& runtime, const String& name, T&& value) const;
/// Sets the property value from a Value or anything which can be
/// used to make one: nullptr_t, bool, double, int, const char*,
/// String, or Object.
template <typename T>
void setProperty(Runtime& runtime, const PropNameID& name, T&& value) const;
/// Sets the property value from a Value or anything which can be
/// used to make one: nullptr_t, bool, double, int, const char*,
/// String, or Object. This takes a JS Value as the property name, and
/// attempts to convert to a property key. If the conversion fails, this
/// method may throw.
template <typename T>
void setProperty(Runtime& runtime, const Value& name, T&& value) const;
/// Delete the property with the given ascii name. Throws if the deletion
/// failed.
void deleteProperty(Runtime& runtime, const char* name) const;
/// Delete the property with the given String name. Throws if the deletion
/// failed.
void deleteProperty(Runtime& runtime, const String& name) const;
/// Delete the property with the given PropNameID name. Throws if the deletion
/// failed.
void deleteProperty(Runtime& runtime, const PropNameID& name) const;
/// Delete the property with the given Value name. Throws if the deletion
/// failed.
void deleteProperty(Runtime& runtime, const Value& name) const;
/// \return true iff JS \c Array.isArray() would return \c true. If
/// so, then \c getArray() will succeed.
bool isArray(Runtime& runtime) const {
return runtime.isArray(*this);
}
/// \return true iff the Object is an ArrayBuffer. If so, then \c
/// getArrayBuffer() will succeed.
bool isArrayBuffer(Runtime& runtime) const {
return runtime.isArrayBuffer(*this);
}
/// \return true iff the Object is callable. If so, then \c
/// getFunction will succeed.
bool isFunction(Runtime& runtime) const {
return runtime.isFunction(*this);
}
/// \return true iff the Object was initialized with \c createFromHostObject
/// and the HostObject passed is of type \c T. If returns \c true then
/// \c getHostObject<T> will succeed.
template <typename T = HostObject>
bool isHostObject(Runtime& runtime) const;
/// \return an Array instance which refers to the same underlying
/// object. If \c isArray() would return false, this will assert.
Array getArray(Runtime& runtime) const&;
/// \return an Array instance which refers to the same underlying
/// object. If \c isArray() would return false, this will assert.
Array getArray(Runtime& runtime) &&;
/// \return an Array instance which refers to the same underlying
/// object. If \c isArray() would return false, this will throw
/// JSIException.
Array asArray(Runtime& runtime) const&;
/// \return an Array instance which refers to the same underlying
/// object. If \c isArray() would return false, this will throw
/// JSIException.
Array asArray(Runtime& runtime) &&;
/// \return an ArrayBuffer instance which refers to the same underlying
/// object. If \c isArrayBuffer() would return false, this will assert.
ArrayBuffer getArrayBuffer(Runtime& runtime) const&;
/// \return an ArrayBuffer instance which refers to the same underlying
/// object. If \c isArrayBuffer() would return false, this will assert.
ArrayBuffer getArrayBuffer(Runtime& runtime) &&;
/// \return a Function instance which refers to the same underlying
/// object. If \c isFunction() would return false, this will assert.
Function getFunction(Runtime& runtime) const&;
/// \return a Function instance which refers to the same underlying
/// object. If \c isFunction() would return false, this will assert.
Function getFunction(Runtime& runtime) &&;
/// \return a Function instance which refers to the same underlying
/// object. If \c isFunction() would return false, this will throw
/// JSIException.
Function asFunction(Runtime& runtime) const&;
/// \return a Function instance which refers to the same underlying
/// object. If \c isFunction() would return false, this will throw
/// JSIException.
Function asFunction(Runtime& runtime) &&;
/// \return a shared_ptr<T> which refers to the same underlying
/// \c HostObject that was used to create this object. If \c isHostObject<T>
/// is false, this will assert. Note that this does a type check and will
/// assert if the underlying HostObject isn't of type \c T
template <typename T = HostObject>
std::shared_ptr<T> getHostObject(Runtime& runtime) const;
/// \return a shared_ptr<T> which refers to the same underlying
/// \c HostObject that was used to create this object. If \c isHostObject<T>
/// is false, this will throw.
template <typename T = HostObject>
std::shared_ptr<T> asHostObject(Runtime& runtime) const;
/// \return whether this object has native state of type T previously set by
/// \c setNativeState.
template <typename T = NativeState>
bool hasNativeState(Runtime& runtime) const;
/// \return a shared_ptr to the state previously set by \c setNativeState.
/// If \c hasNativeState<T> is false, this will assert. Note that this does a
/// type check and will assert if the native state isn't of type \c T
template <typename T = NativeState>
std::shared_ptr<T> getNativeState(Runtime& runtime) const;
/// Set the internal native state property of this object, overwriting any old
/// value. Creates a new shared_ptr to the object managed by \p state, which
/// will live until the value at this property becomes unreachable.
///
/// Throws a type error if this object is a proxy or host object.
void setNativeState(Runtime& runtime, std::shared_ptr<NativeState> state)
const;
/// \return same as \c getProperty(name).asObject(), except with
/// a better exception message.
Object getPropertyAsObject(Runtime& runtime, const char* name) const;
/// \return similar to \c
/// getProperty(name).getObject().getFunction(), except it will
/// throw JSIException instead of asserting if the property is
/// not an object, or the object is not callable.
Function getPropertyAsFunction(Runtime& runtime, const char* name) const;
/// \return an Array consisting of all enumerable property names in
/// the object and its prototype chain. All values in the return
/// will be isString(). (This is probably not optimal, but it
/// works. I only need it in one place.)
Array getPropertyNames(Runtime& runtime) const;
/// Inform the runtime that there is additional memory associated with a given
/// JavaScript object that is not visible to the GC. This can be used if an
/// object is known to retain some native memory, and may be used to guide
/// decisions about when to run garbage collection.
/// This method may be invoked multiple times on an object, and subsequent
/// calls will overwrite any previously set value. Once the object is garbage
/// collected, the associated external memory will be considered freed and may
/// no longer factor into GC decisions.
void setExternalMemoryPressure(Runtime& runtime, size_t amt) const;
protected:
void setPropertyValue(
Runtime& runtime,
const String& name,
const Value& value) const {
return runtime.setPropertyValue(*this, name, value);
}
void setPropertyValue(
Runtime& runtime,
const PropNameID& name,
const Value& value) const {
return runtime.setPropertyValue(*this, name, value);
}
void setPropertyValue(Runtime& runtime, const Value& name, const Value& value)
const {
return runtime.setPropertyValue(*this, name, value);
}
friend class Runtime;
friend class Value;
};
/// Represents a weak reference to a JS Object. If the only reference
/// to an Object are these, the object is eligible for GC. Method
/// names are inspired by C++ weak_ptr. Movable, not copyable.
class JSI_EXPORT WeakObject : public Pointer {
public:
using Pointer::Pointer;
WeakObject(WeakObject&& other) = default;
WeakObject& operator=(WeakObject&& other) = default;
/// Create a WeakObject from an Object.
WeakObject(Runtime& runtime, const Object& o)
: WeakObject(runtime.createWeakObject(o)) {}
/// \return a Value representing the underlying Object if it is still valid;
/// otherwise returns \c undefined. Note that this method has nothing to do
/// with threads or concurrency. The name is based on std::weak_ptr::lock()
/// which serves a similar purpose.
Value lock(Runtime& runtime) const;
friend class Runtime;
};
/// Represents a JS Object which can be efficiently used as an array
/// with integral indices.
class JSI_EXPORT Array : public Object {
public:
Array(Array&&) = default;
/// Creates a new Array instance, with \c length undefined elements.
Array(Runtime& runtime, size_t length) : Array(runtime.createArray(length)) {}
Array& operator=(Array&&) = default;
/// \return the size of the Array, according to its length property.
/// (C++ naming convention)
size_t size(Runtime& runtime) const {
return runtime.size(*this);
}
/// \return the size of the Array, according to its length property.
/// (JS naming convention)
size_t length(Runtime& runtime) const {
return size(runtime);
}
/// \return the property of the array at index \c i. If there is no
/// such property, returns the undefined value. If \c i is out of
/// range [ 0..\c length ] throws a JSIException.
Value getValueAtIndex(Runtime& runtime, size_t i) const;
/// Sets the property of the array at index \c i. The argument
/// value behaves as with Object::setProperty(). If \c i is out of
/// range [ 0..\c length ] throws a JSIException.
template <typename T>
void setValueAtIndex(Runtime& runtime, size_t i, T&& value) const;
/// There is no current API for changing the size of an array once
/// created. We'll probably need that eventually.
/// Creates a new Array instance from provided values
template <typename... Args>
static Array createWithElements(Runtime&, Args&&... args);
/// Creates a new Array instance from initializer list.
static Array createWithElements(
Runtime& runtime,
std::initializer_list<Value> elements);
private:
friend class Object;
friend class Value;
friend class Runtime;
void setValueAtIndexImpl(Runtime& runtime, size_t i, const Value& value)
const {
return runtime.setValueAtIndexImpl(*this, i, value);
}
Array(Runtime::PointerValue* value) : Object(value) {}
};
/// Represents a JSArrayBuffer
class JSI_EXPORT ArrayBuffer : public Object {
public:
ArrayBuffer(ArrayBuffer&&) = default;
ArrayBuffer& operator=(ArrayBuffer&&) = default;
ArrayBuffer(Runtime& runtime, std::shared_ptr<MutableBuffer> buffer)
: ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {}
/// \return the size of the ArrayBuffer storage. This is not affected by
/// overriding the byteLength property.
/// (C++ naming convention)
size_t size(Runtime& runtime) const {
return runtime.size(*this);
}
size_t length(Runtime& runtime) const {
return runtime.size(*this);
}
uint8_t* data(Runtime& runtime) const {
return runtime.data(*this);
}
private:
friend class Object;
friend class Value;
friend class Runtime;
ArrayBuffer(Runtime::PointerValue* value) : Object(value) {}
};
/// Represents a JS Object which is guaranteed to be Callable.
class JSI_EXPORT Function : public Object {
public:
Function(Function&&) = default;
Function& operator=(Function&&) = default;
/// Create a function which, when invoked, calls C++ code. If the
/// function throws an exception, a JS Error will be created and
/// thrown.
/// \param name the name property for the function.
/// \param paramCount the length property for the function, which
/// may not be the number of arguments the function is passed.
/// \note The std::function's dtor will be called when the GC finalizes this
/// function. As with HostObject, this may be as late as when the Runtime is
/// shut down, and may occur on an arbitrary thread. If the function contains
/// any captured values, you are responsible for ensuring that their
/// destructors are safe to call on any thread.
static Function createFromHostFunction(
Runtime& runtime,
const jsi::PropNameID& name,
unsigned int paramCount,
jsi::HostFunctionType func);
/// Calls the function with \c count \c args. The \c this value of the JS
/// function will not be set by the C++ caller, similar to calling
/// Function.prototype.apply(undefined, args) in JS.
/// \b Note: as with Function.prototype.apply, \c this may not always be
/// \c undefined in the function itself. If the function is non-strict,
/// \c this will be set to the global object.
Value call(Runtime& runtime, const Value* args, size_t count) const;
/// Calls the function with a \c std::initializer_list of Value
/// arguments. The \c this value of the JS function will not be set by the
/// C++ caller, similar to calling Function.prototype.apply(undefined, args)
/// in JS.
/// \b Note: as with Function.prototype.apply, \c this may not always be
/// \c undefined in the function itself. If the function is non-strict,
/// \c this will be set to the global object.
Value call(Runtime& runtime, std::initializer_list<Value> args) const;
/// Calls the function with any number of arguments similarly to
/// Object::setProperty(). The \c this value of the JS function will not be
/// set by the C++ caller, similar to calling
/// Function.prototype.call(undefined, ...args) in JS.
/// \b Note: as with Function.prototype.call, \c this may not always be
/// \c undefined in the function itself. If the function is non-strict,
/// \c this will be set to the global object.
template <typename... Args>
Value call(Runtime& runtime, Args&&... args) const;
/// Calls the function with \c count \c args and \c jsThis value passed
/// as the \c this value.
Value callWithThis(
Runtime& Runtime,
const Object& jsThis,
const Value* args,
size_t count) const;
/// Calls the function with a \c std::initializer_list of Value
/// arguments and \c jsThis passed as the \c this value.
Value callWithThis(
Runtime& runtime,
const Object& jsThis,
std::initializer_list<Value> args) const;
/// Calls the function with any number of arguments similarly to
/// Object::setProperty(), and with \c jsThis passed as the \c this value.
template <typename... Args>
Value callWithThis(Runtime& runtime, const Object& jsThis, Args&&... args)
const;
/// Calls the function as a constructor with \c count \c args. Equivalent
/// to calling `new Func` where `Func` is the js function reqresented by
/// this.
Value callAsConstructor(Runtime& runtime, const Value* args, size_t count)
const;
/// Same as above `callAsConstructor`, except use an initializer_list to
/// supply the arguments.
Value callAsConstructor(Runtime& runtime, std::initializer_list<Value> args)
const;
/// Same as above `callAsConstructor`, but automatically converts/wraps
/// any argument with a jsi Value.
template <typename... Args>
Value callAsConstructor(Runtime& runtime, Args&&... args) const;
/// Returns whether this was created with Function::createFromHostFunction.
/// If true then you can use getHostFunction to get the underlying
/// HostFunctionType.
bool isHostFunction(Runtime& runtime) const {
return runtime.isHostFunction(*this);
}
/// Returns the underlying HostFunctionType iff isHostFunction returns true
/// and asserts otherwise. You can use this to use std::function<>::target
/// to get the object that was passed to create the HostFunctionType.
///
/// Note: The reference returned is borrowed from the JS object underlying
/// \c this, and thus only lasts as long as the object underlying
/// \c this does.
HostFunctionType& getHostFunction(Runtime& runtime) const {
assert(isHostFunction(runtime));
return runtime.getHostFunction(*this);
}
private:
friend class Object;
friend class Value;
friend class Runtime;
Function(Runtime::PointerValue* value) : Object(value) {}
};
/// Represents any JS Value (undefined, null, boolean, number, symbol,
/// string, or object). Movable, or explicitly copyable (has no copy
/// ctor).
class JSI_EXPORT Value {
public:
/// Default ctor creates an \c undefined JS value.
Value() noexcept : Value(UndefinedKind) {}
/// Creates a \c null JS value.
/* implicit */ Value(std::nullptr_t) : kind_(NullKind) {}
/// Creates a boolean JS value.
/* implicit */ Value(bool b) : Value(BooleanKind) {
data_.boolean = b;
}
/// Creates a number JS value.
/* implicit */ Value(double d) : Value(NumberKind) {
data_.number = d;
}
/// Creates a number JS value.
/* implicit */ Value(int i) : Value(NumberKind) {
data_.number = i;
}
/// Moves a Symbol, String, or Object rvalue into a new JS value.
template <
typename T,
typename = std::enable_if_t<
std::is_base_of<Symbol, T>::value ||
std::is_base_of<BigInt, T>::value ||
std::is_base_of<String, T>::value ||
std::is_base_of<Object, T>::value>>
/* implicit */ Value(T&& other) : Value(kindOf(other)) {
new (&data_.pointer) T(std::move(other));
}
/// Value("foo") will treat foo as a bool. This makes doing that a
/// compile error.
template <typename T = void>
Value(const char*) {
static_assert(
!std::is_same<void, T>::value,
"Value cannot be constructed directly from const char*");
}
Value(Value&& other) noexcept;
/// Copies a Symbol lvalue into a new JS value.
Value(Runtime& runtime, const Symbol& sym) : Value(SymbolKind) {
new (&data_.pointer) Symbol(runtime.cloneSymbol(sym.ptr_));
}
/// Copies a BigInt lvalue into a new JS value.
Value(Runtime& runtime, const BigInt& bigint) : Value(BigIntKind) {
new (&data_.pointer) BigInt(runtime.cloneBigInt(bigint.ptr_));
}
/// Copies a String lvalue into a new JS value.
Value(Runtime& runtime, const String& str) : Value(StringKind) {
new (&data_.pointer) String(runtime.cloneString(str.ptr_));
}
/// Copies a Object lvalue into a new JS value.
Value(Runtime& runtime, const Object& obj) : Value(ObjectKind) {
new (&data_.pointer) Object(runtime.cloneObject(obj.ptr_));
}
/// Creates a JS value from another Value lvalue.
Value(Runtime& runtime, const Value& value);
/// Value(rt, "foo") will treat foo as a bool. This makes doing
/// that a compile error.
template <typename T = void>
Value(Runtime&, const char*) {
static_assert(
!std::is_same<T, void>::value,
"Value cannot be constructed directly from const char*");
}
~Value();
// \return the undefined \c Value.
static Value undefined() {
return Value();
}
// \return the null \c Value.
static Value null() {
return Value(nullptr);
}
// \return a \c Value created from a utf8-encoded JSON string.
static Value
createFromJsonUtf8(Runtime& runtime, const uint8_t* json, size_t length) {
return runtime.createValueFromJsonUtf8(json, length);
}
/// \return according to the Strict Equality Comparison algorithm, see:
/// https://262.ecma-international.org/11.0/#sec-strict-equality-comparison
static bool strictEquals(Runtime& runtime, const Value& a, const Value& b);
Value& operator=(Value&& other) noexcept {
this->~Value();
new (this) Value(std::move(other));
return *this;
}
bool isUndefined() const {
return kind_ == UndefinedKind;
}
bool isNull() const {
return kind_ == NullKind;
}
bool isBool() const {
return kind_ == BooleanKind;
}
bool isNumber() const {
return kind_ == NumberKind;
}
bool isString() const {
return kind_ == StringKind;
}
bool isBigInt() const {
return kind_ == BigIntKind;
}
bool isSymbol() const {
return kind_ == SymbolKind;
}
bool isObject() const {
return kind_ == ObjectKind;
}
/// \return the boolean value, or asserts if not a boolean.
bool getBool() const {
assert(isBool());
return data_.boolean;
}
/// \return the boolean value, or throws JSIException if not a
/// boolean.
bool asBool() const;
/// \return the number value, or asserts if not a number.
double getNumber() const {
assert(isNumber());
return data_.number;
}
/// \return the number value, or throws JSIException if not a
/// number.
double asNumber() const;
/// \return the Symbol value, or asserts if not a symbol.
Symbol getSymbol(Runtime& runtime) const& {
assert(isSymbol());
return Symbol(runtime.cloneSymbol(data_.pointer.ptr_));
}
/// \return the Symbol value, or asserts if not a symbol.
/// Can be used on rvalue references to avoid cloning more symbols.
Symbol getSymbol(Runtime&) && {
assert(isSymbol());
auto ptr = data_.pointer.ptr_;
data_.pointer.ptr_ = nullptr;
return static_cast<Symbol>(ptr);
}
/// \return the Symbol value, or throws JSIException if not a
/// symbol
Symbol asSymbol(Runtime& runtime) const&;
Symbol asSymbol(Runtime& runtime) &&;
/// \return the BigInt value, or asserts if not a bigint.
BigInt getBigInt(Runtime& runtime) const& {
assert(isBigInt());
return BigInt(runtime.cloneBigInt(data_.pointer.ptr_));
}
/// \return the BigInt value, or asserts if not a bigint.
/// Can be used on rvalue references to avoid cloning more bigints.
BigInt getBigInt(Runtime&) && {
assert(isBigInt());
auto ptr = data_.pointer.ptr_;
data_.pointer.ptr_ = nullptr;
return static_cast<BigInt>(ptr);
}
/// \return the BigInt value, or throws JSIException if not a
/// bigint
BigInt asBigInt(Runtime& runtime) const&;
BigInt asBigInt(Runtime& runtime) &&;
/// \return the String value, or asserts if not a string.
String getString(Runtime& runtime) const& {
assert(isString());
return String(runtime.cloneString(data_.pointer.ptr_));
}
/// \return the String value, or asserts if not a string.
/// Can be used on rvalue references to avoid cloning more strings.
String getString(Runtime&) && {
assert(isString());
auto ptr = data_.pointer.ptr_;
data_.pointer.ptr_ = nullptr;
return static_cast<String>(ptr);
}
/// \return the String value, or throws JSIException if not a
/// string.
String asString(Runtime& runtime) const&;
String asString(Runtime& runtime) &&;
/// \return the Object value, or asserts if not an object.
Object getObject(Runtime& runtime) const& {
assert(isObject());
return Object(runtime.cloneObject(data_.pointer.ptr_));
}
/// \return the Object value, or asserts if not an object.
/// Can be used on rvalue references to avoid cloning more objects.
Object getObject(Runtime&) && {
assert(isObject());
auto ptr = data_.pointer.ptr_;
data_.pointer.ptr_ = nullptr;
return static_cast<Object>(ptr);
}
/// \return the Object value, or throws JSIException if not an
/// object.
Object asObject(Runtime& runtime) const&;
Object asObject(Runtime& runtime) &&;
// \return a String like JS .toString() would do.
String toString(Runtime& runtime) const;
private:
friend class Runtime;
enum ValueKind {
UndefinedKind,
NullKind,
BooleanKind,
NumberKind,
SymbolKind,
BigIntKind,
StringKind,
ObjectKind,
PointerKind = SymbolKind,
};
union Data {
// Value's ctor and dtor will manage the lifecycle of the contained Data.
Data() {
static_assert(
sizeof(Data) == sizeof(uint64_t),
"Value data should fit in a 64-bit register");
}
~Data() {}
// scalars
bool boolean;
double number;
// pointers
Pointer pointer; // Symbol, String, Object, Array, Function
};
Value(ValueKind kind) : kind_(kind) {}
constexpr static ValueKind kindOf(const Symbol&) {
return SymbolKind;
}
constexpr static ValueKind kindOf(const BigInt&) {
return BigIntKind;
}
constexpr static ValueKind kindOf(const String&) {
return StringKind;
}
constexpr static ValueKind kindOf(const Object&) {
return ObjectKind;
}
ValueKind kind_;
Data data_;
// In the future: Value becomes NaN-boxed. See T40538354.
};
/// Not movable and not copyable RAII marker advising the underlying
/// JavaScript VM to track resources allocated since creation until
/// destruction so that they can be recycled eagerly when the Scope
/// goes out of scope instead of floating in the air until the next
/// garbage collection or any other delayed release occurs.
///
/// This API should be treated only as advice, implementations can
/// choose to ignore the fact that Scopes are created or destroyed.
///
/// This class is an exception to the rule allowing destructors to be
/// called without proper synchronization (see Runtime documentation).
/// The whole point of this class is to enable all sorts of clean ups
/// when the destructor is called and this proper synchronization is
/// required at that time.
///
/// Instances of this class are intended to be created as automatic stack
/// variables in which case destructor calls don't require any additional
/// locking, provided that the lock (if any) is managed with RAII helpers.
class JSI_EXPORT Scope {
public:
explicit Scope(Runtime& rt) : rt_(rt), prv_(rt.pushScope()) {}
~Scope() {
rt_.popScope(prv_);
}
Scope(const Scope&) = delete;
Scope(Scope&&) = delete;
Scope& operator=(const Scope&) = delete;
Scope& operator=(Scope&&) = delete;
template <typename F>
static auto callInNewScope(Runtime& rt, F f) -> decltype(f()) {
Scope s(rt);
return f();
}
private:
Runtime& rt_;
Runtime::ScopeState* prv_;
};
/// Base class for jsi exceptions
class JSI_EXPORT JSIException : public std::exception {
protected:
JSIException() {}
JSIException(std::string what) : what_(std::move(what)) {}
public:
JSIException(const JSIException&) = default;
virtual const char* what() const noexcept override {
return what_.c_str();
}
virtual ~JSIException() override;
protected:
std::string what_;
};
/// This exception will be thrown by API functions on errors not related to
/// JavaScript execution.
class JSI_EXPORT JSINativeException : public JSIException {
public:
JSINativeException(std::string what) : JSIException(std::move(what)) {}
JSINativeException(const JSINativeException&) = default;
virtual ~JSINativeException();
};
/// This exception will be thrown by API functions whenever a JS
/// operation causes an exception as described by the spec, or as
/// otherwise described.
class JSI_EXPORT JSError : public JSIException {
public:
/// Creates a JSError referring to provided \c value
JSError(Runtime& r, Value&& value);
/// Creates a JSError referring to new \c Error instance capturing current
/// JavaScript stack. The error message property is set to given \c message.
JSError(Runtime& rt, std::string message);
/// Creates a JSError referring to new \c Error instance capturing current
/// JavaScript stack. The error message property is set to given \c message.
JSError(Runtime& rt, const char* message)
: JSError(rt, std::string(message)) {}
/// Creates a JSError referring to a JavaScript Object having message and
/// stack properties set to provided values.
JSError(Runtime& rt, std::string message, std::string stack);
/// Creates a JSError referring to provided value and what string
/// set to provided message. This argument order is a bit weird,
/// but necessary to avoid ambiguity with the above.
JSError(std::string what, Runtime& rt, Value&& value);
/// Creates a JSError referring to the provided value, message and stack. This
/// constructor does not take a Runtime parameter, and therefore cannot result
/// in recursively invoking the JSError constructor.
JSError(Value&& value, std::string message, std::string stack);
JSError(const JSError&) = default;
virtual ~JSError();
const std::string& getStack() const {
return stack_;
}
const std::string& getMessage() const {
return message_;
}
const jsi::Value& value() const {
assert(value_);
return *value_;
}
private:
// This initializes the value_ member and does some other
// validation, so it must be called by every branch through the
// constructors.
void setValue(Runtime& rt, Value&& value);
// This needs to be on the heap, because throw requires the object
// be copyable, and Value is not.
std::shared_ptr<jsi::Value> value_;
std::string message_;
std::string stack_;
};
/// Helper function to cast the object pointed to by \p ptr into an interface
/// specified by \c U. If cast is successful, return a pointer to the object
/// as a raw pointer of \c U. Otherwise, return nullptr.
/// The returned interface same lifetime as the object referenced by \p ptr.
template <typename U, typename T>
U* castInterface(T* ptr) {
if (ptr) {
return static_cast<U*>(ptr->castInterface(U::uuid));
}
return nullptr;
}
/// Helper function to cast the object managed by the shared_ptr \p ptr into an
/// interface specified by \c U. If the cast is successful, return a shared_ptr
/// of type \c U to the object. Otherwise, return an empty pointer.
/// The returned shared_ptr shares ownership of the object with \p ptr.
template <typename U, typename T>
std::shared_ptr<U> dynamicInterfaceCast(T&& ptr) {
auto* p = ptr->castInterface(U::uuid);
U* res = static_cast<U*>(p);
if (res) {
return std::shared_ptr<U>(std::forward<T>(ptr), res);
}
return nullptr;
}
} // namespace jsi
} // namespace facebook
#include <jsi/jsi-inl.h>
这份代码是 Facebook JSI (JavaScript Interface) 的核心头文件实现,JSI 是一套跨 JavaScript 运行时(Hermes、V8、JSC 等)的通用 C++ 抽象层,核心目标是实现 C++ 与 JavaScript 的高效、低侵入性双向交互,替代传统的桥接模式(如 React Native 旧版桥接),大幅降低交互开销。以下从代码架构、核心模块、设计思想三个维度进行详细拆解。
一、 代码前置基础:预处理与宏定义
1. 头文件保护与依赖引入
cpp
#pragma once
#include <cassert>
#include <cstdint>
#include <cstring>
#include <exception>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#pragma once:现代编译器支持的头文件防重复包含机制,相比传统的#ifndef/#define/#endif更简洁,无宏命名冲突风险。- 标准库依赖:均为 JSI 必需的基础依赖,无第三方依赖,保证跨平台兼容性:
- 基础类型:
cstdint(固定宽度整数类型,如uint64_t) - 内存操作:
cstring(字符串 / 内存操作函数,如strlen) - 异常处理:
exception(继承std::exception实现自定义异常) - 函数包装:
functional(std::function封装 C++ 宿主函数) - 内存管理:
memory(智能指针std::shared_ptr/std::unique_ptr管理资源) - 数据载体:
string/vector(存储字符串和集合数据)
- 基础类型:
2. 跨平台符号导出宏 JSI_EXPORT
cpp
#ifndef JSI_EXPORT
#ifdef _MSC_VER
#ifdef CREATE_SHARED_LIBRARY
#define JSI_EXPORT __declspec(dllexport)
#else
#define JSI_EXPORT
#endif // CREATE_SHARED_LIBRARY
#else // _MSC_VER
#define JSI_EXPORT __attribute__((visibility("default")))
#endif // _MSC_VER
#endif // !defined(JSI_EXPORT)
- 核心作用:实现跨编译器(MSVC / GCC/Clang)的动态库符号导出,确保 JSI 公共接口能被外部程序调用(用于生成 DLL/so 动态库)。
- 分支逻辑详解:
_MSC_VER:MSVC 编译器标识(Windows 平台)- 当
CREATE_SHARED_LIBRARY定义时,使用__declspec(dllexport)导出符号,供外部程序链接。 - 未定义
CREATE_SHARED_LIBRARY时,不添加导出标识(作为静态库使用或导入外部符号)。
- 当
- 非 MSVC 编译器(GCC/Clang,Linux/macOS 平台):
- 使用
__attribute__((visibility("default")))显式暴露符号,默认情况下 GCC/Clang 会隐藏所有符号,仅显式声明的接口可见,此举可减小动态库体积、提升安全性并加快链接速度。
- 使用
二、 核心基础模块:支撑 JSI 架构的底层能力
1. UUID 类:接口唯一标识
cpp
class JSI_EXPORT UUID {
public:
// 编译期构造函数:支持 UUID v1 原始字段初始化
constexpr UUID(uint32_t timeLow, uint16_t timeMid, uint16_t timeHighAndVersion, uint16_t variantAndClockSeq, uint64_t node);
// 默认构造函数:零值 UUID
constexpr UUID() : high(0), low(0) {}
// 比较运算符:支持相等判断、排序(用于 std::map)
constexpr bool operator==(const UUID& other) const;
constexpr bool operator!=(const UUID& other) const;
constexpr bool operator<(const UUID& other) const;
// 哈希支持:用于 std::unordered_map
struct Hash {
std::size_t operator()(const UUID& uuid) const noexcept;
};
// 格式化为 8-4-4-4-12 标准字符串(如 550e8400-e29b-41d4-a716-446655440000)
std::string toString() const;
// 获取 UUID 各字段
constexpr uint32_t getTimeLow() const;
// ... 其他字段的 getter 方法
private:
uint64_t high; // 存储前 64 位 UUID 数据
uint64_t low; // 存储后 64 位 UUID 数据
};
- 核心用途:为 JSI 中的抽象接口(如
ISerialization)提供全局唯一标识,支撑接口的动态安全转换(替代 C++ RTTI)。 - 关键设计:
- 紧凑存储:用两个
uint64_t存储 128 位 UUID 数据,仅占用 16 字节,比字符串存储更高效。 - 编译期能力:
constexpr构造函数和成员方法,支持编译期初始化 UUID 常量(如接口默认 UUID),无运行时开销。 - 容器兼容:实现
operator<(支持std::map)和Hash结构体(支持std::unordered_map),可作为关联容器的键。 - 标准兼容:
toString()方法输出符合 RFC 4122 标准的 UUID 字符串,便于调试和日志输出。
- 紧凑存储:用两个
2. ICast 接口:动态类型转换基石
cpp
struct JSI_EXPORT ICast {
// 纯虚方法:接口动态转换核心
virtual ICast* castInterface(const UUID& interfaceUUID) = 0;
protected:
// 保护析构:防止直接删除 ICast 指针,避免内存泄漏
// 非虚析构:减少虚表复杂度,降低继承开销
~ICast() = default;
};
- 核心用途:JSI 所有抽象接口的基类,提供不依赖 RTTI 的跨类型安全转换能力 ,类似 C++ 的
dynamic_cast,但更高效、更灵活。 - 核心方法解析:
castInterface- 入参:目标接口的
UUID(唯一标识)。 - 返回值:若当前对象支持目标接口,返回非空
ICast*指针(可强制转换为目标接口类型);否则返回nullptr。 - 生命周期:返回的接口指针与底层对象生命周期一致,无需手动释放,由底层对象管理内存。
- 入参:目标接口的
- 设计考量:
- 保护析构:避免用户直接
delete ICast*,强制通过具体子类管理生命周期,提升安全性。 - 非虚析构:由于
ICast仅作为接口基类,不参与直接销毁,非虚析构可减少虚表大小,提升运行时性能。
- 保护析构:避免用户直接
3. 缓冲区家族:C++ 与 JS 运行时的数据载体
JSI 设计了 3 种缓冲区类型,核心区别在于不可变性 和数据可修改性,用于在 C++ 与 JS 运行时之间高效传递二进制数据 / 字符串。
(1)Buffer:不可变只读缓冲区
cpp
class JSI_EXPORT Buffer {
public:
virtual ~Buffer(); // 虚析构:确保子类析构函数正常调用
virtual size_t size() const = 0; // 纯虚:返回缓冲区字节长度
virtual const uint8_t* data() const = 0; // 纯虚:返回只读数据指针
};
- 核心特性:完全不可变 ,构造后
size()返回值、data()返回指针、指针指向的内存内容均不可修改。 - 适用场景:传递只读数据,如 JS 源码(UTF-8 字符串)、JS 字节码(Hermes/V8 预编译代码)、静态配置数据等。
- 设计:纯虚类,由子类实现具体的存储逻辑(如
StringBuffer),具备良好的扩展性。
(2)StringBuffer:基于 std::string 的不可变缓冲区
cpp
class JSI_EXPORT StringBuffer : public Buffer {
public:
// 移动构造:避免 std::string 拷贝,提升性能
StringBuffer(std::string s) : s_(std::move(s)) {}
// 重写 Buffer 纯虚方法
size_t size() const override { return s_.size(); }
const uint8_t* data() const override {
// 类型转换:将 std::string 的 char* 转换为 const uint8_t*,兼容 Buffer 接口
return reinterpret_cast<const uint8_t*>(s_.data());
}
private:
std::string s_; // 存储字符串数据
};
- 核心用途:封装
std::string为Buffer子类,快速实现字符串类型数据的传递(如 JS 源码字符串),无需手动管理字符数组内存。 - 性能优化:使用
std::move构造函数,避免std::string的深拷贝,减少内存开销。
(3)MutableBuffer:可变可写缓冲区
cpp
class JSI_EXPORT MutableBuffer {
public:
virtual ~MutableBuffer();
virtual size_t size() const = 0; // 缓冲区长度(不可变)
virtual uint8_t* data() = 0; // 可写数据指针(内容可修改)
};
- 核心特性:半不可变 ,构造后
size()返回值不可修改,但data()返回可写指针,支持 C++ 或 JS 运行时修改缓冲区内容。 - 适用场景:传递需要双向修改的数据,如 JS 运行时的输入输出缓冲区、动态生成的二进制数据等。
- 注意事项:用户需自行保证多线程访问的同步性,JSI 不提供线程安全保障,避免数据竞争。
4. PreparedJavaScript:预编译 JS 代码载体
cpp
class JSI_EXPORT PreparedJavaScript {
protected:
PreparedJavaScript() = default;
public:
virtual ~PreparedJavaScript() = 0; // 纯虚析构:抽象类
};
- 核心用途:表示运行时特定的预编译 JS 代码 ,由
Runtime::prepareJavaScript()创建,用于提升 JS 执行效率(避免重复解析 / 编译 JS 源码)。 - 特性说明:
- 抽象类:纯虚析构函数,无具体实现,由各 JS 运行时(Hermes/V8)提供子类实现(如 Hermes 的字节码载体)。
- 实验性 API:注释明确标注为 "experimental API",可能随 JSI 版本迭代发生变更。
- 跨运行时兼容:不同运行时的
PreparedJavaScript实例不可交叉使用(如 Hermes 预编译代码无法在 V8 中执行)。
三、 核心业务模块:C++ 与 JS 的交互能力
1. HostFunctionType:C++ 函数的 JS 包装类型
cpp
using HostFunctionType = std::function<
Value(Runtime& rt, const Value& thisVal, const Value* args, size_t count)>;
- 核心用途:定义可注册到 JS 运行时的 C++ 函数类型,实现 C++ 函数被 JS 调用(JS 调用 C++ 的核心入口)。
- 参数详解:
Runtime& rt:当前 JS 运行时的引用,所有 JSI 操作都需要依赖运行时实例。const Value& thisVal:JS 函数调用时的this指向(未强制转换为对象,可能是 undefined/null/ 基本类型 / 对象,兼容 JS 严格模式与非严格模式)。const Value* args:JS 传入的参数数组,类型为Value(封装所有 JS 类型),只读不可修改。size_t count:参数数组的长度,即 JS 传入的参数个数。
- 返回值与异常:
- 返回值:
Value类型,对应 JS 函数的返回值,可封装任意 JS 类型。 - 异常处理:若 C++ 函数抛出异常(继承
std::exception),JSI 会自动将其转换为 JSError对象,异常信息为std::exception::what()返回值;若抛出非std::exception异常,JSI 会创建通用 JSError对象。
- 返回值:
2. HostObject:C++ 对象的 JS 包装类
cpp
class JSI_EXPORT HostObject {
public:
virtual ~HostObject(); // 虚析构:支持子类扩展
// JS 读取属性时调用,默认返回 undefined
virtual Value get(Runtime&, const PropNameID& name);
// JS 设置属性时调用,默认抛出类型错误(模拟冻结对象行为)
virtual void set(Runtime&, const PropNameID& name, const Value& value);
// JS 获取属性名列表时调用,默认返回空数组
virtual std::vector<PropNameID> getPropertyNames(Runtime& rt);
};
- 核心用途:封装 C++ 对象为 JS 可访问的对象,实现 C++ 对象的属性在 JS 中被读取 / 设置(C++ 对象暴露给 JS 的核心载体)。
- 核心方法解析:
get:JS 执行obj.name或obj[name]时触发,入参PropNameID是 JS 属性名的高效标识,返回Value作为属性值。set:JS 执行obj.name = value或obj[name] = value时触发,入参Value是 JS 传入的属性值,默认实现抛出JSError(模拟 JS 冻结对象Object.freeze的行为)。getPropertyNames:JS 执行Object.keys(obj)或for...in遍历对象时触发,返回属性名列表(PropNameID数组)。
- 生命周期管理:
- 析构时机:
HostObject的析构函数在 JS 垃圾回收(GC)回收对应的 JS 对象时调用,可能延迟至Runtime关闭时执行。 - 析构限制:析构函数中不可执行 JS 运行时操作(如
Runtime::evaluateJavaScript),且可能在任意线程执行,需保证析构逻辑线程安全、轻量无阻塞。
- 析构时机:
3. NativeState:JS 对象的附加原生状态
cpp
class JSI_EXPORT NativeState {
public:
virtual ~NativeState(); // 虚析构:支持子类扩展
};
- 核心用途:为任意 JS 对象 (无需继承
HostObject)附加 C++ 原生状态,实现 JS 对象与 C++ 数据的绑定,无需修改 JS 对象的原型或属性。 - 使用方式:通过
Runtime::setNativeState为 JS 对象设置NativeState,通过Runtime::getNativeState获取该状态,生命周期与 JS 对象绑定(JS 对象被 GC 回收时,NativeState也会被自动销毁)。 - 设计优势:低侵入性,不影响 JS 对象的原有属性和行为,仅作为隐藏的原生数据载体,适用于需要为 JS 内置对象(如
Array/Function)绑定 C++ 数据的场景。
4. ISerialization:JS 对象序列化接口(不稳定 API)
cpp
#ifdef JSI_UNSTABLE
class JSI_EXPORT ISerialization : public ICast {
public:
// 固定 UUID:用于接口转换标识
static constexpr jsi::UUID uuid{0xd40fe0ec, 0xa47c, 0x42c9, 0x8c09, 0x661aeab832d8};
// 基础序列化:可多次反序列化,不转移所有权
virtual std::shared_ptr<Serialized> serialize(Value& value) = 0;
virtual Value deserialize(const std::shared_ptr<Serialized>& serialized) = 0;
// 带传输列表的序列化:仅可反序列化一次,转移所有权
virtual std::unique_ptr<Serialized> serializeWithTransfer(Value& value, const Array& transferList) = 0;
virtual Array deserializeWithTransfer(std::unique_ptr<Serialized>& serialized) = 0;
protected:
~ISerialization() = default;
};
#endif // JSI_UNSTABLE
- 核心用途:支持 JS 对象在同类型 JS 运行时之间的拷贝(如 Hermes 运行时 A 到 Hermes 运行时 B),基于 ECMAScript 结构化克隆算法实现,不支持跨运行时类型拷贝(如 Hermes → V8)。
- 核心能力解析:
- 基础序列化 / 反序列化:
serialize:将 JSValue序列化为Serializedopaque 对象(智能指针管理),该对象可多次传入deserialize方法,生成多个独立的 JS 对象副本,原 JS 对象仍可正常使用。deserialize:将Serialized对象反序列化为当前运行时的 JSValue,不影响原Serialized对象的可用性。
- 带传输列表的序列化 / 反序列化:
serializeWithTransfer:入参transferList是 JSArray,存储需要 "转移所有权" 的 JS 对象(如ArrayBuffer),序列化后原对象在源运行时中不可用,Serialized对象仅可反序列化一次。deserializeWithTransfer:将Serialized对象反序列化为当前运行时的 JSArray,数组第一个元素是原序列化对象,后续元素是传输列表中的对象,所有权转移至当前运行时。
- 基础序列化 / 反序列化:
- 特性说明:
- 实验性 API:仅当
JSI_UNSTABLE宏定义时启用,API 可能随 JSI 版本迭代发生不兼容变更。 - 依赖
ICast:继承ICast接口,可通过castInterface方法判断某个Runtime是否支持序列化能力。
- 实验性 API:仅当
5. Runtime:JS 运行时核心入口
cpp
class JSI_EXPORT Runtime : public ICast {
public:
virtual ~Runtime(); // 虚析构:支持子类扩展
// 接口转换:重写 ICast 纯虚方法
ICast* castInterface(const UUID& interfaceUUID) override;
// JS 代码执行:核心能力
virtual Value evaluateJavaScript(const std::shared_ptr<const Buffer>& buffer, const std::string& sourceURL) = 0;
// JS 代码预编译:提升重复执行效率
virtual std::shared_ptr<const PreparedJavaScript> prepareJavaScript(const std::shared_ptr<const Buffer>& buffer, std::string sourceURL) = 0;
virtual Value evaluatePreparedJavaScript(const std::shared_ptr<const PreparedJavaScript>& js) = 0;
// 微任务管理:兼容 ECMA262 标准
virtual void queueMicrotask(const jsi::Function& callback) = 0;
virtual bool drainMicrotasks(int maxMicrotasksHint = -1) = 0;
// 全局对象获取:JS 全局环境入口
virtual Object global() = 0;
// 运行时描述与调试
virtual std::string description() = 0;
virtual bool isInspectable() = 0; // 是否支持 Chrome 远程调试
// 自定义数据管理:基于 UUID 存储/获取任意 C++ 数据
void setRuntimeData(const UUID& uuid, const std::shared_ptr<void>& data);
std::shared_ptr<void> getRuntimeData(const UUID& uuid);
protected:
// 内部辅助方法:类型克隆、属性操作、作用域管理等
virtual PointerValue* cloneSymbol(const Runtime::PointerValue* pv) = 0;
virtual Value getProperty(const Object&, const PropNameID& name) = 0;
virtual void setPropertyValue(const Object&, const PropNameID& name, const Value& value) = 0;
// ... 其他内部纯虚方法
};
-
核心地位:
Runtime是 JSI 的核心入口类,封装了 JS 运行时的所有核心能力,所有 C++ 与 JS 的交互都必须通过Runtime实例完成。 -
核心能力分类解析:
- JS 代码执行与预编译
evaluateJavaScript:执行 JS 源码 / 字节码,sourceURL用于异常栈跟踪(报错时显示对应的文件路径),执行失败抛出JSError。prepareJavaScript:预编译 JS 代码为PreparedJavaScript,避免重复解析 / 编译,提升多次执行的效率。evaluatePreparedJavaScript:执行预编译后的 JS 代码,效率高于直接执行源码。
- 微任务管理
queueMicrotask:向 JS 微任务队列中添加一个回调函数(符合 ECMA262 Job 队列标准),在当前宏任务执行完成后执行。drainMicrotasks:主动排空微任务队列,maxMicrotasksHint用于限制最大执行微任务数量(-1 表示无限制),返回值表示队列是否已排空。
- 全局对象与类型操作
global:返回 JS 全局对象(对应浏览器中的window、Node.js 中的global),是访问 JS 全局变量 / 函数的入口。- 内部纯虚方法:
cloneSymbol/getProperty/setPropertyValue等,封装了 JS 类型的克隆、属性读写等底层操作,由具体运行时实现。
- 自定义数据与调试
setRuntimeData/getRuntimeData:基于 UUID 存储 / 获取任意 C++ 共享指针数据,用于在运行时中附加自定义状态(如日志器、配置信息)。description:返回运行时的可读描述(如 "Hermes Runtime v0.10.0"),用于调试。isInspectable:判断运行时是否支持 Chrome 远程调试协议,用于开发者工具集成。
-
关键特性:
- 可移动不可拷贝:
Runtime未提供拷贝构造函数和赋值运算符,支持移动语义(隐含),避免运行时实例的重复创建与销毁。 - 非线程安全:
Runtime及其所有方法均非线程安全,用户需自行保证单线程访问或通过互斥锁实现线程同步。 - 抽象类:核心方法均为纯虚方法,由具体 JS 运行时(Hermes/V8/JSC)提供实现,具备良好的扩展性。
- 可移动不可拷贝:
四、 JS 类型封装模块:C++ 对 JS 类型的精准映射
JSI 封装了所有 JS 标准类型,实现 C++ 对 JS 类型的精准操作,所有类型均遵循可移动不可拷贝的设计原则,避免不必要的资源开销。
1. 基础指针类 Pointer:JS 引用类型的基类
cpp
class JSI_EXPORT Pointer {
protected:
// 移动构造:转移指针所有权,源对象指针置空
explicit Pointer(Pointer&& other) noexcept : ptr_(other.ptr_) {
other.ptr_ = nullptr;
}
// 析构函数:释放指针资源(调用 invalidate 方法)
~Pointer() {
if (ptr_) {
ptr_->invalidate();
}
}
// 移动赋值:转移指针所有权
Pointer& operator=(Pointer&& other) noexcept;
// 构造函数:接收 Runtime::PointerValue 内部指针
explicit Pointer(Runtime::PointerValue* ptr) : ptr_(ptr) {}
Runtime::PointerValue* ptr_; // 指向 JS 运行时内部对象的指针
friend class Runtime;
friend class Value;
};
- 核心用途:所有 JS 引用类型(
Symbol/String/Object等)的基类,封装 JS 运行时内部指针PointerValue,统一管理引用类型的生命周期。 - 设计优势:
- 统一生命周期管理:析构函数自动调用
ptr_->invalidate()释放 JS 运行时内部资源,避免内存泄漏。 - 移动语义优先:仅提供移动构造和移动赋值,禁止拷贝,避免 JS 引用类型的重复拷贝(提升性能,避免悬挂指针)。
- 保护级别:构造函数、析构函数均为保护级别,仅允许子类继承,避免直接创建
Pointer实例。
- 统一生命周期管理:析构函数自动调用
2. 具体 JS 类型封装
(1)PropNameID:JS 属性名高效标识
cpp
class JSI_EXPORT PropNameID : public Pointer {
public:
// 静态创建方法:支持多种编码格式
static PropNameID forAscii(Runtime& runtime, const char* str, size_t length);
static PropNameID forUtf8(Runtime& runtime, const uint8_t* utf8, size_t length);
static PropNameID forUtf16(Runtime& runtime, const char16_t* utf16, size_t length);
static PropNameID forString(Runtime& runtime, const jsi::String& str);
static PropNameID forSymbol(Runtime& runtime, const jsi::Symbol& sym);
// 编码转换:转换为 C++ 字符串
std::string utf8(Runtime& runtime) const;
std::u16string utf16(Runtime& runtime) const;
// 属性名比较
static bool compare(Runtime& runtime, const jsi::PropNameID& a, const jsi::PropNameID& b);
};
- 核心用途:表示 JS 对象的属性名(支持字符串和 Symbol 类型),是 JS 属性操作的高效标识,避免重复的字符串哈希计算和类型转换。
- 关键优势:
- 多编码支持:支持 ASCII/UTF-8/UTF-16 字符串和 JS
String/Symbol类型,兼容各种属性名场景。 - 高效比较:
compare方法直接比较 JS 运行时内部的属性名标识,比字符串比较更高效。 - 低开销:封装 JS 运行时内部的属性名缓存,避免重复创建相同属性名的标识,减少内存占用。
- 多编码支持:支持 ASCII/UTF-8/UTF-16 字符串和 JS
(2)Symbol/BigInt/String:JS 基本引用类型
这三个类均继承 Pointer,封装对应的 JS 基本引用类型,设计模式一致,以 String 为例:
cpp
class JSI_EXPORT String : public Pointer {
public:
// 移动构造与赋值:默认继承
String(String&& other) = default;
String& operator=(String&& other) = default;
// 静态创建方法:支持多种编码格式
static String createFromAscii(Runtime& runtime, const char* str, size_t length);
static String createFromUtf8(Runtime& runtime, const uint8_t* utf8, size_t length);
static String createFromUtf16(Runtime& runtime, const char16_t* utf16, size_t length);
// 严格相等比较:对应 JS 的 === 运算符
static bool strictEquals(Runtime& runtime, const String& a, const String& b);
// 编码转换:转换为 C++ 字符串
std::string utf8(Runtime& runtime) const;
std::u16string utf16(Runtime& runtime) const;
};
- 核心特性:
- 严格相等比较:
strictEquals方法对应 JS 的===运算符,实现值的精准比较(如String比较字符内容,Symbol比较引用地址)。 - 多编码支持:创建方法支持多种字符编码,方便 C++ 字符串与 JS 字符串的转换。
- 移动语义:默认支持移动构造和赋值,禁止拷贝,提升性能。
- 严格相等比较:
(3)Object:JS 对象核心封装
cpp
class JSI_EXPORT Object : public Pointer {
public:
// 移动构造与赋值:默认继承
Object(Object&& other) = default;
Object& operator=(Object&& other) = default;
// 对象创建:多种创建方式
explicit Object(Runtime& runtime); // 创建空对象(对应 JS {})
static Object createFromHostObject(Runtime& runtime, std::shared_ptr<HostObject> ho); // 封装 HostObject
static Object create(Runtime& runtime, const Value& prototype); // 创建带自定义原型的对象
// 属性操作:获取/设置/删除
Value getProperty(Runtime& runtime, const char* name) const;
template <typename T>
void setProperty(Runtime& runtime, const char* name, T&& value) const;
void deleteProperty(Runtime& runtime, const char* name) const;
// 类型判断:是否为特定 JS 类型
bool isArray(Runtime& runtime) const;
bool isFunction(Runtime& runtime) const;
bool isHostObject(Runtime& runtime) const;
// 类型转换:转换为子类(get 断言失败,as 抛出异常)
Array getArray(Runtime& runtime) const&;
Array asArray(Runtime& runtime) const&;
Function getFunction(Runtime& runtime) const&;
Function asFunction(Runtime& runtime) const&;
// 原生状态管理
void setNativeState(Runtime& runtime, std::shared_ptr<NativeState> state) const;
template <typename T = NativeState>
std::shared_ptr<T> getNativeState(Runtime& runtime) const;
};
-
核心用途:封装 JS 普通对象,提供属性操作、类型判断、类型转换、原生状态管理等核心能力,是 JS 对象操作的入口。
-
关键能力解析:
- 对象创建 :支持创建空对象、封装
HostObject的对象、带自定义原型的对象,覆盖 JS 对象的常见创建场景。 - 属性操作:
- 模板方法
setProperty:支持自动将 C++ 类型(如bool/int/std::string)转换为 JSValue类型,简化属性设置逻辑。 getProperty/deleteProperty:对应 JS 的属性读取和删除操作,返回Value类型或抛出异常。
- 类型判断与转换:
- 类型判断:
isArray/isFunction等方法对应 JS 内置类型判断逻辑(如Array.isArray),返回布尔值。 - 类型转换:
getXXX方法在类型不匹配时断言失败(调试模式下崩溃),asXXX方法在类型不匹配时抛出JSIException(生产模式下安全报错),满足不同场景的错误处理需求。
- 原生状态管理 :
setNativeState/getNativeState方法为 JS 对象附加 / 获取NativeState,实现 C++ 数据与 JS 对象的绑定。
- 对象创建 :支持创建空对象、封装
(4)WeakObject:JS 对象弱引用
cpp
class JSI_EXPORT WeakObject : public Pointer {
public:
// 构造函数:从 JS Object 创建弱引用
WeakObject(Runtime& runtime, const Object& o);
// 锁定弱引用:获取强引用(对象未回收则返回 Object,否则返回 undefined)
Value lock(Runtime& runtime) const;
};
- 核心用途:封装 JS 对象的弱引用,不增加对象的引用计数,不影响 JS GC 回收对象,用于解决 "循环引用" 和 "缓存非必需对象" 的场景。
- 关键方法:
lock方法尝试获取 JS 对象的强引用,返回Value类型:- 若对象未被 GC 回收,返回封装该对象的
Value。 - 若对象已被 GC 回收,返回
undefined类型的Value。
- 若对象未被 GC 回收,返回封装该对象的
- 设计优势:避免内存泄漏,适用于缓存、监听器等场景(无需手动管理引用计数,由 JS GC 自动回收)。
(5)Array/ArrayBuffer/Function:Object 子类
这三个类均继承 Object,封装对应的 JS 内置对象,具备针对性的功能扩展:
Array:JS 数组封装- 核心能力:索引访问(
getValueAtIndex/setValueAtIndex)、长度获取(size/length)、批量创建(createWithElements)。 - 适用场景:操作 JS 数组的元素,比
Object的属性操作更高效(直接通过索引访问,无需属性名转换)。
- 核心能力:索引访问(
ArrayBuffer:JS 二进制缓冲区封装- 核心能力:获取数据指针(
data)、获取缓冲区长度(size/length),支持 C++ 直接操作 JS 二进制数据。 - 适用场景:高效传输二进制数据(如图片、音频、网络数据),避免数据拷贝,提升交互性能。
- 核心能力:获取数据指针(
Function:JS 函数封装- 核心能力:
- 函数调用:
call(无this)、callWithThis(指定this)、callAsConstructor(作为构造函数调用)。 - 宿主函数创建:
createFromHostFunction将HostFunctionType封装为 JS 函数,实现 C++ 函数暴露给 JS。 - 函数信息获取:
isHostFunction判断是否为宿主函数,getHostFunction获取底层HostFunctionType。
- 函数调用:
- 适用场景:调用 JS 函数、将 C++ 函数暴露给 JS,是 C++ 与 JS 双向函数调用的核心载体。
- 核心能力:
(6)Value:JS 任意值的统一封装
cpp
class JSI_EXPORT Value {
public:
// 构造函数:覆盖所有 JS 类型
Value() noexcept; // undefined
Value(std::nullptr_t); // null
Value(bool b); // 布尔
Value(double d); // 数字
Value(int i); // 整数(自动转换为 double)
template <typename T>
Value(T&& other); // 引用类型(Symbol/String/Object 等)
// 类型判断:是否为特定 JS 类型
bool isUndefined() const;
bool isNull() const;
bool isBool() const;
bool isNumber() const;
bool isString() const;
bool isObject() const;
// 类型获取:getXXX(断言失败)/asXXX(抛出异常)
bool getBool() const;
double getNumber() const;
Object getObject(Runtime& runtime) const&;
Object asObject(Runtime& runtime) const&;
// 严格相等比较:对应 JS === 运算符
static bool strictEquals(Runtime& runtime, const Value& a, const Value& b);
private:
// 类型枚举:标识存储的 JS 类型
enum ValueKind { UndefinedKind, NullKind, BooleanKind, NumberKind, SymbolKind, BigIntKind, StringKind, ObjectKind };
// 联合数据:存储不同类型的数据,节省内存
union Data {
bool boolean;
double number;
Pointer pointer; // 引用类型指针
};
ValueKind kind_; // 类型标识
Data data_; // 数据存储
};
-
核心地位:
Value是 JSI 中最核心的数据类型,封装了所有 JS 值类型(undefined、null、布尔、数字、Symbol、String、Object),是 C++ 与 JS 之间数据传递的统一载体。 -
关键设计优势:
- 类型安全:
- 类型判断:
isXXX方法精准判断存储的 JS 类型,避免类型错误。 - 类型获取:
getXXX方法在类型不匹配时断言失败(调试模式),asXXX方法在类型不匹配时抛出JSIException(生产模式),提供严格的类型校验。
- 高效存储:
- 使用
union(Data)存储不同类型的数据,仅占用 16 字节(ValueKind1 字节 +Data8/16 字节,对齐后为 16 字节),比结构体存储更节省内存。 - 引用类型存储
Pointer,复用引用类型的生命周期管理逻辑,避免重复代码。
- 便捷转换:
- 构造函数支持自动转换 C++ 基本类型(
bool/int/double)为 JS 类型,无需手动封装。 - 移动语义支持:引用类型的构造函数支持移动,避免不必要的拷贝开销。
- 语义兼容 :
strictEquals方法对应 JS 的===运算符,实现与 JS 一致的相等判断逻辑,避免跨语言的语义差异。
五、 辅助模块:异常处理与作用域管理
1. Scope:RAII 作用域管理
cpp
class JSI_EXPORT Scope {
public:
// 构造函数:创建作用域,关联 Runtime
explicit Scope(Runtime& rt) : rt_(rt), prv_(rt.pushScope()) {}
// 析构函数:销毁作用域,主动回收资源
~Scope() {
rt_.popScope(prv_);
}
// 禁止拷贝与移动:确保作用域仅在当前栈帧有效
Scope(const Scope&) = delete;
Scope(Scope&&) = delete;
Scope& operator=(const Scope&) = delete;
Scope& operator=(Scope&&) = delete;
// 静态方法:在新作用域中执行函数
template <typename F>
static auto callInNewScope(Runtime& rt, F f) -> decltype(f()) {
Scope s(rt);
return f();
}
private:
Runtime& rt_; // 关联的 JS 运行时
Runtime::ScopeState* prv_; // 作用域内部状态
};
- 核心用途:RAII 风格的作用域标记,提示 JS 运行时在作用域销毁时主动回收当前作用域内的资源(如临时对象、未使用的引用),而非等待 JS GC 自动回收,提升资源释放效率。
- 设计原则:
- 栈上局部变量:仅允许作为栈上局部变量创建,析构函数在作用域结束时自动调用,无需手动管理。
- 禁止拷贝移动:确保作用域的生命周期与栈帧一致,避免作用域嵌套混乱。
- 辅助方法:
callInNewScope静态方法简化 "创建作用域 + 执行函数" 的逻辑,提升代码简洁性。
- 作用机制:构造时调用
Runtime::pushScope()创建作用域,析构时调用Runtime::popScope()销毁作用域,JS 运行时在popScope时主动回收该作用域内的临时资源。
2. 异常体系:分层错误处理
JSI 设计了三层异常类,继承自 std::exception,实现分层、精准的错误处理:
cpp
// 1. 基础异常类:所有 JSI 异常的父类
class JSI_EXPORT JSIException : public std::exception {
protected:
JSIException() {}
JSIException(std::string what) : what_(std::move(what)) {}
public:
virtual const char* what() const noexcept override {
return what_.c_str();
}
virtual ~JSIException() override;
protected:
std::string what_; // 错误信息
};
// 2. 原生异常:C++ 侧逻辑错误(与 JS 执行无关)
class JSI_EXPORT JSINativeException : public JSIException {
public:
JSINativeException(std::string what) : JSIException(std::move(what)) {}
virtual ~JSINativeException();
};
// 3. JS 执行异常:对应 JS 侧抛出的 Error
class JSI_EXPORT JSError : public JSIException {
public:
// 构造函数:创建包含 JS 栈信息的异常
JSError(Runtime& rt, std::string message);
JSError(Runtime& rt, const char* message);
// 获取错误信息与栈跟踪
const std::string& getStack() const { return stack_; }
const std::string& getMessage() const { return message_; }
private:
std::shared_ptr<jsi::Value> value_; // 对应的 JS Error 对象
std::string message_; // 错误消息
std::string stack_; // JS 栈跟踪信息
};
-
分层设计优势:
- 职责清晰:
JSIException:基础异常类,定义通用错误接口(what()方法)。JSINativeException:处理 C++ 侧逻辑错误(如参数非法、内存不足),与 JS 执行无关。JSError:处理 JS 侧执行错误(如语法错误、抛出 Error 对象),包含 JS 栈跟踪信息,便于调试。
- 便于捕获 :用户可根据异常类型分层捕获(如优先捕获
JSError处理 JS 执行错误,再捕获JSINativeException处理 C++ 逻辑错误),实现精细化的错误处理。 - 信息完整 :
JSError包含 JS 栈跟踪信息和错误消息,可还原 JS 异常的完整上下文,便于问题定位。
3. 接口转换辅助函数:简化类型转换
cpp
// 原始指针接口转换
template <typename U, typename T>
U* castInterface(T* ptr) {
if (ptr) {
return static_cast<U*>(ptr->castInterface(U::uuid));
}
return nullptr;
}
// 共享指针接口转换
template <typename U, typename T>
std::shared_ptr<U> dynamicInterfaceCast(T&& ptr) {
auto* p = ptr->castInterface(U::uuid);
U* res = static_cast<U*>(p);
if (res) {
return std::shared_ptr<U>(std::forward<T>(ptr), res);
}
return nullptr;
}
- 核心用途:基于
ICast接口和UUID,简化 JSI 接口的动态转换逻辑,替代手动调用castInterface并强制转换的繁琐代码。 - 优势说明:
- 类型安全:通过模板参数指定目标接口类型,避免手动强制转换的类型错误。
- 简洁高效:一行代码完成接口转换,无需手动处理
nullptr判断和UUID传入。 - 支持智能指针:
dynamicInterfaceCast支持std::shared_ptr,自动管理指针生命周期,避免内存泄漏。
六、 整体设计思想与核心优势
1. 核心设计思想
- 跨运行时抽象:屏蔽 Hermes/V8/JSC 等 JS 运行时的实现差异,提供统一的 C++ 接口,实现 "一次编写,多运行时兼容",降低跨平台开发成本。
- 高效性优先 :
- 移动语义:所有引用类型支持移动,禁止拷贝,避免不必要的资源拷贝。
- 避免 RTTI:基于
UUID实现接口转换,减少运行时开销,提升执行效率。 - 紧凑存储:使用
union、uint64_t等类型,最小化内存占用,提升缓存命中率。 - 零拷贝:
ArrayBuffer支持 C++ 直接操作 JS 二进制数据,避免数据拷贝,提升交互性能。
- 类型安全 :
- 严格类型校验:
isXXX类型判断、getXXX/asXXX类型获取,避免类型错误导致的崩溃。 - 编译期检查:模板方法和 constexpr 常量,在编译期发现类型错误,提升代码可靠性。
- 严格类型校验:
- 生命周期管理 :
- 智能指针:
std::shared_ptr/std::unique_ptr管理 C++ 资源,避免内存泄漏。 - 移动语义:转移引用类型所有权,避免悬挂指针和重复释放。
- 弱引用:
WeakObject支持 JS 对象的弱引用,配合 JS GC 自动回收资源,解决循环引用问题。
- 智能指针:
- 低侵入性 :
- 不依赖 JS 运行时内部实现:仅通过抽象接口交互,便于集成和扩展。
- 不修改 JS 对象原生行为:
NativeState附加原生状态,不影响 JS 对象的属性和原型,低侵入性高。
2. 核心优势
- 高性能:相比传统桥接模式(JSON 序列化 / 反序列化),JSI 直接操作 JS 运行时内部对象,避免数据拷贝和序列化开销,大幅提升 C++ 与 JS 的交互性能。
- 跨平台兼容:支持 Hermes/V8/JSC 等主流 JS 运行时,支持 Windows/Linux/macOS/iOS/Android 等平台,具备良好的跨平台兼容性。
- 类型安全:严格的类型校验和编译期检查,减少运行时错误,提升代码可靠性和可维护性。
- 易于扩展 :抽象类设计(
Runtime/Buffer/HostObject)支持子类扩展,便于接入新的 JS 运行时和自定义功能。 - 低侵入性:不修改 JS 运行时源码,不影响 JS 代码的正常执行,便于集成到现有项目中。
总结
这份代码是 JSI 的核心实现,构建了一套完整的 C++ 与 JavaScript 交互的抽象层,核心要点可概括为:
- 基础支撑 :
UUID(接口标识)、ICast(动态转换)、缓冲区家族(数据传递)构成 JSI 的底层支撑体系。 - 核心入口 :
Runtime是 JS 运行时的核心入口,封装了 JS 代码执行、微任务管理、全局对象访问等核心能力。 - 双向交互 :
HostFunctionType(C++ 函数暴露给 JS)、HostObject(C++ 对象暴露给 JS)、Function(JS 函数调用)实现 C++ 与 JS 的双向高效交互。 - 类型封装 :
Value(统一值封装)、Object及其子类(JS 对象封装)实现 C++ 对 JS 类型的精准映射和操作。 - 辅助能力 :
Scope(资源管理)、异常体系(错误处理)、接口转换函数(类型安全转换)提升开发效率和代码可靠性。 - 设计目标:高性能、跨平台、类型安全、低侵入性,支撑 React Native 等框架的高性能 JS/C++ 交互场景。