From 76080f6c8caa4d8542f3fc63cc116ae86d5703c5 Mon Sep 17 00:00:00 2001 From: Anthony Beaumont Date: Sat, 27 Jan 2024 17:25:14 +0700 Subject: [PATCH] unify structure --- README.md | 61 ++++++++++++++++++++------------------ lib/ffi-napi/helper.js | 38 ++++++++++++++++++++++-- lib/koffi/helper.js | 45 ++++++++++++++++++++++++++-- test/dlopen.js | 19 ++++-------- test/load.js | 19 ++++-------- test/struct.js | 40 +++++++++++++++++++++++++ types/ffi-napi/helper.d.ts | 9 +++++- types/koffi/helper.d.ts | 9 +++++- 8 files changed, 176 insertions(+), 64 deletions(-) create mode 100644 test/struct.js diff --git a/README.md b/README.md index 5ebd9e0..ab1bc20 100644 --- a/README.md +++ b/README.md @@ -313,15 +313,15 @@ This is a class wrapper to the FFI library's callback function(s) inspired by De ##### Properties - - `pointer: unknown` + - `pointer: unknown` _(read only)_ The pointer to the callback. - - `address: number | BigInt | null` + - `address: number | BigInt | null` _(read only)_ The memory address of the pointer. - - `type: unknown` + - `type: unknown` _(read only)_ The type of the callback. @@ -409,10 +409,33 @@ const dylib = dlopen("shell32.dll", { }, { abi: "stdcall" }); ``` -#### `struct(schema: unknown): unknown` +#### `struct(schema: object): object` -Just a shorthand to define a structure. +💡 It is worth noting that while the goal of this lib is to write the same code with different FFI libraries; +when using Koffi you can just use Koffi's `struct()` function as Koffi converts JS objects to C structs, and vice-versa. +Define a structure. The returned object has 2 properties: + +- `type: unknown` + +The type of the struct. + +- `create: ()=> Class instance` + +Return an instance of a class wrapper to the FFI library's struct functions. + +##### Class properties + + - `pointer: unknown` _(read only)_ + + The pointer to the struct. + + - `values: object` + + Get or set the values of the struct. + +##### Example + ```js import { dlopen, types, struct, pointer } from "@xan105/ffi/[ napi | koffi ]"; @@ -424,33 +447,13 @@ const POINT = struct({ //define struct const dylib = dlopen("user32.dll", { //lib loading GetCursorPos: { result: types.win32.BOOL, - parameters: [ pointer(POINT, "out") ] //struct pointer + parameters: [ pointer(POINT.type, "out") ] //struct pointer } }, { abi: "stdcall" }); -``` - -⚠️ NB: Struct are use differently afterwards: - -- Koffi - -```js -const cursorPos = {}; -GetCursorPos(cursorPos); -console.log(cursorPos) -//{ x: 0, y: 0 } -``` - -- ffi-napi - -```js -const cursorPos = new POINT(); -GetCursorPos(cursorPos.ref()); - -//access the properties directly -console.log({ x: cursorPos.x, y: cursorPos.y }); //{ x: 0, y: 0 } -//or call .toObject()/.toJSON() (alias) to get a JS Object -console.log(cursorPos.toObject()); //{ x: 0, y: 0 } +const cursorPos = POINT.create(); +GetCursorPos(cursorPos.pointer); +console.log(cursorPos.values) //{ x: 0, y: 0 } ``` #### `alloc(type: unknown): { pointer: Buffer, get: ()=> unknown }` diff --git a/lib/ffi-napi/helper.js b/lib/ffi-napi/helper.js index b658126..8d6f06d 100644 --- a/lib/ffi-napi/helper.js +++ b/lib/ffi-napi/helper.js @@ -74,8 +74,42 @@ function pointer(value){ return ref.refType(value); } +class Struct{ + + #ref = null; + #value = null; + #members = null; + + constructor(type){ + this.#ref = type; + this.#value = new this.#ref(); + this.#members = Object.keys(this.#value.toObject()); + } + + get pointer(){ + return this.#value.ref(); + } + + set values(object){ + shouldObj(object); + for (const [ name, value ] of Object.entries(object)){ + if (this.#members.includes(name)) this.#value[name] = value; + } + } + + get values(){ + return this.#value.toObject(); + } +} + function struct(schema){ - return StructType(schema); + shouldObj(schema); + return Object.assign(Object.create(null),{ + type: StructType(schema), + create: function(){ + return new Struct(this.type); + } + }); } function alloc(type){ @@ -95,7 +129,7 @@ function lastError(option = {}){ export { Callback, - pointer, + pointer, struct, alloc, lastError diff --git a/lib/koffi/helper.js b/lib/koffi/helper.js index 6db02dc..59b7847 100644 --- a/lib/koffi/helper.js +++ b/lib/koffi/helper.js @@ -73,8 +73,47 @@ function pointer(value, direction = "in"){ } } +class Struct{ + + #ref = null; + #value = {}; + #members = null; + + constructor(type){ + this.#ref = type; + this.#members = Object.keys(koffi.introspect(this.#ref).members); + } + + get pointer(){ + return this.#value; + } + + set values(object){ + shouldObj(object); + for (const [ name, value ] of Object.entries(object)){ + if (this.#members.includes(name)) this.#value[name] = value; + } + } + + get values(){ + //Workaround to mimic ffi-napi output when empty/non-init by FFI lib + if(Object.keys(this.#value).length === 0){ + for (const { name, type } of Object.values(koffi.introspect(this.#ref).members)){ + this.#value[name] = alloc(type).get(); + } + } + return this.#value; + } +} + function struct(schema){ - return koffi.struct(schema); + shouldObj(schema); + return Object.assign(Object.create(null),{ + type: koffi.struct(schema), + create: function(){ + return new Struct(this.type); + } + }); } function alloc(type){ @@ -94,8 +133,8 @@ function lastError(option = {}){ export { Callback, - pointer, - struct, + pointer, + struct, alloc, lastError }; \ No newline at end of file diff --git a/test/dlopen.js b/test/dlopen.js index c063ca8..d9cb695 100644 --- a/test/dlopen.js +++ b/test/dlopen.js @@ -31,26 +31,17 @@ for (const [name, ffi] of Object.entries(APIs)) getCursorPos: { symbol: "GetCursorPos", result: ffi.types.win32.BOOL, - parameters: [ ffi.pointer(POINT, "out") ] + parameters: [ ffi.pointer(POINT.type, "out") ] } }, { abi: "stdcall" }); const expected = { x: 1, y: 1 }; setCursorPos(...Object.values(expected)); - if(name === "ffi-napi") - { - const cursorPos = new POINT(); - getCursorPos(cursorPos.ref()); - const actual = cursorPos.toObject(); - assert.deepEqual(actual, expected); - } - else - { - const actual = {}; - getCursorPos(actual); - assert.deepEqual(actual, expected); - } + const cursorPos = POINT.create(); + getCursorPos(cursorPos.pointer); + const actual = cursorPos.values; + assert.deepEqual(actual, expected); }); }; \ No newline at end of file diff --git a/test/load.js b/test/load.js index a5f2f99..f495bef 100644 --- a/test/load.js +++ b/test/load.js @@ -26,25 +26,16 @@ for (const [name, ffi] of Object.entries(APIs)) }); const getCursorPos = lib("GetCursorPos", ffi.types.win32.BOOL, [ - ffi.pointer(POINT, "out") + ffi.pointer(POINT.type, "out") ]); const expected = { x: 0, y: 0 }; setCursorPos(...Object.values(expected)); - if(name === "ffi-napi") - { - const cursorPos = new POINT(); - getCursorPos(cursorPos.ref()); - const actual = { x: cursorPos.x, y: cursorPos.y }; - assert.deepEqual(actual, expected); - } - else - { - const actual = {}; - getCursorPos(actual); - assert.deepEqual(actual, expected); - } + const cursorPos = POINT.create(); + getCursorPos(cursorPos.pointer); + const actual = cursorPos.values; + assert.deepEqual(actual, expected); }); }; \ No newline at end of file diff --git a/test/struct.js b/test/struct.js new file mode 100644 index 0000000..a766acc --- /dev/null +++ b/test/struct.js @@ -0,0 +1,40 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { isWindows } from "@xan105/is"; + +const APIs = { + koffi: await import("../lib/koffi/index.js"), + "ffi-napi": await import("../lib/ffi-napi/index.js") +}; + +for (const [name, ffi] of Object.entries(APIs)) +{ + + test(`[${name}] Struct: no values`, () => { + + const POINT = new ffi.struct({ + x: ffi.types.i32, + y: ffi.types.i32 + }); + + const expected = { x: 0, y: 0 }; + const cursorPos = POINT.create(); + const actual = cursorPos.values; + assert.deepEqual(actual, expected); + }); + + test(`[${name}] Struct: get/set values`, () => { + + const POINT = new ffi.struct({ + x: ffi.types.i32, + y: ffi.types.i32 + }); + + const expected = { x: 1, y: 2 }; + const cursorPos = POINT.create(); + cursorPos.values = { x: 1, y: 2, z: 3 }; + const actual = cursorPos.values; + assert.deepEqual(actual, expected); + }); + +}; \ No newline at end of file diff --git a/types/ffi-napi/helper.d.ts b/types/ffi-napi/helper.d.ts index 3c2e4d2..dd4a4b5 100644 --- a/types/ffi-napi/helper.d.ts +++ b/types/ffi-napi/helper.d.ts @@ -8,7 +8,14 @@ export class Callback { #private; } export function pointer(value: unknown): ref.Type>; -export function struct(schema: unknown): ref_struct.StructType; +declare class Struct { + constructor(schema: object); + get pointer(): ref_struct.StructType; + set values(object: object); + get values(): object; + #private; +} +export function struct(schema: object): { type: ref.Type, create: () => Struct }; export function alloc(type: unknown): { pointer: Buffer; get: () => unknown; diff --git a/types/koffi/helper.d.ts b/types/koffi/helper.d.ts index d632dc0..b562d56 100644 --- a/types/koffi/helper.d.ts +++ b/types/koffi/helper.d.ts @@ -8,7 +8,14 @@ export class Callback { #private; } export function pointer(value: unknown, direction?: string): koffi.IKoffiCType; -export function struct(schema: unknown): koffi.IKoffiCType; +declare class Struct { + constructor(type: koffi.IKoffiCType); + get pointer(): object; + set values(object: object); + get values(): object; + #private; +} +export function struct(schema: object): { type: koffi.IKoffiCType, create: () => Struct }; export function alloc(type: unknown): { pointer: Buffer; get: () => unknown;