https://blog.csdn.net/yxp_xa/article/details/73158988
FileSystemObject 文件系统对象,简称 FSO,它是微软提供的在 windows 中操作本地文件和文件夹的 ActiveX 技术支持,在 win32/64 系统中通用。
FSO 对象模型简单易用。可以实现文件(夹)的创建、改变、移动和删除等常见操作,也可以获取文件(夹)的名称、大小、属性、创建日期或最近修改日期等信息。
通过 FSO 对象模型还可以获取当前系统驱动器信息,如驱动器的种类、序列号、磁盘剩余空间等。
无论是 AutoLisp 还是 VisualLisp 对文件(夹)和驱动器的操作都是弱项,引入 FSO 对象模型,可以让你的程序更加强大。

创建 FSO 对象

FSO 对象被封装在 scrrun.dll 里,类型库名称为 scripting.FileSystemObject。在 Lisp 里一般通过后期绑定的方式,创建 FSO 对象。

  1. ;;示例1
  2. (setq FSO (vlax-create-object "scripting.FileSystemObject"))

可以通过 vlax-dump-object 函数来查询 FSO 对象的属性和方法。使用完毕,建议释放 FSO 对象:

  1. ;;示例2
  2. (vlax-dump-object FSO t) ;;查询对象的属性和方法
  3. (vlax-release-object FSO) ;;释放对象(结束进程)

FSO 的属性

