├── .gitignore ├── analysis_options.yaml ├── AUTHORS ├── .github ├── dependabot.yml └── workflows │ └── test-package.yml ├── pubspec.yaml ├── lib ├── ffi.dart └── src │ ├── utf8.dart │ ├── utf16.dart │ ├── arena.dart │ └── allocation.dart ├── example └── main.dart ├── README.md ├── LICENSE ├── test ├── allocation_test.dart ├── utf16_test.dart ├── utf8_test.dart └── arena_test.dart └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | .dart_tool 2 | .packages 3 | pubspec.lock 4 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-casts: true 6 | strict-inference: true 7 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the Dart project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google LLC 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file. 2 | # See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates 3 | version: 2 4 | 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | schedule: 9 | interval: monthly 10 | labels: 11 | - autosubmit 12 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ffi 2 | version: 2.1.1 3 | description: Utilities for working with Foreign Function Interface (FFI) code. 4 | repository: https://github.com/dart-lang/ffi 5 | 6 | topics: 7 | - interop 8 | - ffi 9 | - codegen 10 | 11 | environment: 12 | sdk: '>=3.3.0-279.1.beta <4.0.0' 13 | 14 | dev_dependencies: 15 | test: ^1.21.2 16 | lints: ^2.0.0 17 | -------------------------------------------------------------------------------- /lib/ffi.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'src/allocation.dart' show calloc, malloc; 6 | export 'src/arena.dart'; 7 | export 'src/utf8.dart'; 8 | export 'src/utf16.dart'; 9 | -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi'; 2 | 3 | import 'package:ffi/ffi.dart'; 4 | 5 | void main() { 6 | // Allocate and free some native memory with calloc and free. 7 | final pointer = calloc(); 8 | pointer.value = 3; 9 | print(pointer.value); 10 | calloc.free(pointer); 11 | 12 | // Use the Utf8 helper to encode zero-terminated UTF-8 strings in native memory. 13 | final String myString = '😎👿💬'; 14 | final Pointer charPointer = myString.toNativeUtf8(); 15 | print('First byte is: ${charPointer.cast().value}'); 16 | print(charPointer.toDartString()); 17 | calloc.free(charPointer); 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This package has moved! 2 | 3 | The package can now be found at https://github.com/dart-lang/native/tree/main/pkgs/ffi 4 | 5 | ----- 6 | 7 | [![Build Status](https://github.com/dart-lang/ffi/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/ffi/actions?query=workflow%3A"Dart+CI") 8 | [![pub package](https://img.shields.io/pub/v/ffi.svg)](https://pub.dev/packages/ffi) 9 | [![package publisher](https://img.shields.io/pub/publisher/ffi.svg)](https://pub.dev/packages/ffi/publisher) 10 | 11 | Utilities for working with Foreign Function Interface (FFI) code, incl. 12 | converting between Dart strings and C strings encoded with UTF-8 and UTF-16. 13 | 14 | Please see the [API reference](https://pub.dev/documentation/ffi/latest/ffi/ffi-library.html) for more documentation and the [tests](https://github.com/dart-lang/ffi/tree/main/test) for example usage. 15 | 16 | For additional details about Dart FFI (`dart:ffi`), see 17 | https://dart.dev/guides/libraries/c-interop. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | # Run on PRs and pushes to the default branch. 5 | push: 6 | branches: [ main ] 7 | pull_request: 8 | branches: [ main ] 9 | schedule: 10 | - cron: "0 0 * * 0" 11 | 12 | env: 13 | PUB_ENVIRONMENT: bot.github 14 | 15 | jobs: 16 | # Check code formatting and static analysis on a single OS (linux) 17 | # against Dart dev. 18 | analyze: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | sdk: [dev] 24 | steps: 25 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 26 | - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d 27 | with: 28 | sdk: ${{ matrix.sdk }} 29 | - id: install 30 | name: Install dependencies 31 | run: dart pub get 32 | - name: Check formatting 33 | run: dart format --output=none --set-exit-if-changed . 34 | if: always() && steps.install.outcome == 'success' 35 | - name: Analyze code 36 | run: dart analyze --fatal-infos 37 | if: always() && steps.install.outcome == 'success' 38 | 39 | # Run tests on a matrix consisting of two dimensions: 40 | # 1. OS: ubuntu-latest, (macos-latest, windows-latest) 41 | # 2. release channel: dev 42 | test: 43 | needs: analyze 44 | runs-on: ${{ matrix.os }} 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | # Add macos-latest and/or windows-latest if relevant for this package. 49 | os: [ubuntu-latest] 50 | sdk: [beta, dev] 51 | steps: 52 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 53 | - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d 54 | with: 55 | sdk: ${{ matrix.sdk }} 56 | - id: install 57 | name: Install dependencies 58 | run: dart pub get 59 | - name: Run VM tests 60 | run: dart test --platform vm 61 | if: always() && steps.install.outcome == 'success' 62 | -------------------------------------------------------------------------------- /test/allocation_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ffi'; 6 | import 'dart:math' show Random; 7 | 8 | import 'package:ffi/ffi.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | const testRuns = 1000; 12 | 13 | void main() async { 14 | test('calloc', () { 15 | // Tests that calloc successfully zeroes out memory. 16 | for (var i = 0; i < testRuns; i++) { 17 | final allocBytes = Random().nextInt(1000); 18 | final mem = calloc(allocBytes); 19 | expect(mem.asTypedList(allocBytes).where(((element) => element != 0)), 20 | isEmpty); 21 | calloc.free(mem); 22 | } 23 | }); 24 | 25 | test('testPointerAllocateTooLarge', () { 26 | // Try to allocate something that doesn't fit in 64 bit address space. 27 | int maxInt = 9223372036854775807; // 2^63 - 1 28 | expect(() => calloc(maxInt), throwsA(isA())); 29 | 30 | // Try to allocate almost the full 64 bit address space. 31 | int maxInt1_8 = 1152921504606846975; // 2^60 -1 32 | expect(() => calloc(maxInt1_8), throwsA(isA())); 33 | }); 34 | 35 | test('testPointerAllocateNegative', () { 36 | // Passing in -1 will be converted into an unsigned integer. So, it will try 37 | // to allocate SIZE_MAX - 1 + 1 bytes. This will fail as it is the max 38 | // amount of addressable memory on the system. 39 | expect(() => calloc(-1), throwsA(isA())); 40 | }); 41 | 42 | test('nativeFree', () { 43 | // malloc.nativeFree should be able to free memory allocated by malloc. 44 | final ptr1 = malloc.allocate(1024); 45 | malloc.nativeFree.asFunction)>()(ptr1.cast()); 46 | // calloc.nativeFree should be able to free memory allocated by calloc. 47 | final ptr2 = calloc.allocate(1024); 48 | calloc.nativeFree.asFunction)>()(ptr2.cast()); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /test/utf16_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ffi'; 6 | import 'dart:typed_data'; 7 | 8 | import 'package:ffi/ffi.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | void main() { 12 | test('toUtf16 ASCII', () { 13 | final String start = 'Hello World!\n'; 14 | final Pointer converted = start.toNativeUtf16().cast(); 15 | final Uint16List end = converted.asTypedList(start.codeUnits.length + 1); 16 | final matcher = equals(start.codeUnits.toList()..add(0)); 17 | expect(end, matcher); 18 | calloc.free(converted); 19 | }); 20 | 21 | test('toUtf16 emoji', () { 22 | final String start = '😎'; 23 | final Pointer converted = start.toNativeUtf16().cast(); 24 | final int length = start.codeUnits.length; 25 | final Uint16List end = converted.cast().asTypedList(length + 1); 26 | final matcher = equals(start.codeUnits.toList()..add(0)); 27 | expect(end, matcher); 28 | calloc.free(converted); 29 | }); 30 | 31 | test('from Utf16 ASCII', () { 32 | final string = 'Hello World!\n'; 33 | final utf16Pointer = string.toNativeUtf16(); 34 | final stringAgain = utf16Pointer.toDartString(); 35 | expect(stringAgain, string); 36 | calloc.free(utf16Pointer); 37 | }); 38 | 39 | test('from Utf16 emoji', () { 40 | final string = '😎'; 41 | final utf16Pointer = string.toNativeUtf16(); 42 | final stringAgain = utf16Pointer.toDartString(); 43 | expect(stringAgain, string); 44 | calloc.free(utf16Pointer); 45 | }); 46 | 47 | test('zero bytes', () { 48 | final string = 'Hello\x00World!\n'; 49 | final utf16Pointer = string.toNativeUtf16(); 50 | final stringAgain = utf16Pointer.toDartString(length: 13); 51 | expect(stringAgain, string); 52 | calloc.free(utf16Pointer); 53 | }); 54 | 55 | test('length', () { 56 | final string = 'Hello'; 57 | final utf16Pointer = string.toNativeUtf16(); 58 | expect(utf16Pointer.length, 5); 59 | calloc.free(utf16Pointer); 60 | }); 61 | 62 | test('fromUtf8 with negative length', () { 63 | final string = 'Hello'; 64 | final utf16 = string.toNativeUtf16(); 65 | expect(() => utf16.toDartString(length: -1), throwsRangeError); 66 | calloc.free(utf16); 67 | }); 68 | 69 | test('nullptr.toDartString()', () { 70 | final Pointer utf16 = nullptr; 71 | try { 72 | utf16.toDartString(); 73 | } on UnsupportedError { 74 | return; 75 | } 76 | fail('Expected an error.'); 77 | }); 78 | 79 | test('nullptr.length', () { 80 | final Pointer utf16 = nullptr; 81 | try { 82 | utf16.length; 83 | } on UnsupportedError { 84 | return; 85 | } 86 | fail('Expected an error.'); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/utf8.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:ffi'; 7 | import 'dart:typed_data'; 8 | 9 | import 'package:ffi/ffi.dart'; 10 | 11 | /// The contents of a native zero-terminated array of UTF-8 code units. 12 | /// 13 | /// The Utf8 type itself has no functionality, it's only intended to be used 14 | /// through a `Pointer` representing the entire array. This pointer is 15 | /// the equivalent of a char pointer (`const char*`) in C code. 16 | final class Utf8 extends Opaque {} 17 | 18 | /// Extension method for converting a`Pointer` to a [String]. 19 | extension Utf8Pointer on Pointer { 20 | /// The number of UTF-8 code units in this zero-terminated UTF-8 string. 21 | /// 22 | /// The UTF-8 code units of the strings are the non-zero code units up to the 23 | /// first zero code unit. 24 | int get length { 25 | _ensureNotNullptr('length'); 26 | final codeUnits = cast(); 27 | return _length(codeUnits); 28 | } 29 | 30 | /// Converts this UTF-8 encoded string to a Dart string. 31 | /// 32 | /// Decodes the UTF-8 code units of this zero-terminated byte array as 33 | /// Unicode code points and creates a Dart string containing those code 34 | /// points. 35 | /// 36 | /// If [length] is provided, zero-termination is ignored and the result can 37 | /// contain NUL characters. 38 | /// 39 | /// If [length] is not provided, the returned string is the string up til 40 | /// but not including the first NUL character. 41 | String toDartString({int? length}) { 42 | _ensureNotNullptr('toDartString'); 43 | final codeUnits = cast(); 44 | if (length != null) { 45 | RangeError.checkNotNegative(length, 'length'); 46 | } else { 47 | length = _length(codeUnits); 48 | } 49 | return utf8.decode(codeUnits.asTypedList(length)); 50 | } 51 | 52 | static int _length(Pointer codeUnits) { 53 | var length = 0; 54 | while (codeUnits[length] != 0) { 55 | length++; 56 | } 57 | return length; 58 | } 59 | 60 | void _ensureNotNullptr(String operation) { 61 | if (this == nullptr) { 62 | throw UnsupportedError( 63 | "Operation '$operation' not allowed on a 'nullptr'."); 64 | } 65 | } 66 | } 67 | 68 | /// Extension method for converting a [String] to a `Pointer`. 69 | extension StringUtf8Pointer on String { 70 | /// Creates a zero-terminated [Utf8] code-unit array from this String. 71 | /// 72 | /// If this [String] contains NUL characters, converting it back to a string 73 | /// using [Utf8Pointer.toDartString] will truncate the result if a length is 74 | /// not passed. 75 | /// 76 | /// Unpaired surrogate code points in this [String] will be encoded as 77 | /// replacement characters (U+FFFD, encoded as the bytes 0xEF 0xBF 0xBD) in 78 | /// the UTF-8 encoded result. See [Utf8Encoder] for details on encoding. 79 | /// 80 | /// Returns an [allocator]-allocated pointer to the result. 81 | Pointer toNativeUtf8({Allocator allocator = malloc}) { 82 | final units = utf8.encode(this); 83 | final Pointer result = allocator(units.length + 1); 84 | final Uint8List nativeString = result.asTypedList(units.length + 1); 85 | nativeString.setAll(0, units); 86 | nativeString[units.length] = 0; 87 | return result.cast(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/utf16.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ffi'; 6 | import 'dart:typed_data'; 7 | 8 | import 'package:ffi/ffi.dart'; 9 | 10 | /// The contents of a native zero-terminated array of UTF-16 code units. 11 | /// 12 | /// The Utf16 type itself has no functionality, it's only intended to be used 13 | /// through a `Pointer` representing the entire array. This pointer is 14 | /// the equivalent of a char pointer (`const wchar_t*`) in C code. The 15 | /// individual UTF-16 code units are stored in native byte order. 16 | final class Utf16 extends Opaque {} 17 | 18 | /// Extension method for converting a`Pointer` to a [String]. 19 | extension Utf16Pointer on Pointer { 20 | /// The number of UTF-16 code units in this zero-terminated UTF-16 string. 21 | /// 22 | /// The UTF-16 code units of the strings are the non-zero code units up to 23 | /// the first zero code unit. 24 | int get length { 25 | _ensureNotNullptr('length'); 26 | final codeUnits = cast(); 27 | return _length(codeUnits); 28 | } 29 | 30 | /// Converts this UTF-16 encoded string to a Dart string. 31 | /// 32 | /// Decodes the UTF-16 code units of this zero-terminated code unit array as 33 | /// Unicode code points and creates a Dart string containing those code 34 | /// points. 35 | /// 36 | /// If [length] is provided, zero-termination is ignored and the result can 37 | /// contain NUL characters. 38 | /// 39 | /// If [length] is not provided, the returned string is the string up til 40 | /// but not including the first NUL character. 41 | String toDartString({int? length}) { 42 | _ensureNotNullptr('toDartString'); 43 | final codeUnits = cast(); 44 | if (length == null) { 45 | return _toUnknownLengthString(codeUnits); 46 | } else { 47 | RangeError.checkNotNegative(length, 'length'); 48 | return _toKnownLengthString(codeUnits, length); 49 | } 50 | } 51 | 52 | static String _toKnownLengthString(Pointer codeUnits, int length) => 53 | String.fromCharCodes(codeUnits.asTypedList(length)); 54 | 55 | static String _toUnknownLengthString(Pointer codeUnits) { 56 | final buffer = StringBuffer(); 57 | var i = 0; 58 | while (true) { 59 | final char = (codeUnits + i).value; 60 | if (char == 0) { 61 | return buffer.toString(); 62 | } 63 | buffer.writeCharCode(char); 64 | i++; 65 | } 66 | } 67 | 68 | static int _length(Pointer codeUnits) { 69 | var length = 0; 70 | while (codeUnits[length] != 0) { 71 | length++; 72 | } 73 | return length; 74 | } 75 | 76 | void _ensureNotNullptr(String operation) { 77 | if (this == nullptr) { 78 | throw UnsupportedError( 79 | "Operation '$operation' not allowed on a 'nullptr'."); 80 | } 81 | } 82 | } 83 | 84 | /// Extension method for converting a [String] to a `Pointer`. 85 | extension StringUtf16Pointer on String { 86 | /// Creates a zero-terminated [Utf16] code-unit array from this String. 87 | /// 88 | /// If this [String] contains NUL characters, converting it back to a string 89 | /// using [Utf16Pointer.toDartString] will truncate the result if a length is 90 | /// not passed. 91 | /// 92 | /// Returns an [allocator]-allocated pointer to the result. 93 | Pointer toNativeUtf16({Allocator allocator = malloc}) { 94 | final units = codeUnits; 95 | final Pointer result = allocator(units.length + 1); 96 | final Uint16List nativeString = result.asTypedList(units.length + 1); 97 | nativeString.setRange(0, units.length, units); 98 | nativeString[units.length] = 0; 99 | return result.cast(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.1.1 2 | 3 | - Require Dart 3.3.0 or greater. 4 | - Migrate `elementAt` use to `operator +`. 5 | 6 | ## 2.1.0 7 | 8 | - Require Dart 3.0.0 or greater. 9 | - Expose native equivalent to `free` (`nativeFree`) from `malloc` and 10 | `calloc` allocators. 11 | 12 | ## 2.0.2 13 | 14 | - Fixed a typo in a doc comment. 15 | - Added package topics to the pubspec file. 16 | 17 | ## 2.0.1 18 | 19 | - Only zero out memory on successful allocation on Windows. 20 | - Upgrade test dev dependency. 21 | 22 | ## 2.0.0 23 | 24 | - Switch Windows memory allocation to use `CoTaskMemAlloc` and `CoTaskMemFree`, 25 | which will enable support for `NativeFinalizer`. 26 | - Require Dart 2.17.0 or greater. 27 | 28 | ## 1.2.1 29 | 30 | Revert added common C integer types as ABI-specific integers. 31 | Instead, these are available in Dart 2.17. 32 | 33 | ## 1.2.0 (retracted) 34 | 35 | This release requires Dart `2.16.0` or greater. 36 | 37 | ## 1.2.0-dev.0 38 | 39 | Added common C integer types as ABI-specific integers. These common C integer 40 | types will make their way into `dart:ffi` in 2.17 and be deprecated from this 41 | package. Having them in this package enables using them in Dart 2.16. 42 | 43 | This pre-release requires Dart `2.16.0-118.0.dev` or greater. 44 | 45 | ## 1.1.2 46 | 47 | Fixed unhandled exception in `withZoneArena` (#107). 48 | 49 | ## 1.1.1 50 | 51 | Adds a sanity check to `Pointer` and `Pointer` extension methods 52 | that receiver is not `nullptr`. 53 | 54 | ## 1.1.0 55 | 56 | Adds the `arena` allocator. 57 | 58 | Moves from static analysis with lints in package:pedantic to package:lints. 59 | 60 | ## 1.0.0 61 | 62 | Bumping the version of this package to `1.0.0`. 63 | 64 | Removes all deprecated methods, use `0.3.0-nullsafety.3` for migration. 65 | 66 | ## 0.3.1-nullsafety.0 67 | 68 | Deprecates the static methods on `Utf8` and `Utf16` and introduces 69 | extension methods to replace them. 70 | 71 | ## 0.3.0-nullsafety.3 72 | 73 | Adds back in deprecated `allocate` and `free` to ease migration. 74 | These will be removed in the next release. 75 | 76 | This pre-release requires Dart `2.12.0-259.9.beta` or greater. 77 | 78 | ## 0.3.0-nullsafety.1 79 | 80 | This pre-release requires Dart `2.12.0-259.8.beta` or greater. 81 | 82 | Note that this pre-release does _not_ work in Flutter versions containing Dart 83 | `2.12.0-260.0.dev` - `2.12.0-264.0.dev`. 84 | Using `Allocator.call` throws a `NoSuchMethodError` in these versions. 85 | See [Flutter Engine #23954](https://github.com/flutter/engine/pull/23954) for more info. 86 | 87 | ## 0.3.0-nullsafety.0 88 | 89 | Changes `Utf8` and `Utf16` to extend `Opaque` instead of `Struct`. 90 | This means `.ref` is no longer available and `Pointer` should be used. 91 | See [breaking change #44622](https://github.com/dart-lang/sdk/issues/44622) for more info. 92 | 93 | Removes `allocate` and `free`. 94 | Instead, introduces `calloc` which implements the new `Allocator` interface. 95 | See [breaking change #44621](https://github.com/dart-lang/sdk/issues/44621) for more info. 96 | 97 | This pre-release requires Dart `2.12.0-265.0.dev` or greater. 98 | 99 | ## 0.2.0-nullsafety.1 100 | 101 | Adds an optional named `length` argument to `Utf8.fromUtf8()`. 102 | 103 | ## 0.2.0-nullsafety.0 104 | 105 | Pre-release (non-stable) release supporting null safety. 106 | Requires Dart 2.12.0 or greater. 107 | 108 | ## 0.1.3 109 | 110 | Stable release incorporating all the previous dev release changes. 111 | 112 | Bump SDK constraint to `>= 2.6.0`. 113 | 114 | ## 0.1.3-dev.4 115 | 116 | Bump SDK constraint to `>= 2.6.0-dev.8.2` which contains the new API of `dart:ffi`. 117 | 118 | ## 0.1.3-dev.3 119 | 120 | Replace use of deprecated `asExternalTypedData` with `asTypedList`. 121 | 122 | ## 0.1.3-dev.2 123 | 124 | Incorporate struct API changes, drop type argument of structs. 125 | 126 | ## 0.1.3-dev.1 127 | 128 | * Adds top-level `allocate()` and `free()` methods which can be used as a 129 | replacement for the deprecated `Pointer.allocate()` and `Pointer.free()` 130 | members in `dart:ffi`. 131 | 132 | ## 0.1.1+2 133 | 134 | * Expand readme 135 | 136 | ## 0.1.1+1 137 | 138 | * Fix documentation link 139 | 140 | ## 0.1.1 141 | 142 | * Add basic Utf16 support 143 | 144 | ## 0.1.0 145 | 146 | * Initial release supporting Utf8 147 | -------------------------------------------------------------------------------- /test/utf8_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ffi'; 6 | import 'dart:typed_data'; 7 | 8 | import 'package:ffi/ffi.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | Pointer _bytesFromList(List ints) { 12 | final Pointer ptr = calloc(ints.length); 13 | final Uint8List list = ptr.asTypedList(ints.length); 14 | list.setAll(0, ints); 15 | return ptr; 16 | } 17 | 18 | void main() { 19 | test('toUtf8 ASCII', () { 20 | final String start = 'Hello World!\n'; 21 | final Pointer converted = start.toNativeUtf8().cast(); 22 | final Uint8List end = converted.asTypedList(start.length + 1); 23 | final matcher = 24 | equals([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]); 25 | expect(end, matcher); 26 | calloc.free(converted); 27 | }); 28 | 29 | test('fromUtf8 ASCII', () { 30 | final Pointer utf8 = _bytesFromList( 31 | [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast(); 32 | final String end = utf8.toDartString(); 33 | expect(end, 'Hello World!\n'); 34 | }); 35 | 36 | test('toUtf8 emoji', () { 37 | final String start = '😎👿💬'; 38 | final Pointer converted = start.toNativeUtf8().cast(); 39 | final int length = converted.length; 40 | final Uint8List end = converted.cast().asTypedList(length + 1); 41 | final matcher = 42 | equals([240, 159, 152, 142, 240, 159, 145, 191, 240, 159, 146, 172, 0]); 43 | expect(end, matcher); 44 | calloc.free(converted); 45 | }); 46 | 47 | test('formUtf8 emoji', () { 48 | final Pointer utf8 = _bytesFromList( 49 | [240, 159, 152, 142, 240, 159, 145, 191, 240, 159, 146, 172, 0]).cast(); 50 | final String end = utf8.toDartString(); 51 | expect(end, '😎👿💬'); 52 | }); 53 | 54 | test('fromUtf8 invalid', () { 55 | final Pointer utf8 = _bytesFromList([0x80, 0x00]).cast(); 56 | expect(() => utf8.toDartString(), throwsA(isFormatException)); 57 | }); 58 | 59 | test('fromUtf8 ASCII with length', () { 60 | final Pointer utf8 = _bytesFromList( 61 | [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast(); 62 | final String end = utf8.toDartString(length: 5); 63 | expect(end, 'Hello'); 64 | }); 65 | 66 | test('fromUtf8 emoji with length', () { 67 | final Pointer utf8 = _bytesFromList( 68 | [240, 159, 152, 142, 240, 159, 145, 191, 240, 159, 146, 172, 0]).cast(); 69 | final String end = utf8.toDartString(length: 4); 70 | expect(end, '😎'); 71 | }); 72 | 73 | test('fromUtf8 with zero length', () { 74 | final Pointer utf8 = _bytesFromList( 75 | [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast(); 76 | final String end = utf8.toDartString(length: 0); 77 | expect(end, ''); 78 | }); 79 | 80 | test('fromUtf8 with negative length', () { 81 | final Pointer utf8 = _bytesFromList( 82 | [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast(); 83 | expect(() => utf8.toDartString(length: -1), throwsRangeError); 84 | }); 85 | 86 | test('fromUtf8 with length and containing a zero byte', () { 87 | final Pointer utf8 = _bytesFromList( 88 | [72, 101, 108, 108, 111, 0, 87, 111, 114, 108, 100, 33, 10]).cast(); 89 | final String end = utf8.toDartString(length: 13); 90 | expect(end, 'Hello\x00World!\n'); 91 | }); 92 | 93 | test('length', () { 94 | final string = 'Hello'; 95 | final utf8Pointer = string.toNativeUtf8(); 96 | expect(utf8Pointer.length, 5); 97 | calloc.free(utf8Pointer); 98 | }); 99 | 100 | test('nullptr.toDartString()', () { 101 | final Pointer utf8 = nullptr; 102 | try { 103 | utf8.toDartString(); 104 | } on UnsupportedError { 105 | return; 106 | } 107 | fail('Expected an error.'); 108 | }); 109 | 110 | test('nullptr.length', () { 111 | final Pointer utf8 = nullptr; 112 | try { 113 | utf8.length; 114 | } on UnsupportedError { 115 | return; 116 | } 117 | fail('Expected an error.'); 118 | }); 119 | 120 | test('zero terminated', () { 121 | final string = 'Hello'; 122 | final utf8Pointer = string.toNativeUtf8(); 123 | final charPointer = utf8Pointer.cast(); 124 | expect(charPointer[utf8Pointer.length], 0); 125 | calloc.free(utf8Pointer); 126 | }); 127 | } 128 | -------------------------------------------------------------------------------- /test/arena_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:ffi'; 7 | 8 | import 'package:ffi/ffi.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | void main() async { 12 | test('sync', () async { 13 | List freed = []; 14 | void freeInt(int i) { 15 | freed.add(i); 16 | } 17 | 18 | using((Arena arena) { 19 | arena.using(1234, freeInt); 20 | expect(freed.isEmpty, true); 21 | }); 22 | expect(freed, [1234]); 23 | }); 24 | 25 | test('async', () async { 26 | /// Calling [using] waits with releasing its resources until after 27 | /// [Future]s complete. 28 | List freed = []; 29 | void freeInt(int i) { 30 | freed.add(i); 31 | } 32 | 33 | Future myFutureInt = using((Arena arena) { 34 | return Future.microtask(() { 35 | arena.using(1234, freeInt); 36 | return 1; 37 | }); 38 | }); 39 | 40 | expect(freed.isEmpty, true); 41 | await myFutureInt; 42 | expect(freed, [1234]); 43 | }); 44 | 45 | test('throw', () { 46 | /// [using] waits with releasing its resources until after [Future]s 47 | /// complete. 48 | List freed = []; 49 | void freeInt(int i) { 50 | freed.add(i); 51 | } 52 | 53 | // Resources are freed also when abnormal control flow occurs. 54 | var didThrow = false; 55 | try { 56 | using((Arena arena) { 57 | arena.using(1234, freeInt); 58 | expect(freed.isEmpty, true); 59 | throw Exception('Exception 1'); 60 | }); 61 | } on Exception { 62 | expect(freed.single, 1234); 63 | didThrow = true; 64 | } 65 | expect(didThrow, true); 66 | }); 67 | 68 | test( 69 | 'allocate', 70 | () { 71 | final countingAllocator = CountingAllocator(); 72 | // To ensure resources are freed, wrap them in a [using] call. 73 | using((Arena arena) { 74 | final p = arena(2); 75 | p[1] = p[0]; 76 | }, countingAllocator); 77 | expect(countingAllocator.freeCount, 1); 78 | }, 79 | ); 80 | 81 | test('allocate throw', () { 82 | // Resources are freed also when abnormal control flow occurs. 83 | bool didThrow = false; 84 | final countingAllocator = CountingAllocator(); 85 | try { 86 | using((Arena arena) { 87 | final p = arena(2); 88 | p[0] = 25; 89 | throw Exception('Exception 2'); 90 | }, countingAllocator); 91 | } on Exception { 92 | expect(countingAllocator.freeCount, 1); 93 | didThrow = true; 94 | } 95 | expect(didThrow, true); 96 | }); 97 | 98 | test('toNativeUtf8', () { 99 | final countingAllocator = CountingAllocator(); 100 | using((Arena arena) { 101 | final p = 'Hello world!'.toNativeUtf8(allocator: arena); 102 | expect(p.toDartString(), 'Hello world!'); 103 | }, countingAllocator); 104 | expect(countingAllocator.freeCount, 1); 105 | }); 106 | 107 | test('zone', () async { 108 | List freed = []; 109 | void freeInt(int i) { 110 | freed.add(i); 111 | } 112 | 113 | withZoneArena(() { 114 | zoneArena.using(1234, freeInt); 115 | expect(freed.isEmpty, true); 116 | }); 117 | expect(freed.length, 1); 118 | expect(freed.single, 1234); 119 | }); 120 | 121 | test('zone async', () async { 122 | /// [using] waits with releasing its resources until after [Future]s 123 | /// complete. 124 | List freed = []; 125 | void freeInt(int i) { 126 | freed.add(i); 127 | } 128 | 129 | Future myFutureInt = withZoneArena(() { 130 | return Future.microtask(() { 131 | zoneArena.using(1234, freeInt); 132 | return 1; 133 | }); 134 | }); 135 | 136 | expect(freed.isEmpty, true); 137 | await myFutureInt; 138 | expect(freed.length, 1); 139 | expect(freed.single, 1234); 140 | }); 141 | 142 | test('zone throw', () { 143 | /// [using] waits with releasing its resources until after [Future]s 144 | /// complete. 145 | List freed = []; 146 | void freeInt(int i) { 147 | freed.add(i); 148 | } 149 | 150 | // Resources are freed also when abnormal control flow occurs. 151 | bool didThrow = false; 152 | try { 153 | withZoneArena(() { 154 | zoneArena.using(1234, freeInt); 155 | expect(freed.isEmpty, true); 156 | throw Exception('Exception 3'); 157 | }); 158 | } on Exception { 159 | expect(freed.single, 1234); 160 | didThrow = true; 161 | } 162 | expect(didThrow, true); 163 | expect(freed.single, 1234); 164 | }); 165 | 166 | test('zone future error', () async { 167 | bool caughtError = false; 168 | bool uncaughtError = false; 169 | 170 | Future asyncFunction() async { 171 | throw Exception('Exception 4'); 172 | } 173 | 174 | final future = runZonedGuarded(() { 175 | return withZoneArena(asyncFunction).catchError((error) { 176 | caughtError = true; 177 | return 5; 178 | }); 179 | }, (error, stackTrace) { 180 | uncaughtError = true; 181 | }); 182 | 183 | final result = (await Future.wait([future!])).single; 184 | 185 | expect(result, 5); 186 | expect(caughtError, true); 187 | expect(uncaughtError, false); 188 | }); 189 | 190 | test('allocate during releaseAll', () { 191 | final countingAllocator = CountingAllocator(); 192 | final arena = Arena(countingAllocator); 193 | 194 | arena.using(arena(), (Pointer discard) { 195 | arena(); 196 | }); 197 | 198 | expect(countingAllocator.allocationCount, 1); 199 | expect(countingAllocator.freeCount, 0); 200 | 201 | arena.releaseAll(reuse: true); 202 | 203 | expect(countingAllocator.allocationCount, 2); 204 | expect(countingAllocator.freeCount, 2); 205 | }); 206 | } 207 | 208 | /// Keeps track of the number of allocates and frees for testing purposes. 209 | class CountingAllocator implements Allocator { 210 | final Allocator wrappedAllocator; 211 | 212 | int allocationCount = 0; 213 | int freeCount = 0; 214 | 215 | CountingAllocator([this.wrappedAllocator = calloc]); 216 | 217 | @override 218 | Pointer allocate(int byteCount, {int? alignment}) { 219 | allocationCount++; 220 | return wrappedAllocator.allocate(byteCount, alignment: alignment); 221 | } 222 | 223 | @override 224 | void free(Pointer pointer) { 225 | freeCount++; 226 | return wrappedAllocator.free(pointer); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /lib/src/arena.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | // 5 | // Explicit arena used for managing resources. 6 | 7 | import 'dart:async'; 8 | import 'dart:ffi'; 9 | 10 | import 'package:ffi/ffi.dart'; 11 | 12 | /// An [Allocator] which frees all allocations at the same time. 13 | /// 14 | /// The arena allows you to allocate heap memory, but ignores calls to [free]. 15 | /// Instead you call [releaseAll] to release all the allocations at the same 16 | /// time. 17 | /// 18 | /// Also allows other resources to be associated with the arena, through the 19 | /// [using] method, to have a release function called for them when the arena 20 | /// is released. 21 | /// 22 | /// An [Allocator] can be provided to do the actual allocation and freeing. 23 | /// Defaults to using [calloc]. 24 | class Arena implements Allocator { 25 | /// The [Allocator] used for allocation and freeing. 26 | final Allocator _wrappedAllocator; 27 | 28 | /// Native memory under management by this [Arena]. 29 | final List> _managedMemoryPointers = []; 30 | 31 | /// Callbacks for releasing native resources under management by this [Arena]. 32 | final List _managedResourceReleaseCallbacks = []; 33 | 34 | bool _inUse = true; 35 | 36 | /// Creates a arena of allocations. 37 | /// 38 | /// The [allocator] is used to do the actual allocation and freeing of 39 | /// memory. It defaults to using [calloc]. 40 | Arena([Allocator allocator = calloc]) : _wrappedAllocator = allocator; 41 | 42 | /// Allocates memory and includes it in the arena. 43 | /// 44 | /// Uses the allocator provided to the [Arena] constructor to do the 45 | /// allocation. 46 | /// 47 | /// Throws an [ArgumentError] if the number of bytes or alignment cannot be 48 | /// satisfied. 49 | @override 50 | Pointer allocate(int byteCount, {int? alignment}) { 51 | _ensureInUse(); 52 | final p = _wrappedAllocator.allocate(byteCount, alignment: alignment); 53 | _managedMemoryPointers.add(p); 54 | return p; 55 | } 56 | 57 | /// Registers [resource] in this arena. 58 | /// 59 | /// Executes [releaseCallback] on [releaseAll]. 60 | /// 61 | /// Returns [resource] again, to allow for easily inserting 62 | /// `arena.using(resource, ...)` where the resource is allocated. 63 | T using(T resource, void Function(T) releaseCallback) { 64 | _ensureInUse(); 65 | releaseCallback = Zone.current.bindUnaryCallback(releaseCallback); 66 | _managedResourceReleaseCallbacks.add(() => releaseCallback(resource)); 67 | return resource; 68 | } 69 | 70 | /// Registers [releaseResourceCallback] to be executed on [releaseAll]. 71 | void onReleaseAll(void Function() releaseResourceCallback) { 72 | _managedResourceReleaseCallbacks.add(releaseResourceCallback); 73 | } 74 | 75 | /// Releases all resources that this [Arena] manages. 76 | /// 77 | /// If [reuse] is `true`, the arena can be used again after resources 78 | /// have been released. If not, the default, then the [allocate] 79 | /// and [using] methods must not be called after a call to `releaseAll`. 80 | /// 81 | /// If any of the callbacks throw, [releaseAll] is interrupted, and should 82 | /// be started again. 83 | void releaseAll({bool reuse = false}) { 84 | if (!reuse) { 85 | _inUse = false; 86 | } 87 | // The code below is deliberately wirtten to allow allocations to happen 88 | // during `releaseAll(reuse:true)`. The arena will still be guaranteed 89 | // empty when the `releaseAll` call returns. 90 | while (_managedResourceReleaseCallbacks.isNotEmpty) { 91 | _managedResourceReleaseCallbacks.removeLast()(); 92 | } 93 | for (final p in _managedMemoryPointers) { 94 | _wrappedAllocator.free(p); 95 | } 96 | _managedMemoryPointers.clear(); 97 | } 98 | 99 | /// Does nothing, invoke [releaseAll] instead. 100 | @override 101 | void free(Pointer pointer) {} 102 | 103 | void _ensureInUse() { 104 | if (!_inUse) { 105 | throw StateError( 106 | 'Arena no longer in use, `releaseAll(reuse: false)` was called.'); 107 | } 108 | } 109 | } 110 | 111 | /// Runs [computation] with a new [Arena], and releases all allocations at the 112 | /// end. 113 | /// 114 | /// If the return value of [computation] is a [Future], all allocations are 115 | /// released when the future completes. 116 | /// 117 | /// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ 118 | /// cleaned up. 119 | R using(R Function(Arena) computation, 120 | [Allocator wrappedAllocator = calloc]) { 121 | final arena = Arena(wrappedAllocator); 122 | bool isAsync = false; 123 | try { 124 | final result = computation(arena); 125 | if (result is Future) { 126 | isAsync = true; 127 | return (result.whenComplete(arena.releaseAll) as R); 128 | } 129 | return result; 130 | } finally { 131 | if (!isAsync) { 132 | arena.releaseAll(); 133 | } 134 | } 135 | } 136 | 137 | /// Creates a zoned [Arena] to manage native resources. 138 | /// 139 | /// The arena is available through [zoneArena]. 140 | /// 141 | /// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ 142 | /// cleaned up. 143 | R withZoneArena(R Function() computation, 144 | [Allocator wrappedAllocator = calloc]) { 145 | final arena = Arena(wrappedAllocator); 146 | var arenaHolder = [arena]; 147 | bool isAsync = false; 148 | try { 149 | return runZoned(() { 150 | final result = computation(); 151 | if (result is Future) { 152 | isAsync = true; 153 | return result.whenComplete(() { 154 | arena.releaseAll(); 155 | }) as R; 156 | } 157 | return result; 158 | }, zoneValues: {#_arena: arenaHolder}); 159 | } finally { 160 | if (!isAsync) { 161 | arena.releaseAll(); 162 | arenaHolder.clear(); 163 | } 164 | } 165 | } 166 | 167 | /// A zone-specific [Arena]. 168 | /// 169 | /// Asynchronous computations can share a [Arena]. Use [withZoneArena] to create 170 | /// a new zone with a fresh [Arena], and that arena will then be released 171 | /// automatically when the function passed to [withZoneArena] completes. 172 | /// All code inside that zone can use `zoneArena` to access the arena. 173 | /// 174 | /// The current arena must not be accessed by code which is not running inside 175 | /// a zone created by [withZoneArena]. 176 | Arena get zoneArena { 177 | final arenaHolder = Zone.current[#_arena] as List?; 178 | if (arenaHolder == null) { 179 | throw StateError('Not inside a zone created by `useArena`'); 180 | } 181 | if (arenaHolder.isNotEmpty) { 182 | return arenaHolder.single; 183 | } 184 | throw StateError('Arena has already been cleared with releaseAll.'); 185 | } 186 | -------------------------------------------------------------------------------- /lib/src/allocation.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ffi'; 6 | import 'dart:io'; 7 | 8 | // Note that ole32.dll is the correct name in both 32-bit and 64-bit. 9 | final DynamicLibrary stdlib = Platform.isWindows 10 | ? DynamicLibrary.open('ole32.dll') 11 | : DynamicLibrary.process(); 12 | 13 | typedef PosixMallocNative = Pointer Function(IntPtr); 14 | typedef PosixMalloc = Pointer Function(int); 15 | final PosixMalloc posixMalloc = 16 | stdlib.lookupFunction('malloc'); 17 | 18 | typedef PosixCallocNative = Pointer Function(IntPtr num, IntPtr size); 19 | typedef PosixCalloc = Pointer Function(int num, int size); 20 | final PosixCalloc posixCalloc = 21 | stdlib.lookupFunction('calloc'); 22 | 23 | typedef PosixFreeNative = Void Function(Pointer); 24 | typedef PosixFree = void Function(Pointer); 25 | final Pointer> posixFreePointer = 26 | stdlib.lookup('free'); 27 | final PosixFree posixFree = posixFreePointer.asFunction(); 28 | 29 | typedef WinCoTaskMemAllocNative = Pointer Function(Size); 30 | typedef WinCoTaskMemAlloc = Pointer Function(int); 31 | final WinCoTaskMemAlloc winCoTaskMemAlloc = 32 | stdlib.lookupFunction( 33 | 'CoTaskMemAlloc'); 34 | 35 | typedef WinCoTaskMemFreeNative = Void Function(Pointer); 36 | typedef WinCoTaskMemFree = void Function(Pointer); 37 | final Pointer> winCoTaskMemFreePointer = 38 | stdlib.lookup('CoTaskMemFree'); 39 | final WinCoTaskMemFree winCoTaskMemFree = winCoTaskMemFreePointer.asFunction(); 40 | 41 | /// Manages memory on the native heap. 42 | /// 43 | /// Does not initialize newly allocated memory to zero. Use [_CallocAllocator] 44 | /// for zero-initialized memory on allocation. 45 | /// 46 | /// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses 47 | /// `CoTaskMemAlloc`. 48 | final class MallocAllocator implements Allocator { 49 | const MallocAllocator._(); 50 | 51 | /// Allocates [byteCount] bytes of of unitialized memory on the native heap. 52 | /// 53 | /// For POSIX-based systems, this uses `malloc`. On Windows, it uses 54 | /// `CoTaskMemAlloc`. 55 | /// 56 | /// Throws an [ArgumentError] if the number of bytes or alignment cannot be 57 | /// satisfied. 58 | // TODO: Stop ignoring alignment if it's large, for example for SSE data. 59 | @override 60 | Pointer allocate(int byteCount, {int? alignment}) { 61 | Pointer result; 62 | if (Platform.isWindows) { 63 | result = winCoTaskMemAlloc(byteCount).cast(); 64 | } else { 65 | result = posixMalloc(byteCount).cast(); 66 | } 67 | if (result.address == 0) { 68 | throw ArgumentError('Could not allocate $byteCount bytes.'); 69 | } 70 | return result; 71 | } 72 | 73 | /// Releases memory allocated on the native heap. 74 | /// 75 | /// For POSIX-based systems, this uses `free`. On Windows, it uses 76 | /// `CoTaskMemFree`. It may only be used against pointers allocated in a 77 | /// manner equivalent to [allocate]. 78 | @override 79 | void free(Pointer pointer) { 80 | if (Platform.isWindows) { 81 | winCoTaskMemFree(pointer); 82 | } else { 83 | posixFree(pointer); 84 | } 85 | } 86 | 87 | /// Returns a pointer to a native free function. 88 | /// 89 | /// This function can be used to release memory allocated by [allocated] 90 | /// from the native side. It can also be used as a finalization callback 91 | /// passed to `NativeFinalizer` constructor or `Pointer.atTypedList` 92 | /// method. 93 | /// 94 | /// For example to automatically free native memory when the Dart object 95 | /// wrapping it is reclaimed by GC: 96 | /// 97 | /// ```dart 98 | /// class Wrapper implements Finalizable { 99 | /// static final finalizer = NativeFinalizer(malloc.nativeFree); 100 | /// 101 | /// final Pointer data; 102 | /// 103 | /// Wrapper() : data = malloc.allocate(length) { 104 | /// finalizer.attach(this, data); 105 | /// } 106 | /// } 107 | /// ``` 108 | /// 109 | /// or to free native memory that is owned by a typed list: 110 | /// 111 | /// ```dart 112 | /// malloc.allocate(n).asTypedList(n, finalizer: malloc.nativeFree) 113 | /// ``` 114 | /// 115 | Pointer get nativeFree => 116 | Platform.isWindows ? winCoTaskMemFreePointer : posixFreePointer; 117 | } 118 | 119 | /// Manages memory on the native heap. 120 | /// 121 | /// Does not initialize newly allocated memory to zero. Use [calloc] for 122 | /// zero-initialized memory allocation. 123 | /// 124 | /// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses 125 | /// `CoTaskMemAlloc` and `CoTaskMemFree`. 126 | const MallocAllocator malloc = MallocAllocator._(); 127 | 128 | /// Manages memory on the native heap. 129 | /// 130 | /// Initializes newly allocated memory to zero. 131 | /// 132 | /// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses 133 | /// `CoTaskMemAlloc` and `CoTaskMemFree`. 134 | final class CallocAllocator implements Allocator { 135 | const CallocAllocator._(); 136 | 137 | /// Fills a block of memory with a specified value. 138 | void _fillMemory(Pointer destination, int length, int fill) { 139 | final ptr = destination.cast(); 140 | for (var i = 0; i < length; i++) { 141 | ptr[i] = fill; 142 | } 143 | } 144 | 145 | /// Fills a block of memory with zeros. 146 | /// 147 | void _zeroMemory(Pointer destination, int length) => 148 | _fillMemory(destination, length, 0); 149 | 150 | /// Allocates [byteCount] bytes of zero-initialized of memory on the native 151 | /// heap. 152 | /// 153 | /// For POSIX-based systems, this uses `malloc`. On Windows, it uses 154 | /// `CoTaskMemAlloc`. 155 | /// 156 | /// Throws an [ArgumentError] if the number of bytes or alignment cannot be 157 | /// satisfied. 158 | // TODO: Stop ignoring alignment if it's large, for example for SSE data. 159 | @override 160 | Pointer allocate(int byteCount, {int? alignment}) { 161 | Pointer result; 162 | if (Platform.isWindows) { 163 | result = winCoTaskMemAlloc(byteCount).cast(); 164 | } else { 165 | result = posixCalloc(byteCount, 1).cast(); 166 | } 167 | if (result.address == 0) { 168 | throw ArgumentError('Could not allocate $byteCount bytes.'); 169 | } 170 | if (Platform.isWindows) { 171 | _zeroMemory(result, byteCount); 172 | } 173 | return result; 174 | } 175 | 176 | /// Releases memory allocated on the native heap. 177 | /// 178 | /// For POSIX-based systems, this uses `free`. On Windows, it uses 179 | /// `CoTaskMemFree`. It may only be used against pointers allocated in a 180 | /// manner equivalent to [allocate]. 181 | @override 182 | void free(Pointer pointer) { 183 | if (Platform.isWindows) { 184 | winCoTaskMemFree(pointer); 185 | } else { 186 | posixFree(pointer); 187 | } 188 | } 189 | 190 | /// Returns a pointer to a native free function. 191 | /// 192 | /// This function can be used to release memory allocated by [allocated] 193 | /// from the native side. It can also be used as a finalization callback 194 | /// passed to `NativeFinalizer` constructor or `Pointer.atTypedList` 195 | /// method. 196 | /// 197 | /// For example to automatically free native memory when the Dart object 198 | /// wrapping it is reclaimed by GC: 199 | /// 200 | /// ```dart 201 | /// class Wrapper implements Finalizable { 202 | /// static final finalizer = NativeFinalizer(calloc.nativeFree); 203 | /// 204 | /// final Pointer data; 205 | /// 206 | /// Wrapper() : data = calloc.allocate(length) { 207 | /// finalizer.attach(this, data); 208 | /// } 209 | /// } 210 | /// ``` 211 | /// 212 | /// or to free native memory that is owned by a typed list: 213 | /// 214 | /// ```dart 215 | /// calloc.allocate(n).asTypedList(n, finalizer: calloc.nativeFree) 216 | /// ``` 217 | /// 218 | Pointer get nativeFree => 219 | Platform.isWindows ? winCoTaskMemFreePointer : posixFreePointer; 220 | } 221 | 222 | /// Manages memory on the native heap. 223 | /// 224 | /// Initializes newly allocated memory to zero. Use [malloc] for uninitialized 225 | /// memory allocation. 226 | /// 227 | /// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses 228 | /// `CoTaskMemAlloc` and `CoTaskMemFree`. 229 | const CallocAllocator calloc = CallocAllocator._(); 230 | --------------------------------------------------------------------------------