如何在 VBA for Mac 中加速外壳功能



我一直在使用这个问题的最高答案中的函数在VBA(execShell函数)中运行shell脚本,它使用libc.dylib:

Office 2011 for Mac 中的 VBA 命令行管理程序函数

函数内有一个 while 循环,它的运行速度比 popen 慢得多。我猜这部分正在 shell 中执行,而 popen 正在初始化,但我真的不明白它在做什么。

Time taken to run popen:
0.01171875 seconds
Time taken to run while loop:
0.8710938 seconds

我想知道如果执行时间很长,是否可以中止或超时函数?有没有一种方法可以用来处理多个函数调用?我在 shell 脚本中的 curl 命令中添加了超时。"curl --connect-timeout 5 --max-time 10 --retry 2 --retry-delay 0 --retry-max-time 40 ",并在每次调用前增加了 0.1 秒的延迟。如果它运行顺利,那不会有问题,但有时它似乎会完全锁定。如果 curl 请求有很长的延迟,我宁愿中止它并继续使用脚本。也许在连续几次失败后完全终止脚本。我以为函数调用会线性处理,但我认为它可能同时处理 shell 脚本,这可能会导致问题。理想情况下,我希望获得有关脚本状态的反馈,我尝试使用状态栏实现脚本,但由于 2011 版 Excel 的限制,这不起作用。在运行开始时,我可以看到工作表正在更新并且一切运行顺利,但稍后在脚本中 excel 冻结直到完成。

另一种解决方案是使用 MacScript 命令,但此 shell 脚本包含带有"\"和多个引号的正则表达式,并且很难转换为 applescript,所以我更喜欢使用此方法,因为它已经有效。使用 system() 命令不是一个选项,因为 VBA 脚本需要输出。

Private Declare Function popen Lib "libc.dylib" (ByVal command As String, ByVal mode As String) As Long
Private Declare Function pclose Lib "libc.dylib" (ByVal file As Long) As Long
Private Declare Function fread Lib "libc.dylib" (ByVal outStr As String, ByVal size As Long, ByVal items As Long, ByVal stream As Long) As Long
Private Declare Function feof Lib "libc.dylib" (ByVal file As Long) As Long
Function execShell(command As String, Optional ByRef exitCode As Long) As String
Dim file As Long
file = popen(command, "r")
If file = 0 Then
Exit Function
End If
While feof(file) = 0
Dim chunk As String
Dim read As Long
chunk = Space(50)
read = fread(chunk, 1, Len(chunk) - 1, file)
If read > 0 Then
chunk = Left$(chunk, read)
execShell = execShell & chunk
End If
Wend
exitCode = pclose(file)
End Function

popen运行得更快,因为它是用 C/C++ 编写的,并且是二进制可执行文件。它在操作系统级别运行,而不是在 VBA 虚拟机 (VBA VM) 中运行。 您的 While 循环和 VBA 中的其他所有内容都是在 VBA VM 中运行的脚本语言,它必须将您的脚本转换为 p 代码,然后由 VBA VM 执行。如果您有兴趣,维基百科上的"Visual Basic for Applications"文章提供了更详细的解释。

VBA 代码将始终比 Excel 对象或外部函数(如popen)提供的任何方法慢。 例如,使用范围对象自动填充方法始终比VBA更快,同时循环用值填充范围。

向代码添加"超时">的最佳方法是在 while 循环中添加 VBA 方法 DoEvents。 然后

我会把它放在End IfWend之间,如下所示:

End If
VBA.DoEvents
Wend

这将导致While循环允许 Excel 和 Windows 处理事件。 除了 VBAShell()函数外,VBA 代码是串行执行的。 如果没有VBA.DoEvents,您的代码将继续执行,直到完成或出错。 这个堆栈溢出问题线程提供了一个很好的解释。

可以加快代码速度的一件事是使用此命令关闭屏幕更新Application.ScreenUpdating = False. 记得在潜艇结束时用Application.ScreenUpdating = True重新打开它。 我会像这样把它放在While循环中:

Application.ScreenUpdating = False
While feof(file) = 0 
.
.
.
Wend
Application.ScreenUpdating = True

此外,如果您真的很喜欢冒险,可以编写一个事件处理程序。Chip Pearson有一篇很棒的文章"VBA中的事件和事件过程"。

最新更新