一
前言
一些可以使用的工具
Unlocker
、LockHunter
、IObit Unlocker
,由于未实际使用过,这里不再展开介绍。handle64 "C:\Users\xxx\Desktop\demo.gif"
handle64 -nobanner -c 1CE8 -y -p 20392
三
自己编码实现
解锁文件
选项的逻辑。如果包含参数,说明程序是通过右键菜单运行的,根据传递的参数(即文件路径)执行相应的文件解锁操作。以下不展示全部代码,完整代码可在 前言
中的GitHub查看,全部逻辑都在 main.cpp 中。
unlockfile
的注册键,包含两个键值,一个默认项解锁文件
对应右键菜单显示的名称,一个Icon
设置为应用程序的地址对应右键菜单显示的图标。unlockfile
下添加名为command
的子键,值是程序路径和 "%1"(对应传递的文件路径参数用于文件解锁操作)。使用注册表时要特别注意文件编码,字符串类型转换的处理。
QVariant showInfo;
string appPath = QCoreApplication::applicationDirPath()
.replace(QRegExp("/"), "\\").toStdString() + "\\unlockfile.exe";
if (setRightMenu("unlockfile", "解锁文件", appPath))
{
showInfo = u8"注册表添加成功";
}
else
{
showInfo = u8"注册表添加失败, 请确保以管理员身份运行";
}
QMetaObject::invokeMethod(root, "showInfo", Q_ARG(QVariant, showInfo));/// <summary>
/// 设置右键菜单
/// </summary>
/// <param name="strRegKeyKey">注册键</param>
/// <param name="strRegKeyName">注册名</param>
/// <param name="strApplication">应用地址</param>
/// <returns>是否添加成功</returns>
bool setRightMenu(string strRegKeyKey, string strRegKeyName, string strApplication)
{
HKEY hresult;
string strRegKey = "*\\shell\\" + strRegKeyKey;
string strRegSubkey = strRegKey + "\\command";
string strApplicationValue = "\"" + strApplication + "\"" + " \"%1\"";
DWORD dwPos;
// 创建注册表键, 对应右键菜单项
if (RegCreateKeyEx(HKEY_CLASSES_ROOT, stringToWString(strRegKey.c_str()), 0,
NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_ALL_ACCESS, NULL, &hresult, &dwPos) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}// 创建注册表值, 对应右键菜单项显示的内容
if (RegSetValueEx(hresult, NULL, 0, REG_SZ, (BYTE*)stringToWString(strRegKeyName.c_str()), (wcslen(stringToWString(strApplicationValue.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}// 设置右键菜单图标
if (RegSetValueEx(hresult, stringToWString("Icon"), 0, REG_SZ, (BYTE*)stringToWString(strApplication.c_str()), (wcslen(stringToWString(strApplication.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}// 创建注册表子项键, 对应点击右键菜单项后的命令项
if (RegCreateKeyEx(HKEY_CLASSES_ROOT, stringToWString(strRegSubkey.c_str()), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_ALL_ACCESS, NULL, &hresult, &dwPos) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}// 创建注册表子项值, 对应点击右键菜单项后的具体执行命令
if (RegSetValueEx(hresult, NULL, 0, REG_SZ, (BYTE*)stringToWString(strApplicationValue.c_str()), (wcslen(stringToWString(strApplicationValue.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
RegCloseKey(hresult);
return true;
}
解锁文件
就是我们创建的:init()
进行初始化的操作,包括加载 Native API 和遍历系统中所有句柄。getFileObjectTypeNumber()
获取文件句柄对应的编号(句柄有很多种,比如窗口、文件、图标和菜单),经测试,不同系统版本的编号也有所不同:win11: 40 win10: 37 win7: 28
。init()
得到的系统所有句柄信息,只处理其中类型为文件且不属于系统进程的句柄。特别注意,在 ring3 级调用 NtQueryObject
会出现阻塞的情况,因此需要通过开一个线程增加超时处理,避免程序卡住。此外,由于是跨进程处理句柄,因此需要调用DuplicateHandle
方法。
/// <summary>
/// 查询对象信息
/// </summary>
/// <param name="lpParam">参数</param>
/// <returns>返回值</returns>
DWORD queryObj(LPVOID lpParam)
{
return NtQueryObject(hCopy, 1, pObject, MAX_PATH * 2, NULL);
}/// <summary>
/// 获取文件名
/// </summary>
/// <param name="hCopy">文件句柄</param>
/// <param name="hCopy">文件名</param>
void getFileName(string& fileName)
{
// 查找句柄对象信息并分配内存进行保存
pObject = (POBJECT_NAME_INFORMATION)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * 2);
if (pObject == 0)
{
HeapFree(GetProcessHeap(), 0, pObject);
return;
}// NtQueryObject 调用会出现阻塞, 启动线程增加超时处理
HANDLE hThread = CreateThread(NULL, 0, queryObj, NULL, 0, NULL);
if (hThread == 0)
{
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
DWORD dwSatus = WaitForSingleObject(hThread, 200);
if (dwSatus == WAIT_TIMEOUT)
{
HeapFree(GetProcessHeap(), 0, pObject);
return;
}// 返回文件名
if (pObject->NameBuffer != NULL)
{
DWORD n = WideCharToMultiByte(CP_OEMCP, NULL, pObject->NameBuffer, -1, NULL, 0, NULL, FALSE);
char* name = new char[n + 1];
memset(name, 0, n + 1);
WideCharToMultiByte(CP_OEMCP, NULL, pObject->NameBuffer, -1, name, n, NULL, FALSE);
fileName = name;
delete[] name;
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
HeapFree(GetProcessHeap(), 0, pObject);
return;
}/// <summary>
/// 初始化处理
/// </summary>
/// <returns>是否正常初始化</returns>
bool init()
{
// 从 ntdll.dll 中加载 Native API: NtQuerySystemInformation 用于遍历获取系统信息
HMODULE hNtDll = LoadLibrary(L"ntdll.dll");
if (hNtDll == NULL)
{
return false;
}
NTQUERYSYSTEMINFOMATION NtQuerySystemInformation = (NTQUERYSYSTEMINFOMATION)GetProcAddress(hNtDll, "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL)
{
return false;
}// 用于获取操作系统中文件类型句柄对应的对象类型数字
nulFileHandle = CreateFile(L"NUL", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
if (nulFileHandle == NULL)
{
return false;
}// 从 ntdll.dll 中加载 Native API: NtQueryObject 用于获取句柄对象信息
NtQueryObject = (PNtQueryObject)GetProcAddress(hNtDll, "NtQueryObject");// 查找所有的句柄信息并分配内存进行保存
DWORD nSize = 4096;
pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), 0, nSize);
while (NtQuerySystemInformation(SystemHandleInformation, pHandleInfo, nSize, NULL) == STATUS_INFO_LENGTH_MISMATCH)
{
HeapFree(GetProcessHeap(), 0, pHandleInfo);
nSize += 4096;
pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), 0, nSize);
}
if (pHandleInfo == NULL)
{
return false;
}
return true;
}/// <summary>
/// 获取文件类型对应的对象编号, 经测试 win11: 40 win10: 37 win7: 28, 默认返回 win11 下的编码
/// </summary>
/// <returns>文件类型对应的对象编号</returns>
int getFileObjectTypeNumber()
{
// 遍历所有的句柄
for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
{
PSYSTEM_HANDLE pHandle = (PSYSTEM_HANDLE) & (pHandleInfo->HandleInfo[i]);if ((int)GetCurrentProcessId() == pHandle->ProcessId && pHandle->Handle == (USHORT)nulFileHandle)
{
return (int)pHandle->ObjectTypeNumber;
}
}
return 40;
}/// <summary>
/// 关闭文件
/// </summary>
/// <param name="closeFileName">关闭的文件名</param>
void closeFile(string& closeFileName)
{
int fileObjectTypeNumber = getFileObjectTypeNumber();
// 遍历所有的句柄
for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
{
PSYSTEM_HANDLE pHandle = (PSYSTEM_HANDLE) & (pHandleInfo->HandleInfo[i]);
// 只处理类型为文件且不属于系统进程(id 为 4)的句柄
if (pHandle->ObjectTypeNumber != fileObjectTypeNumber || pHandle->ProcessId == 4 || pHandle->Handle == 0)
{
continue;
}
// 打开句柄对应的进行并进行复制用于后续操作
HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pHandle->ProcessId);
if (hProcess == NULL)
{
continue;
}
hCopy = 0;
if (!DuplicateHandle(hProcess, (HANDLE)pHandle->Handle, GetCurrentProcess(), &hCopy, MAXIMUM_ALLOWED, FALSE, 0))
{
continue;
}// 根据句柄获取文件名
int pid = pHandle->ProcessId;
string fileName;
getFileName(fileName);
if (fileName.find(closeFileName) != -1)
{
// 获取占用的进程名称
WCHAR tmpName[MAX_PATH] = {};
DWORD size = MAX_PATH;
QueryFullProcessImageName(hProcess, 0, tmpName, &size);
wStringToString(processName, tmpName);// 关闭占用的文件句柄
HANDLE h_tar = NULL;
if (DuplicateHandle(hProcess, (HANDLE)pHandle->Handle, GetCurrentProcess(), &h_tar, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
{
CloseHandle(h_tar);
}
CloseHandle(hCopy);
CloseHandle(hProcess);
return;
}
CloseHandle(hCopy);
CloseHandle(hProcess);
}
HeapFree(GetProcessHeap(), 0, pHandleInfo);
return;
}
注册表功能实现
部分的代码开头可以看到)。import QtQuick 2.9
import QtQuick.Window 2.2Window {
id: w
visible: true
width: 320
height: 120
title: "unlockfile"function showInfo(infoText) {
info.text = infoText
}Text {
id: info
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: "Enjoy!"
}
}
查找中...
展示,在解锁文件完成后会通过showFile
函数展示占用的进程名。import QtQuick 2.9
import QtQuick.Window 2.2Window {
id: w
visible: true
width: 480
height: 200
title: "unlockfile"property bool run: true
property int count: 0function showFile(fileText) {
file.text = fileText
run = false
}Text {
id: file
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: "查找中"
}Timer {
interval: 1000
running: run
repeat: true
onTriggered: {
let str = ""
for (let i = 0; i < count; i++) {
str += "."
}
file.text = "查找中" + str
count = (count + 1) % 4
}
}
}
QThreadPool::globalInstance()->start([=]() {
string fileName = gbkToUTF8(argv[1]).substr(3);
if (init())
{
closeFile(fileName);
string info = u8"解锁成功, 占用程序: " + processName;
QMetaObject::invokeMethod(root, "showFile",
Q_ARG(QVariant, QString::fromStdString(info)));
}
});
Inno Setup
工具进行制作了,步骤如下:安装程序也可以在GitHub中找到,目前只在 win10 和 win11 进行了测试。
四
总结
◆注册表添加项无法自定义,同时未提供删除注册表的操作;
◆不是列出所有占用项让用户选择进行解锁;
◆只测试了 win10 和 win11 环境下的运行;
◆未实现批量解除文件占用的功能;
◆...
看雪ID:庄周の蝴蝶
https://bbs.kanxue.com/user-home-830009.htm
# 往期推荐
球分享
球点赞
球在看