FileSystemObject 调用执行...太快了吗?
FileSystemObject calls executing... too fast?
我正在开发一个修改文件的系统 "in-place"。使用引号是因为实际过程如下:
- 将修改写入具有临时名称的新文件。
- 移动(重命名)扩展名为“.bak”的源文件
- 将新文件移动(重命名)为源文件的名称。
- 删除中间文件。
此过程涉及使用 PDFtk 修改 PDF 文件。问题是,当我 运行 代码时,它有时会 "skips" 一路上的某个点的一个步骤,有时会导致与未发生的先前步骤之一相关的错误。
当我使用调试器单步执行代码时,每次都一切正常。如果我在每个文件系统调用之间添加以下代码,它也可以工作。
Public Sub Wait(seconds As Integer)
Dim dTimer As Double
dTimer = Timer
Do While Timer < dTimer + seconds
DoEvents
Loop
End Sub
(来源:this VBForums post)
我真的不想这样做,因为它会增加执行时间,而且我真的不知道对于将要使用此应用程序的各种客户端来说,暂停需要多长时间 "enough" .有没有更好的方法让上述功能正常工作?也许根本不使用 FileSystemObject
?
这是我正在使用的代码:
If LCase$(inputFilename) = LCase$(outputFilename) Then
' Saving changes to original file.
Set fso = New FileSystemObject
tempPath = fso.GetParentFolderName(outputFilename) & "\" & _
Format$(Now, "mm_dd_yyyy_hh_nn_ss") & ".pdf"
' Create temp file
CreateSubsetFromPDF = Shell("""" & App.Path & "\pdftk"" """ & inputFilename & _
""" cat " & pages & " output """ & tempPath & """", vbHide)
' Copy temp file to actual destination, overwriting.
fso.CopyFile tempPath, outputFilename, True
fso.DeleteFile tempPath
End If
我怀疑您的问题是 shell return 在启动(或未能启动)进程后立即出现。我会建议使用 API 调用 CreateProcess 并等待至少一点时间让过程 return 得到结果。我用得够多了,我为此创建了一个方法。有时我必须等待几个小时才能使生成的程序达到 运行,因此我的方法使用了无限等待。因为您只希望等待几秒钟,所以我会修改等待时间以适合您自己。等待时间就是在失败之前等待的时间。否则,它会在生成的进程完成后立即 return。
Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadID As Long
End Type
Private Declare Function WaitForSingleObjectEx Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long, ByVal bAlertable As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, lpExitCode As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function CreateProcessA Lib "kernel32" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, ByVal _
lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As String, _
lpStartupInfo As STARTUPINFO, lpProcessInformation As _
PROCESS_INFORMATION) As Long
Private Const INFINITE = -1&
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const SW_SHOW = 5
Private Const STARTF_USESHOWWINDOW = &H1
Public Function ExecAndWait(ByVal Program As String, Optional ByVal Parms As Variant, _
Optional ByVal hStdOutput As Long) As Long
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
Dim RET As Long
Dim strCommandLine As String
Dim lExitCode As Long
Dim lWaitTime As Long
On Error GoTo errExecAndWait
' quote the commandline and parameters if necessary
If InStr(Program, """") = 0 Then ' if quotes are found assume that any necessary quoting has already been done
If InStr(Program, " ") > 0 Then
'do not include any Program parms (" /parm" in the quotes
If InStr(Program, " /") > 0 Then
strCommandLine = Chr$(34) & Left$(Program, InStr(Program, " /") - 1) & Chr$(34) & Right$(Program, Len(Program) - InStr(Program, " /") + 1)
Else
strCommandLine = Chr$(34) & Program & Chr$(34)
End If
Else
strCommandLine = Program
End If
Else
strCommandLine = Program
End If
If Not IsMissing(Parms) Then
If Len(Parms) > 0 Then
If InStr(Program, """") = 0 Then ' if quotes are found assume that any necessary quoting has already been done
If InStr(Parms, " ") > 0 Then
strCommandLine = strCommandLine & " " & Chr$(34) & Parms & Chr$(34)
Else
strCommandLine = strCommandLine & " " & Parms
End If
Else
strCommandLine = strCommandLine & " " & Parms
End If
End If
End If
start.dwFlags = STARTF_USESHOWWINDOW
start.wShowWindow = SW_SHOW
lWaitTime = INFINITE 'forever
' Initialize the STARTUPINFO structure:
start.cb = Len(start)
' Start the shelled application:
RET& = CreateProcessA(vbNullString, strCommandLine, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, vbNullString, start, proc)
' change the return value to 0 if the dll returned other than 0 else return the dll error
If RET& = 0 Then 'error
ExecAndWait = 0
Err.Raise Err.LastDllError, strCommandLine, Translate_DLL_Error(Err.LastDllError)
Else
Call Sleep(2000) ' Wait for the shelled application to get going:
RET& = WaitForSingleObjectEx(proc.hProcess, lWaitTime, False)
RET& = GetExitCodeProcess(proc.hProcess, lExitCode)
ExecAndWait = RET&
RET& = CloseHandle(proc.hProcess)
RET& = CloseHandle(proc.hThread)
End If
Exit Function
errExecAndWait:
Err.Raise Err.Number, Err.Source & ":ExecAndWait", Err.Description
End Function
我正在开发一个修改文件的系统 "in-place"。使用引号是因为实际过程如下:
- 将修改写入具有临时名称的新文件。
- 移动(重命名)扩展名为“.bak”的源文件
- 将新文件移动(重命名)为源文件的名称。
- 删除中间文件。
此过程涉及使用 PDFtk 修改 PDF 文件。问题是,当我 运行 代码时,它有时会 "skips" 一路上的某个点的一个步骤,有时会导致与未发生的先前步骤之一相关的错误。
当我使用调试器单步执行代码时,每次都一切正常。如果我在每个文件系统调用之间添加以下代码,它也可以工作。
Public Sub Wait(seconds As Integer)
Dim dTimer As Double
dTimer = Timer
Do While Timer < dTimer + seconds
DoEvents
Loop
End Sub
(来源:this VBForums post)
我真的不想这样做,因为它会增加执行时间,而且我真的不知道对于将要使用此应用程序的各种客户端来说,暂停需要多长时间 "enough" .有没有更好的方法让上述功能正常工作?也许根本不使用 FileSystemObject
?
这是我正在使用的代码:
If LCase$(inputFilename) = LCase$(outputFilename) Then
' Saving changes to original file.
Set fso = New FileSystemObject
tempPath = fso.GetParentFolderName(outputFilename) & "\" & _
Format$(Now, "mm_dd_yyyy_hh_nn_ss") & ".pdf"
' Create temp file
CreateSubsetFromPDF = Shell("""" & App.Path & "\pdftk"" """ & inputFilename & _
""" cat " & pages & " output """ & tempPath & """", vbHide)
' Copy temp file to actual destination, overwriting.
fso.CopyFile tempPath, outputFilename, True
fso.DeleteFile tempPath
End If
我怀疑您的问题是 shell return 在启动(或未能启动)进程后立即出现。我会建议使用 API 调用 CreateProcess 并等待至少一点时间让过程 return 得到结果。我用得够多了,我为此创建了一个方法。有时我必须等待几个小时才能使生成的程序达到 运行,因此我的方法使用了无限等待。因为您只希望等待几秒钟,所以我会修改等待时间以适合您自己。等待时间就是在失败之前等待的时间。否则,它会在生成的进程完成后立即 return。
Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadID As Long
End Type
Private Declare Function WaitForSingleObjectEx Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long, ByVal bAlertable As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, lpExitCode As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function CreateProcessA Lib "kernel32" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, ByVal _
lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As String, _
lpStartupInfo As STARTUPINFO, lpProcessInformation As _
PROCESS_INFORMATION) As Long
Private Const INFINITE = -1&
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const SW_SHOW = 5
Private Const STARTF_USESHOWWINDOW = &H1
Public Function ExecAndWait(ByVal Program As String, Optional ByVal Parms As Variant, _
Optional ByVal hStdOutput As Long) As Long
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
Dim RET As Long
Dim strCommandLine As String
Dim lExitCode As Long
Dim lWaitTime As Long
On Error GoTo errExecAndWait
' quote the commandline and parameters if necessary
If InStr(Program, """") = 0 Then ' if quotes are found assume that any necessary quoting has already been done
If InStr(Program, " ") > 0 Then
'do not include any Program parms (" /parm" in the quotes
If InStr(Program, " /") > 0 Then
strCommandLine = Chr$(34) & Left$(Program, InStr(Program, " /") - 1) & Chr$(34) & Right$(Program, Len(Program) - InStr(Program, " /") + 1)
Else
strCommandLine = Chr$(34) & Program & Chr$(34)
End If
Else
strCommandLine = Program
End If
Else
strCommandLine = Program
End If
If Not IsMissing(Parms) Then
If Len(Parms) > 0 Then
If InStr(Program, """") = 0 Then ' if quotes are found assume that any necessary quoting has already been done
If InStr(Parms, " ") > 0 Then
strCommandLine = strCommandLine & " " & Chr$(34) & Parms & Chr$(34)
Else
strCommandLine = strCommandLine & " " & Parms
End If
Else
strCommandLine = strCommandLine & " " & Parms
End If
End If
End If
start.dwFlags = STARTF_USESHOWWINDOW
start.wShowWindow = SW_SHOW
lWaitTime = INFINITE 'forever
' Initialize the STARTUPINFO structure:
start.cb = Len(start)
' Start the shelled application:
RET& = CreateProcessA(vbNullString, strCommandLine, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, vbNullString, start, proc)
' change the return value to 0 if the dll returned other than 0 else return the dll error
If RET& = 0 Then 'error
ExecAndWait = 0
Err.Raise Err.LastDllError, strCommandLine, Translate_DLL_Error(Err.LastDllError)
Else
Call Sleep(2000) ' Wait for the shelled application to get going:
RET& = WaitForSingleObjectEx(proc.hProcess, lWaitTime, False)
RET& = GetExitCodeProcess(proc.hProcess, lExitCode)
ExecAndWait = RET&
RET& = CloseHandle(proc.hProcess)
RET& = CloseHandle(proc.hThread)
End If
Exit Function
errExecAndWait:
Err.Raise Err.Number, Err.Source & ":ExecAndWait", Err.Description
End Function