windows-shortcut-ffi
v1.0.0
Published
Create and query Windows .lnk shortcuts from Node.js through node:ffi. No C++, no native addon, no child process.
Maintainers
Readme
windows-shortcut-ffi
English | 简体中文
在 Windows 上创建 / 读取 .lnk 快捷方式。功能对标 windows-shortcut-napi,
但不使用任何 C++ / Node-API 原生插件,全部通过 Node.js 内置的 node:ffi 直接调用 Win32 与 COM。
- 纯 JavaScript(CommonJS),零第三方依赖
- 路径、参数、描述以 UTF-16 直接传给 Windows Shell Link API,不受控制台 ANSI 代码页限制,中文/日文等路径不会乱码
- 自带 TypeScript 声明
前置条件
- 仅 Windows。
- Node.js 构建需带
node:ffi,并且运行时要显式开启实验开关:
set NODE_OPTIONS=--experimental-ffi
node your-app.js# PowerShell
$env:NODE_OPTIONS = "--experimental-ffi"
node your-app.jsnode:ffi 是实验特性,启动时会打印 ExperimentalWarning: FFI is an experimental feature,
可用 --no-warnings 抑制,或在 NODE_OPTIONS 里一起加上。若未开启开关,本库在 require 时会给出明确报错而不是莫名其妙地失败。
安装
npm install windows-shortcut-ffi用法
const shortcut = require('windows-shortcut-ffi');
// 基础用法
shortcut.create('C:/Users/YourName/Desktop/Notepad.lnk', 'C:/Windows/System32/notepad.exe');
// 完整选项
shortcut.create('C:/Users/YourName/Desktop/Notepad.lnk', {
target: 'C:/Windows/System32/notepad.exe',
args: 'C:/temp/示例.txt',
workingDir: 'C:/temp',
runStyle: shortcut.SW_SHOWNORMAL,
icon: 'C:/Windows/System32/shell32.dll',
iconIndex: 0,
hotkey: 0,
desc: '用记事本打开示例.txt',
});
// 读取
const info = shortcut.query('C:/Users/YourName/Desktop/Notepad.lnk');
// { target, args, workingDir, icon, iconIndex, hotkey, runStyle }TypeScript:
import * as shortcut from 'windows-shortcut-ffi';
shortcut.create('C:/temp/App.lnk', {
target: 'C:/Program Files/My App/app.exe',
args: '--profile default',
runStyle: shortcut.SW_SHOWMAXIMIZED,
});API
create(path, target)
创建指向 target 的快捷方式。
create(path, options)
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| target | string | 目标文件或可执行程序路径(必填) |
| args | string \| null | 命令行参数 |
| workingDir | string \| null | 工作目录 |
| runStyle | number \| null | 窗口显示方式 |
| icon | string \| null | 图标来源路径 |
| iconIndex | number \| null | 图标资源索引 |
| hotkey | number \| null | 快捷键,直接透传给 Shell Link API |
| desc | string \| null | 快捷方式描述 |
query(path)
返回 { target, args, workingDir, icon, iconIndex, hotkey, runStyle }。
常量
SW_SHOWNORMALSW_SHOWMAXIMIZEDSW_SHOWMINNOACTIVE
错误
参数非法抛 TypeError;原生调用失败抛 ShortcutError:
try {
shortcut.create('C:/invalid/path/test.lnk', { target: 'C:/missing/file.exe' });
} catch (error) {
if (error instanceof shortcut.ShortcutError) {
console.error(error.message); // 含 HRESULT
console.error('reason:', error.reason); // 例如 "ERROR_PATH_NOT_FOUND: The system cannot find the path specified."
console.error('status:', error.status); // 内部阶段码
console.error('hr:', error.hr); // 无符号 32 位 HRESULT
}
}实现原理
COM 接口在内存中的形态是:对象首字段是虚表指针,虚表里是函数指针数组。C 里 p->lpVtbl->SetPath(p, path)
就是「读虚表第 N 项,按地址调用」。
node:ffi 只能按符号名解析函数(library.getFunction(name, sig),内部走 GetProcAddress),
没有把运行时读出来的地址变成可调用 JS 函数的入口,所以无法直接跳转调用虚表里的函数指针,
也不允许为此写 C++ 跳板。
因此本库用 oleaut32!DispCallFunc 作为通用虚表调用器 —— 它接收「对象指针 + 虚表字节偏移 + 参数 VARIANT 数组」,
内部完成的就是上面那条调用链,语义等价于 C 的直接调用,只是这段跳板由系统 DLL 提供:
HRESULT DispCallFunc(void* pvInstance, ULONG_PTR oVft, CALLCONV cc,
VARTYPE vtReturn, UINT cActuals, VARTYPE* prgvt,
VARIANTARG** prgpvarg, VARIANT* pvargResult);其余部分:
ole32.dll:CoInitializeEx/CoUninitialize/CoCreateInstanceIShellLinkW、IPersistFile的虚表槽位在lib/vtable.js中声明(对照 Windows SDK 头文件核实)- 原生内存用
Buffer+ffi.getRawPointer(),由 GC 管理,无需手动释放也不会泄漏 - 字符串用
ffi.exportString(str, ptr, len, 'utf16le')写入,即 NUL 结尾的 UTF-16LE
测试
npm test # node --experimental-ffi test/smoke.js
npm run probe # 只验证 DispCallFunc 这一条调用通路是否可用
npm run example # 在当前目录创建 Notepad.lnk许可
ISC
