亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C++?Qt實現(xiàn)一個解除文件占用小工具

 更新時間:2023年09月18日 08:32:34   作者:莊周de蝴蝶  
這篇文章主要為大家詳細介紹了如何利用C++?Qt實現(xiàn)一個解除文件占用小工具,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

前言

相信大家或多或少都遇到過想刪除一個文件,卻提示被占用的情況:

不知道各位都是如何處理的,反正我一直都是用的火絨??。但是作為一名程序員,自己寫一個小程序?qū)崿F(xiàn)多有意思,是吧。況且為了一個小工具去安裝一個殺毒軟件,不是一個合格的程序員,你們說對不對???;谝陨系脑?,最終出現(xiàn)了這篇文章,效果如下,本文所對應(yīng)的完整代碼已上傳到GitHub,可自行取用~~~

一些可以使用的工具

在正式編碼之前,這里先介紹一些已有的工具,如果想看編碼實現(xiàn),可以跳過本節(jié)。

火絨等殺毒軟件

這里以火絨自帶的工具為例,使用方式如下所示:

通過火絨自帶的工具,可以看到文件被什么程序占用了,然后進行解鎖。

專用工具

Unlocker、LockHunterIObit Unlocker,由于未實際使用過,這里不再展開介紹。

任務(wù)管理器

通過Windows 自帶的任務(wù)管理器也可以查詢文件的占用狀態(tài),缺點是無法只解鎖文件,只能關(guān)閉占用的進程。

Sysinternals 下的 handle

Sysinternals 是 Windows 平臺上使用的一個工具集合,可以監(jiān)控系統(tǒng)的絕大部分文件,磁盤,網(wǎng)絡(luò),進程線程,模塊,工具全集可以在微軟官網(wǎng)進行下載,這里只講解用于句柄操作的 Handle:

首先在官網(wǎng)進行下載,可以發(fā)現(xiàn)包含的文件很簡單,exe 文件可以直接運行:

在這里我們選擇其中的 handle64 即可,首先以管理員身份運行終端,然后運行以下命令:

handle64 "C:\Users\xxx\Desktop\demo.gif"

然后我們就可以看到上圖所示的占用的程序進程號和對應(yīng)的文件句柄,之后我們就可以運行以下命令去解除占用了,其中 1CE8 和 20392 分別是上述命令獲取到的文件句柄和占用進程號:

handle64 -nobanner -c 1CE8 -y -p 20392

自己編碼實現(xiàn)

以上講解了一些解除文件占用的第三方功能,下面則開始步入正題,從零實現(xiàn)一個解除文件占用的小工具。

軟硬件運行環(huán)境及工具

  • Windows11
  • Visual Studio 2022
  • Qt5.15.2/QML(用于展示簡單結(jié)果文本,不了解 Qt 也沒什么影響)
  • Inno Setup(用于創(chuàng)建程序的安裝程序)

編碼實現(xiàn)

首先說明以下程序的整體思路:程序初始判斷是否有傳參,如果無參說明程序是手動運行,執(zhí)行添加注冊表實現(xiàn)右鍵菜單包含解鎖文件選項的邏輯。如果包含參數(shù),說明程序是通過右鍵菜單運行的,根據(jù)傳遞的參數(shù)(即文件路徑)執(zhí)行相應(yīng)的文件解鎖操作。

以下不展示全部代碼,完整代碼可在前言中的GitHub查看,全部邏輯都在 main.cpp 中。

注冊表功能實現(xiàn)

最終效果如下:

結(jié)合上圖和以下代碼即注釋,相關(guān)代碼不難理解,主要步驟如下:

1.添加名為unlockfile的注冊鍵,包含兩個鍵值,一個默認(rèn)項解鎖文件對應(yīng)右鍵菜單顯示的名稱,一個Icon設(shè)置為應(yīng)用程序的地址對應(yīng)右鍵菜單顯示的圖標(biāo)。

2.在unlockfile下添加名為command的子鍵,值是程序路徑和 "%1"(對應(yīng)傳遞的文件路徑參數(shù)用于文件解鎖操作)。

使用注冊表時要特別注意文件編碼,字符串類型轉(zhuǎn)換的處理。

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>
/// 設(shè)置右鍵菜單
/// </summary>
/// <param name="strRegKeyKey">注冊鍵</param>
/// <param name="strRegKeyName">注冊名</param>
/// <param name="strApplication">應(yīng)用地址</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;
	// 創(chuàng)建注冊表鍵, 對應(yīng)右鍵菜單項
	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;
	}
	// 創(chuàng)建注冊表值, 對應(yīng)右鍵菜單項顯示的內(nèi)容
	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;
	}
	// 設(shè)置右鍵菜單圖標(biāo)
	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;
	}
	// 創(chuàng)建注冊表子項鍵, 對應(yīng)點擊右鍵菜單項后的命令項
	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;
	}
	// 創(chuàng)建注冊表子項值, 對應(yīng)點擊右鍵菜單項后的具體執(zhí)行命令
	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;
}

