From d0774776d2de4ba6e677744d64119a6f1fa0f9bb Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Mon, 7 Aug 2023 11:38:26 +0200 Subject: [PATCH 1/6] Expose pointer to the free from allocators --- lib/src/allocation.dart | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/lib/src/allocation.dart b/lib/src/allocation.dart index 8b58884..43c6a36 100644 --- a/lib/src/allocation.dart +++ b/lib/src/allocation.dart @@ -22,19 +22,25 @@ final PosixCalloc posixCalloc = typedef PosixFreeNative = Void Function(Pointer); typedef PosixFree = void Function(Pointer); -final PosixFree posixFree = - stdlib.lookupFunction('free'); +final Pointer> posixFreePtr = + stdlib.lookup('free'); +final PosixFree posixFree = posixFreePtr.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> winCoTaskMemFreePtr = + stdlib.lookup('CoTaskMemFree'); +final WinCoTaskMemFree winCoTaskMemFree = winCoTaskMemFreePtr.asFunction(); + +abstract class AllocatorExposingNativeFinalizer implements Allocator { + Pointer get nativeFree; +} /// Manages memory on the native heap. /// @@ -43,7 +49,7 @@ final WinCoTaskMemFree winCoTaskMemFree = stdlib /// /// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc`. -class _MallocAllocator implements Allocator { +class _MallocAllocator implements AllocatorExposingNativeFinalizer { const _MallocAllocator(); /// Allocates [byteCount] bytes of of unitialized memory on the native heap. @@ -81,6 +87,10 @@ class _MallocAllocator implements Allocator { posixFree(pointer); } } + + @override + Pointer get nativeFree => + Platform.isWindows ? winCoTaskMemFreePtr : posixFreePtr; } /// Manages memory on the native heap. @@ -90,7 +100,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 AllocatorExposingNativeFinalizer malloc = _MallocAllocator(); /// Manages memory on the native heap. /// @@ -98,7 +108,7 @@ const Allocator malloc = _MallocAllocator(); /// /// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc` and `CoTaskMemFree`. -class _CallocAllocator implements Allocator { +class _CallocAllocator implements AllocatorExposingNativeFinalizer { const _CallocAllocator(); /// Fills a block of memory with a specified value. @@ -153,6 +163,10 @@ class _CallocAllocator implements Allocator { posixFree(pointer); } } + + @override + Pointer get nativeFree => + Platform.isWindows ? winCoTaskMemFreePtr : posixFreePtr; } /// Manages memory on the native heap. @@ -162,4 +176,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 AllocatorExposingNativeFinalizer calloc = _CallocAllocator(); From 2fdf7910a414cd5e77983c778802b4467733d101 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Mon, 7 Aug 2023 13:38:09 +0200 Subject: [PATCH 2/6] * Address comments * Bump version and update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ lib/src/allocation.dart | 10 +++++----- lib/src/utf16.dart | 2 +- lib/src/utf8.dart | 2 +- pubspec.yaml | 4 ++-- 5 files changed, 15 insertions(+), 9 deletions(-) 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 43c6a36..82a0cb1 100644 --- a/lib/src/allocation.dart +++ b/lib/src/allocation.dart @@ -38,7 +38,7 @@ final Pointer> winCoTaskMemFreePtr = stdlib.lookup('CoTaskMemFree'); final WinCoTaskMemFree winCoTaskMemFree = winCoTaskMemFreePtr.asFunction(); -abstract class AllocatorExposingNativeFinalizer implements Allocator { +abstract interface class NativeFreeableAllocator implements Allocator { Pointer get nativeFree; } @@ -49,7 +49,7 @@ abstract class AllocatorExposingNativeFinalizer implements Allocator { /// /// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc`. -class _MallocAllocator implements AllocatorExposingNativeFinalizer { +class _MallocAllocator implements NativeFreeableAllocator { const _MallocAllocator(); /// Allocates [byteCount] bytes of of unitialized memory on the native heap. @@ -100,7 +100,7 @@ class _MallocAllocator implements AllocatorExposingNativeFinalizer { /// /// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc` and `CoTaskMemFree`. -const AllocatorExposingNativeFinalizer malloc = _MallocAllocator(); +const NativeFreeableAllocator malloc = _MallocAllocator(); /// Manages memory on the native heap. /// @@ -108,7 +108,7 @@ const AllocatorExposingNativeFinalizer malloc = _MallocAllocator(); /// /// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc` and `CoTaskMemFree`. -class _CallocAllocator implements AllocatorExposingNativeFinalizer { +class _CallocAllocator implements NativeFreeableAllocator { const _CallocAllocator(); /// Fills a block of memory with a specified value. @@ -176,4 +176,4 @@ class _CallocAllocator implements AllocatorExposingNativeFinalizer { /// /// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc` and `CoTaskMemFree`. -const AllocatorExposingNativeFinalizer calloc = _CallocAllocator(); +const NativeFreeableAllocator 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 From 85dc66960444b7eff96de75784c4bc92b5ed4ab1 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Mon, 7 Aug 2023 13:41:01 +0200 Subject: [PATCH 3/6] Update testing workflow --- .github/workflows/test-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From af210683664608a075c1b8b8d8f0eb725b995b74 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Tue, 8 Aug 2023 09:19:09 +0200 Subject: [PATCH 4/6] Address comments --- lib/src/allocation.dart | 42 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/src/allocation.dart b/lib/src/allocation.dart index 82a0cb1..f0f19a8 100644 --- a/lib/src/allocation.dart +++ b/lib/src/allocation.dart @@ -22,9 +22,9 @@ final PosixCalloc posixCalloc = typedef PosixFreeNative = Void Function(Pointer); typedef PosixFree = void Function(Pointer); -final Pointer> posixFreePtr = +final Pointer> posixFreePointer = stdlib.lookup('free'); -final PosixFree posixFree = posixFreePtr.asFunction(); +final PosixFree posixFree = posixFreePointer.asFunction(); typedef WinCoTaskMemAllocNative = Pointer Function(Size); typedef WinCoTaskMemAlloc = Pointer Function(int); @@ -34,13 +34,9 @@ final WinCoTaskMemAlloc winCoTaskMemAlloc = typedef WinCoTaskMemFreeNative = Void Function(Pointer); typedef WinCoTaskMemFree = void Function(Pointer); -final Pointer> winCoTaskMemFreePtr = +final Pointer> winCoTaskMemFreePointer = stdlib.lookup('CoTaskMemFree'); -final WinCoTaskMemFree winCoTaskMemFree = winCoTaskMemFreePtr.asFunction(); - -abstract interface class NativeFreeableAllocator implements Allocator { - Pointer get nativeFree; -} +final WinCoTaskMemFree winCoTaskMemFree = winCoTaskMemFreePointer.asFunction(); /// Manages memory on the native heap. /// @@ -49,8 +45,8 @@ abstract interface class NativeFreeableAllocator implements Allocator { /// /// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc`. -class _MallocAllocator implements NativeFreeableAllocator { - const _MallocAllocator(); +final class MallocAllocator implements Allocator { + const MallocAllocator._(); /// Allocates [byteCount] bytes of of unitialized memory on the native heap. /// @@ -88,9 +84,14 @@ class _MallocAllocator implements NativeFreeableAllocator { } } - @override + /// 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. Pointer get nativeFree => - Platform.isWindows ? winCoTaskMemFreePtr : posixFreePtr; + Platform.isWindows ? winCoTaskMemFreePointer : posixFreePointer; } /// Manages memory on the native heap. @@ -100,7 +101,7 @@ class _MallocAllocator implements NativeFreeableAllocator { /// /// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc` and `CoTaskMemFree`. -const NativeFreeableAllocator malloc = _MallocAllocator(); +const MallocAllocator malloc = MallocAllocator._(); /// Manages memory on the native heap. /// @@ -108,8 +109,8 @@ const NativeFreeableAllocator malloc = _MallocAllocator(); /// /// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc` and `CoTaskMemFree`. -class _CallocAllocator implements NativeFreeableAllocator { - 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) { @@ -164,9 +165,14 @@ class _CallocAllocator implements NativeFreeableAllocator { } } - @override + /// 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. Pointer get nativeFree => - Platform.isWindows ? winCoTaskMemFreePtr : posixFreePtr; + Platform.isWindows ? winCoTaskMemFreePointer : posixFreePointer; } /// Manages memory on the native heap. @@ -176,4 +182,4 @@ class _CallocAllocator implements NativeFreeableAllocator { /// /// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses /// `CoTaskMemAlloc` and `CoTaskMemFree`. -const NativeFreeableAllocator calloc = _CallocAllocator(); +const CallocAllocator calloc = CallocAllocator._(); From 018c9ba4ef83333b0aee8909d06c9e7bc44a100a Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Tue, 8 Aug 2023 10:17:58 +0200 Subject: [PATCH 5/6] Update docs --- lib/src/allocation.dart | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lib/src/allocation.dart b/lib/src/allocation.dart index f0f19a8..4202145 100644 --- a/lib/src/allocation.dart +++ b/lib/src/allocation.dart @@ -90,6 +90,28 @@ final class MallocAllocator implements Allocator { /// 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; } @@ -171,6 +193,28 @@ final class CallocAllocator implements Allocator { /// 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; } From 9cb773a897e743e66a749c155fc66c41416ba91d Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Tue, 8 Aug 2023 10:31:25 +0200 Subject: [PATCH 6/6] Add test --- test/allocation_test.dart | 9 +++++++++ 1 file changed, 9 insertions(+) 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()); + }); }