FSO 只有一个 Drives 属性,该属性是 Drive 对象的集合。

  1. ;;示例3
  2. (setq DRs (vlax-get-property FSO 'Drives))

一般来说,一个对象集合均具备 count 属性和 Item 属性,通过指定 Item 属性的值可以调用该集合的对象,但是 FSO 对象很奇怪,只能用 vlax-for 来遍历对象所有条目,也就是下面的语句将返回一个 Automation 错误。

  1. ;;示例4
  2. (vlax-get-property DRs 'Item 1) ;;这是一个错误的示例

上面的代码得不到 Drives 集合的第二个 Drive 对象。解决的办法也很简单,用 vlax-for 将对象集合转为一个 list 表,这样可用 nth 来提取指定的 Item 项。下面定义一个 Lisp 函数来代替 Item 功能:

  1. ;;示例5
  2. (defun GetDrive(n / d)
  3. (vlax-for x (vlax-get FSO 'Drives)(setq d (cons x d)))
  4. (nth n (reverse d))
  5. )

例如,返回第一个驱动器的 Drive 对象(一般是 C: 盘),可以用:(GetDrive 0) 来表示
若需得到一台计算机的驱动器 list 列表,可提取 Drive 对象的 path 属性,代码如下:

  1. ;;示例6
  2. (setq dr '())
  3. (vlax-for x (vlax-get FSO 'Drives)(setq dr (cons(vlax-get x 'Path) dr)))

用 AutoLisp 的 findfile 函数也可以判断驱动是否存在:

  1. ;;示例7
  2. (findfile "f:")

当 findfile 函数返回 nil 时表示 f: 盘不存在,但是在 win8/10 系统里,如果没用管理员模式打开 AutoCAD,则 C: 即使存在,也会返回 nil;使用 FSO 对象则绕过了管理员账户,对查询 C: 盘并不影响:

  1. ;;示例8
  2. (vlax-invoke FSO 'DriveExists "c:") ;; 返回 -1 代表存在

在程序注册加密时,我们通常获取驱动器的序列号作为机器码,下面代码将返回第2个驱动器的序列号:

  1. ;;示例9
  2. (vlax-get (GetNs 1) 'SerialNumber)

也可以调用 FSO 的方法来获取指定驱动器的序列号,代码如下:

  1. ;;示例10
  2. (vlax-get (vlax-invoke FSO 'GetDrive "d:") 'SerialNumber)

所以说,FSO 提供的方法是冗余的,通过不同的途径可以获取相同的结果。采用 FSO 的方法来获取序列号时,应判断 d: 是否存在:

  1. ;;示例11
  2. (vlax-invoke FSO 'DriveExists "d:")

FSO 的方法

FSO 对象提供了 26 个方法,主要方法说明和参数如下:

方法与参数 功能说明
BuildPath(path,name) 连接一个名字到一个路径,与 strcat 函数类似
CreateTextFile(strfile,blnoverwrite) 创建一个空文本文件
CreateFolder(strfolder) 创建一个空的文件夹
CopyFile(source,destination[,overwrite]) 将一个或多个文件从某位置复制到另一位置
CopyFolder(source,destination[,overwrite]) 将文件夹从某位置复制到另一位置
DeleteFile(strfile,force) 删除一个文件
DeleteFolder(strfolder,force) 删除一个文件夹
DriveExists(drivespec) 判断一个驱动器是否存在,返回 0 不存在,-1 存在
FileExists(strfile) 判断文件是否存在
FolderExists(strfolder) 判断文件夹是否存在
GetAbsolutePathName(pathspec) 从路径中返回一个完整、明确的路径(类似Dos命令)
GetDriveName(path) 返回包含指定路径的驱动器名字的字符串
GetDrive(drivespec) 返回与指定路径中的驱动器相对应的 Drive 对象
GetBaseName(path) 返回包含路径中最后部件的基本名字(去掉任何文件扩展名)的字符串
GetExtensionName(path) 获取文件后缀名
GetFileName(pathspec) 指定路径中的最后部件,该路径不是驱动器说明的一部分
GetFile(filespec) 返回和指定路径中文件相对应的 File 对象
GetFolder(folderspec) 返回和指定路径中文件夹相对应的 Folder 对象
GetParentFolderName(path) 返回包含指定路径最后部件父文件夹名字的字符串
GetSpecialFolder(folderspec) 特殊文件夹,folderspec 常数 0:windows 文件夹, 1:System 文件夹, 2:临时文件夹
GetTempName 随机产生的临时文件或文件夹名字,该名字在执行某些操作时有用
OpenTextFile(filename) 打开一指定的文本文件,并返回一个 TextStream 对象
MoveFile(source,destination) 将一个或多个文件从某位置移动到另一位置
MoveFolder(source,destination) 将一个或多个文件夹从某位置移动到另一位置

BuildPath 方法

参数: (path,name)
功能:在已有的路径 path 上增添名字为 name 的文件或文件夹,如果需要,则增添路径分隔符 :

  1. ;;示例12
  2. (vlax-invoke FSO 'BuildPath "d:\\aa" "abc")

以上示例将返回一个字符串: "d:\\aa\\abc" ,程序自动在 aa 后面添加了一个 "\\";这个方法并未在磁盘里建立真正文件或文件夹,仅仅是返回了一个路径,运行效果相当于 strcat 函数:

  1. ;;示例13
  2. (strcat "d:\\aa" "\\" "abc")

尚不明白这个方法有什么其他特别作用,它并不判断路径是否存在。

CopyFolder 方法:

参数: (source,destination [,overwrite])
功能:从指定的源文件夹 source(可以包含通配符)中复制一个或多个文件夹到指定的目标文件夹 destination,复制包含了源文件夹中的所有文件及子文件夹,overwrite 默认为 true,覆盖模式。

  1. ;;示例14
  2. (vlax-invoke FSO 'CopyFolder "d:\\fff" "d:\\aa\\")
  3. ;;参数 "d:\\aa\\" 和 "d:\\aa" 结果是不同的

注意,destination 参数后面是否加 "\\" 是有区别的,有斜杠是 fff 文件夹复制,包括 fff 自身;没有斜杠时复制 fff 文件夹的所有内容,但不含 fff 名称。

FSO 子对象的属性和方法

通过操作 FSO 对象属性或方法可以获取一些子对象,包括: drive 驱动器对象、folder 文件夹对象、file 文件对象和 TextStream 文本流对象。

drive 对象

FSO 对象唯一的 Drives 属性将返回一个 drive 对象的集合。drive 对象属性表示意义如下:

属性 功能说明
AvailableSpace 驱动器的可用空间,考虑了某些限制,字节单位
DriveLetter 哪个符号被赋给了该驱动器
DriveType 驱动器的类型,如不可识别的、可移动的、固定的、网络的、cd-rom 或 ram 磁盘
FileSystem 驱动器使用的文件系统类型,如 fat、fat32、ntfs 等
FreeSpace 驱动器剩余的可用空间,字节单位
IsReady 驱动器是否可以使用,-1 代表可用, 0 代表不可用
Path 驱动器的路径
RootFolder 驱动器的根目录对象
SerialNumber 驱动器的序列号
ShareName 如果是一个网络驱动器,则返回驱动器的网络共享名
TotalSize 驱动器的总容量,字节单位
VolumeName 驱动器卷标名

drive 对象没有支持的方法。

folder 对象

调用 FSO 对象的 GetFolder 方法将返回一个 folder 对象,下面代码在 CAD 中查看 folder 对象的属性和方法:

  1. ;;示例15
  2. (vlax-dump-object (vlax-invoke FSO 'GetFolder "d:\\dc") t)

folder 对象的属性:

属性 功能说明
Attributes 文件属性,包括 0-正常、1-只读、2-隐藏、4-系统、9-名称、16-文件夹
DateCreated 返回该文件夹的创建日期和时间
DateLastAccessed 返回最后一次访问该文件夹的日期和时间
DateLastModified 返回最后一次修改该文件夹的日期和时间
Drive 返回该文件夹所在的驱动器 Drive 对象
Files 返回该文件夹下的文件对象集合
IsRootFolder 是否根目录
Name 返回文件夹的名字
ParentFolder 返回该文件的父文件夹的 Folder 对象
Path 返回文件的绝对路径,可使用长文件名
ShortName 返回DOS风格的8.3形式的文件名
ShortPath 返回DOS风格的8.3形式的文件绝对路径
Size 返回该文件的大小(字节)
SubFolders 子文件夹对象
type 返回一个文件类型的说明字符串

folder 对象支持的方法:

方法与参数 功能说明
Copy(destination,overwrite) 将这个文件复制到 destination 指定的文件夹。如果 destination 的末尾是路径分隔符,那么认为 destination 是放置拷贝文件的文件夹。否则认为 destination 是要创建的新文件的路径和名字。如果目标文件已经存在且 overwrite 参数设置为 False,将产生错误,缺省的 overwrite 参数是 True
Delete(force) 删除这个文件。如果可选的 force 参数设置为 True,即使文件具有只读属性也会被删除。缺省的 force是 False
Move(destination) 将文件移动到destination指定的文件夹
CreateTextFile (filename,overwrite,unicode) 用指定的文件名创建一个新的文本文件,并且返回一个相应的 TextStream 对象。如果可选的 overwrite 参数设置为 True,将覆盖任何已有的同名文件。缺省的 overwrite 参数是 False。如果可选的 unicode 参数设置为 True,文件的内容将存储为 unicode 文本。缺省的 unicode 是False
OpenAsTextStream(iomode,format) 打开指定文件并且返回一个TextStream对象,用于文件的读、写或追加。iomode 参数指定了要求的访问类型,允许值是 ForReading(1) (缺省值)、ForWrite(2)、ForAppending(8)。format 参数说明了读、写文件的数据格式。允许值是 0(缺省): ascii 数据格式;-1: Unicode 数据格式;-2: 系统缺省格式

一个文件夹对象的属性,有子文件夹对象集合-SubFolders、有父文件夹对象-ParentFolder、还有驱动器对象-Drive。通过这三个属性可以在整个文件系统中导航,获取磁盘驱动器的所有文件。

file 对象

调用 FSO 对象的 GetFile 方法将返回一个 file 对象,下面代码查看 file 对象的属性和方法:

  1. ;;示例16
  2. (vlax-dump-object (vlax-invoke FSO 'GetFile "d:\\d.pdf") t)
  3. ;特性值:
  4. ; Attributes = 32
  5. ; DateCreated (RO) = 42864.5
  6. ; DateLastAccessed (RO) = 42864.5
  7. ; DateLastModified (RO) = 42864.5
  8. ; Drive (RO) = #<VLA-OBJECT IDrive 0000000008a48d08>
  9. ; Name = "d.pdf"
  10. ; ParentFolder (RO) = #<VLA-OBJECT IFolder 0000000008a02178>
  11. ; Path (RO) = "D:\\d.pdf"
  12. ; ShortName (RO) = "d.pdf"
  13. ; ShortPath (RO) = "D:\\d.pdf"
  14. ; Size (RO) = 2104235
  15. ; Type (RO) = "Adobe Acrobat 文档"
  16. ;支持的方法:
  17. ; Copy (2)
  18. ; Delete (1)
  19. ; Move (1)
  20. ; OpenAsTextStream (2)

file 对象的属性:
Attributes 返回文件的属性。可以是下列值中的一个或其组合:
Normal(0)、ReadOnly(1)、Hidden(2)、System(4)、Volume(9)、Directory(16)、Archive(32)、Alias(64)和Compressed(128)
其余属性及方法与 folder 对象相同,不再赘述。

应用示例

以下函数用递归法求某文件夹里文件和子文件夹数量(在制作文件处理进度条时可能有用)。

  1. ;;示例17
  2. ;;调用 (fnSum "d:\\dc")
  3. ;;返回 (11446 2266)
  4. (defun fnSum(p / F Filesum Foldsum)
  5. (setq F (vlax-create-object "scripting.FileSystemObject")
  6. Filesum 0 Foldsum 0)
  7. (defun getsum(FD / FDs)
  8. (setq FDs (vlax-get FD 'SubFolders)
  9. Filesum (+ Filesum (vlax-get (vlax-get FD 'Files) 'count))
  10. Foldsum (+ Foldsum (vlax-get FDs 'count)))
  11. (vlax-for x FDs (getsum x))
  12. )
  13. (if (= -1 (vlax-invoke F 'FolderExists p))(getsum (vlax-invoke F 'GetFolder p)))
  14. (vlax-release-object F)
  15. (list Filesum Foldsum)
  16. )

FSO 并不是必须的,轻量级的文件夹操作,使用 Vlisp 也可以满足要求,例如遍历文件夹,返回某路径下的文件夹及子文件夹的列表。

  1. ;;示例18
  2. ;;返回某路径下的文件夹及子文件夹
  3. ;;参数: p 为路径,调用 (Get_Folds "d:\\fff")
  4. (defun fnSum2(p / d)
  5. (defun Fold(s)
  6. (setq d (cons s d))
  7. (foreach x (cddr(vl-directory-files s nil -1))(Fold(strcat s "\\" x)))
  8. )
  9. (if (findfile p)(Fold p))
  10. (reverse d)
  11. )

用 Vlisp 函数求文件夹所有子文件及文件夹数量,实现示例17 的效果。

  1. ;;示例19
  2. (defun fnSum2(p / Filesum Foldsum)
  3. (setq Filesum 0 Foldsum 0)
  4. (defun Fold(s / d)
  5. (setq d (cddr(vl-directory-files s nil -1))
  6. Foldsum (+ Foldsum (length d))
  7. Filesum (+ Filesum (length (vl-directory-files s nil 1))))
  8. (foreach x d (Fold(strcat s "\\" x)))
  9. )
  10. (if (findfile p)(Fold p))
  11. (list Filesum Foldsum)
  12. )

两种方法的效率对比测试:

  1. ;;示例20
  2. (defun c:test( / i AA)
  3. (vl-load-com)
  4. (setq time0 (getvar "date"))(fnSum "d:\\dc") ;;调用 FSO 对象函数
  5. (setq time1 (getvar "date"))(fnSum2 "d:\\dc") ;;调用 VLisp 函数
  6. (princ (strcat "\n调用 FSO 对象函数耗时: " (rtos (* 86400 (- time1 time0)) 2 4) " 秒"))
  7. (princ (strcat "\n调用 VLisp 函数耗时: " (rtos (* 86400 (- (getvar "date") time1)) 2 4) " 秒"))
  8. (princ)
  9. )

测试文件夹数据为:文件 1.2 万个,文件夹 2300 个
调用 FSO 对象函数耗时: 0.574 秒
调用 VLisp 函数耗时: 0.732 秒
可以看出效率相差不大,可能是由于 lisp 的递归结构降低了 FSO 对象的使用效率。