實現(xiàn)的效果如下,其中解鎖文件就是我們創(chuàng)建的:

解鎖文件邏輯實現(xiàn)

這部分邏輯稍微復(fù)雜一些,具體步驟如下:

  • 首先執(zhí)行init()進行初始化的操作,包括加載 Native API 和遍歷系統(tǒng)中所有句柄。
  • 調(diào)用getFileObjectTypeNumber()獲取文件句柄對應(yīng)的編號(句柄有很多種,比如窗口、文件、圖標(biāo)和菜單),經(jīng)測試,不同系統(tǒng)版本的編號也有所不同:win11: 40 win10: 37 win7: 28。
  • 遍歷執(zhí)行init()得到的系統(tǒng)所有句柄信息,只處理其中類型為文件且不屬于系統(tǒng)進程的句柄。
  • 對符合條件的文件句柄去獲取其文件名,如果文件名和傳遞的文件名相同,則關(guān)閉相應(yīng)的句柄即可實現(xiàn)解鎖文件的效果,同時獲取占用的進程路徑展示給用戶。

特別注意,在 ring3 級調(diào)用NtQueryObject會出現(xiàn)阻塞的情況,因此需要通過開一個線程增加超時處理,避免程序卡住。此外,由于是跨進程處理句柄,因此需要調(diào)用DuplicateHandle方法。

/// <summary>
/// 查詢對象信息
/// </summary>
/// <param name="lpParam">參數(shù)</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)
{
    // 查找句柄對象信息并分配內(nèi)存進行保存
    pObject = (POBJECT_NAME_INFORMATION)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * 2);
    if (pObject == 0)
    {
        HeapFree(GetProcessHeap(), 0, pObject);
        return;
    }
    // NtQueryObject 調(diào)用會出現(xiàn)阻塞, 啟動線程增加超時處理
    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 用于遍歷獲取系統(tǒng)信息
    HMODULE hNtDll = LoadLibrary(L"ntdll.dll");
    if (hNtDll == NULL)
    {
        return false;
    }
    NTQUERYSYSTEMINFOMATION NtQuerySystemInformation = (NTQUERYSYSTEMINFOMATION)GetProcAddress(hNtDll, "NtQuerySystemInformation");
    if (NtQuerySystemInformation == NULL)
    {
        return false;
    }
    // 用于獲取操作系統(tǒng)中文件類型句柄對應(yīng)的對象類型數(shù)字
    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");
    // 查找所有的句柄信息并分配內(nèi)存進行保存
    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>
/// 獲取文件類型對應(yīng)的對象編號, 經(jīng)測試 win11: 40 win10: 37 win7: 28, 默認(rèn)返回 win11 下的編碼
/// </summary>
/// <returns>文件類型對應(yīng)的對象編號</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>
/// 關(guān)閉文件
/// </summary>
/// <param name="closeFileName">關(guān)閉的文件名</param>
void closeFile(string& closeFileName)
{
    int fileObjectTypeNumber = getFileObjectTypeNumber();
    // 遍歷所有的句柄
    for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
    {
        PSYSTEM_HANDLE pHandle = (PSYSTEM_HANDLE) & (pHandleInfo->HandleInfo[i]);
        // 只處理類型為文件且不屬于系統(tǒng)進程(id 為 4)的句柄
        if (pHandle->ObjectTypeNumber != fileObjectTypeNumber || pHandle->ProcessId == 4 || pHandle->Handle == 0)
        {
            continue;
        }
        // 打開句柄對應(yīng)的進行并進行復(fù)制用于后續(xù)操作
        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;
        }
        // 根據(jù)句柄獲取文件名
        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);
            // 關(guān)閉占用的文件句柄
            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;
}

界面展示實現(xiàn)

界面展示這里使用了 Qt 的 QML 進行實現(xiàn),頁面比較簡單,包含以下兩個界面。

主界面

主界面只是簡單展示一下文本,其中文本會根據(jù)注冊表添加成功或失敗展示相應(yīng)的信息(在注冊表功能實現(xiàn)部分的代碼開頭可以看到)。

import QtQuick 2.9
import QtQuick.Window 2.2
Window {
    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!"
    }
}

解鎖界面

解鎖界面稍微復(fù)雜一些,通過 Timer 定時器實現(xiàn)動態(tài)的查找中...展示,在解鎖文件完成后會通過showFile函數(shù)展示占用的進程名。

import QtQuick 2.9
import QtQuick.Window 2.2
Window {
    id: w
    visible: true
    width: 480
    height: 200
    title: "unlockfile"
    property bool run: true
    property int count: 0
    function 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
        }
    }
}

其中設(shè)置進程名的代碼操作在 main.cpp 文件中:

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)));
    }
});

制作安裝程序

最后再介紹如何制作程序的安裝程序,前提是需要先對 Qt 程序進行打包(此處省略 500 字),然后就可以使用Inno Setup工具進行制作了,步驟如下:

1.設(shè)置應(yīng)用的名稱版本:

2.設(shè)置應(yīng)用的安裝路徑,同時允許用戶進行自定義:

3.設(shè)置執(zhí)行程序的路徑和根文件夾路徑:

4.之后全部點擊下一步,然后在選擇語言時按需選擇:

5.然后可以設(shè)置程序的圖標(biāo)和安裝程序輸出路徑,之后全部點擊下一步即可:

6.然后就可以在輸出路徑看到生成的安裝程序:

7.點擊運行就是熟悉的程序安裝界面了,按需進行選擇后即可使用,同時需要以管理員身份運行:

安裝程序也可以在GitHub中找到,目前只在 win10 和 win11 進行了測試。

總結(jié)

本文講解了如何實現(xiàn)一個解除文件占用的小程序,不過還存在很多不完善的地方:

  • 注冊表添加項無法自定義,同時未提供刪除注冊表的操作
  • 不是列出所有占用項讓用戶選擇進行解鎖
  • 只測試了 win10 和 win11 環(huán)境下的運行
  • 未實現(xiàn)批量解除文件占用的功能
  • ...

以上就是C++ Qt實現(xiàn)一個解除文件占用小工具的詳細內(nèi)容,更多關(guān)于C++ Qt解除文件占用的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C++中int、DWORD和QWORD示例詳解

    C++中int、DWORD和QWORD示例詳解

    當(dāng)談?wù)揅++編程語言時,以下術(shù)語經(jīng)常被提及:int、DWORD和QWORD,它們是用于表示不同數(shù)據(jù)類型和長度的關(guān)鍵字,本文通過舉例給大家詳細介紹,感興趣的朋友一起看看吧
    2024-06-06
  • C/C++ Qt 自定義Dialog對話框組件應(yīng)用案例詳解

    C/C++ Qt 自定義Dialog對話框組件應(yīng)用案例詳解

    有時候我們需要一次性修改多個數(shù)據(jù),使用默認(rèn)的模態(tài)對話框似乎不太夠用,此時我們需要自己創(chuàng)建一個自定義對話框。這篇文章主要介紹了Qt自定義Dialog對話框組件的應(yīng)用,感興趣的同學(xué)可以學(xué)習(xí)一下
    2021-11-11
  • stringstream操縱string的方法總結(jié)

    stringstream操縱string的方法總結(jié)

    下面小編就為大家?guī)硪黄猻tringstream操縱string的方法總結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-12-12
  • C語言基于哈希表實現(xiàn)通訊錄

    C語言基于哈希表實現(xiàn)通訊錄

    這篇文章主要為大家詳細介紹了C語言基于哈希表實現(xiàn)通訊錄,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • C++實現(xiàn)棧的操作(push和pop)

    C++實現(xiàn)棧的操作(push和pop)

    這篇文章主要介紹了C++實現(xiàn)棧的操作(push和pop),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • 淺談C/C++中指針和數(shù)組的不同

    淺談C/C++中指針和數(shù)組的不同

    本文主要解析了C/C++中數(shù)組和指針的區(qū)別,文章簡單易懂,對各位的工作學(xué)習(xí)有所幫助,有需求的朋友可以了解下
    2020-05-05
  • 一波二叉樹遍歷問題的C++解答實例分享

    一波二叉樹遍歷問題的C++解答實例分享

    這篇文章主要介紹了一波二叉樹遍歷問題的C++解答實例分享,包括節(jié)點打印和轉(zhuǎn)換為鏡像等問題的解答,需要的朋友可以參考下
    2016-02-02
  • C++ 自由存儲區(qū)是否等價于堆你知道嗎

    C++ 自由存儲區(qū)是否等價于堆你知道嗎

    自由存儲是C++中通過new與delete動態(tài)分配和釋放對象的抽象概念,而堆(heap)是C語言和操作系統(tǒng)的術(shù)語,是操作系統(tǒng)維護的一塊動態(tài)分配內(nèi)存
    2021-08-08
  • c/c++獲取系統(tǒng)時間函數(shù)的方法示例

    c/c++獲取系統(tǒng)時間函數(shù)的方法示例

    這篇文章主要介紹了c/c++獲取系統(tǒng)時間函數(shù)的方法示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • C++中關(guān)于多態(tài)實現(xiàn)和使用方法

    C++中關(guān)于多態(tài)實現(xiàn)和使用方法

    這篇文章主要介紹了C++中關(guān)于多態(tài)實現(xiàn)和使用方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07

最新評論