一、需求
在windows中运行网页+nodejs服务时,
在网页端请求nodejs接口,
唤起文件/文件夹选择窗口,
将选择的文件/目录实际路径显示在网页中
(非C:/fakepath)
二、流程图
三、C++实现
#include <windows.h>
#include <shobjidl.h> // 包含 IFileDialog 接口所需的头文件
#include <iostream>
#include <string>
std::string WStringToUTF8(const std::wstring& wstr) {
if (wstr.empty()) return std::string();
int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
std::string strTo(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
return strTo;
}
// 将路径中的 '\' 替换为 '\\\\'
std::string EscapeBackslashes(const std::string& path) {
std::string escapedPath;
for (char ch : path) {
if (ch == '\\') {
escapedPath += "\\\\\\\\"; // 替换为四个反斜杠
} else {
escapedPath += ch;
}
}
return escapedPath;
}
void OpenFileDialog() {
// 初始化 COM 库
CoInitialize(nullptr);
// 创建 IFileDialog 对象
IFileDialog* pFileDialog = nullptr;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, IID_IFileDialog, reinterpret_cast<void**>(&pFileDialog));
if (SUCCEEDED(hr)) {
// 显示文件对话框
hr = pFileDialog->Show(nullptr);
if (SUCCEEDED(hr)) {
IShellItem* pItem;
hr = pFileDialog->GetResult(&pItem);
if (SUCCEEDED(hr)) {
// 获取选定文件的路径
LPWSTR pszName;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszName);
if (SUCCEEDED(hr)) {
std::wstring filePath(pszName);
CoTaskMemFree(pszName); // 释放内存
pItem->Release(); // 释放 IShellItem 对象
pFileDialog->Release(); // 释放 IFileDialog 对象
CoUninitialize(); // 释放 COM 库
std::string filePathStr = WStringToUTF8(filePath);
filePathStr = EscapeBackslashes(filePathStr);
std::cout << "{\"selectedFile\":\"" << filePathStr << "\"}" << std::endl;
return;
}
pItem->Release();
}
}
pFileDialog->Release(); // 释放 IFileDialog 对象
}
CoUninitialize(); // 释放 COM 库
std::cout << "{\"selectedFile\":\"\"}" << std::endl;
}
void SelectFolderDialog() {
// 初始化 COM 库
CoInitialize(nullptr);
// 创建 IFileDialog 对象
IFileDialog* pFileDialog = nullptr;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, IID_IFileDialog, reinterpret_cast<void**>(&pFileDialog));
if (SUCCEEDED(hr)) {
// 设置对话框为文件夹选择模式
pFileDialog->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM);
// 显示对话框
hr = pFileDialog->Show(nullptr);
if (SUCCEEDED(hr)) {
IShellItem* pItem;
hr = pFileDialog->GetResult(&pItem);
if (SUCCEEDED(hr)) {
// 获取选定文件夹的路径
LPWSTR pszName;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszName);
if (SUCCEEDED(hr)) {
std::wstring folderPath(pszName);
CoTaskMemFree(pszName); // 释放内存
pItem->Release(); // 释放 IShellItem 对象
pFileDialog->Release(); // 释放 IFileDialog 对象
CoUninitialize(); // 释放 COM 库
std::string folderPathStr = WStringToUTF8(folderPath);
folderPathStr = EscapeBackslashes(folderPathStr);
std::cout << "{\"selectedFolder\":\"" << folderPathStr << "\"}" << std::endl;
return;
}
pItem->Release();
}
}
pFileDialog->Release(); // 释放 IFileDialog 对象
}
CoUninitialize(); // 释放 COM 库
std::cout << "{\"selectedFolder\":\"\"}" << std::endl;
}
int main(int argc, char *argv[]) {
if (argc > 1 && std::string(argv[1]) == "selectFile") {
OpenFileDialog();
} else if (argc > 1 && std::string(argv[1]) == "selectFolder") {
SelectFolderDialog();
} else {
std::cout << "{}" << std::endl;
}
return 0;
}
四、使用MINGW64编译
g++ dialog.cpp -o dialog.exe -lole32 -lshell32 -luuid
如果此步骤报错,需要先在MINGW64中安装c++编译环境
pacman -Syu
pacman -S mingw-w64-x86_64-toolchain
五、调用方式
const { spawn } = require('child_process');
// 执行 dialog.exe
const child = spawn('./path/to/dialog.exe', ['selectFile']);
// 或者选择目录(文件夹)
// const child = spawn('./dialog.exe', ['selectFolder']);
// 处理标准输出
child.stdout.on('data', (data) => {
console.log(`输出: ${data}`);
});
// 处理标准错误输出
child.stderr.on('data', (data) => {
console.error(`错误: ${data}`);
});
// 处理进程结束
child.on('close', (code) => {
console.log(`子进程退出,代码: ${code}`);
});
六、局限
只能用于前端和nodejs服务端在同一机器中的场景
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。