Перекрытие окна на вызывает события Paint

Программирование на Visual Basic, главный форум. Обсуждение тем программирования на VB 1—6.
Даже если вы плохо разбираетесь в VB и программировании вообще — тут вам помогут. В разумных пределах, конечно.
Правила форума
Темы, в которых будет сначала написано «что нужно сделать», а затем просьба «помогите», будут закрыты.
Читайте требования к создаваемым темам.
Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Перекрытие окна на вызывает события Paint

Сообщение Mikle » 20.11.2018 (Вт) 13:45

В проекте несколько окон, изображение на главную форму отображается с помощью SetDIBitsToDevice из 2D массива, равного по размеру пиксельному размеру формы. В обработчике события формы Paint стоит одна строка с SetDIBitsToDevice, это восстанавливает изображение на форме при перетаскивании по ней других форм этого проекта, в том числе модальных, а также сторонних форм, типа проводника. Однако некоторые формы почему-то Paint не вызывают, это MsgBox, диалог открытия файла из Common Dialog Control 6.0, возможно ещё какие-то, не проверял.
Можно как-то с этим бороться?

Teranas
Бывалый
Бывалый
Аватара пользователя
 
Сообщения: 224
Зарегистрирован: 13.12.2008 (Сб) 4:26
Откуда: Новосибирск

Re: Перекрытие окна на вызывает события Paint

Сообщение Teranas » 20.11.2018 (Вт) 19:10

Mikle
Приветствую друг!

Насколько я знаю paint и не будет срабатывать, можно только вызвать Form_Paint после диалогов, для обновления.
С уважением, Андрей.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Перекрытие окна на вызывает события Paint

Сообщение Mikle » 20.11.2018 (Вт) 19:55

Приветствую.
Teranas писал(а):Насколько я знаю paint и не будет срабатывать

Естественно, Form_Paint.
Teranas писал(а):можно только вызвать Form_Paint после диалогов

Во-первых, мне нужно не после, а во время диалога, чтобы окно диалога можно было перетаскивать, не портя картинку.
Во-вторых, другие окна же работают корректно. Что особого в этих окнах диалога? ShowInTaskbar=False я делал окнам, работают, Form_Paint основной формы вызывают.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Перекрытие окна на вызывает события Paint

Сообщение Mikle » 20.11.2018 (Вт) 20:04

Вот простой пример, левой кнопкой вызываем модальное окно, правой MsgBox:
Вложения
Test.zip
(47.49 Кб) Скачиваний: 197

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Перекрытие окна на вызывает события Paint

Сообщение Хакер » 20.11.2018 (Вт) 20:09

Mikle писал(а): Однако некоторые формы почему-то Paint не вызывают, это MsgBox, диалог открытия файла из Common Dialog Control 6.0, возможно ещё какие-то, не проверял.

Во-первых, почему формы? Диалоги, окна, но никак не формы.

почему-то Paint не вызывают

Оно и понятно почему: представь, что в обработчике события Paint глупый программист поставил MsgBox "test" — после запуска он получит неконтролируемую рекурсию, когда покрешит проект, закончившись только при исчерпании стека или общесистемных ресурсов (свободных хендов, памяти под структуры WND в ядре).

Поэтому так оно и сделано. Тут конечно можно было бы сделать чуть по-другому, сделав флаг реентерабельности: что событие Paint не генерируется, если уже был вход в обработчик, но ещё не было выхода из него. Можно помечтать о добавлении формам такого свойства.

Но причина не только в риске возникновения рекурсии, а как бы в идеологии, что программист должен думать о программе как об однопоточной, и что если у него есть функция, которая, будучи вызванной, не вернёт управление, пока юзер не кликнул по кнопке, никакой пользовательский код не должен получить шанса начать выполняться до того момента, как пользователь на покончит с диалоговым окном. Рекурсия — это просто частный случай следования этому принципу, а в широком смысле этот принцип гарантирует, что пока пользовательский код встал на паузу в ожидании решения человека, никакой другой пользовательский код не должен иметь шанса выполняться и как-то поменять внутреннее состояние программы.

Т.е. если программист написал код, который проверяет значение переменной и затем что-то спрашивает у пользователя, то надо дать программисту гарантию, что с момента проверки переменной её значение не изменится (каким-то другим не очевидным кодом), и что после получения ответа не надо беспокоиться за значение переменной.
Код: Выделить всё
If SomeVar = 125 Then
    MsgBox "Really?"
    ' Ой, а может в этот момент SomeVar уже не 125?



Можно как-то с этим бороться?

Если не нужно, чтобы содержимое окна постоянно обновлялось на время показа диалога (как в случае какой-то анимации), то достаточно «сфотографировать» растр внутри окна, запомнить его, поменять AutoRedraw на True, нарисовать запомненный растр и можно показывать диалог — родная оконная процедура будет запомненный растр перерисовывать даже во время показа диалогов. И диалоги не смогут как erase tool при перетаскивании стирать то, что раньше было нарисовано в окне с помощью SetDIBitsToDevice.

Если нужно и во время показа диалога обновлять графику, то придётся сабклассить окно и самому обрабатывать WM_PAINT.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Перекрытие окна на вызывает события Paint

Сообщение Mikle » 20.11.2018 (Вт) 22:59

Хакер писал(а):Во-первых, почему формы? Диалоги, окна, но никак не формы.

Да, я написал "формы", имея ввиду "окна".
Хакер писал(а):Оно и понятно почему: представь, что в обработчике события Paint глупый программист поставил MsgBox "test" — после запуска он получит неконтролируемую рекурсию, когда покрешит проект, закончившись только при исчерпании стека или общесистемных ресурсов (свободных хендов, памяти под структуры WND в ядре).

Честно говоря, аргумент так себе - есть куча других способов получить неконтролируемую рекурсию, программист должен сам за этим следить.

Вот модальные окна самого VB6 ведут себя правильно, они вырубают только события управления, типа MouseDown, KeyPress, я ожидал того же и от диалогов. А диалоги попросту не выполняют DoEvents, некрасиво это, когда даже таймеры во время диалогов останавливаются.

Хакер писал(а):Если не нужно, чтобы содержимое окна постоянно обновлялось на время показа диалога (как в случае какой-то анимации), то достаточно «сфотографировать» растр внутри окна, запомнить его, поменять AutoRedraw на True, нарисовать запомненный растр и можно показывать диалог — родная оконная процедура будет запомненный растр перерисовывать даже во время показа диалогов.

Да, это понятно, я обернул вызовы диалогов так:
Код: Выделить всё
    AutoRedraw = True
    SetDiBitsToDevice ...............
    MsgBox ""
    AutoRedraw = False

Это работает, просто мне показалось это костылём.

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: Перекрытие окна на вызывает события Paint

Сообщение The trick » 21.11.2018 (Ср) 9:47

Mikle, в скомпилированном варианте все работает.
UA6527P

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Перекрытие окна на вызывает события Paint

Сообщение Mikle » 21.11.2018 (Ср) 12:00

The trick писал(а):в скомпилированном варианте все работает.

Вообще сюрприз...
Вот не зря я всегда старался делать все диалоги сам (почему и задаю сейчас вопросы новичка по готовым диалогам), а меня убеждали взять готовое, проверенное. А это проверенное "индусы" и делали, и проверяли. Сейчас наткнулся ещё на одну "фичу" - диалог открытия файла меняет текущую папку. В отладке под средой это вообще приводит к тому, что при повторном запуске проект перестаёт видеть dll, лежащую в папке с проектом, приходится после диалога ставить ChDrive и ChDir.

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: Перекрытие окна на вызывает события Paint

Сообщение The trick » 21.11.2018 (Ср) 12:41

Mikle писал(а):Сейчас наткнулся ещё на одну "фичу" - диалог открытия файла меняет текущую папку.

OFN_NOCHANGEDIR
UA6527P

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Перекрытие окна на вызывает события Paint

Сообщение Mikle » 21.11.2018 (Ср) 14:12

The trick писал(а):OFN_NOCHANGEDIR

Спасибо, нашёл. Только в списке флагов контрола это называется cdlOFNNoChangeDir.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Перекрытие окна на вызывает события Paint

Сообщение Хакер » 22.11.2018 (Чт) 13:30

Mikle писал(а):Честно говоря, аргумент так себе - есть куча других способов получить неконтролируемую рекурсию, программист должен сам за этим следить.


Других способов нет — все другие способы на самом деле именно другие. Одно дело, когда одна часть кода, написанная программистом, вызывает другую часть кода, написанную программистом. Здесь может возникнуть неконтролируемая рекурсия, и тогда, да, программист должен следить. И совсем другое дело (как здесь), когда кто-то вызывает твою функцию, а не ты сам. Когда часть кода, написанная программистом, вызывается не из другой части его же кода, а из чужого кода, над которым у программиста нет контроля и чёткого понимания.

