首页 / 资讯中心 / 文章详情

易语言磁盘操作实战:枚举驱动器列表与获取磁盘空间

易语言磁盘操作实战:枚举驱动器列表与获取磁盘空间 ★ FEATURED ARTICLE
最近把“511遇见”这个易语言小项目重新完整撸了一遍核心就是磁盘操作里的两件事把驱动器列表枚举出来再逐个取出磁盘的总空间和可用空间。听起来简单但真正动手时涉及的 Windows API 参数传递、驱动器类型判断、容量单位换算还有 32/64 位兼容问题一个都不少。这篇文章把整个项目的设计思路、核心代码、调试过程和踩坑记录完整拆开讲。适合刚学完易语言基础、想往系统底层方向走一步的朋友也适合想在工具类项目里直接抄作业的老手。做这种系统级小工具最大的价值不是那几十行代码而是搞清楚“Windows 到底怎么看待磁盘”这件事。下面我从项目拆解开始一步步说清楚。1. 项目整体设计磁盘操作到底在操作什么1.1 需求拆解两个核心功能点项目需求一句话“取磁盘空间和驱动器列表”。但这句话背后其实压着两个完全不同的功能层次。第一驱动器列表。系统当前到底识别了哪些盘符这些盘符是本地硬盘分区、U 盘、光驱、还是网络映射盘不同盘符的读写特性和空间获取方式都不一样所以要先枚举再判断类型。第二磁盘空间。针对用户选中的某个盘符需要读出总容量和剩余可用容量。这两步如果只做一个项目都不完整只有合在一起才是一个能用的磁盘信息工具。我见过不少初学者直接写“先取列表再循环取空间”思路没问题但落地时会发现一堆细节要不要过滤网络驱动器光驱没有容量怎么办容量超过 2TB 之后整数型溢出怎么办盘符类型怎么判断这些如果不提前拆解清楚后面代码只会越写越乱。所以项目第一步不是敲代码而是把需求拆成“列表枚举”“类型判断”“空间读取”“界面展示”四个模块每个模块独立实现再串起来。1.2 技术选型用易语言核心库命令还是直接调 API易语言本身带“取磁盘剩余空间”“取磁盘总空间”这类系统核心支持库命令底层其实也是封装 Windows API。如果只是临时用一下直接调用这些命令当然最快。但我建议还是直接使用 API原因有三个。第一支持库命令对返回值做了一定封装但遇到大容量磁盘时精度和溢出风险不可控。第二支持库命令只能拿到空间拿不到驱动器类型和完整列表最终还是得配合 API。第三直接调 API 能让你理解 Windows 系统功能的底层逻辑以后换 C、C、Python 写同类工具思路完全通用。这次项目我选择GetLogicalDrivesGetDriveTypeAGetDiskFreeSpaceA三个 API 组合。这个组合在易语言里声明简单参数全部是普通整数不需要处理复杂的指针结构体是最适合易语言中文编程环境的方案。实际测试下来Win7 到 Win11 都能稳定运行代码量也少。1.3 为什么选易语言做这类系统级工具很多人对易语言有偏见觉得它只能写写小脚本。但系统级 API 调用这块易语言的能力和 C 语言其实差不了太多。它支持 DLL 命令声明、传址参数、位运算、长整数型这些都是跟底层交互的基础能力。真正决定一个工具能不能做出来的不是语言本身而是你对 API 的理解深度。另外易语言做用户界面非常快拖一个按钮、一个列表框、几个标签几十行代码就能出一个完整的工具界面不需要像 C 那样先写一大堆窗口创建代码。对个人开发者来说用易语言做 Windows 系统小工具性价比确实高。这个“511遇见”项目就是典型例子——核心逻辑不复杂但界面完整适合当入门系统编程的练手项目。2. 核心细节解析与实操要点2.1 驱动器列表的三种获取姿势要拿到系统里的驱动器列表Windows 提供了不同层级的 API我至少试过三种方式各有优缺点。第一种GetLogicalDrives。它返回一个 32 位整数每个位对应 A 到 Z 中的一个盘符比如返回值第 3 位是 1说明 C 盘存在。这种方式优点是调用极简单一个函数就拿到全部盘符掩码缺点是你不知道这个盘符是硬盘还是光驱必须再调GetDriveTypeA判断类型。第二种GetLogicalDriveStringsA。它会把盘符列表以字符串数组的形式写入缓冲区格式类似C:\0D:\0E:\0\0每个盘符以\0分隔末尾两个\0表示结束。这种方式能看到完整路径需要手动解析缓冲区里的多段字符串代码比较啰嗦在易语言里还得处理字节集不太划算。第三种直接循环判断。从 A 到 Z 挨个调GetDriveTypeA如果返回值不是DRIVE_NO_ROOT_DIR就认为盘符存在。这个方式最直观但会多出无效调用代码也不算最简。我最终选择了GetLogicalDrives加GetDriveTypeA的组合理由很简单代码量最少逻辑最清晰也最容易往其他语言迁移。三种方式的对比放在表里方案核心函数优点缺点位掩码遍历GetLogicalDrives调用简单性能好需要配合类型判断字符串解析GetLogicalDriveStringsA直接得到路径列表多字符串解析麻烦逐个试探GetDriveTypeA思路最直接无效调用多代码啰嗦2.2 磁盘空间的读取GetDiskFreeSpaceA 详解磁盘空间读取我选了GetDiskFreeSpaceA而不是GetDiskFreeSpaceExA这是有原因的。GetDiskFreeSpaceExA能直接返回总空间和可用空间但它的输出参数是 64 位整数结构体指针在易语言里处理起来要绕不少弯子。而GetDiskFreeSpaceA的四个参数都是 32 位 DWORD 指针即“每簇扇区数、每扇区字节数、可用簇数、总簇数”在易语言里声明成“整数型传址”就能直接用完全不用碰指针。空间容量计算公式总容量 每簇扇区数 × 每扇区字节数 × 总簇数可用容量 每簇扇区数 × 每扇区字节数 × 可用簇数为什么要用“扇区”和“簇”来算这里要补充一个 Windows 文件系统的基础概念。磁盘格式化后扇区是最小的物理存储单元通常 512 字节或 4096 字节。但文件系统一次分配空间的最小单位是“簇”一个簇可能包含 4 个、8 个或者更多扇区。比如一块普通 NTFS 分区常见的配置是每扇区 512 字节、每簇 8 个扇区那么一个簇就是 4KB。上面公式里的三个乘数相乘算出来的就是整个磁盘按字节计量的容量。计算时有个特别注意点三个参数都是整数型直接相乘可能得到错误结果。比如每簇扇区数 8、每扇区字节数 512、总簇数 1000000乘积约 4GB已经超过 32 位整数上限。所以写代码时先用到长整数型()把第一个参数转成 64 位再做乘法。这个坑我一开始就踩过后面会在代码里体现。2.3 磁盘类型的判断与过滤GetDriveTypeA的返回值是整数对应不同磁盘类型返回值常量含义说明0DRIVE_UNKNOWN无法识别1DRIVE_NO_ROOT_DIR路径无效2DRIVE_REMOVABLE可移动磁盘如 U 盘3DRIVE_FIXED固定磁盘如本地硬盘4DRIVE_REMOTE网络驱动器5DRIVE_CDROM光驱6DRIVE_RAMDISK内存盘实际工具里我会把类型 2 和 3 显示出来类型 4、5、6 默认也可以显示但放在下面方便用户看到完整情况。光驱和网络盘在读取空间时很容易出现超时或返回 0 的情况代码里要容错不能一报错就崩溃。3. 实操过程与核心环节实现3.1 界面布局与控件规划这个项目界面不复杂我是这样规划的窗口顶部放一个“刷新”按钮中间放一个列表框用来显示所有盘符、类型和总容量底部放几个静态标签显示当前选中盘符的详细空间信息。这样用户一打开程序就能看到全盘概况选中某个盘符后能看详细信息交互逻辑非常顺。界面控件的“分工”也要提前想清楚列表框负责展示概要信息标签负责展示详情。另外列表里每一项我都用“盘符 | 类型 | 容量”的固定格式组织虽然简单但足够让用户一眼看清。不需要复杂的表格控件易语言自带的列表框加一点文本拼接就能实现。我想特别提一下命名的习惯。按钮点击事件、列表选择事件、空间读取子程序、容量格式化子程序全部用独立子程序封装不要让所有逻辑都堆在按钮事件里。这样后面加“自动刷新”功能或者把接口接到其他项目拆起来非常方便。3.2 DLL 声明与 API 封装在易语言里调用 API第一步就是写 DLL 命令声明。下面是本次项目用到的全部声明.版本 2 .支持库 spec .程序集 窗口程序集_启动窗口 .DLL命令 GetLogicalDrives, 整数型, kernel32.dll, GetLogicalDrives .DLL命令 GetDriveTypeA, 整数型, kernel32.dll, GetDriveTypeA .参数 根路径, 文本型 .DLL命令 GetDiskFreeSpaceA, 整数型, kernel32.dll, GetDiskFreeSpaceA .参数 根路径, 文本型 .参数 每簇扇区数, 整数型, 传址 .参数 每扇区字节数, 整数型, 传址 .参数 可用簇数, 整数型, 传址 .参数 总簇数, 整数型, 传址这里重点说两个易语言调用 API 的细节。第一GetDiskFreeSpaceA的四个输出参数必须在声明里加“传址”。加上这个关键字后易语言会自动把变量的内存地址传给 API 函数函数内部写入值后你的变量就拿到了结果。如果漏掉“传址”函数只会把参数的值传进去函数写入的结果全部丢失返回值为 0磁盘空间读出来永远是 0。第二DLL 命令名要与原函数名保持一致但易语言允许你给 DLL 命令起一个别名例如声明成GetDiskFreeSpace但真正调用的是GetDiskFreeSpaceA。在项目里我直接保持同名方便对照官方文档。3.3 核心代码枚举驱动器并获取空间DLL 声明好后下一步就是写核心功能。先看枚举和刷新的完整代码.子程序 _按钮刷新_被单击 .局部变量 位掩码, 整数型 .局部变量 i, 整数型 .局部变量 盘符, 文本型 .局部变量 磁盘类型, 整数型 .局部变量 总空间, 长整数型 .局部变量 可用空间, 长整数型 列表框1.清空 () 位掩码 GetLogicalDrives () 计次循环首 (26, i) 如果真 (位与 (位掩码, 左移 (1, i 1)) ≠ 0) 盘符 字符 (i 64) “:” 磁盘类型 GetDriveTypeA (盘符) 如果真 (磁盘类型 ≠ 1) 取磁盘空间信息 (盘符, 总空间, 可用空间) 列表框1.加项目 (盘符 “ ” 磁盘类型名称 (磁盘类型) “ ” 格式化容量 (总空间), 磁盘类型) 如果真结束 如果真结束 计次循环尾 ()这段代码的逻辑不复杂我拆开讲几个关键点。GetLogicalDrives返回的位掩码里第 0 位代表 A 盘第 1 位代表 B 盘依次类推到第 25 位代表 Z 盘。所以循环 26 次每次用左移(1, i1)生成对应位的值再用位与判断这一位是不是 1。如果是 1说明该盘符存在。i1时生成字符(65)即字母 “A”拼上冒号变成A:正好是 API 调用需要的根路径格式。类型判断上我只排除了DRIVE_NO_ROOT_DIR类型 1因为这种盘符虽然枚举到了但其实是无效路径。剩下的类型都显示出来包括光驱和网络盘。这样做不是因为懒得过滤而是想保留完整信息让使用者自己决定要不要看这些盘。3.4 空间读取与容量格式化子程序空间读取子程序如下.子程序 取磁盘空间信息 .参数 盘符路径, 文本型 .参数 总空间字节, 长整数型, 传址 .参数 可用空间字节, 长整数型, 传址 .局部变量 每簇扇区数, 整数型 .局部变量 每扇区字节数, 整数型 .局部变量 可用簇数, 整数型 .局部变量 总簇数, 整数型 GetDiskFreeSpaceA (盘符路径, 每簇扇区数, 每扇区字节数, 可用簇数, 总簇数) 总空间字节 到长整数型 (每簇扇区数) × 每扇区字节数 × 总簇数 可用空间字节 到长整数型 (每簇扇区数) × 每扇区字节数 × 可用簇数这里最容易翻车的就是乘法溢出。每簇扇区数、每扇区字节数、总簇数在声明里都是整数型三个 32 位整数相乘结果可能远超 32 位上限。我先把第一个乘数转成长整数型后面乘法就在 64 位空间里进行安全很多。如果你发现读 2TB 以上硬盘时容量变成负数基本都是这里出了问题。容量格式化子程序.子程序 格式化容量, 文本型 .参数 容量字节, 长整数型 .局部变量 容量MB, 双精度小数型 .局部变量 容量GB, 双精度小数型 如果真 (容量字节 1024 × 1024) 返回 (到文本 (容量字节) “ B”) 如果真结束 如果真 (容量字节 1024 × 1024 × 1024) 容量MB 容量字节 ÷ 1024 ÷ 1024 返回 (到文本 (四舍五入 (容量MB, 2)) “ MB”) 如果真结束 容量GB 容量字节 ÷ 1024 ÷ 1024 ÷ 1024 返回 (到文本 (四舍五入 (容量GB, 2)) “ GB”)注意易语言里“/”运算符是整数除法会丢弃小数部分做容量换算时要用“÷”做数学除法才能保留小数否则所有容量都变成整数 GB丢失精度。这一点新手容易忽略。3.5 列表选择事件与详情展示刷新完成后用户点击列表项还要在下面的标签里显示详细空间信息。这里我直接用列表项的文本做处理取前两个字符作为盘符然后调用空间读取子程序。.子程序 列表框1_列表项被选择 .局部变量 盘符路径, 文本型 .局部变量 总空间, 长整数型 .局部变量 可用空间, 长整数型 盘符路径 取文本左边 (列表框1.取项目文本 (列表框1.现行选中项), 2) 取磁盘空间信息 (盘符路径, 总空间, 可用空间) 标签总容量.标题 “总容量” 格式化容量 (总空间) 标签可用容量.标题 “可用容量” 格式化容量 (可用空间) 标签可用比例.标题 “可用比例” 到文本 (四舍五入 (可用空间 ÷ 总空间 × 100, 1)) “%”整个程序到这里已经具备完整功能点“刷新”列表展示所有盘符、类型和总容量点列表项底部显示选中盘的详细空间信息。整个项目从零到能跑编码时间不超过半小时。4. 常见问题与排查技巧实录4.1 枚举出来的盘符列表不全有朋友在别的机器上运行时发现有的 U 盘识别了有的移动硬盘没显示。这类问题我排查过几次最常见的原因不是代码逻辑而是GetLogicalDrives返回的位掩码被误判。位掩码是一个 32 位整数如果某个高位为 1在易语言里如果声明成“整数型”可能被当成有符号数打印出来是负数。但负数的二进制表示其实仍然是正确的位图所以循环判断时不能直接跟 0 比较大小必须用位与按位判断。另外有些读卡器在没有插入存储卡时也会占一个盘符类型是DRIVE_REMOVABLE但读空间会失败这是硬件层面的正常现象代码里要判断取磁盘空间信息的返回值。4.2 读网络驱动器时界面卡死如果局域网里有映射网络驱动器点击刷新时GetDiskFreeSpaceA会对网络路径发起 IO 请求网络不通或对方共享权限异常时这个调用可能阻塞好几秒甚至更久。遇到这种情况最简单的处理是把类型 4 的网络驱动器过滤掉不参与空间读取。如果必须读网络盘就加一个“是否包含网络盘”的选框让用户自己决定。我在实际项目里是把按钮刷新改成“刷新本地盘”网络盘只在选中时提示不支持读取。这样既保留了信息展示又不会让刷新过程卡到怀疑人生。4.3 容量超过 2TB 后显示负数或错误值这个问题前面埋过伏笔极大概率是计算时用了 32 位整数。GetDiskFreeSpaceA返回的每簇扇区数、总簇数等都是 DWORD单位运算后很容易突破 2GB。很多人在易语言里图省事直接用“整数型”存容量值然后发现显示的是负数。记住一条原则所有参与容量乘法的变量要么转成长整数型要么直接用双精度小数型接收最终结果。我自己的习惯是读取时用长整数型展示时再转双精度两头都不丢精度。4.4 易语言程序被杀毒软件误报这是易语言工具的常见问题我也中过招。项目编译出来后第一次在另一台电脑上运行系统直接拦截。原因不外乎易语言运行时特征、花指令或者加壳后的行为特征被安全软件识别。严格来说这是易语言生态的现状不是代码问题。应对办法有三个一是用官方的独立版编译二是给程序加正规数字签名三是在发布说明里写清楚程序的行为。作为个人工具我一般会建议用户对文件夹加白名单或者直接用源码运行。4.5 程序在 64 位系统上读取不到某些盘现在的电脑基本都是 64 位系统而易语言默认编译出来的是 32 位程序它会运行在系统提供的 WOW64 兼容层里。API 调用本身没有问题但如果你尝试读取 32 位重定向视角下的系统路径比如C:\Windows\SysWOW64需要注意路径是不是被系统重定向了。磁盘空间读取不涉及这个问题但如果你下一步扩展成文件管理工具就得特别注意。遇到读取不到盘符的情况先检查是不是有权限限制再检查是不是 UAC 虚拟化影响了某些分区最后才怀疑代码。5. 扩展与优化方向5.1 自动刷新与磁盘监控这个工具如果只靠手动刷新日常用起来不够顺手。扩展方案很简单往窗口上加一个“时钟”组件设置周期为 3000 毫秒在时钟事件里调用刷新逻辑就能变成一个自动刷新的磁盘监控小面板。需要注意的是API 调用本身有一定开销自动刷新间隔不要太短否则频频繁读取会产生不必要的 IO。另外列表自动刷新时最好先判断盘符列表有没有变化再决定是否更新空间数字避免列表闪烁。5.2 加入“打开盘符”和“复制路径”等系统集成下一步很自然的扩展是右键菜单。比如在列表框里右键点击某个盘符弹出菜单可以直接“打开资源管理器”“复制盘符路径”。在易语言里实现这个功能一是使用“弹出菜单”命令二是调用ShellExecuteA打开路径。代码量不大但做完之后工具就从“只读信息”变成了“可操作系统工具”实用度提升一个台阶。5.3 同时支持中文卷标和长路径当前项目里用的GetDriveTypeA和GetDiskFreeSpaceA都是 ANSI 版本函数处理纯盘符没问题。如果后续想读取带中文卷标的信息或者路径带有中文建议换成对应的GetDriveTypeW和GetDiskFreeSpaceExW。Windows 内部字符串是 UTF-16 编码API 的宽字符版本对中文更友好。不过盘符枚举本身不涉及中文所以当前项目不换也能用等扩展到文件级操作时再切不迟。还有一个扩展是显示卷标使用GetVolumeInformationA可以拿到每个分区的卷标名称格式化成类似“C:系统”这样的列表项用户体验会好很多。写在最后的一点体会“511遇见”这个项目虽然小但算是我练系统级 API 时收获比较大的一个。它把“数据获取”“数据格式化”“界面展示”三条线完整串了起来而且每个环节都有值得琢磨的细节DLL 命令要不要传址、容量计算怎么防溢出、网络盘怎么避免卡死。这些经验不是看文档能看出来的都得实际跑一遍才记得住。如果你也是刚开始用易语言做系统工具我建议先从这个项目开始把它当成一个模板。跑通之后试着往里面加卷标显示、右键打开目录、自动刷新这些功能很快就能长成一个小而全的系统工具箱。过程中遇到 API 调用问题别硬猜优先去查微软官方文档里对应函数的参数说明再对照易语言的声明方式逐项核对90% 的问题都能解决。这个项目后续我还会继续扩展下一步准备把文件占用、进程磁盘占用也加进来做成一个更完整的系统诊断面板。你有更好的扩展思路也可以直接在这个基础上折腾。
阅读完成 · 觉得有帮助?
咨询建站