UNPKG

win32-def

Version:
310 lines (304 loc) 10.2 kB
import assert from 'node:assert'; import koffi from 'koffi'; import { loadOptionsDefault } from '../../lib/config.js'; import { Def } from '../../lib/def.enum.js'; import { CallingConvention } from '../../lib/ffi.types.js'; import { structFactoryMap } from '../../lib/struct/struct.factory-map.js'; import { expandFFIParamArray } from '../ffi.js'; import { LoaderCache } from './loader.cache.js'; import { createProxyMethod, updateMultipleChoiceMapperToCache } from './multiple-choice-param.helper.js'; export const isArch64 = process.arch.includes('64'); export const defGroupNumber = [ Def.float, Def.int16, Def.int32, Def.int64, Def.int8, Def.uint16, Def.uint32, Def.uint64, Def.uint8, Def.long, Def.ulong, Def.longlong, Def.ulonglong, ]; export const defGroupPointer = [ Def.boolPtr, Def.bytePtr, Def.charPtr, Def.intPtr, Def.int8Ptr, Def.int16Ptr, Def.int32Ptr, Def.int64Ptr, Def.floatPtr, Def.longPtr, Def.uintPtr, Def.uint8Ptr, Def.intPtrPtr, Def.uint16Ptr, Def.uint32Ptr, Def.uint64Ptr, Def.ulonglongPtr, Def.voidPtr, Def.uintPtrPtr, Def.uint16PtrPtr, Def.uint32PtrPtr, Def.uint64PtrPtr, Def.ulonglongPtrPtr, Def.voidPtrPtr, ]; // #region loadIKoffiLibAndBindProperties export function loadIKoffiLib(libName) { const lib = koffi.load(libName); assert(lib, `load library ${libName} failed`); LoaderCache.setLibByName(libName, lib); return lib; } export function bindFLibExtMethods(libName, lib, inst) { assert(lib, `load library undefined`); if (typeof inst.unload === 'undefined') { Object.defineProperty(inst, 'unload', { enumerable: false, value: () => { LoaderCache.removeLibByName(libName); lib.unload(); }, }); } if (typeof inst.updateMultipleChoiceMapper === 'undefined') { Object.defineProperty(inst, 'updateMultipleChoiceMapper', { enumerable: false, value: function (opts2) { updateMultipleChoiceMapperToCache({ ...opts2, lib: this, }); }.bind(lib), }); } } // #region bindMethodsFromFuncDefList export function bindMethodsFromFuncDefList(options) { const { lib, inst, loadOptions, funcDefList, multipleChoiceMapperList: multipleChoiceMapper } = options; for (const [name, params] of funcDefList) { const funcs = registerFunction({ lib, fnName: name, fnFullParams: params, /* c8 ignore next */ convention: loadOptions.convention ?? CallingConvention.Cdecl, forceRegister: !!loadOptions.forceRegister, }); const { size } = funcs; if (size === 1) { for (const [, fn] of funcs.entries()) { bindMethod(inst, name, fn); } } else if (size > 1) { if (!multipleChoiceMapper) { console.info(`multipleChoiceMapper should be set and be a function when multiple choice used. Syntax: (fnName: string, params: string[]) => string[] const user32 = load({ dll: 'user32.dll', dllFuncs: defWin32, multipleChoiceMapperList: multipleChoiceMapperList }) // OR const user32 = load({ dll: 'user32.dll', dllFuncs: defWin32, }) user32.updateMultipleChoiceMapper({ mapperList: mapperList }) `); } const fn = createProxyMethod({ lib, name, fnDefRetType: params[0], fnDefCallParams: params[1], }); bindMethod(inst, name, fn); } } } function bindMethod(inst, name, fn) { const nameSync = name; if (typeof inst[nameSync] === 'undefined') { Object.defineProperty(inst, nameSync, { enumerable: true, value: fn, }); } const nameAsync = `${name}_Async`; if (typeof inst[nameAsync] === 'undefined') { Object.defineProperty(inst, nameAsync, { enumerable: true, value: (...args) => callFnAsync(fn, args), }); } } function callFnAsync(fn, args) { return new Promise((done, reject) => { const asyncCallback = (err, result) => { if (err) { reject(err); return; } done(result); }; fn.async(...args, asyncCallback); }); } export function parse_settings(options) { const opts = { ...loadOptionsDefault, ...options, }; return opts; } // #region createStructFromFuncDefList export function createStructFromFuncDefList(input) { const structFactories = prepareStructFromFuncDefList(input); structFactories.forEach((factory) => { factory(); }); } function prepareStructFromFuncDefList(input) { const fns = new Set(); for (const [name, params] of input) { try { const p2 = expandFFIParamArray(params[1]); p2.forEach((args) => { const structSet = retrieveStructFactoryFromParams(args); if (structSet.size) { structSet.forEach((fn) => { fns.add(fn); }); } }); } catch (ex) { assert(ex instanceof Error); const msg = `Failed to create struct for function: ${name}, you may need to create it manually. Error: ${ex.message}`; throw new Error(msg, { cause: ex }); } } return fns; } function retrieveStructFactoryFromParams(params) { const fns = new Set(); const structNames = retrieveStructTypeStringFromParams(params); if (!structNames.length) { return fns; } const failed = []; structNames.forEach((key) => { const factoryName = `${key}_Factory`; const fn = structFactoryMap.get(factoryName); if (fn) { fns.add(fn); } else { failed.push(key); } }); if (failed.length) { throw new Error(`Failed to find struct factory for: ${failed.join(', ')}`); } return fns; } const DefValuesSet = new Set(Object.values(Def)); // @ts-expect-error DefValuesSet.add('char'); function retrieveStructTypeStringFromParams(params) { const ret = []; // '_Inout_ POINT*' or 'POINT *' or 'POINT*' const regex = /\b(\w+)\s?\*$/u; params.map((val) => { const match = regex.exec(val); const key = match?.[1]?.trim(); if (key) { // if Def contains key, then skip // @ts-expect-error if (DefValuesSet.has(key)) { return; } ret.push(key); } }); return ret; } // #region registerFunction /** * @note do not call it directly, use `load()` instead! * Case of making sure the library is loaded only once */ function registerFunction(options) { const { lib, fnName, fnFullParams: params, convention = CallingConvention.Stdcall } = options; const cache = options.forceRegister ? null : LoaderCache.getRegisteredFuncMap(lib, fnName); if (cache?.size) { return cache; } const map = new Map(); const { [0]: retType, [1]: args } = params; if (args.length === 0) { const params2 = [retType, []]; // @ts-expect-error params2 is FnDefParams const func = _registerFunction({ lib, fnName: fnName, fnFullParams: params2, convention }); map.set([], func); return map; } // 对于 args 每个成员检查是否为数组,是则迭代处理每个组合进行多次绑定方法 const ps = expandFFIParamArray(args); ps.forEach((args2) => { const params2 = [retType, args2]; // @ts-expect-error params2 is FnDefParams const func = _registerFunction({ lib, fnName: fnName, fnFullParams: params2, convention }); map.set(args2, func); }); return map; } function _registerFunction(options) { const { lib, fnName, fnFullParams, convention = CallingConvention.Stdcall } = options; const { [0]: retType, [1]: args } = fnFullParams; assert(retType, 'retType is empty'); assert(args, 'args is empty'); // let func: KoffiFunction // const func = user32.func('GetCursorPos', 'int', [`_Out_ ${comb.pointer}`]) // switch (convention) { // case CallingConvention.Cdecl: // func = lib.func(fnName, retType, args) // break // default: // func = lib.func(convention, fnName, retType, args) // } const func = convention ? lib.func(convention, fnName, retType, args) : lib.func(fnName, retType, args); // console.log(func.info) LoaderCache.setRegisteredFuncToCache(lib, fnName, func, args); return func; } // #region SaveFnMultipleChoiceMapper export function saveFnMultipleChoiceMapperList(lib, fnMultipleChoiceMapperList) { assert(fnMultipleChoiceMapperList.size > 0, 'options.fnMultipleChoiceMapperList contains no item'); LoaderCache.updateMultipleChoiceListMapper(lib, fnMultipleChoiceMapperList); } /* export function prepareDllFile(file: string): string { if (file.startsWith('file://')) { return file } else if (file.startsWith('http://') || file.startsWith('https://')) { return file } else if (file.startsWith('/')) { return file } try { const stat = statSync(file) if (stat.isFile()) { return file } } catch { // void } const { HOME, WINDIR } = process.env assert(HOME, 'HOME is not defined') assert(WINDIR, 'WINDIR is not defined') const sys32dir = `${WINDIR}/system32` const path = `${sys32dir}/${file}` const target = `${HOME}/${file}.dll` const stat = statSync(path) if (! stat.isFile()) { throw new Error(`${file} is not found in path: "${path}"`) } try { const stat2 = statSync(target) if (stat2.isFile()) { return target } copyFileSync(path, target) } catch { copyFileSync(path, target) } return target } */ //# sourceMappingURL=loader.helper.js.map