我在各个地方读到API定时器在VBA中存在风险,如果在定时器运行时编辑单元格,它将导致Excel崩溃。但是由于Jordan Goldmeier而来自http://optionexplicitvba.wordpress.com的代码似乎没有这个问题。它使用计时器淡化弹出窗口,当它消失时,我可以单击并在单元格和公式栏中输入文本而没有任何问题。
API计时器什么时候安全?什么时候不安全?是否有一些 具体的 原则可以帮助我理解? 崩溃的机制是什么:究竟是什么让Excel崩溃?
Option Explicit
Public Declare Function SetTimer Lib "user32" ( _
ByVal HWnd As Long, _
ByVal nIDEvent As Long, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" ( _
ByVal HWnd As Long, _
ByVal nIDEvent As Long) As Long
Public TimerID As Long
Public TimerSeconds As Single
Public bTimerEnabled As Boolean
Public iCounter As Integer
Public bComplete As Boolean
Public EventType As Integer
Public Sub Reset()
With Sheet1.Shapes("MyLabel")
.Fill.Transparency = 0
.Line.Transparency = 0
.TextFrame2.TextRange.Font.Fill.ForeColor.RGB = RGB(0, 0, 0)
End With
Sheet1.Shapes("MyLabel").Visible = msoTrue
End Sub
Sub StartTimer()
iCounter = 1
Reset
TimerID = SetTimer(0&, 0&, 0.05 * 1000&, AddressOf TimerProc)
End Sub
Sub EndTimer()
KillTimer 0&, TimerID
bTimerEnabled = False
bComplete = True
End Sub
Sub TimerProc(ByVal HWnd As Long, ByVal uMsg As Long, _
ByVal nIDEvent As Long, ByVal dwTimer As Long)
On Error Resume Next
Debug.Print iCounter
If iCounter > 50 Then
With Sheet1.Shapes("MyLabel")
.Fill.Transparency = (iCounter - 50) / 50
.Line.Transparency = (iCounter - 50) / 50
.TextFrame2.TextRange.Font.Fill.ForeColor.RGB = _
RGB((iCounter - 50) / 50 * 224, _
(iCounter - 50) / 50 * 224, _
(iCounter - 50) / 50 * 224)
End With
End If
If iCounter > 100 Then
Sheet1.Shapes("MyLabel").Visible = msoFalse
EndTimer
End If
iCounter = iCounter + 1
End Sub
Public Function ShowPopup(index As Integer)
Sheet1.Range("Hotzone.Index").Value = index
iCounter = 1
If bTimerEnabled = False Then
StartTimer
bTimerEnabled = True
Reset
Else
Reset
End If
With Sheet1.Shapes("MyLabel")
.Left = Sheet1.Range("Hotzones").Cells(index, 1).Left + _
Sheet1.Range("Hotzones").Cells(index, 1).Width
.Top = Sheet1.Range("Hotzones").Cells(index, 1).Top - _
(.Height / 2)
End With
Sheet1.Range("a4:a6").Cells(index, 1).Value = index
End Function
答案 0 :(得分:6)
@CoolBlue:崩溃的机制是什么:究竟是为了让Excel崩溃?
我能为你扩展Siddarth Rout的答案,但不是一个完整的解释。
API调用不是VBA:它们存在于VBA的错误处理程序之外,当出现问题时,它们将不执行任何操作,或调用内存中不存在的资源,或尝试读取(或写入!)超出Excel.exe指定内存空间的内存
当发生这种情况时,操作系统将介入并关闭您的应用程序。我们过去称之为“一般保护错误”,这仍然是对该过程的有用描述。
现在了解一些细节。
当你在VBA中调用一个函数时,你只需写下名字 - 让我们称之为'CheckMyFile()' - 这就是你在VBA中需要知道的全部内容。如果没有任何名为'CheckMyFile'的调用,或者它被声明你的调用无法看到它,编译器或运行时引擎将在编译和运行之前以断点或警告的形式引发错误。
在幕后,有一个与字符串'CheckMyFile'相关联的数字地址:我简化了一点,但我们将该地址称为功能指针 - 跟随该地址,我们获取一个结构化的内存块,用于存储函数参数的定义,存储值的空间,以及后面的地址,将这些参数引导到为执行VBA而创建的函数结构中,并将值返回到函数输出的地址。 / p>
事情可能出错,VBA做了很多工作,以确保当它们出错时,所有这些都可以优雅地折叠起来。
如果您将该函数指针指向非VBA的东西 - 外部应用程序或(例如) API计时器调用 - 您的函数仍然可以被调用,它仍然可以运行,并且一切会工作的。
但最好在该指针后面有一个有效的函数。
如果没有,外部应用程序将调用自己的错误处理程序,并且它们不会像VBA那样宽容。
如果Excel和VBA处于“忙碌”状态,或者当它尝试使用该功能指针时不可用,它可能只是放弃调用而不执行任何操作:您可能很幸运,只需要一次。但它可能会在Excel.exe进程中调用操作系统的愤怒。
如果调用导致错误,并且代码没有处理该错误,VBA会将错误提交给调用者 - 并且由于调用者不是VBA,它可能无法处理那:它将从操作系统中寻求'帮助'。
如果是API调用,那么它是为假定在调用代码中实现错误处理和应急管理的开发人员编写的。
这些假设是:
通过API调用,调用者 操作系统,并且它对检测到错误的响应将关闭您。
所以这是一个非常简单的过程大纲 - 一个'为什么'而不是一个'什么'的解释。
对于C ++开发人员来说,没有过度简化的完整解释。如果你真的想深入了解答案,你必须学会用指针编程;并且你必须熟练掌握内存分配,异常,错误指针的后果以及操作系统用来管理正在运行的应用程序和检测无效操作的机制的概念和实践。
VBA旨在保护您免受该知识的影响,并简化编写应用程序的任务。
答案 1 :(得分:5)
正如所承诺的,这里是Timer API的32位和64位API声明,使用LongLong和安全指针类型:
Option Explicit Option Private Module
#If VBA7 And Win64 Then ' 64 bit Excel under 64-bit windows ' Use LongLong and LongPtr
Private Declare PtrSafe Function SetTimer Lib "user32" _ (ByVal hwnd As LongPtr, _ ByVal nIDEvent As LongPtr, _ ByVal uElapse As LongLong, _ ByVal lpTimerFunc As LongPtr _ ) As LongLong
Public Declare PtrSafe Function KillTimer Lib "user32" _ (ByVal hwnd As LongPtr, _ ByVal nIDEvent As LongPtr _ ) As LongLong Public TimerID As LongPtr
#ElseIf VBA7 Then ' 64 bit Excel in all environments ' Use LongPtr only, LongLong is not available
Private Declare PtrSafe Function SetTimer Lib "user32" _ (ByVal hwnd As LongPtr, _ ByVal nIDEvent As Long, _ ByVal uElapse As Long, _ ByVal lpTimerFunc As LongPtr) As LongPtr
Private Declare PtrSafe Function KillTimer Lib "user32" _ (ByVal hwnd As LongPtr, _ ByVal nIDEvent As Long) As Long
Public TimerID As LongPtr
#Else ' 32 bit Excel
Private Declare Function SetTimer Lib "user32" _ (ByVal hwnd As Long, _ ByVal nIDEvent As Long, _ ByVal uElapse As Long, _ ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" _ (ByVal hwnd As Long, _ ByVal nIDEvent As Long) As Long
Public TimerID As Long
#End If
' Call the timer as: ' SetTimer 0&, 0&, lngMilliseconds, AddressOf TimerProc
#If VBA7 And Win64 Then ' 64 bit Excel under 64-bit windows ' Use LongLong and LongPtr ' Note that wMsg is always the WM_TIMER message, which actually fits in a Long
Public Sub TimerProc(ByVal hwnd As LongPtr, _ ByVal wMsg As LongLong, _ ByVal idEvent As LongPtr, _ ByVal dwTime As LongLong) On Error Resume Next
KillTimer hwnd, idEvent ' Kill the recurring callback here, if that's what you want to do ' Otherwise, implement a lobal KillTimer call on exit
' **** YOUR TIMER PROCESS GOES HERE ****
End Sub#ElseIf VBA7 Then ' 64 bit Excel in all environments
' Use LongPtr only
Public Sub TimerProc(ByVal hwnd As LongPtr, _ ByVal wMsg As Long, _ ByVal idEvent As LongPtr, _ ByVal dwTime As Long) On Error Resume Next
KillTimer hwnd, idEvent ' Kill the recurring callback here, if that's what you want to do ' Otherwise, implement a lobal KillTimer call on exit
' **** YOUR TIMER PROCESS GOES HERE ****
End Sub#Else ' 32 bit Excel
Public Sub TimerProcInputBox(ByVal hwnd As Long, _ ByVal wMsg As Long, _ ByVal idEvent As Long, _ ByVal dwTime As Long) On Error Resume Next
KillTimer hwnd, idEvent ' Kill the recurring callback here, if that's what you want to do ' Otherwise, implement a lobal KillTimer call on exit
' **** YOUR TIMER PROCESS GOES HERE ****
End Sub#End If
在上面的示例代码中,hwnd参数设置为零,如果您从VBA调用此参数而不是将调用与(例如)InputBox或表单相关联,则应始终为零。
这个Timer API的完整工作示例,包括使用窗口的hwnd参数,可在Excellerando网站上找到:
Using the VBA InputBox for passwords and hiding the user's keyboard input with asterisks.
脚注:
这已作为单独的回复发布,我对与调用Timer API相关的系统错误的解释没有仔细的错误处理:它是一个单独的主题,StackOverflow将受益于一个单独的,可搜索的答案Windows Timer API的指针安全和64位声明。
网上有API声明的坏例子;在32位Windows环境中安装的VBA7(支持安全指针类型)的常见情况很少见(不支持64位' LongLong'整数)
答案 2 :(得分:4)
我在各个地方读到API定时器在VBA中存在风险
声明应该是I read in various places that API timers are risky
?我之所以这么说是因为这些API可以在VB6 / VBA / VB.Net等中使用。
他们有风险吗?是的,但他们是tight rope walking。一个错误的举动,你就完成了。仅SetTimer API
而不是几乎任何API都不是这种情况。
我在2009年创建了一个示例,使用SetTimer API
在Excel中创建启动画面。这是LINK。
现在,如果您解压缩文件并直接打开Excel文件,那么您将看到Excel崩溃。要使其工作,请按 SHIFT 键,然后打开Excel以使宏不运行。接下来改变图像的路径。新路径将是您从zip文件中提取的图像的路径。更改路径后,只需保存并关闭文件即可。下次运行时,Excel不会崩溃。
以下是Excel文件中的代码
Public Declare Function SetTimer Lib "user32" ( _
ByVal HWnd As Long, ByVal nIDEvent As Long, _
ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" ( _
ByVal HWnd As Long, ByVal nIDEvent As Long) As Long
Public TimerID As Long, TimerSeconds As Single, tim As Boolean
Dim Counter As Long
Sub StartTimer()
'~~ Set the timer.
TimerSeconds = 1
TimerID = SetTimer(0&, 0&, TimerSeconds * 1000&, AddressOf TimerProc)
End Sub
Sub EndTimer()
On Error Resume Next
KillTimer 0&, TimerID
End Sub
Sub TimerProc(ByVal HWnd As Long, ByVal uMsg As Long, _
ByVal nIDEvent As Long, ByVal dwTimer As Long)
If tim = False Then
UserForm1.Image1.Picture = LoadPicture("C:\temp\1.bmp")
tim = True
Else
UserForm1.Image1.Picture = LoadPicture("C:\temp\2.bmp")
tim = False
End If
Counter = Counter + 1
If Counter = 10 Then
EndTimer
Unload UserForm1
End If
End Sub
API计时器什么时候安全?什么时候不安全?是否有一些广泛的原则可以帮助我理解?
所以这一切归结为一个事实。 您的代码有多强大。如果您的代码处理每个方案,那么SetTimer API
或事实上任何API都不会失败。
答案 3 :(得分:2)
@CoolBlue我写了你上面发布的代码。确切地说,API可以无法预测地发挥作用,至少与普通代码相比。但是,如果你的代码足够健壮(遵循@Siddharth Rout'上面的评论),那么它就不再是一个预测。事实上,在开发过程中会出现这种不可预测性。
例如,在我上面创建的翻转弹出窗口的第一次迭代中,我不小心在IF语句中键入了KillTimer。基本上,现在EndTimer存在的地方我写了KillTimer。我不假思索地这样做了。我知道我有一个程序可以结束计时器,但我暂时将EndTimer与KillTimer混淆了。
所以这就是我提出这个问题的原因:通常,当你在Excel中犯这种错误时,你会收到运行时错误。但是,因为您正在使用API,所以您只会收到非法错误,并且整个Excel应用程序都会无响应并退出。所以,如果你在开始计时器之前没有保存,你会失去一切(这基本上是第一次发生在我身上的事情)。更糟糕的是,因为您没有收到运行时错误,所以您不会立即知道哪一行导致了错误。在这样的项目中,您必须预期几个非法错误(以及随后重新加载Excel)来诊断错误。有时这可能是一个痛苦的过程。但这是您在使用API时发生的典型调试情况。错误没有被直接突出显示 - 非法错误似乎是随机发生的 - 这就是为什么许多人将API描述为不可预测和风险的原因。
但只要您能找到并诊断出错误,它们就没有风险。在我上面的代码中,我相信我已经创建了一个基本封闭的表单解决方案。没有人可以引入任何可能导致问题的错误。 (不要把它当成挑战人士。)
只是为了给你一些避免错误的具体指导原则:
另外,为了清楚起见:使用API计时器并同时编辑单元格没有问题。没有关于计时器会排除你的能力编辑工作表上的任何内容。
答案 4 :(得分:0)
我也遇到过Excel在输入值时崩溃并找到此贡献的事实。大! 我添加这一行后,我的问题就解决了:
On Error Resume Next
到“TimerProc”。
答案 5 :(得分:0)
使用API计时器,一旦我将时间间隔设置得太短,Excel就会崩溃,因为它在安排下一个任务之前没有完成上一个定时任务。使用ontime不会发生这种情况,因为您在 完成TimerProc之后设置了ontime。
也许有可能在Timerproc中杀死计时器的第一件事,并在结束之前设置一个新的计时器。
您应该注意,Killtimer有时实际上会失败,从而使计时器保持活动状态并继续永久调用该过程。因此,必须使用带有反馈控制的机枪代码来确保它真的死了。
//pseudo code :
start_kill = timer()
While still_alive= (negative result of killtimer) do
Still_ailve = KillTimer TimerID.
If timer - start_kill > 10 then msgbox "not dead find a bigger gun" Exit sub
Wend
当然,您需要超时才能退出此循环。