一、需求
在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服務端在同一機器中的場景