Пользователь
Information
- Rating
- 2,147-th
- Location
- Петропавловск, Северо-Казахстанская обл., Казахстан
- Registered
- Activity
Specialization
Software Developer, Embedded Software Engineer
Pure C
Assembler
X86 asm
Win32 API
Visual Basic
MySQL
Git
OOP
Electronics Development
Reverse development
Кто говорил о кросс-процессности?
"нтерпретация байт-кода" для режима P-CODE, потому что в таком режиме библиотека машины все равно интерпретирует его по ходу выполнения
Тогда скомпилированные Java-программы работают путём интерпретации Java-байкода, дотнетовские программы работают путём интерпретации MSIL-байткода. Да и процессоры интерпретируют машинный код.
Можно было бы говорить «в отличие от режима компиляции в Native», если бы суть обработки P-кодных инструкций заключалась бы в том, что виртуальная машина P-код переписывала бы в Native-код (генерируя для каждой пи-кодной инструкции дюжину машинных), а потом отдавала бы сгенерированный Native-код на выполнению процессору.
Но она же не так делает. В том большом комменте описано, как она это делает.
Коротко и упрощённо говоря: для каждой P-кодной инструкции есть свой хендлер, опкод P-кодной инструкции является индексом в таблице указателей на соответствующие обработчики.
Приведённое выше академическое определение понятия «интерпретор» настаивает на том, что входными данными для него является исходный текст программы, а обработка происходит построчно.
В данном случае нет ни построчности, ни исходного текста в роли входной последовательности для виртуальной машины.
В теории можно было бы вообще создать архитектуру процессора, которая бы машинно умела исполнять VB-шный P-код. Ну какая же это интерпретация?
Да что же в этом дорогого? Это не было дорогим удовольствием 25 лет назад, а уж тем более сейчас.
Это не дороже, чем две лишних локальных переменных.
Я смотрю, пока я писал, вы перекроили свой коммент.
Как понять «незакрытый вызов WinAPI»? Вызовы WinAPI были не хаком, а штатной возможностью, точнее даже были осуществимы через два разных механизма: через Declare Sub/Function и через TLB.
Процедуры генерировались в чистейший машинный код.
Давайте разделять понятия: виртуальная машина — это то, что, принимая на вход цепочку команд, каким-то образом выполняет их, предпринимая какие-то действия по поводу каждой команды и меняя своё внутреннее состояние.
Реализация виртуальной машины VB-шного P-кода жила в MSVBVMxx.DLL (что и давало название этой библиотеки), но помимо виртуальной машины значительную часть библиотеки составляло то, что можно назвать «стандартной библиотекой» по аналогии с языками Си/Си++. Т.е. это просто различные «встроенные функции», заявленные как «часть языка», а также служебные функции (в случае C++ примером такой функции может стать _purecall). Это обычные процедуры в виде машинного кода, они к виртуальной машине отношения не имеют.
Так вот, то, что генерировалось в режиме создания Native-кодных исполняемых файлов, представляло собой обычный машинный код x86 и не с вкраплениями P-кодных кусочков, а с вкраплениями вызовов служебны функций для выполнения тех задач, которые приходится очень часто решать и которые раздули бы объём кода, если бы эти задачи инлайнились.
Например — копирование структур. Копирование структур можно было бы «инлайнить», сгенерировав код для копирования каждого члена структуры (в общем случае структуру нельзя копировать банальными memcpy, ведь там могут быть указатели на строки или COM-интерфейсы, массивы — и копировать это надо по умному). Вместо генерировалась одна единственная call-инструкция, вызывающая функцию рантайма, которая брала на себя все заботы по правильному копированию структуры. Ей передавался некий дескриптор структуры, предопределявший правила копирования.
Или же после каждого вызова метода COM-интерфейса (читай «метода вызова любого VB-объекта или любого внешнего COM-объекта»), который мог выбросить ошибку путём возврата HRESULT, ставился вызов __vbaHresultCheck, который брал на себя всю работу по «транслированию» кода сбоя (HRESULT) в VB-ошибку (VB использовал SEH-исключения для работы своего механизма ошибок).
Плохо читали — ничего там не упущено. Оно скрыто под спойлером «Немного подробностей о том, как происходит компиляция в Native-код», внутри которого есть свой под-спойлер «Интересный вопрос касательно IL на входе бэкенда C2».
В Википедии приводится вот такое определения термина «интерпретация» в контексте ЯП:
Интерпрета́ция — построчный анализ, обработка и выполнение исходного кода программы или запроса, в отличие от компиляции, где весь текст программы, перед запуском анализируется и транслируется в машинный или байт-код без её выполнения[4][5][6]
Источниками такого определения назван не Вася Пупкин, а литература, написанная авторами, заслуживающими уважения:
Так что в соответствтии с общеприянтым определением это не интерпретация, а компиляция и исполнение байт-кода виртуальной машины. Видимо многим просто нелегко принять факт, что презираемый очень многими инструмент с репутацией «для чайников в программирование» имел крутейший и непревзойдённый механизм патчинга скомпилированного байт-кода по живому. «Ну не может же такого быть, что оно вот так легко на лету перекомпилирует и перекраивает скомпилированную процедуру, в данный момент исполнявшуюся — значит там под капотом банальная интерпретация» — так видимо рассуждало большинство.
SEH-фрейм занимает
8 байт2 sizeof(void) на стеке — разве это дорого? В обходе односвязного списка и вызове обработчика по указанному адресу тоже вряд ли что-то дорогое можно узреть. Скорее всего по сравнению с тем, что обработчики будут делать, overhead от обхода цепочки вызовов будет незначительным.SEH был дорогим по совсем другой причине. Вызов RaiseException приводит к переходу в режим ядра, а это довольно медлительная вещь. В этом смысле SEH-исключения не могли стать альтернативой для кодов возрата статуса и должны были использоваться только для реально исключительны ситуаций. Попытка переделать цикл, проходящийся по миллиону элементов и вызывающий для каждого элемента какую-нибудь функцию, которая с вероятностью 30...50% может сбойнуть на выкидывания исключений очень значительно замедлила бы работу такого цикла, потому что 500 000 раз выкидывать исключение — это 500 переходов в режим ядра и обратно, что на порядок или несколько порядков медленнее, чем если бы вызываемая функция просто возвращала код ошибки, при условии что в в вызываемой функции только какая-нибудь арифметика и манипуляция данными и нет системных вызовов.
Как это элегантно сформулировать?
«В Си в стиле WinAPI или POSIX или подавляющего большинства паттернов обработки ошибок в Си или С++ в стилке Microsoft»?
Что характерно, ядро Windows написано на Си, но там очень много где (если IRQL позволяет) используется расширение try...except для обуздания SEH.
Почему не в «Си или C++ в стиле Microsoft»?
При вызове WinAPI вы обязаны проверять GetLastError cами.
При вызове COM-методов вы обязаны проверять HRESULT сами.
Я вас умоляю. Читайте вот этот коммент до просветления:
https://habr.com/ru/post/582566/#comment_23578554
В Windows 9x разве что. Хорошая многозадачность в винде, не надо наговаривать. По крайней мере на уровне ядра всё сделано безупречно.
Вот пользовательская подсистема может грешить. Один из примеров грешка: буфер обмена. Если какое-то приложение захватит буфер обмена и не будет отпускать, другие приложения, которые тоже захотят получить доступ к буферу обмена, встанут. Но те, которые не попытаются, не встанут уж точно.
Не согласны — предоставьте PoC-код подвешивания системы длительным глухим циклом.
Разумный подход, как я считаю, состоит в том, чтобы не использовать GoTo, чтобы на его базе делать циклы. Не надо использовать GoTo вместо For, вместо While, вместо Do. Не надо использовать GoTo для перепрыгивания блока кода вместо использования блочного If или If Else.
Но вообще объявлять GoTo абсолютным злом и запрещать его использовать абсолютно всегда — это глупость и максимализм.
Тогда нужно запретить и break/continue в Си, ведь это тот же goto.
Более того, Джоэль Спольски как-то высказал мысль, что обработка исключений — это тот же скрытый GoTo, только даже не в пределах одной процедуры, а через несколько процедур.
Такие контракты могут просто отсутствовать, именно так и было для большинства контролов во времена «золотого времени» VB. Если вы пишите контрол на том же VB, вы обычно вообще не имплементируете свойства Height/Width, имплементация как бы унаследована от базового класса UserControl.
Но бог с ними, с контрактами.
Перемасштабирование не удалось потому, что не хватило памяти. Что более умное можно сделать, кроме как игнорировать эту ошибку?
Если вообще не обрабатывать её, программа вылетит.
Если обрабатывать, то что делать?
Ничего не делать — тот же On Error Resume Next.
Выходить из процедуры — ломать ресайз всего остального UI из-за одного дефективного контрола.
Выводить сообщение об ошибке — бредовейшая затея. Ресайз превращается в ад (смещение на каждый пиксель вызывает новое сообщение), а в условиях жесткой нехватки памяти попытка показать сообщение обернётся новым исключением/ошибкой, которую, как правило, никто не уже не будет ловить, а это вылет приложения.
Вы подходите с позиции идеалистов, которые считают, что нужно всё покрыть тестами, что есть классный отдел Q&A, который все дефекты выявит, и пользователь получит классный продукт.
Это недостижимая утопия. Те же ОС тестируют в хвост и в гриву, а критические уязвимости и ошибки всё равно появляются.
Описываемый мной подход позволяет дать юзеру программу, которая, пусть и содержит косяк, позволяет в случае его активизации программе работать хоть как-то. Не терять данные, не прекращать работу программы. Ну не может один из контрол дюжины подвинуться, лучше не будем его двигать, чем обрадуем пользователя инста-вылетом программы.
Есть IIf, что приближённо соответствует тернарному ?: в Си. С этой проверкой, как я уже написал, вы застрахуете себя от отрицательных координат, но если автору контрола вздумается, что минимальным размером контрола должно быть 40×80, а не 0×0, ваша страховка внезапно потеряет силу. Но даже если не вздумается, вы не застрахуете себя от Out of memory или Out of stack space. Реально невозможность подвинуть контрол должна быть поводом аварийного завершения программы и потери всех данных? Кроме того, реализация контрола всё равно будет проверять входные данные.
Как понять «в самой попытке присвоения»? Присвоение такому свойству значения это вызов обычной процедуры вида
Это не опция.
Во-первых, контрол писали не вы. А другая фирма из другой страны, очень даже может быть. Контрол вам дали в форме .OCX-файла.
Во-вторых, у контрола может быть своё понятие о минимальном размере. Если вы его нарушаете, контрол генерирует ошибку. Если даже разработчик исправит это персонально ради вас, другие программные продукты от других авторов, которые использовали этот контрол, поломаются, потому что они наоборот могли закладываться на то, что при таких-то условиях контрол должен сгенерировать ошибку (и они этого ждут).
Это нужно будет менять CLSID-контрола, потому что в идеальном случае CLSID должен быть неким хешем идентичности класса и его поведения.
В третьих, может быть вообще и переписывать нечего и нет никого неадекватного. Контрол не может принять новый размер, потому что при попытке это сделать не хватило ресурсов. Вот он и честно сгенерировал ошибку. Что предложите в этом случае? Не генерировать ошибку, если HeapReAlloc сбойнул, а делать вид, что всё окей?
Не «номер строки», а метка (label). Просто номер строки является частным случаем метки, оставленным из соображений совместимости.
И, что характерно, обработчик ошибки, к которому перейдёт выполнение, имеет возможно сделать [Resume], [Resume Next] или ничего из этого.
Т.е. попросить «давай-ка попробуем сделать то же самое, но ещё раз — я вроде бы исправил первопричину неудачи», попросить «ладно, фиг с ним, что там у нас дальше», или же вообще перейти к совершенно иному плану действий.
Диалоге в стиле «Не удалось выполнить <....> Повторить / Пропустить / Отмена» реализуются в буквальном смысле в виде пары строк.
Предлагаю показать реализацию выполнения какой-то потенциально сбойной операции с показом диалога Повторить/Пропустить/Отмена при использовании try...catch-подхода. Как минимум, у вас будет ещё и цикл.
Типичный пример использования On Error Resume Next — это начинка обработчика события Resize формы, которое долго перемасштабировать начинку окна.
Например, у вас окно, всё пространство которого должен занимать TextBox, за исключением рамки в, скажем, 5 пикселей.
И вы пишите что-то вроде
При сворачивании окна или при его ресайзе таком, что окно будет иметь слишком маленький размер, у вас получатся отрицательные координаты. Что приведёт к ошибке, и ваше приложение сразу мигом завершится.
On Error Resume Next «улаживал» ситуацию. В случае try...catch-подхода пришлось бы каждую строку обрамить в try с пустым catch. Или вы можете сначала вычислять ширину/высоту, проверять, меньше ли она нуля, и если меньше, то не присваивать полунный результат свойству Height/Width, или же присваивать, но «клэмпнув» до нуля. Поздравляю, вы делаете двойную работу дважды: и кода у вас будет больше, и всё равно внутри set-хендлеров свойств Height/Width выше значение проверяет на <0, чтобы, в случае чего выкинуть исключение.
То есть и вы делаете проверку, и ваши проверенные данные всё равно второй раз подвергнут проверке, чтобы в случае чего выкинуть исключения. Так может быть лучше, раз вам в руки дали механизм реагирования на исключения, воспользоваться и сказать: «просто проигнорируй это»?
На самом деле, примеров может быть очень много. В GUI-программах очень много какого кода, если в нём происходит ошибка, может продолжать работать дальше. Во всяком случае, это лучше, чем если приложение молча вылетит.
Скажем, у вас какой-то многошаговый процесс, в ходе которого должен проиграться «бымц» и показаться сообщение. Мало ли, что может пойти не так: пусть лучше мы молча проглотим тот факт, что не получилось проигрять «бымц» или показать красивую плашку, чем программа рухнет из-за невозможности претворить в жизнь какую-нибудь свистиелку.
Прошу сейчас не передёргивать и не говорить, что при таком подходе может нарушиться бизнес-логика и пострадать данные. On Error Resume Next никогда не был дефолтным поведением. Это то, что программист сознательно выбирал или не выбирал для использования.
В GUI-приложениях, где много какого кода отвечает за обновление UI, за визуальные знаки, всплывающие подсказки, звуки, перекраивание UI — очень много случаев, когда ошибка не фатальна, и пусть лучше в приложении контролы не отмасштабируются, чем оно упадёт.
Если же вы предлагаете в случае ошибки выходить из процедуры, то, ещё раз напомнив, что VB не мешает вам поступать именно так, предложу такой пример: в окне формы 10 контроллов, которые надо масштабировать. Контроллы писал неизвестно кто, и каждый при попытки изменить его размеры может взбрыкнуть.
У вас ест процедура с примерно таким кодом:
Итак, любой контрол может взбрыкнуть. Хотя бы, например, потому, что при изменеии размера контрол может пытаться переведить память под какой-нибудь backbuffer, и памяти внезапно может не оказаться.
Так вот, что вы выбираете?
1) Из-за облома в одном контроле (возможно облом был единократным), вся программа вылетает
2) Из-за того, что один контрол взбрыкнул, при изменении размеров окна все контролы перестают менять свои размеры, потому что кто-то в коде написал On Error Goto exit_sub
3) Только один косячный контрол перестаёт реагировать на изменение размеров окна, а остальные 9 ведут себя адекватно.
Какой подход больше всего обрадовал бы пользователя? Обеспечивал бы сохранность данные? А какой взбесил бы больше всего?
Если уж на то пошло, то и в VB конструкция «On Error Resume Next» не означает «пропустить строку в случае ошибки».
DoEvents ни капли не аналог Sleep(0).
Sleep(0) не делает ничего с очередью сообщений, он просто провоцирует планировщик переключить контекст на выполнение какой-нибудь другой задачи из тех, кто имеет на этой «шанс» в данный момент.
DoEvents это это аналог PeekMessage—>TranslateMessage—>DispatchMessage.
Если говорить о VB6/VBA, то помимо прокачки очереди оконны сообщений и вызова соответствющих им обработчиков (оконных процедур), которые транслируют оконное сообщение в срабатывание какого-нибудь ООП-событий, DoEvents может ряд других интересных и потенциально очень тяжеловесных вещей (именно по этому я всегда критикую использование DoEvents в цикле).
На самом деле, не многие догадываются, что VB является слиянием двух технологий: Ruby и EB — где EB это то, что позже стало известно как VBA, а Ruby это нечто, что придумал когда-то Алан Купер без всякой бейсиковости за этим.
EB aka VBA — это технология добавления «программируемости» к чему угодно. Хотите — к Офису, а хотите — к АвтоКАДу. То, к чему прикручивают технологию VBA по терминлогиюю VBA называется «хостом» — хостом может быть Excel, Access, AutoCAD и т.п.
В случае VB хостом является нечто, что люди из Microsoft называли Ruby — и в данном случае это не имеет ничего общего с ЯП «Ruby».
Так вот DoEvents — это функция из «епархии» VBA. DoEvents можно найти и в самостоятельном VB, и в VBA (в отличие, например, от «объекта» App, которого в офисах не будет, так как эту сущность привнёс Ruby).
И начинка rtcDoEvents (так называется реализация DoEvents) просто вызывает callback-функцию HostDoEvents: она не является частью EB/VBA, её должен предоставить хост, к которому прикручивают VBA. Что в этой функции будет сидеть — одному богу известно. Это зависит от хоста, точнее от фантазии его создателей.
В случае standalone VB реализация HostDoEvents является частью исходного кода Ruby. Помимо того, что там сидит пресловутая цепочка PeekMessage—>TranslateMessage—>DispatchMessage, и вызов Sleep, там сидит вызов CheckFreeUnusedLibs, что приводит к вызову ole32!CoFreeUnusedLibraries.
А этот вызов может оказаться «чёрной дырой» при неблагоприятных условиях.
Как же мне «нравится», что программирование из технической и инженерной дисциплины превратилось в нечто, где есть место моде.
Я представляю, если бы так работало машиностроение:
— Из какого сплава сделаем эту ответственную деталь? Я предлагаю 12ХН3А.
— Ты что, сбрендил? Из этой допотопной стали 20 лет назад детали делали! Сейчас все делают из смеси алюминиевого порошка с перхотью индейцев. Это сейчас самое модное направление. К тому же оно позволяет экономить время разработки и деньги в продакшене. Мне евангелист индейской перхоти вчера так сказал.
Базовых классов может быть несколько.
PERSON_ELEMENT унаследован от PERSON_DATA, при этом PERSON_DATA не находится в самом начале PERSON_ELEMENT.
Во-первых, у VB и VBA не очень хорошая репутация (и совершенно напрасно — это тот случай, когда не инструмент красит человека, а человек инструмент — похожим образом у DAW «FL Studio» есть репутация недо-программы для недомузыкантов, хотя как DAW она не хуже других). Так что я просто опасаюсь, что какашками закидают и скажут «да кому интересен этот продукт, последний релиз которого состоялся 23 года назад».
Во-вторых, тем не менее, я последние годы потратил на глубочайший реверс-инжерининг VB и VBA и в связи с этим пишу курс статей «VB Internals» (могу дать ссылки, если интересует). В нём пока только 2 опубликованных параграфа, и несколько почти готовых.
А вот эта тема по преобразованию текстового представления кода в PCR-дерево, построения BSCR-цепочек по PCR-дереву, реконструкции BSCR обратно в текст для отрисовки и других целей, которая тоже проходит через стадию дерева, но совершенно другого, ничего общего с PCR не имеющего — это где-то параграф сотый наверное исходя из плана тем для цикла статей.
А генерация P-кода JIT-компилятором, оптимизация, анатомия виртуальной машины и отладчика, генерация x86-кода — это вообще ближе к парагрфу номер 200.
Была мысль эти статьи продублировать и сюда, но к сотому параграфу надо же как-то подобраться с самых азов, пусть не через 99 промежуточных статей, но хотя бы через 10.
С другой стороны, я конечно могу именно про эту уникальную особенность VB/VBA сделать статью, дескать, а вы и не знали героя в своём отечестве, что существует такая IDE, которая начинает компилировать код уже в момент его ввода в редактор, и которая настолько lazy/just-in-time/on-demand/инкрементально-компилирующая, что иной раз при нажатии кнопки Compile вообще ничего внутри не происходит — все резултаты компиляции уже готовы.
Но писать такую статью надо с нуля. Надо тщательно работать над размером статьи. Надо учитывать, что в аудитории будут люди, которые не знакомы с VB/VBA. Надо учитывать, что будут люди, предвзято относящиеся к этим продуктом. Надо рисовать много картинок, поясняющих схемок, анимационных GIF-ок.
В общем-то, я не против, но это отдельная работа.
А этот длиннокоммент я просто написал на одном дыхании, даже не перечитывая его. Там и опечатки, и какие-нибудь дублирование одного и того же могут встречаться, и какие-нибудь логические ошибки.