Во всех таких случаях должны быть определены жёсткие и простые правила игры: в каких случаях твоей код может быть вызван сторонним кодом, и какие твои действия могут спровоцировать сторонний код на вызов твоего кода. И если эти правила будут довольно широкими и мягкими, может получиться, что очень широкий диапазон твоих действий может привести к вызову сторонним кодом твоего кода, который эти действия и совершает. Это путь к неконтролируемой, и, что страшнее, неизбежной рекурсии, причём там, где она никому не нужна.

Mikle писал(а):А диалоги попросту не выполняют DoEvents, некрасиво это, когда даже таймеры во время диалогов останавливаются.

Это не некрасиво, а это очень правильно, и если хорошо подумать, то поймёшь, что по другому нельзя/не стоит делать.

Во-первых, фраза про невызов DoEvents в корне неверная. DoEvents это обёртка над GetMessage+DispatchMessage. Именно эти два вызова она делает (возможно несколько раз, чтобы обработать все сообщения, скопившиеся в очереди оконных сообщений). Все диалоги именно это и делают в своём цикле, иначе бы UI просто заморозился, потому что оконные сообщения не доставлялись бы оконным процедурам, никакие контролы бы не реагировали на пользовательские действия и ничего бы даже не отрисовывалось и не перерисовывалось.

При этом цикл прокачки оконных сообщений (как внутри MsgBox, так и внутри CommonDialog) никак не фильтрует и не отсеивает сообщения по принципу «если это сообщения перерисовки или таймера родительского окна — мы не будем доставлять его». Ничего подобного там нет, доставляются абсолютно все сообщения.

Это не цикл прокачки сообщений отказывается доставлять «неуместные» сообщения, это оконная процедура самой VB-шной формы (которая получает все сообщения, из тех, что для посланы соотв. окну) отказывается по поводу полученных сообщений WM_PAINT/WM_TIMER вызывать обработчики событий, если в данный момент идёт показ MsgBox'а/InputBox'а/CommonDialog'а.

И это правильно; и помимо риска неконтролируемой рекурсии есть вещь пострашнее — не по деструктивности, а по неразрешимости.

Представим себе, что на время показа чужеродных модальных диалогов вызов обработчиков событий Paint/Timer не блокировался бы. Представим себе, что в обработчики события есть нечто такое: If Rnd < 0.13 Then End
То есть с некоторой вероятностью (13%) при возникновении события вызывается End.

Вопрос: как в таком сценарии End должна смочь (чисто технически) разрулить возникшую необходимость в остановке работы проекта?

Звучит, наверное, не очень убедительно и не очень проблемо-центрично, потому что у кого-то может возникнуть соблазн сказать: «А в чём вообще проблема? Пусть End сделает это так же, как всегда делает, как во всех остальных случаях».

А теперь я поясню. Что вообще делает End с технической точки зрения? Когда наш VB-проект выполняется, это выполнение является продолжением выполнения самого процесса VB IDE. В нашем коде одни наши процедуры могут вызывать другие наши же процедуры, другие наши же процедуры могут вызывать третьи наши же процедуры — во всех таких случаях растёт стек вызовов, растёт просто стек текущего потока, на нём размещаются какие-то переменные, аргументы вызовов, в этих переменных лежат какие-то данные, указатели на объекты/массивы/строки. А наши процедуры (нами написанные) вызываются из кода VB IDE.

И, к примеру, если в какой-то момент пользователь кликнул на кнопочку на форме, обработчик Command1_Click вызвал Foo, Foo вызвала Bar, Bar вызвала Baaaz, а Baaaz вызвала End, на момент вызова End мы имеем такую цепочку вызовов (это трассировка стека вызовов, прямая, а не обратная):

vb6.exe!WinMain
 ➥ ...
   ➥ vb6.exe!ThunderMsgLoop (в этой процедуре крутится оконный цикл)
     ➥ user32.dll!DispatchMessage
       ➥ vb6.exe!StdCtlWndProc (общая для всех VB-шных оконных классов оконная процедура-обёртка)
         ➥ vb6.exe!CommonGizWndProc (общая для всех VB-шных контролов оконная процедура-обёртка)
           ➥ vb6.exe!PushCtlProc (оконная процедура VB-шных кнопкок)
             ➥ vb6.exe!_DoClick
               ➥ vb6.exe!EvtErrFire
                 ➥ vb6.exe!EvtErrFireWorker
                   ➥ vb6.exe!InvokeEvent
                     ➥ vb6.exe!InvokeVtblEvent
                       ➥ vb6.exe!CallProcWithArgs
                         ➥ Project1!Form1::Command1_Click
                           ➥ Project1!Module1::Foo
                             ➥ Project1!Module2::Bar
                               ➥ Project1!Module1::Baaaz

Здесь синие процедуры — это процедуры написанные на С/С++, скомпилированные в Native-код и выполняемые непосредственно процессором, а красное — это процедуры, написанные на VB, преобразованные в P-код и выполняемые P-кодной виртуальной машиной.

Сама по себе P-кодная виртуальная машина, выполняющая P-код, состоит из Native-кода, выполняемого процессором. Поэтому формально, если рассматривать с точки зрения Native-кода, трассировка стека вызовов выглядела бы так:

vb6.exe!WinMain
 ➥ ...
   ➥ vb6.exe!ThunderMsgLoop (в этой процедуре крутится оконный цикл)
     ➥ user32.dll!DispatchMessage
       ➥ vb6.exe!StdCtlWndProc (общая для всех VB-шных оконных классов оконная процедура-обёртка)
         ➥ vb6.exe!CommonGizWndProc (общая для всех VB-шных контролов оконная процедура-обёртка)
           ➥ vb6.exe!PushCtlProc (оконная процедура VB-шных кнопкок)
             ➥ vb6.exe!_DoClick
               ➥ vb6.exe!EvtErrFire
                 ➥ vb6.exe!EvtErrFireWorker
                   ➥ vb6.exe!InvokeEvent
                     ➥ vb6.exe!InvokeVtblEvent
                       ➥ vb6.exe!CallProcWithArgs
                         ➥ анонимный_переходничок (сгенерированный VBA6.DLL переходничок-делегат с Native-кода на P-код)
                           ➥ vba6.dll!MethCallEngine (в этой ф-ции работает цикл, исполняющий P-кодные инструкции одну за другой)

Именно так бы показали трассировку обычные Native-кодный отладчики, именно таким способом мы бы думали о происходящем, если бы ничего не знали о P-коде, но с логической (смысловой), а не формальной точки зрения, трассировка выглядит (повторю её один в один) именно так:

vb6.exe!WinMain
 ➥ ...
   ➥ vb6.exe!ThunderMsgLoop (в этой процедуре крутится оконный цикл)
     ➥ user32.dll!DispatchMessage
       ➥ vb6.exe!StdCtlWndProc (общая для всех VB-шных оконных классов оконная процедура-обёртка)
         ➥ vb6.exe!CommonGizWndProc (общая для всех VB-шных контролов оконная процедура-обёртка)
           ➥ vb6.exe!PushCtlProc (оконная процедура VB-шных кнопкок)
             ➥ vb6.exe!_DoClick
               ➥ vb6.exe!EvtErrFire
                 ➥ vb6.exe!EvtErrFireWorker
                   ➥ vb6.exe!InvokeEvent
                     ➥ vb6.exe!InvokeVtblEvent
                       ➥ vb6.exe!CallProcWithArgs
                         ➥ Project1!Form1::Command1_Click
                           ➥ Project1!Module1::Foo
                             ➥ Project1!Module2::Bar
                               ➥ Project1!Module1::Baaaz

В норме, без всякого End, Baaaz выполнится до конца и произойдёт возврат из Baaaz в Bar.
Затем Bar выполнится до конца и произойдёт возврат в Foo.
Затем Foo выполнится до конца и произойдёт возврат в Command1_Click.
Затем Command1_Click выполнится до конца и произойдёт возврат в vb6.exe!CallProcWithArgs.
И так далее, и до тех пор, пока мы не вернёмся обратно в цикл прокачки оконных сообщений (внутри ThunderMsgLoop), следующая итерация которого возьмёт из очереди оконных сообщений следующее сообщение, вызовет для него DispatchMessage и оно будет обработано соответствующим образом.

Цикл прокачки сообщений прекращается только при закрытии IDE, а пока IDE не закрыта, абсолютно всё вызывается из него. И код самой IDE вызывается из него, и код VB-программы тоже вызывается из него (и Sub Main, и обработчики событий, цепочку вызовов для которых я показал выше).

