API таймеры в VBA - как сделать безопасным
Я читал в разных местах, что таймеры API рискованны в VBA, что если вы редактируете ячейку во время работы таймера, это приведет к сбою Excel. Но этот код отhttp://optionexplicitvba.wordpress.com из-за Иордании Goldmeier, похоже, не имеет этой проблемы. Он исчезает всплывающее окно с помощью таймера, и в то время как его исчезает, я могу щелкнуть и ввести текст в ячейки и строку формул без каких-либо проблем.
когда таймер 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
5 ответов
@CoolBlue: и каков механизм сбоя: что именно происходит, чтобы сделать Excel аварии?
Я могу дать вам расширение ответа Сиддарта Раута, но не полное объяснение.
вызовы API не являются VBA: они существуют вне обработчиков ошибок VBA, и когда что-то пойдет не так, они либо ничего не сделают, либо вызовут ресурс в памяти, который не существует, или попытаются прочитать (или написать!) в память, которая находится вне назначенной памяти пространство для Excel.exe
когда это произойдет, операционная система вмешается и выключит ваше приложение. Раньше мы называли это "общей ошибкой защиты", и это все еще полезное описание процесса.
теперь для некоторых деталей.
когда вы вызываете функцию в VBA, вы просто напишите имя-назовем его "CheckMyFile ()" - и это все, что вам нужно знать в VBA. Если нет ничего под названием "CheckMyFile", чтобы позвонить, или это объявленный там, где ваш вызов не может его видеть, компилятор или механизм выполнения вызовет ошибку в виде точки останова или предупреждения перед компиляцией и запуском.
за кулисами есть числовой адрес, связанный со строкой "CheckMyFile": я немного упрощаю, но мы ссылаемся на этот адрес как Указатель На Функцию - следуйте этому адресу, и мы попадем в структурированный блок памяти, который хранит определения параметров функции, пространство для их сохраненные значения и, за этим, адреса, направляющие эти параметры в функциональные структуры, созданные для выполнения VBA и возвращающие значения в адрес вывода функции.
все может пойти не так, и VBA делает много работы, чтобы гарантировать, что все это складывается изящно, когда они идут не так.
Если вы дадите указатель этой функции на что - то, что не является VBA-внешним приложением или (скажем) вызов таймера API - ваша функция все еще может быть позвонил, он еще может работать, и все будет работать.
но за этим указателем должна быть действительная функция.
Если нет, внешнее приложение вызовет свои собственные обработчики ошибок, и они не будут такими прощающими, как VBA.
Он может просто отбросить вызов и ничего не делать, если Excel и VBA находятся в состоянии "занят" или иным образом недоступны, когда он пытается использовать этот указатель функции: вам может повезти, только один раз. Но это может вызвать гнев операционной системы на Excel.процесс exe.
Если вызов приводит к ошибке, и эта ошибка не обрабатывается вашим кодом, VBA поднимет ошибку вызывающему абоненту - и, поскольку вызывающий абонент не является VBA, у него, вероятно, не будет способа обработки этого: и он вызовет "помощь" из операционной системы.
Если это вызов API, он был написан для разработчиков, которые, как предполагается, поставили обработку ошибок и управление непредвиденными обстоятельствами в вызывающем коде.
эти предположения:
- определенно будет действительная функция за этим указателем;
- он определенно будет доступен, когда он называется;
- ...И это не вызовет ошибок у вызывающего.
с вызовом API, caller is операционная система, и ее ответ на обнаружение ошибки будет вас закрыть.
Так что это очень простой план процесса - "почему" вместо того, чтобы объяснять "что".
полное объяснение, без излишних упрощений, для разработчиков C++. Если вы действительно хотите получить подробный ответ, вы должны научиться программировать с помощью указателей; и вы должны свободно владеть концепциями и практикой выделения памяти, исключениями, последствиями плохого указателя и механизмами, используемыми операционной системой для управления запущенными приложениями и обнаружения недопустимой операции.
VBA существует, чтобы защитить вас от этого знания и упрощается задача написания приложений.
Я читал в разных местах, что таймеры API рискованны в VBA
ну заявление должно быть I read in various places that API timers are risky
? И причина, по которой я говорю это, заключается в том, что эти API можно использовать в VB6/VBA/VB.Net etc..
так они рискованные? Да они а то так ходьба по канату. Одно неверное движение-и все. И это не просто SetTimer API
но почти с любым API.
Я создал пример еще в 2009 году, что использует SetTimer API
для создания заставок в Excel. Вот это ссылке.
теперь, если вы извлекаете файлы и непосредственно открыть файл 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.bmp")
tim = True
Else
UserForm1.Image1.Picture = LoadPicture("C:\temp.bmp")
tim = False
End If
Counter = Counter + 1
If Counter = 10 Then
EndTimer
Unload UserForm1
End If
End Sub
когда таймер API безопасен, а когда нет? Есть ли какие-то общие принципы, которые помогут мне понять?
Итак, все сводится к одному факту. насколько надежен ваш код. Если ваш код обрабатывает каждый сценарий, то SetTimer API
или по сути любой API не подведет.
указатель-Безопасные и 64-разрядные объявления для Windows Timer API в VBA:
как и было обещано, вот 32-разрядные и 64-разрядные объявления API для 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 или формой.
A полностью отработанный пример этого API таймера, включая использование параметра hwnd для окна, доступен на веб-сайте Excellerando:
использование VBA InputBox для паролей и скрытие ввода клавиатуры пользователя звездочками.
Примечание:
Это было опубликовано как отдельный ответ на мое объяснение системных ошибок, связанных с вызовом API таймера без тщательного обработка ошибок: это отдельная тема, и StackOverflow выиграет от отдельного и доступного для поиска ответа с безопасными указателями и 64-разрядными объявлениями для API таймера Windows.
в интернете есть плохие примеры объявлений API; и есть очень мало примеров для общего случая VBA7 (который поддерживает тип безопасного указателя), установленного в 32-разрядной среде Windows (которая не поддерживает 64-разрядное целое число "LongLong").
@CoolBlue я написал код, который вы выложили выше. Это правда, что API может действовать непредсказуемо, по крайней мере, по сравнению с обычным кодом. Однако, если ваш код достаточно надежен (следуя комментариям @Siddharth Rout сверху), то это больше не предсказание. На самом деле эта непредсказуемость возникает в процессе развития.
например, в моей первой итерации всплывающего окна опрокидывания, созданного выше, я случайно набрал KillTimer в операторе IF. В основном, где EndTimer существует сейчас Я написал KillTimer. Я сделал это не подумав. Я знал, что у меня есть процедура, которая закончит таймер, но я на мгновение перепутал EndTimer с KillTimer.
Итак, вот почему я поднимаю этот вопрос: обычно, когда вы делаете этот тип ошибки в Excel, вы получите ошибку во время выполнения. Однако, поскольку вы работаете с API, вы просто получаете незаконную ошибку, и все приложение Excel перестает отвечать и завершает работу. Итак, если вы не сохранили перед запуском таймера, вы проиграете все (что, по сути, произошло со мной в первый раз). Хуже того, поскольку вы не получаете ошибку времени выполнения, вы не сразу узнаете, какая строка вызвала ошибку. В таком проекте вы должны ожидать нескольких незаконных ошибок (и последующей перезагрузки Excel) для диагностики ошибки. Иногда это может быть болезненным процессом. Но это типичная ситуация отладки, которая происходит при работе с API. Что ошибки не выделяются напрямую - и незаконные ошибки кажутся случайными - вот почему многие описывают API как непредсказуемые и рискованные.
но они не рискованны, пока вы можете найти и диагностировать ошибки. В моем коде выше я считаю, что создал по существу закрытое решение формы. Нет никаких ошибок, которые кто-то мог бы ввести, что вызвало бы проблему позже. (Не воспринимайте это как вызов.)
и просто чтобы дать вам некоторые конкретные рекомендации, чтобы избежать ошибок:
- если вы начинаете таймер, убедись, что убьешь его позже. Если у вас есть ошибка выполнения Excel до того, как таймер будет убит, он может продолжаться вечно и съесть вашу память. Используйте консоль (Debug.Print) для записи строки при каждом вызове TimerProc. Если он продолжает тикать в консоли даже после выполнения кода, у вас есть таймер побега. Закройте Excel и вернитесь, когда это произойдет.
- не используйте несколько таймеров. Используйте один таймер для обработки нескольких элементов синхронизации.
- не запускайте новый таймер, не убивая старый.
- самое главное: проверьте на компьютере вашего друга, чтобы убедиться, что он работает на разных платформах.
кроме того, просто для ясности:нет проблем с использованием таймера API и редактированием ячейки одновременно. нет ничего о таймерах, которые будут препятствовать вашей способности редактировать что-либо на листе.
Я также столкнулся с тем, что Excel аварийно завершает работу при вводе значения и нашел этот вклад. Здорово! Моя проблема была решена, как только я добавил эту строку:
On Error Resume Next
в "TimerProc".