From e2c01a960b84d1074b0a1849909ae2d269d004be Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Tue, 8 Aug 2023 11:14:21 +0200 Subject: [PATCH] Expose pointer to free from allocators (#203) --- .github/workflows/test-package.yml | 2 +- CHANGELOG.md | 6 ++ lib/src/allocation.dart | 92 +++++++++++++++++++++++++----- lib/src/utf16.dart | 2 +- lib/src/utf8.dart | 2 +- pubspec.yaml | 4 +- test/allocation_test.dart | 9 +++ 7 files changed, 98 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 03731f5..64e4340 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -47,7 +47,7 @@ jobs: matrix: # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest] - sdk: [2.17.0, dev] + sdk: [3.0.0, dev] steps: - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ecb903..1632b9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.1.0 + +- Require Dart 3.0.0 or greater. +- Expose native equivalent to `free` (`nativeFree`) from `malloc` and + `calloc` allocators. + ## 2.0.2 - Fixed a typo in a doc comment. diff --git a/lib/src/allocation.dart b/lib/src/allocation.dart index 8b58884..4202145 100644 --- a/lib/src/allocation.dart +++ b/lib/src/allocation.dart @@ -22,19 +22,21 @@ final PosixCalloc posixCalloc = typedef PosixFreeNative = Void Function(Pointer); typedef PosixFree = void Function(Pointer); -final PosixFree posixFree = - stdlib.lookupFunction('free'); +final Pointer> posixFreePointer = + stdlib.lookup('free'); +final PosixFree posixFree = posixFreePointer.asFunction(); -typedef WinCoTaskMemAllocNative = Pointer Function(Size cb); -typedef WinCoTaskMemAlloc = Pointer Function(int cb); +typedef WinCoTaskMemAllocNative = Pointer Function(Size); +typedef WinCoTaskMemAlloc = Pointer Function(int); final WinCoTaskMemAlloc winCoTaskMemAlloc = stdlib.lookupFunction( 'CoTaskMemAlloc'); -typedef WinCoTaskMemFreeNative = Void Function(Pointer pv); -typedef WinCoTaskMemFree = void Function(Pointer pv); -final WinCoTaskMemFree winCoTaskMemFree = stdlib - .lookupFunction('CoTaskMemFree'); +typedef WinCoTaskMemFreeNative = Void Function(Pointer); +typedef WinCoTaskMemFree = void Function(Pointer); +final Pointer> winCoTaskMemFreePointer = + stdlib.lookup('CoTaskMemFree'); +final WinCoTaskMemFree winCoTaskMemFree = winCoTaskMemFreePointer.asFunction(); /// Manages memory on the native heap. /// @@ -43,8 +45,8 @@ final WinCoTaskMemFree winCoTaskMemFree = stdlib /// /// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc`. -class _MallocAllocator implements Allocator { - const _MallocAllocator(); +final class MallocAllocator implements Allocator { + const MallocAllocator._(); /// Allocates [byteCount] bytes of of unitialized memory on the native heap. /// @@ -81,6 +83,37 @@ class _MallocAllocator implements Allocator { posixFree(pointer); } } + + /// Returns a pointer to a native free function. + /// + /// This function can be used to release memory allocated by [allocated] + /// from the native side. It can also be used as a finalization callback + /// passed to `NativeFinalizer` constructor or `Pointer.atTypedList` + /// method. + /// + /// For example to automatically free native memory when the Dart object + /// wrapping it is reclaimed by GC: + /// + /// ```dart + /// class Wrapper implements Finalizable { + /// static final finalizer = NativeFinalizer(malloc.nativeFree); + /// + /// final Pointer data; + /// + /// Wrapper() : data = malloc.allocate(length) { + /// finalizer.attach(this, data); + /// } + /// } + /// ``` + /// + /// or to free native memory that is owned by a typed list: + /// + /// ```dart + /// malloc.allocate(n).asTypedList(n, finalizer: malloc.nativeFree) + /// ``` + /// + Pointer get nativeFree => + Platform.isWindows ? winCoTaskMemFreePointer : posixFreePointer; } /// Manages memory on the native heap. @@ -90,7 +123,7 @@ class _MallocAllocator implements Allocator { /// /// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc` and `CoTaskMemFree`. -const Allocator malloc = _MallocAllocator(); +const MallocAllocator malloc = MallocAllocator._(); /// Manages memory on the native heap. /// @@ -98,8 +131,8 @@ const Allocator malloc = _MallocAllocator(); /// /// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc` and `CoTaskMemFree`. -class _CallocAllocator implements Allocator { - const _CallocAllocator(); +final class CallocAllocator implements Allocator { + const CallocAllocator._(); /// Fills a block of memory with a specified value. void _fillMemory(Pointer destination, int length, int fill) { @@ -153,6 +186,37 @@ class _CallocAllocator implements Allocator { posixFree(pointer); } } + + /// Returns a pointer to a native free function. + /// + /// This function can be used to release memory allocated by [allocated] + /// from the native side. It can also be used as a finalization callback + /// passed to `NativeFinalizer` constructor or `Pointer.atTypedList` + /// method. + /// + /// For example to automatically free native memory when the Dart object + /// wrapping it is reclaimed by GC: + /// + /// ```dart + /// class Wrapper implements Finalizable { + /// static final finalizer = NativeFinalizer(calloc.nativeFree); + /// + /// final Pointer data; + /// + /// Wrapper() : data = calloc.allocate(length) { + /// finalizer.attach(this, data); + /// } + /// } + /// ``` + /// + /// or to free native memory that is owned by a typed list: + /// + /// ```dart + /// calloc.allocate(n).asTypedList(n, finalizer: calloc.nativeFree) + /// ``` + /// + Pointer get nativeFree => + Platform.isWindows ? winCoTaskMemFreePointer : posixFreePointer; } /// Manages memory on the native heap. @@ -162,4 +226,4 @@ class _CallocAllocator implements Allocator { /// /// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc` and `CoTaskMemFree`. -const Allocator calloc = _CallocAllocator(); +const CallocAllocator calloc = CallocAllocator._(); diff --git a/lib/src/utf16.dart b/lib/src/utf16.dart index 25c22ed..e5b3309 100644 --- a/lib/src/utf16.dart +++ b/lib/src/utf16.dart @@ -13,7 +13,7 @@ import 'package:ffi/ffi.dart'; /// through a `Pointer` representing the entire array. This pointer is /// the equivalent of a char pointer (`const wchar_t*`) in C code. The /// individual UTF-16 code units are stored in native byte order. -class Utf16 extends Opaque {} +final class Utf16 extends Opaque {} /// Extension method for converting a`Pointer` to a [String]. extension Utf16Pointer on Pointer { diff --git a/lib/src/utf8.dart b/lib/src/utf8.dart index 49c1cd3..cdf0e7e 100644 --- a/lib/src/utf8.dart +++ b/lib/src/utf8.dart @@ -13,7 +13,7 @@ import 'package:ffi/ffi.dart'; /// The Utf8 type itself has no functionality, it's only intended to be used /// through a `Pointer` representing the entire array. This pointer is /// the equivalent of a char pointer (`const char*`) in C code. -class Utf8 extends Opaque {} +final class Utf8 extends Opaque {} /// Extension method for converting a`Pointer` to a [String]. extension Utf8Pointer on Pointer { diff --git a/pubspec.yaml b/pubspec.yaml index ac21a95..956620e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: ffi -version: 2.0.2 +version: 2.1.0 description: Utilities for working with Foreign Function Interface (FFI) code. repository: https://github.com/dart-lang/ffi @@ -9,7 +9,7 @@ topics: - codegen environment: - sdk: '>=2.17.0 <4.0.0' + sdk: '>=3.0.0 <4.0.0' dev_dependencies: test: ^1.21.2 diff --git a/test/allocation_test.dart b/test/allocation_test.dart index 3fbb82d..899b801 100644 --- a/test/allocation_test.dart +++ b/test/allocation_test.dart @@ -38,4 +38,13 @@ void main() async { // amount of addressable memory on the system. expect(() => calloc(-1), throwsA(isA())); }); + + test('nativeFree', () { + // malloc.nativeFree should be able to free memory allocated by malloc. + final ptr1 = malloc.allocate(1024); + malloc.nativeFree.asFunction)>()(ptr1.cast()); + // calloc.nativeFree should be able to free memory allocated by calloc. + final ptr2 = calloc.allocate(1024); + calloc.nativeFree.asFunction)>()(ptr2.cast()); + }); }