Важно другое: каждая процедура из этой цепочки вложенных вызовов резервирует себе какое-то количество байтов из стека под локальные переменные, создаёт какие-то объекты или выделяет какие-то блоки памяти (под разные нужды), может быть создаёт какие-то окна, может быть захватывает какие-то ресурсы (например входит в критическую секцию с помощью EnterCriticalSection) и указатели на созданные объекты или выделенные блоки памяти, хендлы созданных окон сохраняет в свои локальные переменные (ну или в глобальные), затем делает какую-то работу (в том числе вызов других процедур), а в конце (перед выходом) созданные объекты уничтожаются, блоки памяти высвобождаются, окна уничтожаются, ресурсы отпускаются, и в конце концов область стека, отведённая под локальные переменные освобождается. При выходе из процедуры из стека выкидываются помещённые туда перед её вызовом аргументы (если они были). И это САМЫЙ ВАЖНЫЙ момент, и о нём речь пойдёт в дальнейшем.

Так вот, в ситуации, когда у нес нет никакого End, выполнение доходит до своего пика (в плане вложенности вызовов), а потом происходит череда возвратов чуть ни не до самого верха, и каждая функция подчищает за собой.

И у нас, если смотреть с высокоуровневой точки зрения, не происходит утечки памяти и других ресурсов. А если смотреть с низкоуровневой точки, то у нас не происходит нарушения структуры стека, всё что было в стек помещено, из него вовремя удаляется, все регистры, значение которых не должны меняться на время вызова вложенной процедуры, восстанавливали при выходе из неё свои прежние значения.

Теперь вернёмся к изначальному вопросу: внутри Baaaz в каком-то месте стоит вызов End.

В этом случае естественный ход вещей (череда возвратов из процедуры в процедуру в порядке, обратном череде вызовов) произойти не должен! Весь код, стоящий внутри Baaaz после вызова End выполниться не должен. Возврата из Baaaz в Bar произойти вообще не должно. Остаток Bar, идущий после вызова Baaaz тоже не должен выполниться. Словом, после обращения к End никакой VB-шный код не должен получить шанса выполниться.

Выполнение VB-шного кода должно прекратиться, но выполнение кода вообще (в рамках процесса VB IDE), понятное дело, должно продолжиться с некоторой точки. Ведь End не означает, что и IDE должна остановиться и закрыться — среда должна продолжить работу, поэтому очевидно, что нам нужно как-то вернуться обратно в оконный цикл (внутри ThunderMsgLoop). Но возвращение туда как итог череды возвратов из процедуры в процедуру (как это происходит обычно) здесь не подходит.

Вопрос: как вернуться из Project1!Module1::Baaaz в vb6.exe!ThunderMsgLoop, но так, чтобы минуя все те промежуточные процедуры, которые нам не нужны?

Как сделать своеобразный goto, но не в рамках одной процедуры, а из вызываемой процедуры в вызывающую, причём далеко не через одну, а сразу через несколько? Возможно ли это вообще, либо же это невозможная задача?

Во-первых, это вполне себе решимая задача, она является рядовой задачей в любом механизме обработки исключений, когда из места, где исключение выброшено, мы должны мигом переместиться на один или несколько уровней выше, где исключение будет обработано. И даже в Си, где в отличие от Си++ нет механизма исключений, всегда была пара из функций setjmp/longjmp, одна и которых позволяла запомнить контекст (то есть значение всех регистров процессора + EIP (указатель на «текущую» инструкцию), а вторая меняла контекст потока, заставляя его продолжить выполнение с запомненного места.

Во-вторых, главная дилемма таких возвратов: это, когда мы прыгаем «через голову», а точнее возвращаемся из 10-й (по вложенности) процедуры сразу в 3-ю, минуя уровни 4...9 — это как быть с ресурсами, которые выделили/захватили функции 4—9? Если отмотать указатель на вершину стека (ESP) на ту величину, которая была в момент запоминания контекста, вернуть все регистры, начать выполнение с запомненного места, то код будет выполняться правильно и стек не пострадает, но все те ресурсы, которые были выделены/захвачены, и правильное освобождение которых предполагалось при естественном ходе исполнения, так и останутся в подвешенном состоянии.

Эта проблема тоже имеет классическое решение — процесс называется раскруткой стека (stack unwinding), а всем функциям, которые могут оказаться в раскручиваемой цепочке, предлагается зарегистрировать либо сами ресурсы (которые должны быть освобождены), либо некий хендлер, который будет вызван механизмом исключений для того, чтобы дать промежуточной (в длинной цепочке вызов) функции подчистить за собой — так называемый finally-блок (если говорить в терминах try...catch...finally-подхода).

Так что обе проблемы в какой-то мере решаемы.

Теперь вернёмся к VB. А нужно ли нам, чтобы при обращении к End, выполнение резко перепрыгивало сразу в ThunderMsgLoop?

На самом деле — нет! И так не делается. При срабатывании End выполняется раскрутка только той части стека, которая была выделена красным. И VB делает это с лёгкостью, потому что VB ведёт учёт всех «пользовательских» объектов (объектов тех классов, которые написаны на VB в рамках текущего проекта), всех внешних объектов, всех строк, всех своих локальных переменных, а равно как и знает структуру стека P-кодной виртуальной машины, где в стеке этой виртуальной машины какие переменные, у какой переменной какой тип и как, в зависимости от типа переменной, нужно эту переменную зачистить, чтобы не возникло утечки памяти.

Поэтому раскрутка красной части стека вызовов для VB решается относительно легко: цикл, идущий по цепочке P-кодных инструкция просто прекращается, стековые фреймы, соответствующие красной части стека вызовов, уничтожаются, все ресурсы освобождаются, свои объекты уничтожаются, внешние объекты IUnknown::Release-ятся.

Так что в случае, если в коде обработчика события сидел End (не важно, в самом обработчике, или в другой процедуре, вызованной из обработчика события), с точки зрения vb6.exe!CallProcWithArgs происходит нормальный обыкновенный возврат из вызванной процедуры (кстати CallProcWithArgs даже не знает, что скрывается за обработчиком события, на который у неё имеется адрес: либо это native-кодный обработчик из какой-то скомпилированной DLL, либо же это Native-кодный переходничок на P-кодный код текущего проекта — ей безразлично).

Из vb6.exe!CallProcWithArgs происходит нормальный возврат в vb6.exe!InvokeVtblEvent.
А из неё — в vb6.exe!InvokeEvent.
И так далее, пока мы не вернёмся обратно в цикл прокачки сообщений.

В общем, End делает принудительный многоуровневый перескок только в отношении кода, написанного на VB (и существующего в виде P-кода), и делает раскрутку только тех стековых фреймов, которые порождены работой P-кодной виртуальной машины. То есть только красной части. Синяя часть раскрутке не подвергается, никаких перескоков, нарушающих естественный ход выполнения, там не происходит.

Пока всё хорошо.

А теперь, после затянувшейся вводной части, начинаем приближаться к проблеме.

Пусть внутри обработчика события Form_Paint (или Timer1_Timer) вызвана функция MsgBox.

MsgBox — модальная функция. И нам важно даже не то, что при показе сообщения другие окна блокируются, нам важно то, что внутри MsgBox стартует и крутится свой собственный цикл обработки сообщений, и этот цикл не прекращается, пока диалог с сообщением не закроют, а это означает, что и возврата из MsgBox не произойдёт до тех пор, пока пользователь не разделается с диалогом и пока цикл не прервётся из-за этого.

Посмотрим, как выглядит стек вызовов после того, как из Form1_Paint был сделан вызов MsgBox:

vb6.exe!WinMain
 ➥ ...
   ➥ vb6.exe!ThunderMsgLoop (в этой процедуре крутится оконный цикл)
     ➥ user32.dll!DispatchMessage
       ➥ vb6.exe!StdCtlWndProc (общая для всех VB-шных оконных классов оконная процедура-обёртка)
         ➥ vb6.exe!CommonGizWndProc (общая для всех VB-шных контролов оконная процедура-обёртка)
           ➥ vb6.exe!GrafPaintPicture
             ➥ vb6.exe!EvtErrFire
               ➥ vb6.exe!EvtErrFireWorker
                 ➥ vb6.exe!InvokeEvent
                   ➥ vb6.exe!InvokeVtblEvent
                     ➥ vb6.exe!CallProcWithArgs
                       ➥ Project1!Form1::Form_Paint
                         ➥ vba6.dll!rtcMsgBox
                           ➥ vb6.exe!HostDisplayMsgBox2
                             ➥ vb6.exe!VBMessageBox2
                               ➥ vb6.exe!VBDialogCover2
                                 ➥ vb6.exe!MessageBoxPVoid
                                   ➥ user32.dll!MessageBoxIndirect
                                     ➥ user32.dll!MessageBoxWorker
                                       ➥ user32.dll!SoftModalMessageBox
                                         ➥ user32.dll!InternalDialogBox
                                           ➥ user32.dll!DialogBox2 (здесь крутится новый оконный цикл)

Наиболее важный вывод из этой картины — это то, что у нас в стеке вызовов (то есть в цепочке вызываемых друг из друга процедур) появилось чередование синих и красных участков. И вершина стека вызовов (то есть конец цепочки) у нас синяя, а я напомню, что VB умеет безусловно и насильно прерывать и прекращать работу только «красного» кода и только для красной части стека VB умеет делать unwinding и корректно подчищать за функциями, работа которых была абортирована и которые не получат шанса подчистить за собой.

Но это не страшно, потому что мы точно знаем, что из недр user32.dll, где сейчас крутится и будет крутиться цикл прокачки оконных сообщений (пока диалог не закроют), никто не обратится к End и не поставит VB6 перед лицом наразрешимой задачи.

Этот оконный цикл внутри user32.dll!DialogBox2 — это почти такой же оконный цикл, как и в vb6.exe!ThunderMsgLoop, основная суть которого состоит в итеративном получении сообщений из очереди с помощью GetMessage/PeekMessage и направлении их в соответствующую оконную процедуру с помощью DispatchMessage.

И этот оконный цикл совершенно никак не фильтрует оконные сообщения и не дискриминирует окна VB-форм в плане доставки/недоставки WM_PAINT и др. сообщения (вопреки мнениям, что «MsgBox не делает DoEvents»). Он перелопачивает абсолютно всё, и если мы проведём по окну нашей VB-формы (путём перетаскивания) диалоговым окном с сообщением и затрём таким образом старый растр, который там был, то система как обычно отправит окну нашей VB-формы сообщение WM_PAINT.

И оно будет, в общем случае, незамедлительно подхвачено оконным циклом внутри user32.dll!DialogBox2, будет вызвана ф-ция DispatchMessage, которая найдёт и вызовет оконную процедуру VB-формы.

Цепочка вызовов при этом будет такой:
vb6.exe!WinMain
 ➥ ...
   ➥ vb6.exe!ThunderMsgLoop (в этой процедуре крутится оконный цикл)
     ➥ user32.dll!DispatchMessage
       ➥ vb6.exe!StdCtlWndProc (общая для всех VB-шных оконных классов оконная процедура-обёртка)
         ➥ vb6.exe!CommonGizWndProc (общая для всех VB-шных контролов оконная процедура-обёртка)
           ➥ vb6.exe!FormCtlProc (оконная процедура для окон самих VB-форм)
             ➥ vb6.exe!GrafPaintPicture
               ➥ vb6.exe!EvtErrFire
                 ➥ vb6.exe!EvtErrFireWorker
                   ➥ vb6.exe!InvokeEvent
                     ➥ vb6.exe!InvokeVtblEvent
                       ➥ vb6.exe!CallProcWithArgs
                         ➥ Project1!Form1::Form_Paint
                           ➥ vba6.dll!rtcMsgBox
                             ➥ vb6.exe!HostDisplayMsgBox2
                               ➥ vb6.exe!VBMessageBox2
                                 ➥ vb6.exe!VBDialogCover2
                                   ➥ vb6.exe!MessageBoxPVoid
                                     ➥ user32.dll!MessageBoxIndirect
                                       ➥ user32.dll!MessageBoxWorker
                                         ➥ user32.dll!SoftModalMessageBox
                                           ➥ user32.dll!InternalDialogBox
                                             ➥ user32.dll!DialogBox2 (здесь крутится новый оконный цикл)
                                               ➥ user32.dll!DispatchMessage
                                                 ➥ vb6.exe!StdCtlWndProc
                                                   ➥ vb6.exe!CommonGizWndProc
                                                     ➥ vb6.exe!FormCtlProc
                                                       ➥ vb6.exe!GrafPaintPicture
                                                         ➥ vb6.exe!EvtErrFire
                                                           ➥ vb6.exe!EvtErrFireWorker
                                                            ➥ vb6.exe!InvokeEvent
                                                               ➥ vb6.exe!InvokeVtblEvent
                                                                 ➥ vb6.exe!CallProcWithArgs
                                                                   ➥ Project1!Form1::Form_Paint

Часть цепочки, помеченна зелёной линией, в реальной жизни никогда не выполняется, потому что EvtErrFireWorker вызывает _WillFire, чтобы проверить, должно ли событие сработать, и если сейчас отображается диалог, то _WillFire даёт понять, что обработчик вызван быть не должен — это именно тот момент распутья, наличие которого не нравится Майклу. Если _WillFire вернёт соответствующий код, то InvokeEvent не вызывается и обработчик события не срабатывает. Именно _WillFire отвечает за то, чтобы таймеры не срабатывали, когда проект поставлен на паузу, к примеру. Но представим, что VB был бы устроен не так, как он сейчас устроен, а так, как хотелось бы Майлку, чтобы он был устроен — то есть чтобы во время показа диалогов события Paint/Timer не блокировались.

Тогда у нас в цепочке вызовов было бы два вхождения в Form_Paint. Не настолько важно, что именно это за события и что за обработчики, в верхнем может быть Command1_Click, а в нижнем — Timer1_Timer.

Важно то, что у нас в цепочке есть чередование синих и красных участков, и что второй обработчик события вызван из недр user32!DialogBox2, то есть из чужеродного кода. И вот теперь у нас из второго (нижнего) обработчика вызывается End, а это означает, что нормальной (естественной) череды возвратов наверх из глубины этой длинной цепочки вызовов произойти не должно, а попросту выполнение должно перескочить сразу наверх.

Да только как это сделать, если VB понятия не имеет, как обойтись с синими участками и с частью стека, сформированной чужеродным кодом?

Это серьёзная проблема. Перед тем, как зайти во второй (по вложенности) обработчик события, где-то в недрах user32!MessageBox был вызов CreateWindow, породивший собственно то самое окно (и вернуший его HWND). Код user32.dll написан с абсолютной уверенностью, что если выполнение зашло в MessageBox, то оно рано или поздно вернётся из него, а значит, если по пути выполнения что-то было создано/выделено, то оно обязательно будет освобождено/удалено.

Но когда из MessageBox может быть вызван некий VB-шный код, а из VB-шного кода может быть сделан End, сама суть End предполагает, что выполнение из вызванного VB-шного кода обратно в вызывающую процедуру уже не вернётся. А значит, в частном случае, код user32 не получит шанса довыполнить оставшуюся часть MessageBox и уничтожить окно с сообщением (и тогда кто его уничтожит? Сам VB? Он понятия не имеет, что сторонний код породил какое-то окно, которое теперь уничтожать нужно нам, и где взять хендл), а в общем случае — просто нет способа сделать раскрутку той части стека, которая подкрашена жёлтой линией:

vb6.exe!WinMain
 ➥ ...
   ➥ vb6.exe!ThunderMsgLoop (в этой процедуре крутится оконный цикл)
     ➥ user32.dll!DispatchMessage
       ➥ vb6.exe!StdCtlWndProc (общая для всех VB-шных оконных классов оконная процедура-обёртка)
         ➥ vb6.exe!CommonGizWndProc (общая для всех VB-шных контролов оконная процедура-обёртка)
           ➥ vb6.exe!FormCtlProc (оконная процедура для окон самих VB-форм)
             ➥ vb6.exe!GrafPaintPicture
               ➥ vb6.exe!EvtErrFire
                 ➥ vb6.exe!EvtErrFireWorker
                   ➥ vb6.exe!InvokeEvent
                     ➥ vb6.exe!InvokeVtblEvent
                       ➥ vb6.exe!CallProcWithArgsна этот уровень с самого низа должно перепрыгнуть выполнение после End
                        ➥ Project1!Form1::Form_Paint
                          ➥ vba6.dll!rtcMsgBox
                            ➥ vb6.exe!HostDisplayMsgBox2
                              ➥ vb6.exe!VBMessageBox2
                                ➥ vb6.exe!VBDialogCover2
                                  ➥ vb6.exe!MessageBoxPVoid
                                    ➥ user32.dll!MessageBoxIndirect
                                      ➥ user32.dll!MessageBoxWorker
                                        ➥ user32.dll!SoftModalMessageBox
                                          ➥ user32.dll!InternalDialogBox
                                            ➥ user32.dll!DialogBox2 (здесь крутится новый оконный цикл)
                                              ➥ user32.dll!DispatchMessage
                                                ➥ vb6.exe!StdCtlWndProc
                                                  ➥ vb6.exe!CommonGizWndProc
                                                    ➥ vb6.exe!FormCtlProc
                                                      ➥ vb6.exe!GrafPaintPicture
                                                        ➥ vb6.exe!EvtErrFire
                                                          ➥ vb6.exe!EvtErrFireWorker
                                                            ➥ vb6.exe!InvokeEvent
                                                              ➥ vb6.exe!InvokeVtblEvent
                                                                ➥ vb6.exe!CallProcWithArgs
                                                                  ➥ Project1!Form1::Form_Paint

Как End может удалить/откатить всю эту отмеченную жёлтой линией часть стека и насильно перевести выполнение сразу не насколько уровней вложенности вверх, минуя поочерёдные возвраты из функции в функцию?

Да никак!

End понятия не имеет, где в стеке, построенном чужеродным кодом, локальные переменные, что они обозначают и как их «подчищать». При насильной смене EIP выделенные блоки памяти останутся не освобождёнными, созданные окна останутся не уничтоженными, временно скрытый указатель мыши так и останется скрытым, и так далее.

Чисто гипотетически, End мог бы решить эту проблему, выбросив некое специальное SEH-исключение, которое привело бы к тому, что произошла бы раскрутка стека до его примерно середины силами и средствами SEH, а все функции, сидящие в цепочке, получили бы шанс подчистить за собой через вызов зарегистрированных SEH-хендлеров (finally-обработчиков). Проблема в том, что такого специального SEH-исключения а-ля «отлаживаемая программа обратилась к End» не предусмотрено в Windows, а код user32 не использует SEH и не имеет finally-блоков в принципе.

Поэтому нет способа из некоей процедуры XXX, вызванной в конечном счёте из недр MsgBox, выкинуть исключение так, чтобы магическим образом сразу из XXX переместиться туда, откуда была была вызвана MsgBox (или даже выше уровнем). Не потому, что End так не умеет, не потому, что VB не додуман, а потому что сама архитектура Windows в плане устройства пользовательской подсистемы (user32) этого не предполагает.


Вот именно по этой причине на время, пока показывается какой-то диалог и пока в недрах user32 крутится собственный ассоциированный с показом диалога оконный цикл, срабатывание обработчиков событий в VB блокируется, хотя сами оконные сообщения, предвещающие эти события, исправно доставляются (как раз тем самым ассоциированным с показом диалога оконным циклом).

В ином случае из обработчика события может быть сделан либо End, что поставит перед VB неразрешимую задачу, либо цененаправленно (Err.Raise) или совершенно случайно для программиста в коде обработчика события может быть выкинута ошибка, а ошибка требует точно такой же раскрутки стека и перепрыгивания выполнения на несколько уровней вверх до ближайшего On Error ... (если он вообще есть).

End-то может и редкий случай в обработчике событий, особенно в событии Paint, но вот обычная ошибка (Out of memroy, Subscript out of bounds и т.п) запросто может произойти в обработчике события Paint или Timer, и если это произошло во время показа диалога, НЕ СУЩЕСТВУЕТ способа закрыть диалог (или вынудить диалог закрыться) и переместиться на обработчик ошибки. Так что ошибки и их обработка точно так же страдают от проблемы, как и End.

И не остаётся ничего, кроме как блокировать срабатывание обработчиков событий на время пока диалога. Это звучит как-то не очень привлекательно, пока мы не разобрались с тонкостями, но понимание, что у среды теряется контроль за ходом исполнения кода, если обработчик события вызван из «диалоговой» функции вроде MessageBox, заставляет принять неминуемость такого подхода.

Тогда возникает вопрос: почему при модальном показе экземпляра формы поверх других экзмпляров форм обработчики событий продолжают срабатывать? В чём принципиальное отличие модального показа экземпляра формы (с помощью Show vbModal) от показа модального диалога, если и в том, и в другом случаях инициируется и начинает работать новый цикл прокачки сообщений, который реализован в рамках Native-кодного кода vb6.exe/user32.dll. То есть имеется то же чередование синих и красных зон, но проблемы с раскруткой стека нет и обработчики событий не блокируются на время показа модального окна. Почему?

Рассмотрим такую ситуацию: на форме Form1 кликнули по кнопке и в ответ на это показывается экземпляр Form2 модально к экземпляру Form1, при этом на Form1 лежит SuperTimer, у которого в обработчике SuperTimer_Timer с некоторой вероятностью вызывается End.

Трассировка стека вызовов на момент обращения к End будет такой:
vb6.exe!WinMain
 ➥ ...
   ➥ vb6.exe!ThunderMsgLoop (в этой процедуре крутится оконный цикл)
     ➥ user32.dll!DispatchMessage
       ➥ vb6.exe!StdCtlWndProc (общая для всех VB-шных оконных классов оконная процедура-обёртка)
         ➥ vb6.exe!CommonGizWndProc (общая для всех VB-шных контролов оконная процедура-обёртка)
           ➥ vb6.exe!PushCtlProc (оконная процедура VB-шных кнопкок)
             ➥ vb6.exe!_DoClick
               ➥ vb6.exe!EvtErrFire
                 ➥ vb6.exe!EvtErrFireWorker
                   ➥ vb6.exe!InvokeEvent
                     ➥ vb6.exe!InvokeVtblEvent
                       ➥ vb6.exe!CallProcWithArgs
                         ➥ Project1!Form1::Command1_Click
                           ➥ vb6.exe!_CFORM_vtbl::meth__imethSHOW
                             ➥ vb6.exe!RefCallMethFromVtable
                               ➥ vb6.exe!MethCallMethod
                                 ➥ vb6.exe!CommonGizWndProc
                                   ➥ vb6.exe!FormCtlProc
                                     ➥ vb6.exe!ShowMethShow
                                       ➥ vb6.exe!FormShowModal
                                         ➥ vb6.exe!CMsoComponent::PushMsgLoop
                                           ➥ vb6.exe!CMsoCMHandler::FPushMsgLoop
                                             ➥ vb6.exe!ThunderMsgLoop
                                               ➥ user32.dll!DispatchMessage
                                                 ➥ vb6.exe!StdCtlWndProc
                                                   ➥ vb6.exe!CommonGizWndProc
                                                     ➥ vb6.exe!TimerCtlProc
                                                       ➥ vb6.exe!EvtErrFire
                                                         ➥ vb6.exe!EvtErrFireWorker
                                                           ➥ vb6.exe!InvokeEvent
                                                             ➥ vb6.exe!InvokeVtblEvent
                                                               ➥ vb6.exe!CallProcWithArgs
                                                                 ➥ Project1!SuperTimer_Timer (здесь обращение к End)

Что мы видим? У нас опять есть чередование «синих» и «красных» функций в стеке вызовов, вершина стека (конец цепочки) «красная», но ей предшествует большой синий регион, который VB не умеет раскручивать, и, что самое плохое, в этом регионе есть участок стека, построенный чужеродным кодом из user32 — здесь это сокращённо отмечено как вызов DispatchMessage, хотя на самом деле DispatchMessage это цепочка вызовов:
user32.dll!DispatchMessage
  ➥ user32.dll!DispatchMessageWorker
     ➥ user32.dll!UserCallWinProcCheckWow
        ➥ user32.dll!InternalCallWinProc

Как мы знаем, VB умеет делать unwinding (раскрутку) только красных участков стека, а здесь между двумя красными имеется синий участок, но, тем не менее, модальный показ экземпляра VB-формы не блокирует таймер на родительском окне (допуская появление двух красных участков, разделённых синим), а обращение к End из этого таймера корректно завершает проект (несмотря на наличие синего участка между двумя красными). Так как же VB удаётся разделаться с синим участком стека вызовов, лежащим между двумя красными? Может он всё-таки умеет обходиться с синими участками? А если нет, то в чём принципиальная разница с модальным MsgBox, где события перекрытого окна приходилось блокировать?

Ответ прежний: VB умеет делать раскрутку только красных участков. Раскручивая красный участок, лежащий на вершине стека (т.е. в конце цепочки вызовов), VB разрушает все красные стековые фреймы, доходя до ближайшего синего. Встретив синий стековый фрейм, VB прекращает раскрутку и позволяет синему коду продолжить свой естественный ход выполнения.

То есть, когда внутри SuperTimer_Timer сделано обращение к End, уничтожается только красный фрейм вызова Project1!SuperTimer_Timer и выполнение возвращается в vb6.exe!CallProcWithArgs.

vb6.exe!CallProcWithArgs после того, как выполнение вернулась в неё, отрабатывает ДО КОНЦА (это важно!), как если бы внутри таймерного события не было End. После возврата из vb6.exe!CallProcWithArgs выполнение попадает в vb6.exe!InvokeVtblEvent.

Остаток vb6.exe!InvokeVtblEvent тоже выполняется до конца, и выполнение возвращается в vb6.exe!InvokeEvent — и вот так по цепочке выполнение двигается из глубин цепочки вызовов в сторону её начала. И в скором времени выполнение возвращается из DispatchMessage обратно в ThunderMsgLoop.

Там до обращения к End крутился вторичный цикл обработки сообщений, который должен закончиться только с закрытием модально показанного экземпляра формы Form2. Когда выполнение вернётся из DispatchMessage в ThunderMsgLoop, по неслучайному совпадению этот экземпляр как раз будет закрыт (ибо End уничтожает все VB-шные объекты, точнее зомбифицирует их, помечая как «мёртвые», но оставляя их пока в памяти на случай, если откуда-то будут поступать вызовы IUnknown::Release на этапе раскрутки/терминации работы проекта) и больше ни одной итерации оконного цикла выполнено не будет.

vb6.exe!ThunderMsgLoop в нормальном естественном порядке доработает до своего конца и произойдёт возврат в vb6.exe!CMsoCMHandler::FPushMsgLoop.

И таким образом все функции из синего участка стека вызовов будут в нормальном порядке дорабатывать до своего финала и делать нормальный возврат в вызывающую процедуру.

И так до тех пор, пока в результате череды возвратов мы не попадём в vb6.exe!_CFORM_vtbl::meth__imethSHOW.

vb6.exe!_CFORM_vtbl::meth__imethSHOW — это именно то, указатель на что содержится в ячейке vtable, соответствующей методу Show. По своей сути это обёртка, которая вызов метода COM-объекта превращает в чисто-процедурный вызов RefCallMethFromVtable. Все методы и свойства форм и встроенных в VB контроллов, доступные через их vtable, на деле являются такими фиктивными обёртками, превращающими объектный вызов (в духе COM) в чисто-процедурный. Но это просто информация для справки, на дело это не влияет.

Итак, vb6.exe!_CFORM_vtbl::meth__imethSHOW тоже нормально отработает до конца и сделает возврат выполнения в вызывающую процедуру, и вот тут происходит кое-что интересное, потому что из синей процедуры мы попадаем в красную! Вот участок стека вызовов, о котором сейчас идёт речь:
                     ...
                       ➥ vb6.exe!CallProcWithArgs
                         ➥ Project1!Form1::Command1_Click
                           ➥ vb6.exe!_CFORM_vtbl::meth__imethSHOW
                             ➥ vb6.exe!RefCallMethFromVtable
                               ...

Я уже писал, что красные участки стека вызовов — это в какой-то мере наша условность, потому что они не эквивалентны в полной мере синим участкам, так как красные участки соответствуют P-коду, который исполняется P-кодной виртуальной машиной, которая сама реализована в Native-коде, а синие участки соответствуют Native-кодным процедурам, которые исполняет сам процессор. И за красными участками стека вызовов на самом деле скрывается синий стековый фрейм виртуальной машины (являющейся частью vba6.dll (или msvbvm60.dll в случае скомпилированных проектов)), так как виртуальная машина это просто native-код, который гонит цикл обхода P-кодных инструкций, обходит их одну за другой и для каждой инструкции делает какое-то действие.

Так вот, всякий раз, когда происходит возврат выполнения из синей области в красную (то есть когда чужеродный native-код, вызванный из P-кода, возвращает управление обратно в P-кодную часть цепочки вызовов), в виртуальной машине выполняется проверка поля «режим» структуры EBTHREAD. Если режим «нормальный», виртуальная машина продолжает выполнять P-код дальше как ни в чём не бывало. Обращение же к End меняет это поле в структуре EBTHREAD на режим «выполнение прекращено», поэтому когда происходит возврат из чужерожного native-кода в проектный P-код, выполнение напарывается на эту ловушку. Это ловушка на границе между синей и красной зоной — везде, где возможен возврат из синей в красную.

В случае вызова метода Show формы (строка Form2.Show vbModal, Me), на уровне P-кода за таким вызовом стоит P-кодная инструкция VCallHresult. Возврат из vb6.exe!_CFORM_vtbl::meth__imethSHOW происходит в то место кода виртуальной машины, которое отвечает за P-кодную инструкцию VCallHresult (а она отвечает за вызовы методов COM-интерфейсов через vtable, причём методы должны возвращать HRESULT, что и отражено в названии).

После возврата в виртуальную машину из чужеродного кода проверяется, жив ли ещё текущий поток (поле в структуре EBTHREAD), и если нет (т.е. если было обращение к End), то вместо продолжения нормального исполнения P-кода происходит генерация ошибки (с кодом 0x9C68). Генерация ошибки приводит к раскрутке уже очередного красного участка стека вызовов (на данный момент он опять находится на вершине стека, потому что лежавшая вслед за ним синяя часть самораскрутилась в ходе нормального выполнения).

Этим обеспечивается, что код, стоявший вслед за вызовом метода Show, выполнен не будет, возврат из процедуры, где располагался вызов метода Show в вызывающую процедуру (если вызывающая процедура — тоже часть кода VB-проекта) тоже не произойдёт. Весь VB-код, который соответствует текущему красному участку стека вызовов, после того, как выполнение вернулось из синей части обратно в виртуальную машину, выполняться больше не будет, а вместо этого будут уничтожены красные стековые фреймы (в освобождением ресурсов) и выполняется вернётся в предшествующую красному участку стека вызовов синюю процедуру.

Подытожим:
  1. VB обязан уметь делать раскрутку стека и принудительный возврат выполнения не просто в непосредственно вызывающую процедуру (по отношению к текущей), а сразу на несколько уровней вложенности, минуя промежуточные и не допуская выполнения кода промежуточных процедур. Необходимость уметь это делать обусловлена тремя потребностями:
    • Обращение к End из любого места в коде
    • Программист может остановить работу кода кнопкой «Стоп» из VB IDE в любой момент.
    • Возникновение ошибки в любом месте в коде должно раскрутить стек и перенести выполнение к обработчику ошибок, если он есть.

  2. Но VB умеет абортировать выполнение и делать раскрутку стека только для проектного кода, который представлен P-кодом (и который в трассировке стека вызовов мы условились изображать красным цветом). Чужеродный код (обычно native-код, но вовсе не обязательно) по первом требованию VB абортировать не умеет, и ему ничего не остаётся делать, кроме как давать ему возможность отработать и завершиться «своим ходом».

  3. Когда VB-код исполняется, пока выполнение кода «гуляет» непосредственно по самому VB-коду, стек вызовов выглядит так:

    чужеродный код
     ➥ код проекта

    В этом случае обращение к End или кнопка «Стоп» в IDE раскручивает красную часть и возобновляет выполнение с того места, где был переход из синей в красную. Синяя часть продолжает выполняться как обычно — точно так же, как если бы красная отработала штатно.

  4. Когда VB-код исполняется и из VB-кода делается вызов к внешнему (чужеродному) коду, например, к коду функций рантайма, API-функциям, или к методу сторонних объектов, или же к методам своих объектов, не являющихся (слово относится к методам) частью VB-кода, стек вызовов выглядит так:

    чужеродный код
     ➥ код проекта
       ➥ чужеродный код

    В этом случае VB не может в любой момент остановить выполнение работы кода, да, собственного, у него и нет контроля над выполняемым чуждеродным кодом. VB вынужден дождаться, когда «нижний» чужеродный код закончит работу и вернёт выполнение на «красный уровень», и только тогда VB сможет остановить работу кода и сделать раскрутку стека, но лишь до верхнего чужеродного кода.

  5. Бывают ситуации, когда код проекта передаёт управление в чужеродный код, а из этого чужеродного кода выполнение опять переходит в код проекта (но не в результате возврата, а в результате обратного вызова). В простейшем случае это выглядит так:
    чужеродный код
     ➥ код проекта
       ➥ чужеродный код
         ➥ код проекта

    но в более сложном случае чередование может быть и таким:
    чужеродный код
     ➥ код проекта
       ➥ чужеродный код
         ➥ код проекта
           ➥ чужеродный код
             ➥ код проекта
               ➥ чужеродный код
                 ➥ код проекта

    Во всех подобных случаях при возникновении необходимости раскрутки стеки и переноса выполнения на несколько уровней выше (минуя естественный ход исполнения), раскрутка может быть выполнена только для самого последнего в цепочке красного уровня. После раскрутки и абортирования красного кода (проектного кода) выполнение перейдёт в чужеродный код, и у VB нет рычагов воздействия на чужеродный код — чужеродный код может сколько угодно долго продолжать выполняться или крутить циклы или даже делать попытки из себя опять вызвать какой-то проектный (красный) код. Поскольку рычагов воздействия нет, VB ничего не остаётся делать, кроме как ждать, пока синий код в ходе естественного выполнения дойдёт до своего логического конца и вернёт выполнение обратно в красный код (проектный код).

    По этой причине на время показа диалогов вызов обработчиков событий подавляется, чтобы было так:

    #1 код Windows и VB IDE
    #2  ➥ код проекта, вызвавший показ диалога
    #3    ➥ код, отобразивший диалог и крутящий оконный цикл

    а не так:

    #1 код Windows и VB IDE
    #2  ➥ код проекта, вызвавший показ диалога
    #3    ➥ код, отобразивший диалог и крутящий оконный цикл
    #4      ➥ код проекта — обработчик события

    потому что в последнем случае обращение к End / кнопка «Стоп» / возникновении ошибки лишь заставит выполнение переместиться с четвёртого уровня на третий, а там выполнение продолжит «крутиться» сколь угодно долго, и никакими способами VB не может форсировать переход с третьего уровня на второй.

  6. В общем и целом, когда в стеке вызовов чередуются синие и красные участки, раскрутка идёт по принципу «красное абортируется сразу и бескомпроммисно и выполнение возвращается в синюю часть, а синей мы даём полную свободу выполняться до конца так, как она считает нужным и столько, сколько она считает нужным».

  7. При модальном показе экземпляров форм с помощью Show vbModal вызов обработчиков событий у окна-владельца не блокируется по той причине, что в этом случае хоть и имеется чередование красных и синих участков на стеке вызовов, относительно синего участка, лежащего между двумя красными, имеется гарантия, что после возврата из более удалённого от начала цепочки вызовов красного участка в промежуточный синий участок, выполнение в этом промежуточном (лежащем между двумя красными) синем участке не задержится, не зациклится, не застрянет, а очень скоро перейдёт в менее удалённый от начала цепочки вызовов красный участок. Напомню, что в отношении функций, показывающих диалоги и имеющих реалиацию оконного цикла где-нибудь в user32.dll или comdlg32.dll такой гарантии нет.

Бонус:
Подавление срабатывания обработчиков событий во время отображения модальных диалогов устроено так, что оно не отслеживает сам факт появления модальных диалогов (каким путём бы они ни были при этом созданы), а полагается на флаг, устанавливаемый функциями MsgBox/InputBox/пр.

Поэтому если показывать окно сообщения не через MsgBox, а через MessageBox, подавления срабатывания событий не будет. Это даёт нам возможность посмотреть, как работал бы VB если бы желание Майкла было воплощено:

Код: Выделить всё
Option Explicit
Private Declare Function MessageBox Lib "user32" Alias "MessageBoxA" (ByVal hwnd As Long, ByVal lpText As String, ByVal lpCaption As String, ByVal wType As Long) As Long

Private Sub Form_Activate()
    Timer1.Interval = 250: Timer1.Enabled = True
    MessageBox Me.hwnd, "Do you like it?", "Lan", vbYesNo
End Sub

Private Sub Timer1_Timer()
    Static clr As Long
    clr = clr + 1
    If clr <= 15 Then
        Me.BackColor = QBColor(clr)
    Else
        ' Цвета кончились
        Do Until Me.CurrentY > Me.ScaleHeight
            Me.Print "ЦВЕТА КОНЧИЛИСЬ!!! Сейчас будет обращение к End!"
        Loop
       
        End ' проект должен завершиться, но не тут-то было
    End If
End Sub


В этом случае, если сразу закрыть окно сообщения, то после перебора всех 15 цветов, проект завершится, экземпляр формы закроется, IDE перейдёт в состояние как до запуска проекта. Если же окно сообщения (показанное ф-цией MessageBox) не закрывать, то обработчик таймера будет срабатывать даже при открытом сообщение, однако 16-ое по счёту срабатывание хоть и сделает обращение к End — проект от этого остановлен не будет, экземпляр формы останется существовать, диалоговое окно с сообщением тоже никуда не исчезнет, среда (VB IDE) останется в состоянии «проект выполняется», редактирование кода по прежнему будет заблокированным, однако кнопки «Пауза» и «Стоп» перестанут работать:
Изображение

Лишь только после того, как пользователь вручную закроет окно, показанное ф-цией MessageBox, проект завершится. И хорошо, если от окна можно отделаться, а если окно криво создалось, или имеет нулевой размер, или ему по ошибке установили пустой clip-регион, и закрыть его не представляется никакой возможности? У нас, пока проект в состоянии «запущен», даже меню сохранения кода проекта заблокировано.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Перекрытие окна на вызывает события Paint

Сообщение Хакер » 22.11.2018 (Чт) 14:09

Пост большой, и перед публикацией не прочитывался полностью, поэтому сейчас я буду время от времени находить ошибки/опечатки и править их, просьба при нахождении в тексте непонятного или абсурдного места как минимум перезагрузить страницу (возможно я его уже исправил) или подождать.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Teranas
Бывалый
Бывалый
Аватара пользователя
 
Сообщения: 224
Зарегистрирован: 13.12.2008 (Сб) 4:26
Откуда: Новосибирск

Re: Перекрытие окна на вызывает события Paint

Сообщение Teranas » 22.11.2018 (Чт) 19:32

Ну, ты Хакер монстр, в хорошем смысле, конечно!
Я-то только знал, что paint не работает при диалогах, а ты всё так расписал, круто, уважуха!
!+ + + + + + + + + + + + + + + +!
С уважением, Андрей.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Перекрытие окна на вызывает события Paint

Сообщение Mikle » 22.11.2018 (Чт) 20:22

Teranas писал(а):круто, уважуха!

+1
Я пока изучаю, позже отвечу.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: Перекрытие окна на вызывает события Paint

Сообщение ger_kar » 25.11.2018 (Вс) 18:54

Круто. Хакеру огромнейшее спасибо за подобную статью. После прочтения таких статей у меня всегда идет осознание того факта, насколько глубоко Хакер находится в теме (устройство VB в частности и программирования в общем) и насколько я сам являюсь недалеким и плаваю на поверхности :).

После прочтения статьи многое стало понятным, но далеко не все. И возникли такие вопросы:
В статье затрагивается режим запуска VB проекта из под IDE, но ведь есть и другие режимы, такие, как запуск скомпилированного проекта, который может быть как нативным, так и в P-коде.
Хакер писал(а):VB обязан уметь делать раскрутку стека и принудительный возврат выполнения не просто в непосредственно вызывающую процедуру (по отношению к текущей), а сразу на несколько уровней вложенности, минуя промежуточные и не допуская выполнения кода промежуточных процедур. Необходимость уметь это делать обусловлена тремя потребностями:
1) Обращение к End из любого места в коде.
2) Программист может остановить работу кода кнопкой «Стоп» из VB IDE в любой момент.
3) Возникновение ошибки в любом месте в коде должно раскрутить стек и перенести выполнение к обработчику ошибок, если он есть.

Таким образом, если исключить пункт 2, которого не может быть в случае запуска проекта в скомпилированном виде, то потребность в оставшихся двух пунктах отнюдь не исчезает, но при этом
The trick писал(а):The trick » 21.11.2018 (Ср) 12:47
Mikle, в скомпилированном варианте все работает.

Почему?

И второй вопрос возник такой:
Например имеем некую VB функцию с обработчиком ошибок и имеем цепочку вызовов из этой функции других функций и процедур, в одной из которых в процессе исполнения возникает исключение. При этом во всех этих процедурах/функциях обработчики исключений отсутствуют и выполнение, после возникновения исключения должно перейти в обработчик самой верхней процедуры. Как при этом будет происходить раскрутка стека и освобождение ресурсов, которые были выделены в рамках исполнения цепочки процедур без обработчиков в скомпилированном проекте?
Бороться и искать, найти и перепрятать

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Перекрытие окна на вызывает события Paint

