You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中安全移除挂载为文件夹的USB/SD卡(无需卸载)

在C#中安全移除挂载到文件夹的存储设备

好问题!这种挂载到文件夹的存储设备确实可以在C#里直接执行安全移除,不需要先卸载挂载点——核心是绕开驱动器号,直接操作对应的底层卷设备。下面是具体的实现思路和代码示例:

核心思路

Windows将存储设备挂载到文件夹时,该设备依然对应一个唯一的卷对象。我们只需通过挂载路径找到这个卷的标识,然后调用Windows原生API发送弹出指令,就能实现安全移除操作,全程无需卸载挂载点。

具体实现步骤(附代码)

要实现这个功能,需要通过P/Invoke调用几个Windows核心API,以下是完整的C#代码示例:

首先,定义所需的API和常量:

using System;
using System.Runtime.InteropServices;
using System.Text;

public static class SafeRemovalHelper
{
    // 常量定义
    private const int GENERIC_READ = 0x80000000;
    private const int GENERIC_WRITE = 0x40000000;
    private const int FILE_SHARE_READ = 0x00000001;
    private const int FILE_SHARE_WRITE = 0x00000002;
    private const int OPEN_EXISTING = 3;
    private const int IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;

    // P/Invoke 声明
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool GetVolumeNameForVolumeMountPoint(string lpszVolumeMountPoint, StringBuilder lpszVolumeName, uint cchBufferLength);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);

    [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hObject);

    public static bool EjectMountedFolder(string mountPath)
    {
        // API要求挂载路径必须以反斜杠结尾,这里做兼容处理
        if (!mountPath.EndsWith("\\"))
        {
            mountPath += "\\";
        }

        // 1. 获取挂载点对应的卷名
        StringBuilder volumeName = new StringBuilder(256);
        if (!GetVolumeNameForVolumeMountPoint(mountPath, volumeName, (uint)volumeName.Capacity))
        {
            Console.WriteLine($"获取卷名失败,错误码: {Marshal.GetLastWin32Error()}");
            return false;
        }

        // 2. 打开卷设备
        IntPtr hVolume = CreateFile(
            volumeName.ToString(),
            GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            IntPtr.Zero,
            OPEN_EXISTING,
            0,
            IntPtr.Zero);

        if (hVolume == IntPtr.Zero || hVolume == (IntPtr)(-1))
        {
            Console.WriteLine($"打开卷设备失败,错误码: {Marshal.GetLastWin32Error()}");
            return false;
        }

        try
        {
            // 3. 发送弹出指令
            uint bytesReturned;
            bool result = DeviceIoControl(
                hVolume,
                IOCTL_STORAGE_EJECT_MEDIA,
                IntPtr.Zero,
                0,
                IntPtr.Zero,
                0,
                out bytesReturned,
                IntPtr.Zero);

            if (!result)
            {
                Console.WriteLine($"执行弹出操作失败,错误码: {Marshal.GetLastWin32Error()}");
            }
            return result;
        }
        finally
        {
            // 务必关闭设备句柄,避免资源泄漏
            CloseHandle(hVolume);
        }
    }
}

使用示例

// 调用方法,传入你的挂载文件夹路径
bool success = SafeRemovalHelper.EjectMountedFolder(@"C:\documents\sdcard");
if (success)
{
    Console.WriteLine("存储设备已安全移除");
}
else
{
    Console.WriteLine("安全移除失败");
}

关键注意事项

  • 权限要求:这个操作需要管理员权限,所以你的程序需要以管理员身份运行,或者在应用程序清单中声明requestedExecutionLevel为requireAdministrator。
  • 设备占用问题:如果弹出失败且错误码为32,说明设备正在被占用,需要先关闭所有打开该设备文件的句柄。
  • 系统兼容性:该方法适用于Windows Vista及以上版本,GetVolumeNameForVolumeMountPoint在这些系统中稳定支持。

内容的提问来源于stack exchange,提问作者theNewOne92

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:38:42