在 Windows 平台上,通過 Win32 API 實現光驅彈出、卸載、鎖定/解鎖卷等操作,涉及設備控制碼、文件系統控制碼(FSCTL)與存儲控制碼(IOCTL)的組合調用。本文提供一個完整、健壯的命令行工具參考實現。
GetDriveTypeW 判斷驅動器類型為 DRIVE_CDROM。CreateFileW 打開 \\.\X: 設備路徑,需 GENERIC_READ | GENERIC_WRITE 權限。FSCTL_LOCK_VOLUME 獨佔訪問,防止其他進程干擾。FSCTL_DISMOUNT_VOLUME 刷新緩存並標記卷為卸載狀態。IOCTL_STORAGE_MEDIA_REMOVAL 設置 PreventMediaRemoval = FALSE。IOCTL_STORAGE_EJECT_MEDIA 發送彈出命令(可選,-u 參數跳過)。FSCTL_UNLOCK_VOLUME 釋放獨佔鎖定。CloseHandle。[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(),便於診斷「拒絕訪問」「設備忙」等問題。W 後綴),兼容非 ASCII 卷標。IOCTL_STORAGE_LOAD_MEDIA)。