Сообщение Хакер » 26.11.2018 (Пн) 5:20

ger_kar писал(а):Таким образом, если исключить пункт 2, которого не может быть в случае запуска проекта в скомпилированном виде, то потребность в оставшихся двух пунктах отнюдь не исчезает, но при этом
Mikle, в скомпилированном варианте все работает.

Почему?


В самостоятельном EXE во-первых, риск появления неостановимого кода затрагивает только сам процесс EXE, и не затягивает ещё и IDE в эту ловушку, а потому не так страшен этот случай, а во-вторых, в самостоятельном EXE такое безобразие можно легко прекратить, поставив ExitProcess вместо End (чего нельзя сделать в IDE, потому что прибьём таким образом и IDE вместе с собой). А в третьих, в скомпилированном виде это может работать просто потому, что забыли сделать «как надо».

ger_kar писал(а):имеем цепочку вызовов из этой функции других функций и процедур, в одной из которых в процессе исполнения возникает исключение. При этом во всех этих процедурах/функциях обработчики исключений отсутствуют и выполнение, после возникновения исключения должно перейти в обработчик самой верхней процедуры.


А промежуточные функции в этой цепочке — это VB-шные функции или чужеродный код?

VB-шные процедуры имеют SEH-хендлер даже тогда, когда в нашем коде нет никакого On Error Goto ..., если процедуры не с тривиальным кодом (когда нечего освобождать при раскрутке).

Чужеродный же код поведёт себя в зависимости от того, как он написан: если у него внутри есть SEH-обработчики, то он на раскрутку силами SEH среагирует и что-то «финальное» сделает. А если их нет — то и промежуточные процедуры из этой цепочки не получат шанса подчистить за собой.

В каких случаях вообще нашу VB-процедуру (модульную) могут вызвать из чужеродного кода? Это только случаи callback-ов — если мы вызывали EnumWindows и указали свой callback, или установили свою модульную процедуру как WindowProc или ещё что-то такое. Во всех таких случаях мы должны взять за правило в своей модульной процедуре отлавливать всё и никакие исключения не выпускать наверх (в вызывающую «среду»). Если, конечно, у нас нет достоверных сведений, что чужеродный код, вызывающий наш код, дружит с SEH.

А с не модульными процедурами проще: там ошибки индицируются не с помощью исключений, а с помощью возврата HRESULT.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: Перекрытие окна на вызывает события Paint

Сообщение ger_kar » 26.11.2018 (Пн) 7:06

Хакер писал(а):А промежуточные функции в этой цепочке — это VB-шные функции или чужеродный код?
VB-шные

Хакер писал(а):VB-шные процедуры имеют SEH-хендлер даже тогда, когда в нашем коде нет никакого On Error Goto ..., если процедуры не с тривиальным кодом (когда нечего освобождать при раскрутке).
Получается, что на стадии компиляции компилятор проверяет типы переменных, используемые в процедуре/функции, и если есть типы, которые нужно подчищать и освобождать ресурсы, то он принудительно внедряет в код SEH обработчик? А если переменные Variant и на стадии компиляции не понятно, что они будут содержать? Или например используются переменные, которые потенциально не требуют очистки, но происходит открытие файла, тем более, что код открывающий файл и закрывающий вообще может быть разнесенным по разным процедурам?
Бороться и искать, найти и перепрятать

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Перекрытие окна на вызывает события Paint

