首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

Windows C 語言控制光驅彈出/卸載完整實現

     編輯:關於C

場景:需要以編程方式控制光驅

在 Windows 平台上,通過 Win32 API 實現光驅彈出、卸載、鎖定/解鎖卷等操作,涉及設備控制碼、文件系統控制碼(FSCTL)與存儲控制碼(IOCTL)的組合調用。本文提供一個完整、健壯的命令行工具參考實現。

核心 API 流程

  1. 識別光驅:GetDriveTypeW 判斷驅動器類型為 DRIVE_CDROM。
  2. 打開設備:CreateFileW 打開 \\.\X: 設備路徑,需 GENERIC_READ | GENERIC_WRITE 權限。
  3. 鎖定卷:FSCTL_LOCK_VOLUME 獨佔訪問,防止其他進程干擾。
  4. 卸載文件系統:FSCTL_DISMOUNT_VOLUME 刷新緩存並標記卷為卸載狀態。
  5. 允許介質移除:IOCTL_STORAGE_MEDIA_REMOVAL 設置 PreventMediaRemoval = FALSE。
  6. 彈出介質:IOCTL_STORAGE_EJECT_MEDIA 發送彈出命令(可選,-u 參數跳過)。
  7. 解鎖卷:FSCTL_UNLOCK_VOLUME 釋放獨佔鎖定。
  8. 關閉句柄:CloseHandle。

完整源代碼(可直接編譯為 eject.exe)

[cpp]
/* Eject CDs - Windows 光驅控制工具 */
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

static int unmount_only = 0;
static int eject_all = 0;

/* 獲取驅動器類型 */
static DWORD get_drive_type(WCHAR drive) {
    WCHAR path[] = L" :\\";
    path[0] = drive;
    return GetDriveTypeW(path);
}

/* 執行單個光驅彈出/卸載 */
static BOOL eject_cd(WCHAR drive) {
    if (get_drive_type(drive) != DRIVE_CDROM) {
        printf("Drive %c: is not a CD-ROM or not mounted\n", (char)drive);
        return FALSE;
    }

    WCHAR device[] = L"\\\\.\\ :";
    device[4] = drive;

    HANDLE h = CreateFileW(device, GENERIC_READ | GENERIC_WRITE,
                           FILE_SHARE_READ | FILE_SHARE_WRITE,
                           NULL, OPEN_EXISTING, 0, NULL);
    if (h == INVALID_HANDLE_VALUE) {
        printf("Cannot open device for drive %c: (err %d)\n", (char)drive, GetLastError());
        return FALSE;
    }

    printf("Ejecting %c: ...\n", (char)drive);
    DWORD bytes;

    // 1. 鎖定卷
    if (!DeviceIoControl(h, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytes, NULL))
        printf("  FSCTL_LOCK_VOLUME failed: %d\n", GetLastError());

    // 2. 卸載卷
    if (!DeviceIoControl(h, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &bytes, NULL))
        printf("  FSCTL_DISMOUNT_VOLUME failed: %d\n", GetLastError());

    // 3. 允許介質移除
    PREVENT_MEDIA_REMOVAL pmr = { FALSE };
    if (!DeviceIoControl(h, IOCTL_STORAGE_MEDIA_REMOVAL, &pmr, sizeof(pmr), NULL, 0, &bytes, NULL))
        printf("  IOCTL_STORAGE_MEDIA_REMOVAL failed: %d\n", GetLastError());

    // 4. 彈出介質 (可選)
    if (!unmount_only) {
        if (!DeviceIoControl(h, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &bytes, NULL))
            printf("  IOCTL_STORAGE_EJECT_MEDIA failed: %d\n", GetLastError());
    }

    // 5. 解鎖卷
    if (!DeviceIoControl(h, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &bytes, NULL))
        printf("  FSCTL_UNLOCK_VOLUME failed: %d\n", GetLastError());

    CloseHandle(h);
    return TRUE;
}

/* 自動查找唯一光驅 */
static WCHAR find_cd_drive(void) {
    WCHAR found = 0;
    for (WCHAR d = L'C'; d <= L'Z'; d++) {
        if (get_drive_type(d) == DRIVE_CDROM) {
            if (found) {
                printf("Multiple CD drives found (%c: and %c:), specify one.\n", (char)found, (char)d);
                exit(1);
            }
            found = d;
        }
    }
    return found;
}

/* 命令行參數解析 */
static void parse_options(int *argc, char *argv[]) {
    for (int i = 1; i < *argc; i++) {
        if (argv[i][0] != '-') {
            if (strlen(argv[i]) != 2 || argv[i][1] != ':') {
                printf("Invalid drive argument: %s\n", argv[i]);
                exit(1);
            }
            continue;
        }
        for (char *opt = argv[i] + 1; *opt; opt++) {
            switch (*opt) {
                case 'a': eject_all = 1; break;
                case 'u': unmount_only = 1; break;
                case 'h':
                default:
                    printf("Usage: eject [-u] [-a] [-h] [X:]...\n"
                           "  -a  Eject all CD drives\n"
                           "  -u  Unmount only, don't eject\n"
                           "  -h  Show this help\n"
                           "  X:  Eject specific drive\n");
                    exit(1);
            }
        }
        // 移除已處理的選項
        memmove(&argv[i], &argv[i+1], (*argc - i) * sizeof(char*));
        (*argc)--;
        i--;
    }
}

int main(int argc, char *argv[]) {
    parse_options(&argc, argv);

    if (eject_all) {
        for (WCHAR d = L'C'; d <= L'Z'; d++)
            if (get_drive_type(d) == DRIVE_CDROM && !eject_cd(d)) exit(1);
    }
    else if (argc > 1) {
        for (int i = 1; i < argc; i++)
            if (!eject_cd(argv[i][0])) exit(1);
    }
    else {
        WCHAR d = find_cd_drive();
        if (!d) { printf("No CD drive found\n"); exit(1); }
        if (!eject_cd(d)) exit(1);
    }
    return 0;
}

編譯與使用


# MSVC
cl eject.c

# MinGW
gcc eject.c -o eject.exe

# 使用示例
eject.exe           # 自動查找並彈出唯一光驅
eject.exe D:        # 彈出 D 盤
eject.exe -a        # 彈出所有光驅
eject.exe -u E:     # 僅卸載 E 盤,不彈出托盤
eject.exe -h        # 顯示幫助

關鍵點總結

  • **權限要求**:需管理員權限或對設備有讀寫權限。
  • **錯誤處理**:每步 DeviceIoControl 都檢查返回值並打印 GetLastError(),便於診斷「拒絕訪問」「設備忙」等問題。
  • **Unicode 支持**:全程使用 Wide API (W 後綴),兼容非 ASCII 卷標。
  • **安全性**:嚴格遵循「鎖定→卸載→允許移除→彈出→解鎖」序列,避免數據丟失或設備損壞。

擴展思路

  • 集成到 GUI 程序中,提供圖形化光驅管理界面。
  • 添加「關閉托盤」功能(IOCTL_STORAGE_LOAD_MEDIA)。
  • 支持 USB 大容量存儲設備的安全移除(流程類似,但設備類型判斷不同)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved