NO IMAGE

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驅動器卷標名

drive 物件沒有支援的方法。

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 物件的使用效率。