win32-def
Version:
win32 definitions for node-ffi
310 lines (304 loc) • 10.2 kB
JavaScript
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