Сообщение Хакер » 26.11.2018 (Пн) 7:34

ger_kar писал(а):А если переменные Variant и на стадии компиляции не понятно, что они будут содержать?

В общем случае Variant-переменная содержит VARIANT-значение, которое в общем случае подлежит зачистке в обязательном порядке. Само действие по зачистки будет определено в нужный момент по полю vt.

ger_kar писал(а):Или например используются переменные, которые потенциально не требуют очистки, но происходит открытие файла, тем более, что код открывающий файл и закрывающий вообще может быть разнесенным по разным процедурам?

А причём тут открытие файлов? Зачистке подлежат любые переменные, в составе которых указатели, а именно — объектные ссылки, строки, массивы. До открытых файлов VB дела нет — этот ресурс не фрейм-ассоциированный, а проект-ассоциированный.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: Перекрытие окна на вызывает события Paint

Сообщение ger_kar » 28.11.2018 (Ср) 12:10

Хакер писал(а):Бонус:Подавление срабатывания обработчиков событий во время отображения модальных диалогов устроено так, что оно не отслеживает сам факт появления модальных диалогов (каким путём бы они ни были при этом созданы), а полагается на флаг, устанавливаемый функциями MsgBox/InputBox/пр.Поэтому если показывать окно сообщения не через MsgBox, а через MessageBox, подавления срабатывания событий не будет. Это даёт нам возможность посмотреть, как работал бы VB если бы желание Майкла было воплощено:

Хакер писал(а):В самостоятельном EXE во-первых, риск появления неостановимого кода затрагивает только сам процесс EXE, и не затягивает ещё и IDE в эту ловушку, а потому не так страшен этот случай, а во-вторых, в самостоятельном EXE такое безобразие можно легко прекратить, поставив ExitProcess вместо End
Попробовал работу кода через MessageBox, как предлагалось выше, в скомпилированном виде. Все работает точно также, как и в режиме запуска из под IDE, значит ExitProcess не вызывается, так как если бы было иначе, то приложение бы закрывалось не дожидаясь закрытия диалога.
Бороться и искать, найти и перепрятать

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Перекрытие окна на вызывает события Paint

Сообщение Хакер » 28.11.2018 (Ср) 13:21

ger_kar писал(а):значит ExitProcess не вызывается, так как если бы было иначе, то приложение бы закрывалось не дожидаясь закрытия диалога.

Я и не говорил, что оно вызывается силами End. Я говорил, что программист сам может обернуть End в EndProgram, в которой будет настоящий End под IDE и ExitProcess в EXE (если программиста устраивает метод грубой силы).
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: Перекрытие окна на вызывает события Paint

Сообщение ger_kar » 29.11.2018 (Чт) 12:20

А как вообще END устроен в недрах VB6? Ну кроме того, что уже было описано выше. И есть ли различия в его реализации в рантайме и vba.dll?
Бороться и искать, найти и перепрятать

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Перекрытие окна на вызывает события Paint

Сообщение Хакер » 29.11.2018 (Чт) 12:40

А как вообще END устроен в недрах VB6?

Как генерация ошибки с кодом 0x9C68.

То есть, грубо говоря, End эквивалентен Err.Raise &H9C68&.

Но заменить End на Err.Raise &h9C68& нельзя, потому что метод Raise откажется принять такой аргумент (ибо у него особое зарезервированное значение), код ошибки будет заменён на 440.
Зато строчка Error &H9C68& срабатывает на ура — эффект такой же, как от End

И что самое интересное, употребление End запрещено в проектах типа ActiveX EXE/ActiveX DLL, а вот Error &H9C68& прокатит.

А сама ошибка обрабатывается по тем же принципам, по которой обрабатываются любые другие ошибки с другими кодами, за исключением того, что VB-шные обработчики (On Error) её игнорируют и пропускают выше по стеку вызовов. Как и любые другие ошибки, в модульных процедурах за этой ошибкой стоит выброс и отлов SEH-исключения, а в методах класса стоит возврат соответствующего HRESULT-а.

Так что, говоря именно об End, разницы между msvbvm60.dll и vba6.dll — нет. Разница в самом механизме отлова и обработки ошибок есть, но она распространяется на любые ошибки, а не только на End как частный случай.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Перекрытие окна на вызывает события Paint

Сообщение Хакер » 29.11.2018 (Чт) 13:13

В P-коде для End существует собственная двухбайтовая инструкция FC C8 — встретив её, виртуальная машина вызывает EbRaiseExceptionCode(0x9C68).
При компиляции в Native-код End превращается в вызов функции __vbaEnd, внутри которой всё тот же вызов EbRaiseExceptionCode(0x9C68).
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: Перекрытие окна на вызывает события Paint

Сообщение ger_kar » 29.11.2018 (Чт) 14:56

Хакер писал(а):И что самое интересное, употребление End запрещено в проектах типа ActiveX EXE/ActiveX DLL
А в чем это проявляется? Я попробовал в проекте ActiveX EXE использовать End, ошибки компиляции не возникает.
Бороться и искать, найти и перепрятать

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Перекрытие окна на вызывает события Paint

Сообщение Хакер » 29.11.2018 (Чт) 20:51

Имелась в виду пара OCX/DLL.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ger_kar
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1957
Зарегистрирован: 19.05.2011 (Чт) 19:23
Откуда: Кыргызстан, Иссык-Куль, г. Каракол

Re: Перекрытие окна на вызывает события Paint

Сообщение ger_kar » 29.11.2018 (Чт) 21:22

Хакер писал(а):Имелась в виду пара OCX/DLL.
Попробовал в этих проектах использовать End и сразу ошибка компиляции "Functionality not supported in DLL".
Что конечно же и логично.
Вроде самый простой оператор этот End, а столько нюансов. А оператор Stop, тоже через генерацию ошибки работает?
Бороться и искать, найти и перепрятать

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Перекрытие окна на вызывает события Paint

Сообщение Хакер » 29.11.2018 (Чт) 23:02

ger_kar писал(а):А оператор Stop, тоже через генерацию ошибки работает?


Stop кардинально отличается от End тем, что под IDE и в скомпилированном виде оно работает совершенно по разному.

У Stop тоже есть своя P-кодная инструкция (FC C2).

P-кодная виртуальная машина в msvbvm60.dll при встречи этой инструкции вызывает __vbaStopExe.
В программах, скомпилированных в Native-код, обращение к Stop транслируется в вызов __vbaStopExe.

__vbaStopExe — это вывод сообщения с помощью вызова HostDisplayMsgBox, а затем выброс той же ошибки 0x9C68, как и в случае с End.

P-кодная виртуальная машина в vba6.dll при встречи этой же инструкции делает совершенно иное. Если говорить упрощённо, проверяются флаги структуры EBTHREAD, в случае успеха идёт вызов процедуры, которая переносит пользователя к IDE и начинает крутить оконный цикл, пока пользователь не сделает одно из двух: либо нажмёт «Run», либо нажмёт «Stop». Ожидающий новый оконный цикл крутится в функции, вызванной из кода виртуальной машины, и не возвращает выполнение, пока пользователь не продолжит/остановит программу, чем обеспечивается временное блокирование выполнения P-кода. В случае нажатия на «Run» в отладчике, модальный оконный цикл прерывается и происходит возврат обратно в код виртуальной машины (в обработчик инструкции «Stop»). В случае нажатия «Stop» в отладчике — модальный оконный цикл так же прерывается, но вызывается EbRaiseExceptionCode с всё тем же кодом 0x9C68.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.


Вернуться в Visual Basic 1–6

Кто сейчас на конференции

Сейчас этот форум просматривают: SemrushBot и гости: 56

    TopList