| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 528f8b2 commit 58dc92f
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -182,6 +182,21 @@ The returned object contains: | |||
| 182 | 182 | * `lib` {DynamicLibrary} The loaded library handle. | |
| 183 | 183 | * `functions` {Object} Callable wrappers for the requested symbols. | |
| 184 | 184 | ||
| 185 | + The returned object also implements the explicit resource management protocol, | ||
| 186 | + so it can be used with the [`using`][] declaration. Disposing the returned | ||
| 187 | + object closes the library handle. | ||
| 188 | + | ||
| 189 | + ```mjs | ||
| 190 | + import { dlopen } from 'node:ffi'; | ||
| 191 | + | ||
| 192 | + { | ||
| 193 | + using handle = dlopen('./mylib.so', { | ||
| 194 | + add_i32: { parameters: ['i32', 'i32'], result: 'i32' }, | ||
| 195 | + }); | ||
| 196 | + console.log(handle.functions.add_i32(20, 22)); | ||
| 197 | + } // handle.lib.close() is invoked automatically here. | ||
| 198 | + ``` | ||
| 199 | + | ||
| 185 | 200 | ```mjs | |
| 186 | 201 | import { dlopen } from 'node:ffi'; | |
| 187 | 202 | ||
@@ -275,6 +290,21 @@ An object containing previously resolved symbol addresses as `bigint` values. | |||
| 275 | 290 | ||
| 276 | 291 | Closes the library handle. | |
| 277 | 292 | ||
| 293 | + `DynamicLibrary` implements the explicit resource management protocol, so a | ||
| 294 | + library instance can be managed with the [`using`][] declaration. Leaving the | ||
| 295 | + enclosing scope invokes `library.close()` automatically. | ||
| 296 | + | ||
| 297 | + ```mjs | ||
| 298 | + import { DynamicLibrary } from 'node:ffi'; | ||
| 299 | + | ||
| 300 | + { | ||
| 301 | + using lib = new DynamicLibrary('./mylib.so'); | ||
| 302 | + // Use `lib` here; `lib.close()` is called when the block exits. | ||
| 303 | + } | ||
| 304 | + ``` | ||
| 305 | + | ||
| 306 | + Calling `library.close()` (or disposing the library) more than once is a no-op. | ||
| 307 | + | ||
| 278 | 308 | After a library has been closed: | |
| 279 | 309 | ||
| 280 | 310 | * Resolved function wrappers become invalid. | |
@@ -295,6 +325,16 @@ Calling `library.close()` from one of the library's active callbacks is | |||
| 295 | 325 | unsupported and dangerous. The callback must return before the library is | |
| 296 | 326 | closed. | |
| 297 | 327 | ||
| 328 | + ### `library[Symbol.dispose]()` | ||
| 329 | + | ||
| 330 | + <!-- YAML | ||
| 331 | + added: REPLACEME | ||
| 332 | + --> | ||
| 333 | + | ||
| 334 | + Calls `library.close()`. This allows `DynamicLibrary` instances to be used with | ||
| 335 | + the [`using`][] declaration for automatic cleanup when the enclosing scope | ||
| 336 | + exits. It is a no-op on a library that has already been closed. | ||
| 337 | + | ||
| 298 | 338 | ### `library.getFunction(name, signature)` | |
| 299 | 339 | ||
| 300 | 340 | * `name` {string} | |
@@ -684,3 +724,4 @@ and keep callback and pointer lifetimes explicit on the native side. | |||
| 684 | 724 | [Permission Model]: permissions.md#permission-model | |
| 685 | 725 | [`--allow-ffi`]: cli.md#--allow-ffi | |
| 686 | 726 | [`ffi.toBuffer(pointer, length, copy)`]: #ffitobufferpointer-length-copy | |
| 727 | + [`using`]: https://tc39.es/proposal-explicit-resource-management/#sec-using-declarations | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,6 +3,7 @@ | |||
| 3 | 3 | const { | |
| 4 | 4 | ObjectFreeze, | |
| 5 | 5 | ObjectPrototypeToString, | |
| 6 | + SymbolDispose, | ||
| 6 | 7 | } = primordials; | |
| 7 | 8 | const { Buffer } = require('buffer'); | |
| 8 | 9 | const { emitExperimentalWarning } = require('internal/util'); | |
@@ -55,6 +56,10 @@ const { | |||
| 55 | 56 | ||
| 56 | 57 | require('internal/ffi-shared-buffer'); | |
| 57 | 58 | ||
| 59 | + DynamicLibrary.prototype[SymbolDispose] = function() { | ||
| 60 | + this.close(); | ||
| 61 | + }; | ||
| 62 | + | ||
| 58 | 63 | function checkFFIPermission() { | |
| 59 | 64 | if (!permission.isEnabled() || permission.has('ffi')) { | |
| 60 | 65 | return; | |
@@ -71,7 +76,11 @@ function dlopen(path, definitions) { | |||
| 71 | 76 | const lib = new DynamicLibrary(path); | |
| 72 | 77 | try { | |
| 73 | 78 | const functions = definitions === undefined ? ObjectFreeze({ __proto__: null }) : lib.getFunctions(definitions); | |
| 74 | - return { lib, functions }; | ||
| 79 | + return { | ||
| 80 | + lib, | ||
| 81 | + functions, | ||
| 82 | + [SymbolDispose]() { lib.close(); }, | ||
| 83 | + }; | ||
| 75 | 84 | } catch (error) { | |
| 76 | 85 | lib.close(); | |
| 77 | 86 | throw error; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -129,6 +129,65 @@ test('closed libraries reject subsequent operations', () => { | |||
| 129 | 129 | assert.throws(() => lib.getSymbol('add_i32'), /Library is closed/); | |
| 130 | 130 | }); | |
| 131 | 131 | ||
| 132 | + test('DynamicLibrary supports Symbol.dispose', () => { | ||
| 133 | + const lib = new ffi.DynamicLibrary(libraryPath); | ||
| 134 | + const addI32 = lib.getFunction('add_i32', fixtureSymbols.add_i32); | ||
| 135 | + | ||
| 136 | + assert.strictEqual(typeof lib[Symbol.dispose], 'function'); | ||
| 137 | + assert.strictEqual(addI32(20, 22), 42); | ||
| 138 | + | ||
| 139 | + lib[Symbol.dispose](); | ||
| 140 | + | ||
| 141 | + assert.throws(() => addI32(1, 2), /Library is closed/); | ||
| 142 | + assert.throws(() => lib.getSymbol('add_i32'), /Library is closed/); | ||
| 143 | + | ||
| 144 | + // Disposing twice is a no-op. | ||
| 145 | + lib[Symbol.dispose](); | ||
| 146 | + lib.close(); | ||
| 147 | + }); | ||
| 148 | + | ||
| 149 | + test('using declaration closes DynamicLibrary on scope exit', () => { | ||
| 150 | + let captured; | ||
| 151 | + { | ||
| 152 | + using lib = new ffi.DynamicLibrary(libraryPath); | ||
| 153 | + captured = lib.getFunction('add_i32', fixtureSymbols.add_i32); | ||
| 154 | + assert.strictEqual(captured(20, 22), 42); | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + assert.throws(() => captured(1, 2), /Library is closed/); | ||
| 158 | + }); | ||
| 159 | + | ||
| 160 | + test('dlopen return value is disposable', () => { | ||
| 161 | + let capturedLib; | ||
| 162 | + let capturedFn; | ||
| 163 | + { | ||
| 164 | + using handle = ffi.dlopen(libraryPath, { | ||
| 165 | + add_i32: fixtureSymbols.add_i32, | ||
| 166 | + }); | ||
| 167 | + assert.strictEqual(typeof handle[Symbol.dispose], 'function'); | ||
| 168 | + capturedLib = handle.lib; | ||
| 169 | + capturedFn = handle.functions.add_i32; | ||
| 170 | + assert.strictEqual(capturedFn(20, 22), 42); | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + assert.throws(() => capturedFn(1, 2), /Library is closed/); | ||
| 174 | + assert.throws(() => capturedLib.getSymbol('add_i32'), /Library is closed/); | ||
| 175 | + }); | ||
| 176 | + | ||
| 177 | + test('using still disposes DynamicLibrary when block throws', () => { | ||
| 178 | + const sentinel = new Error('boom'); | ||
| 179 | + let captured; | ||
| 180 | + | ||
| 181 | + assert.throws(() => { | ||
| 182 | + using lib = new ffi.DynamicLibrary(libraryPath); | ||
| 183 | + captured = lib.getFunction('add_i32', fixtureSymbols.add_i32); | ||
| 184 | + assert.strictEqual(captured(20, 22), 42); | ||
| 185 | + throw sentinel; | ||
| 186 | + }, sentinel); | ||
| 187 | + | ||
| 188 | + assert.throws(() => captured(1, 2), /Library is closed/); | ||
| 189 | + }); | ||
| 190 | + | ||
| 132 | 191 | test('dynamic library APIs validate failures and bad signatures', () => { | |
| 133 | 192 | assert.throws(() => { | |
| 134 | 193 | ffi.dlopen('missing-library-for-ffi-tests.so'); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments