React Native 源码分析 -- jsi.h

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 实现自定义异常)
    • 函数包装:functionalstd::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::stringBuffer 子类,快速实现字符串类型数据的传递(如 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 会自动将其转换为 JS Error 对象,异常信息为 std::exception::what() 返回值;若抛出非 std::exception 异常,JSI 会创建通用 JS Error 对象。

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.nameobj[name] 时触发,入参 PropNameID 是 JS 属性名的高效标识,返回 Value 作为属性值。
    • set:JS 执行 obj.name = valueobj[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:将 JS Value 序列化为 Serialized opaque 对象(智能指针管理),该对象可多次传入 deserialize 方法,生成多个独立的 JS 对象副本,原 JS 对象仍可正常使用。
      • deserialize:将 Serialized 对象反序列化为当前运行时的 JS Value,不影响原 Serialized 对象的可用性。
    • 带传输列表的序列化 / 反序列化:
      • serializeWithTransfer:入参 transferList 是 JS Array,存储需要 "转移所有权" 的 JS 对象(如 ArrayBuffer),序列化后原对象在源运行时中不可用,Serialized 对象仅可反序列化一次。
      • deserializeWithTransfer:将 Serialized 对象反序列化为当前运行时的 JS Array,数组第一个元素是原序列化对象,后续元素是传输列表中的对象,所有权转移至当前运行时。
  • 特性说明:
    • 实验性 API:仅当 JSI_UNSTABLE 宏定义时启用,API 可能随 JSI 版本迭代发生不兼容变更。
    • 依赖 ICast:继承 ICast 接口,可通过 castInterface 方法判断某个 Runtime 是否支持序列化能力。

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 实例完成。

  • 核心能力分类解析:

    1. JS 代码执行与预编译
    • evaluateJavaScript:执行 JS 源码 / 字节码,sourceURL 用于异常栈跟踪(报错时显示对应的文件路径),执行失败抛出 JSError
    • prepareJavaScript:预编译 JS 代码为 PreparedJavaScript,避免重复解析 / 编译,提升多次执行的效率。
    • evaluatePreparedJavaScript:执行预编译后的 JS 代码,效率高于直接执行源码。
    1. 微任务管理
    • queueMicrotask:向 JS 微任务队列中添加一个回调函数(符合 ECMA262 Job 队列标准),在当前宏任务执行完成后执行。
    • drainMicrotasks:主动排空微任务队列,maxMicrotasksHint 用于限制最大执行微任务数量(-1 表示无限制),返回值表示队列是否已排空。
    1. 全局对象与类型操作
    • global:返回 JS 全局对象(对应浏览器中的 window、Node.js 中的 global),是访问 JS 全局变量 / 函数的入口。
    • 内部纯虚方法:cloneSymbol/getProperty/setPropertyValue 等,封装了 JS 类型的克隆、属性读写等底层操作,由具体运行时实现。
    1. 自定义数据与调试
    • 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 运行时内部的属性名缓存,避免重复创建相同属性名的标识,减少内存占用。
(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 对象操作的入口。

  • 关键能力解析:

    1. 对象创建 :支持创建空对象、封装 HostObject 的对象、带自定义原型的对象,覆盖 JS 对象的常见创建场景。
    2. 属性操作
    • 模板方法 setProperty:支持自动将 C++ 类型(如 bool/int/std::string)转换为 JS Value 类型,简化属性设置逻辑。
    • getProperty/deleteProperty:对应 JS 的属性读取和删除操作,返回 Value 类型或抛出异常。
    1. 类型判断与转换
    • 类型判断:isArray/isFunction 等方法对应 JS 内置类型判断逻辑(如 Array.isArray),返回布尔值。
    • 类型转换:getXXX 方法在类型不匹配时断言失败(调试模式下崩溃),asXXX 方法在类型不匹配时抛出 JSIException(生产模式下安全报错),满足不同场景的错误处理需求。
    1. 原生状态管理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
  • 设计优势:避免内存泄漏,适用于缓存、监听器等场景(无需手动管理引用计数,由 JS GC 自动回收)。
(5)Array/ArrayBuffer/FunctionObject 子类

这三个类均继承 Object,封装对应的 JS 内置对象,具备针对性的功能扩展:

  1. Array:JS 数组封装
    • 核心能力:索引访问(getValueAtIndex/setValueAtIndex)、长度获取(size/length)、批量创建(createWithElements)。
    • 适用场景:操作 JS 数组的元素,比 Object 的属性操作更高效(直接通过索引访问,无需属性名转换)。
  2. ArrayBuffer:JS 二进制缓冲区封装
    • 核心能力:获取数据指针(data)、获取缓冲区长度(size/length),支持 C++ 直接操作 JS 二进制数据。
    • 适用场景:高效传输二进制数据(如图片、音频、网络数据),避免数据拷贝,提升交互性能。
  3. Function:JS 函数封装
    • 核心能力:
      • 函数调用:call(无 this)、callWithThis(指定 this)、callAsConstructor(作为构造函数调用)。
      • 宿主函数创建:createFromHostFunctionHostFunctionType 封装为 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 之间数据传递的统一载体。

  • 关键设计优势:

    1. 类型安全
    • 类型判断:isXXX 方法精准判断存储的 JS 类型,避免类型错误。
    • 类型获取:getXXX 方法在类型不匹配时断言失败(调试模式),asXXX 方法在类型不匹配时抛出 JSIException(生产模式),提供严格的类型校验。
    1. 高效存储
    • 使用 unionData)存储不同类型的数据,仅占用 16 字节(ValueKind 1 字节 + Data 8/16 字节,对齐后为 16 字节),比结构体存储更节省内存。
    • 引用类型存储 Pointer,复用引用类型的生命周期管理逻辑,避免重复代码。
    1. 便捷转换
    • 构造函数支持自动转换 C++ 基本类型(bool/int/double)为 JS 类型,无需手动封装。
    • 移动语义支持:引用类型的构造函数支持移动,避免不必要的拷贝开销。
    1. 语义兼容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 栈跟踪信息
};
  • 分层设计优势:

    1. 职责清晰
    • JSIException:基础异常类,定义通用错误接口(what() 方法)。
    • JSINativeException:处理 C++ 侧逻辑错误(如参数非法、内存不足),与 JS 执行无关。
    • JSError:处理 JS 侧执行错误(如语法错误、抛出 Error 对象),包含 JS 栈跟踪信息,便于调试。
    1. 便于捕获 :用户可根据异常类型分层捕获(如优先捕获 JSError 处理 JS 执行错误,再捕获 JSINativeException 处理 C++ 逻辑错误),实现精细化的错误处理。
    2. 信息完整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. 核心设计思想

  1. 跨运行时抽象:屏蔽 Hermes/V8/JSC 等 JS 运行时的实现差异,提供统一的 C++ 接口,实现 "一次编写,多运行时兼容",降低跨平台开发成本。
  2. 高效性优先
    • 移动语义:所有引用类型支持移动,禁止拷贝,避免不必要的资源拷贝。
    • 避免 RTTI:基于 UUID 实现接口转换,减少运行时开销,提升执行效率。
    • 紧凑存储:使用 unionuint64_t 等类型,最小化内存占用,提升缓存命中率。
    • 零拷贝:ArrayBuffer 支持 C++ 直接操作 JS 二进制数据,避免数据拷贝,提升交互性能。
  3. 类型安全
    • 严格类型校验:isXXX 类型判断、getXXX/asXXX 类型获取,避免类型错误导致的崩溃。
    • 编译期检查:模板方法和 constexpr 常量,在编译期发现类型错误,提升代码可靠性。
  4. 生命周期管理
    • 智能指针:std::shared_ptr/std::unique_ptr 管理 C++ 资源,避免内存泄漏。
    • 移动语义:转移引用类型所有权,避免悬挂指针和重复释放。
    • 弱引用:WeakObject 支持 JS 对象的弱引用,配合 JS GC 自动回收资源,解决循环引用问题。
  5. 低侵入性
    • 不依赖 JS 运行时内部实现:仅通过抽象接口交互,便于集成和扩展。
    • 不修改 JS 对象原生行为:NativeState 附加原生状态,不影响 JS 对象的属性和原型,低侵入性高。

2. 核心优势

  1. 高性能:相比传统桥接模式(JSON 序列化 / 反序列化),JSI 直接操作 JS 运行时内部对象,避免数据拷贝和序列化开销,大幅提升 C++ 与 JS 的交互性能。
  2. 跨平台兼容:支持 Hermes/V8/JSC 等主流 JS 运行时,支持 Windows/Linux/macOS/iOS/Android 等平台,具备良好的跨平台兼容性。
  3. 类型安全:严格的类型校验和编译期检查,减少运行时错误,提升代码可靠性和可维护性。
  4. 易于扩展 :抽象类设计(Runtime/Buffer/HostObject)支持子类扩展,便于接入新的 JS 运行时和自定义功能。
  5. 低侵入性:不修改 JS 运行时源码,不影响 JS 代码的正常执行,便于集成到现有项目中。

总结

这份代码是 JSI 的核心实现,构建了一套完整的 C++ 与 JavaScript 交互的抽象层,核心要点可概括为:

  1. 基础支撑UUID(接口标识)、ICast(动态转换)、缓冲区家族(数据传递)构成 JSI 的底层支撑体系。
  2. 核心入口Runtime 是 JS 运行时的核心入口,封装了 JS 代码执行、微任务管理、全局对象访问等核心能力。
  3. 双向交互HostFunctionType(C++ 函数暴露给 JS)、HostObject(C++ 对象暴露给 JS)、Function(JS 函数调用)实现 C++ 与 JS 的双向高效交互。
  4. 类型封装Value(统一值封装)、Object 及其子类(JS 对象封装)实现 C++ 对 JS 类型的精准映射和操作。
  5. 辅助能力Scope(资源管理)、异常体系(错误处理)、接口转换函数(类型安全转换)提升开发效率和代码可靠性。
  6. 设计目标:高性能、跨平台、类型安全、低侵入性,支撑 React Native 等框架的高性能 JS/C++ 交互场景。
相关推荐
清酒难咽19 小时前
算法案例之递归
c++·经验分享·算法
Easonmax19 小时前
零基础入门 React Native 鸿蒙跨平台开发:7——双向滚动表格实现
react native·react.js·harmonyos
Easonmax19 小时前
零基础入门 React Native 鸿蒙跨平台开发:6——竖向滚动表格实现
react native·react.js·harmonyos
z203483152020 小时前
C++对象布局
开发语言·c++
戌中横20 小时前
JavaScript——Web APIs DOM
前端·javascript·html
Beginner x_u20 小时前
如何解释JavaScript 中 this 的值?
开发语言·前端·javascript·this 指针
张张努力变强20 小时前
C++ Date日期类的设计与实现全解析
java·开发语言·c++·算法
HWL567920 小时前
获取网页首屏加载时间
前端·javascript·vue.js
沉默-_-21 小时前
力扣hot100滑动窗口(C++)
数据结构·c++·学习·算法·滑动窗口
速易达网络21 小时前
基于RuoYi-Vue 框架美妆系统
前端·javascript·vue.js