blob: 8539b18135d2e392faf361c4db8b3e9e0631c8da [file] [log] [blame] [edit]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of "dart:ffi";
/// A memory range, represented by its starting address.
///
/// Shared supertype of the FFI compound [Struct], [Union], and [Array] types.
///
/// This class is not abstract because instances can be created as an anonymous
/// representation of a memory area, with no structure on top. (In particular,
/// during the transformation of `.address` in FFI leaf calls.)
@pragma("wasm:entry-point")
final class _Compound implements NativeType {
/// The underlying [TypedData] or [Pointer] that a subtype uses.
@pragma("vm:entry-point")
final Object _typedDataBase;
/// Offset in bytes into [_typedDataBase].
@pragma("vm:entry-point")
final int _offsetInBytes;
external _Compound._();
@pragma('vm:prefer-inline')
_Compound._fromTypedDataBase(this._typedDataBase, this._offsetInBytes);
/// Constructs a view on [typedData].
///
/// The length in bytes of [typedData] must at least be [sizeInBytes].
@pragma('vm:prefer-inline')
_Compound._fromTypedData(TypedData typedData, int offset, int sizeInBytes)
: _typedDataBase = typedData,
_offsetInBytes = typedData.elementSizeInBytes * offset {
if (typedData.lengthInBytes <
typedData.elementSizeInBytes * offset + sizeInBytes) {
throw RangeError.range(
typedData.lengthInBytes,
sizeInBytes + typedData.elementSizeInBytes * offset,
null,
'typedData.lengthInBytes',
'The typed list is not large enough',
);
}
}
}
/// The supertype of all FFI struct types.
///
/// FFI struct types should extend this class and declare fields corresponding
/// to the underlying native structure.
///
/// Field declarations in a [Struct] subclass declaration are automatically
/// given a setter and getter implementation which accesses the native struct's
/// field in memory.
///
/// All field declarations in a [Struct] subclass declaration must either have
/// type [int] or [double] and be annotated with a [NativeType] representing the
/// native type, or must be of type [Pointer], [Array] or a subtype of [Struct]
/// or [Union]. For example:
///
/// ```c
/// typedef struct {
/// int a;
/// float b;
/// void* c;
/// } my_struct;
/// ```
///
/// ```dart
/// final class MyStruct extends Struct {
/// @Int32()
/// external int a;
///
/// @Float()
/// external double b;
///
/// external Pointer<Void> c;
/// }
/// ```
///
/// The field declarations of a [Struct] subclass *must* be marked `external`. A
/// struct subclass points directly into a location of native memory ([Pointer])
/// or Dart memory ([TypedData]), and the external field's getter and setter
/// implementations directly read and write bytes at appropriate offsets from
/// that location. This does not allow for non-native fields to also exist.
///
/// An instance of a struct subclass cannot be created with a generative
/// constructor. Instead, an instance can be created by [StructPointer.ref],
/// [Struct.create], FFI call return values, FFI callback arguments,
/// [StructArray], and accessing [Struct] fields. To create an instance backed
/// by native memory, use [StructPointer.ref]. To create an instance backed by
/// Dart memory, use [Struct.create].
abstract base class Struct extends _Compound implements SizedNativeType {
/// Construct a reference to the [nullptr].
///
/// Use [StructPointer]'s `.ref` to gain references to native memory backed
/// structs.
Struct() : super._();
/// Creates a struct view of bytes in [typedData].
///
/// The created instance of the struct subclass will then be backed by the
/// bytes at [TypedData.offsetInBytes] plus [offset] times
/// [TypedData.elementSizeInBytes]. That is, the getters and setters of the
/// external instance variables declared by the subclass, will read an write
/// their values from the bytes of the [TypedData.buffer] of [typedData],
/// starting at [TypedData.offsetInBytes] plus [offset] times
/// [TypedData.elementSizeInBytes]. The [TypedData.lengthInBytes] of
/// [typedData] *must* be sufficient to contain the [sizeOf] of the struct
/// subclass. _It doesn't matter whether the [typedData] is, for example, a
/// [Uint8List], a [Float64List], or any other [TypedData], it's only treated
/// as a view into a [ByteBuffer], through its [TypedData.buffer],
/// [TypedData.offsetInBytes] and [TypedData.lengthInBytes]._
///
/// If [typedData] is omitted, a fresh [ByteBuffer], with precisely enough
/// bytes for the [sizeOf] of the created struct, is allocated on the Dart
/// heap, and used as memory to store the struct fields.
///
/// If [offset] is provided, the indexing into [typedData] is offset by
/// [offset] times [TypedData.elementSizeInBytes].
///
/// Example:
///
/// ```dart import:typed_data
/// final class Point extends Struct {
/// @Double()
/// external double x;
///
/// @Double()
/// external double y;
///
/// /// Creates Dart managed memory to hold a `Point` and returns the
/// /// `Point` view on it.
/// factory Point(double x, double y) {
/// return Struct.create()
/// ..x = x
/// ..y = y;
/// }
///
/// /// Creates a [Point] view on [typedData].
/// factory Point.fromTypedData(TypedData typedData) {
/// return Struct.create(typedData);
/// }
/// }
/// ```
///
/// To create a struct object from a [Pointer], use [StructPointer.ref].
@Since('3.4')
external static T create<T extends Struct>([TypedData typedData, int offset]);
/// Creates a view on a [TypedData] or [Pointer].
///
/// Used in [StructPointer.ref], FFI calls, and FFI callbacks.
@pragma('vm:prefer-inline')
Struct._fromTypedDataBase(super._typedDataBase, super._offsetInBytes)
: super._fromTypedDataBase();
/// Creates a view on [typedData].
///
/// The length in bytes of [typedData] must at least be [sizeInBytes].
///
/// Used in the `external` public constructor of [Struct].
@pragma('vm:prefer-inline')
Struct._fromTypedData(super.typedData, super.offset, super.sizeInBytes)
: super._fromTypedData();
}
/// Annotation to specify on `Struct` subtypes to indicate that its members
/// need to be packed.
///
/// Valid values for [memberAlignment] are 1, 2, 4, 8, and 16.
@Since('2.13')
final class Packed {
final int memberAlignment;
const Packed(this.memberAlignment);
}