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
(setq FSO (vlax-create-object "scripting.FileSystemObject"))
可以通过 vlax-dump-object 函数来查询 FSO 对象的属性和方法。使用完毕,建议释放 FSO 对象:
;;示例2
(vlax-dump-object FSO t) ;;查询对象的属性和方法
(vlax-release-object FSO) ;;释放对象(结束进程)
FSO 的属性
FSO 只有一个 Drives 属性,该属性是 Drive 对象的集合。
;;示例3
(setq DRs (vlax-get-property FSO 'Drives))
一般来说,一个对象集合均具备 count 属性和 Item 属性,通过指定 Item 属性的值可以调用该集合的对象,但是 FSO 对象很奇怪,只能用 vlax-for 来遍历对象所有条目,也就是下面的语句将返回一个 Automation 错误。
;;示例4
(vlax-get-property DRs 'Item 1) ;;这是一个错误的示例
上面的代码得不到 Drives 集合的第二个 Drive 对象。解决的办法也很简单,用 vlax-for 将对象集合转为一个 list 表,这样可用 nth 来提取指定的 Item 项。下面定义一个 Lisp 函数来代替 Item 功能:
;;示例5
(defun GetDrive(n / d)
(vlax-for x (vlax-get FSO 'Drives)(setq d (cons x d)))
(nth n (reverse d))
)
例如,返回第一个驱动器的 Drive 对象(一般是 C: 盘),可以用:(GetDrive 0)
来表示
若需得到一台计算机的驱动器 list 列表,可提取 Drive 对象的 path 属性,代码如下:
;;示例6
(setq dr '())
(vlax-for x (vlax-get FSO 'Drives)(setq dr (cons(vlax-get x 'Path) dr)))
用 AutoLisp 的 findfile 函数也可以判断驱动是否存在:
;;示例7
(findfile "f:")
当 findfile 函数返回 nil 时表示 f: 盘不存在,但是在 win8/10 系统里,如果没用管理员模式打开 AutoCAD,则 C: 即使存在,也会返回 nil;使用 FSO 对象则绕过了管理员账户,对查询 C: 盘并不影响:
;;示例8
(vlax-invoke FSO 'DriveExists "c:") ;; 返回 -1 代表存在
在程序注册加密时,我们通常获取驱动器的序列号作为机器码,下面代码将返回第2个驱动器的序列号:
;;示例9
(vlax-get (GetNs 1) 'SerialNumber)
也可以调用 FSO 的方法来获取指定驱动器的序列号,代码如下:
;;示例10
(vlax-get (vlax-invoke FSO 'GetDrive "d:") 'SerialNumber)
所以说,FSO 提供的方法是冗余的,通过不同的途径可以获取相同的结果。采用 FSO 的方法来获取序列号时,应判断 d: 是否存在:
;;示例11
(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 的文件或文件夹,如果需要,则增添路径分隔符 :
;;示例12
(vlax-invoke FSO 'BuildPath "d:\\aa" "abc")
以上示例将返回一个字符串: "d:\\aa\\abc"
,程序自动在 aa 后面添加了一个 "\\"
;这个方法并未在磁盘里建立真正文件或文件夹,仅仅是返回了一个路径,运行效果相当于 strcat 函数:
;;示例13
(strcat "d:\\aa" "\\" "abc")
尚不明白这个方法有什么其他特别作用,它并不判断路径是否存在。
CopyFolder 方法:
参数: (source,destination [,overwrite])
功能:从指定的源文件夹 source(可以包含通配符)中复制一个或多个文件夹到指定的目标文件夹 destination,复制包含了源文件夹中的所有文件及子文件夹,overwrite 默认为 true,覆盖模式。
;;示例14
(vlax-invoke FSO 'CopyFolder "d:\\fff" "d:\\aa\\")
;;参数 "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 | 驱动器卷标名 |
folder 对象
调用 FSO 对象的 GetFolder 方法将返回一个 folder 对象,下面代码在 CAD 中查看 folder 对象的属性和方法:
;;示例15
(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 对象的属性和方法:
;;示例16
(vlax-dump-object (vlax-invoke FSO 'GetFile "d:\\d.pdf") t)
;特性值:
; Attributes = 32
; DateCreated (RO) = 42864.5
; DateLastAccessed (RO) = 42864.5
; DateLastModified (RO) = 42864.5
; Drive (RO) = #<VLA-OBJECT IDrive 0000000008a48d08>
; Name = "d.pdf"
; ParentFolder (RO) = #<VLA-OBJECT IFolder 0000000008a02178>
; Path (RO) = "D:\\d.pdf"
; ShortName (RO) = "d.pdf"
; ShortPath (RO) = "D:\\d.pdf"
; Size (RO) = 2104235
; Type (RO) = "Adobe Acrobat 文档"
;支持的方法:
; Copy (2)
; Delete (1)
; Move (1)
; OpenAsTextStream (2)
file 对象的属性:
Attributes 返回文件的属性。可以是下列值中的一个或其组合:
Normal(0)、ReadOnly(1)、Hidden(2)、System(4)、Volume(9)、Directory(16)、Archive(32)、Alias(64)和Compressed(128)
其余属性及方法与 folder 对象相同,不再赘述。
应用示例
以下函数用递归法求某文件夹里文件和子文件夹数量(在制作文件处理进度条时可能有用)。
;;示例17
;;调用 (fnSum "d:\\dc")
;;返回 (11446 2266)
(defun fnSum(p / F Filesum Foldsum)
(setq F (vlax-create-object "scripting.FileSystemObject")
Filesum 0 Foldsum 0)
(defun getsum(FD / FDs)
(setq FDs (vlax-get FD 'SubFolders)
Filesum (+ Filesum (vlax-get (vlax-get FD 'Files) 'count))
Foldsum (+ Foldsum (vlax-get FDs 'count)))
(vlax-for x FDs (getsum x))
)
(if (= -1 (vlax-invoke F 'FolderExists p))(getsum (vlax-invoke F 'GetFolder p)))
(vlax-release-object F)
(list Filesum Foldsum)
)
FSO 并不是必须的,轻量级的文件夹操作,使用 Vlisp 也可以满足要求,例如遍历文件夹,返回某路径下的文件夹及子文件夹的列表。
;;示例18
;;返回某路径下的文件夹及子文件夹
;;参数: p 为路径,调用 (Get_Folds "d:\\fff")
(defun fnSum2(p / d)
(defun Fold(s)
(setq d (cons s d))
(foreach x (cddr(vl-directory-files s nil -1))(Fold(strcat s "\\" x)))
)
(if (findfile p)(Fold p))
(reverse d)
)
用 Vlisp 函数求文件夹所有子文件及文件夹数量,实现示例17 的效果。
;;示例19
(defun fnSum2(p / Filesum Foldsum)
(setq Filesum 0 Foldsum 0)
(defun Fold(s / d)
(setq d (cddr(vl-directory-files s nil -1))
Foldsum (+ Foldsum (length d))
Filesum (+ Filesum (length (vl-directory-files s nil 1))))
(foreach x d (Fold(strcat s "\\" x)))
)
(if (findfile p)(Fold p))
(list Filesum Foldsum)
)
两种方法的效率对比测试:
;;示例20
(defun c:test( / i AA)
(vl-load-com)
(setq time0 (getvar "date"))(fnSum "d:\\dc") ;;调用 FSO 对象函数
(setq time1 (getvar "date"))(fnSum2 "d:\\dc") ;;调用 VLisp 函数
(princ (strcat "\n调用 FSO 对象函数耗时: " (rtos (* 86400 (- time1 time0)) 2 4) " 秒"))
(princ (strcat "\n调用 VLisp 函数耗时: " (rtos (* 86400 (- (getvar "date") time1)) 2 4) " 秒"))
(princ)
)
测试文件夹数据为:文件 1.2 万个,文件夹 2300 个调用 FSO 对象函数耗时: 0.574 秒
调用 VLisp 函数耗时: 0.732 秒
可以看出效率相差不大,可能是由于 lisp 的递归结构降低了 FSO 对象的使用效率。