Pull to refresh
37
0.9

Пользователь

Send message
Windows: Structured Exception Handling. Очень вкусно именно в этом плане, но
1) Дороговато

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 сами.

А в VB иначе работало все же, там интерпретатор позволял выходить так из любой (или почти любой) задницы "вполне законно".

Я вас умоляю. Читайте вот этот коммент до просветления:
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, который все дефекты выявит, и пользователь получит классный продукт.


Это недостижимая утопия. Те же ОС тестируют в хвост и в гриву, а критические уязвимости и ошибки всё равно появляются.


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


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

Есть IIf, что приближённо соответствует тернарному ?: в Си. С этой проверкой, как я уже написал, вы застрахуете себя от отрицательных координат, но если автору контрола вздумается, что минимальным размером контрола должно быть 40×80, а не 0×0, ваша страховка внезапно потеряет силу. Но даже если не вздумается, вы не застрахуете себя от Out of memory или Out of stack space. Реально невозможность подвинуть контрол должна быть поводом аварийного завершения программы и потери всех данных? Кроме того, реализация контрола всё равно будет проверять входные данные.


и ошибка в самой попытке присваивания,

Как понять «в самой попытке присвоения»? Присвоение такому свойству значения это вызов обычной процедуры вида


HRESULT Xxxxxxx::put_Width(INT NewVal)
{
   ...
}

Это не опция.


Во-первых, контрол писали не вы. А другая фирма из другой страны, очень даже может быть. Контрол вам дали в форме .OCX-файла.


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


Это нужно будет менять CLSID-контрола, потому что в идеальном случае CLSID должен быть неким хешем идентичности класса и его поведения.


В третьих, может быть вообще и переписывать нечего и нет никого неадекватного. Контрол не может принять новый размер, потому что при попытке это сделать не хватило ресурсов. Вот он и честно сгенерировал ошибку. Что предложите в этом случае? Не генерировать ошибку, если HeapReAlloc сбойнул, а делать вид, что всё окей?

Использовать вместо него on error goto <номер строки> и обрабатывать по сути.

Не «номер строки», а метка (label). Просто номер строки является частным случаем метки, оставленным из соображений совместимости.


И, что характерно, обработчик ошибки, к которому перейдёт выполнение, имеет возможно сделать [Resume], [Resume Next] или ничего из этого.


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


Диалоге в стиле «Не удалось выполнить <....> Повторить / Пропустить / Отмена» реализуются в буквальном смысле в виде пары строк.


Предлагаю показать реализацию выполнения какой-то потенциально сбойной операции с показом диалога Повторить/Пропустить/Отмена при использовании try...catch-подхода. Как минимум, у вас будет ещё и цикл.

Типичный пример использования On Error Resume Next — это начинка обработчика события Resize формы, которое долго перемасштабировать начинку окна.


Например, у вас окно, всё пространство которого должен занимать TextBox, за исключением рамки в, скажем, 5 пикселей.


И вы пишите что-то вроде


   TB.Height = Me.ScaleHeight - Padding*2
   TB.Width = Me.ScaleHeight - Padding*2

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


On Error Resume Next «улаживал» ситуацию. В случае try...catch-подхода пришлось бы каждую строку обрамить в try с пустым catch. Или вы можете сначала вычислять ширину/высоту, проверять, меньше ли она нуля, и если меньше, то не присваивать полунный результат свойству Height/Width, или же присваивать, но «клэмпнув» до нуля. Поздравляю, вы делаете двойную работу дважды: и кода у вас будет больше, и всё равно внутри set-хендлеров свойств Height/Width выше значение проверяет на <0, чтобы, в случае чего выкинуть исключение.


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


На самом деле, примеров может быть очень много. В GUI-программах очень много какого кода, если в нём происходит ошибка, может продолжать работать дальше. Во всяком случае, это лучше, чем если приложение молча вылетит.


Скажем, у вас какой-то многошаговый процесс, в ходе которого должен проиграться «бымц» и показаться сообщение. Мало ли, что может пойти не так: пусть лучше мы молча проглотим тот факт, что не получилось проигрять «бымц» или показать красивую плашку, чем программа рухнет из-за невозможности претворить в жизнь какую-нибудь свистиелку.


Прошу сейчас не передёргивать и не говорить, что при таком подходе может нарушиться бизнес-логика и пострадать данные. On Error Resume Next никогда не был дефолтным поведением. Это то, что программист сознательно выбирал или не выбирал для использования.


В GUI-приложениях, где много какого кода отвечает за обновление UI, за визуальные знаки, всплывающие подсказки, звуки, перекраивание UI — очень много случаев, когда ошибка не фатальна, и пусть лучше в приложении контролы не отмасштабируются, чем оно упадёт.


Если же вы предлагаете в случае ошибки выходить из процедуры, то, ещё раз напомнив, что VB не мешает вам поступать именно так, предложу такой пример: в окне формы 10 контроллов, которые надо масштабировать. Контроллы писал неизвестно кто, и каждый при попытки изменить его размеры может взбрыкнуть.


У вас ест процедура с примерно таким кодом:


ToolBar.Width = NewWidth
LogPane.Width = NewWidth
MainPanel.Width = NewWidth
SecondarySlider.Width = NewWidth
...
StatusBar.Width = NewWidth

Итак, любой контрол может взбрыкнуть. Хотя бы, например, потому, что при изменеии размера контрол может пытаться переведить память под какой-нибудь backbuffer, и памяти внезапно может не оказаться.


Так вот, что вы выбираете?
1) Из-за облома в одном контроле (возможно облом был единократным), вся программа вылетает
2) Из-за того, что один контрол взбрыкнул, при изменении размеров окна все контролы перестают менять свои размеры, потому что кто-то в коде написал On Error Goto exit_sub
3) Только один косячный контрол перестаёт реагировать на изменение размеров окна, а остальные 9 ведут себя адекватно.


Какой подход больше всего обрадовал бы пользователя? Обеспечивал бы сохранность данные? А какой взбесил бы больше всего?

Эм, не согласен. В PHP @ значит не показывать ошибку, но не значит "пропустить строку в случае ошибки".

Если уж на то пошло, то и в 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 лет назад детали делали! Сейчас все делают из смеси алюминиевого порошка с перхотью индейцев. Это сейчас самое модное направление. К тому же оно позволяет экономить время разработки и деньги в продакшене. Мне евангелист индейской перхоти вчера так сказал.

Базовых классов может быть несколько.


struct PERSON_DATA
{
    int Age;
    int Weight;
};

struct LIST_ELM
{
     void* Prev;
     void* Next;
};

struct PERSON_ELEMENT : LIST_ELM, PERSON_DATA {};

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-ок.


В общем-то, я не против, но это отдельная работа.
А этот длиннокоммент я просто написал на одном дыхании, даже не перечитывая его. Там и опечатки, и какие-нибудь дублирование одного и того же могут встречаться, и какие-нибудь логические ошибки.

Не планировал писать статью, собирался написать рядовой комментарий.
Получилось то, что получилось.

Я как-то замерял время между тактами (вот не помню, для какого варианта синхронизации), так вот, оно плавает, хотя нагрузка, вообще говоря, нулевая.

Зачем вам одинаковость времени между тиками для обеспечения плавности и безрывковости анимации?


Замеряйте на каждом игровом тике DeltaTime от предыдущего игрового тика и умножайте DeltaTime на Speed, чтобы вычислить перемещение объекта на экране.


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


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

Интересно, что на ресурсе rosettacode
на языке Viual Basic всего 113 решённых задач
на языке Viual Basic Net 393 решений задач

Что это означает или какой вывод из этого должен быть сделан?


Я просто впервые слышу про Rosetta Code и не знаю, что количество решённых задач должно означать.


Картинка ваша не грузится, кстати.

Теперь что касается самой статьи.


Судя по всему, не только автор портируемой программы был новичком в VB, раз не знал даже о конструкции Select Case, но и вы его очень поверхностно знаете и может быть второй раз в жизни видите.


При этом вы делаете много спорных, дискредитирующих VB или просто неверных утверждений. Понятно, что по сравнению с каким-нибудь новомодным Python-ом, упоминания которого лезут из каждой дырки — из ваканский, из job-offer-ов, из бесконечной рекламы курсов по Python-у, на 2021 год язык VB можно назвать «мёртвым», а мёртвые сраму не имут, как гласит известная поговорка. Но мы на техническом ресурсе, а вы пишите статью, а не простой комментарий, и делать неточные и неверные заявления непозволительно в статье, не делая хотя бы пометку, что язык, с которого вы портировали, вы знаете плохо и мало. Или вы считаете, что вряд ли кто-то в 2021 году посмотрит в сторону этого продукта, и не играет роли, в каком свете вы его выставите? Я и 20 лет видел такие нападки на VB: благодаря им инстумент приобрёл репутацию недоязыка для зелёных программистов и несерьёзных проектов, при этом в большинстве холиваров большинство доводов против было просто мифами или следствием чьего-то незнания. Но с одной стороны холивара на защите VB стояли действительно зелёные новички, которым нечего было противопоставить оппонента в силу своей малообразованности — они и VB-то сами знали едва-едва, не говоря уже о полном отсутствии знаний других языков, низкоуровневого понимания работы всех этих вещей. С другой стороны были зачастую технически грамотные и опытные люди, но грамотные во всём чём угодно, кроме VB, о котором они могли судить и заявлять только по где-то услышанным чужим заявлениям, зачастую совершенно неверным.


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


Теперь непосредственно по поводу изложенных в статье мыслей.


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

Очень странное заявление! Примерно как заявление, что «все люди на Земле способны рожать детей, исключение составляют лишь мужчины».


Вообще-то все функции, процедуры, переменные и константы (то, о чём вы пишите) должны, в соответствии с правилами хорошего тона, быть объявлены либо с ключевым словом Public, либо с ключевым словом Private. Как можно назвать ключевое слово «Private» всего лишь каким-то там исключением, если это, блин, одна из двух доступных опций, и сущность будет соответственно, либо видимой из других модулей, либо невидимой? Вообще-то, кстати, не из двух, потому что есть ещё ключевое слово «Friend» и тогда сущность объектного модуля будет видна из своего проекта, но не видна из чужих проектов.


Из соображений совместимости с кодом, переносимым из QBasic, где никаких ключевых слов Private и Public не было. Там переменные уровня модуля объявлялись так же, как локальные переменные в процедурах: с помощью ключевого слова Dim, но могли быть объявлены глобальные переменные уровня модуля — с помощью ключевого слова Global. То же самое касается объявления процедур без указания ключевых слов Private/Public/Friend. Так вот из соображений переносимости кода, возможность объявлять таким образом переменные и процедуры была оставлена в VB.


Это не значит, что ей нужно пользоваться. Но, тем не менее, переменные уровня модуля, объявленные с ключевым словом Global, как ни странно, видны из других модулей. А переменные уровня модуля, объявленные через ключевое слово Dim, вопреки вашим словам, не видны из других модулей. О каком единственном лишь исключении с ключевым словом Private вы говорите?


Отбросим Friend, применимое только для объектных модулей, поговорим об обычных модулях.


Существует 4 способа объявить переменную уровня модуля в модуле:


Global foo As String  ' *
Dim foo As String
Public foo As String ' *
Private foo As String

И ровно ровно половина даёт видимую извне модуля переменную, половина даёт невидимую.


Что касается процедур, то да, без указания Private/Public процедура будет по умолчанию видна из других модулей.


Из ваших слов просто складывается впечатление, что в VB совершенно нет концепции ограничения видимости и доступа к переменным и функциям, и только какое-то жалкое ключевое слово «Private» дали для сокрытия чего-то там. Между тем, есть Public/Friend/Private, и их очень желательно использовать во всех случаях; а у переменных, объявленных без указания видимости, зона видимости ограничена модулем — они не видны из других модулей.


Напротив, это про C/C++ можно сказать, что все переменные и функции, объявленные в «модуле» являются видимыми из всех остальных модулей, и лишь исключение в виде storage-class'а «static» у переменных и функций делает так, что в объектном модуле на выходе компилятора в таблцие символов сущность не будет присутствовать в виде «экспортируемой» наружу сущности, и значит из других модулей с переменной или с функцией не получится слинковаться, потому что линкер не найдёт соответствующую сущность. «private», «public», «protected» в C++ появился только для членов классов и типов, для просто «глобальных» переменных и обычных функций никакого инструментария для указания видимости нет (кроме «static»).


И не надо говорить, что сущность может считаться условно приватной, если в другом файле она не объявлена в заголовочном файле. Не говоря уже о том, что сами по себе заголовочные файлы — чистая условность — всё что в них содержится может быть вставлено и в сам top-level файл-исходник, публичность/приватность сущности должна предопределяться из того модуля, где она находится, а не из того места, где ей кому-то внезапно захотелось попользоваться. В Си (не Си++), я напомню, можно обратиться к функции, находящейся в другом модуле, вообще не упоминая в данном модуле (напрямую или через включение заголовочного файла) её прототип — за исключением не-cdecl-функций.


Отдельной проблемой стало то, что VisualBasic прямо позволяет именовать структуры и переменные одинаково, буква-в-букву:

Почему это стало проблемой? Да, VB позволяет.


Но и Си позволяет, потому что у struct-ов и union-ов одно пространство имён, а у переменных — другое, и они друг другу не мешают:


Показать код на Си
struct Controls
{
    short Up;
    short Down;
    short Left;
    short Right;
    short Jump;
    short AltJump;
    short Run;
    short AltRun;
    short Drop;
    short Start;
};

struct Controls Controls;

По какой-то причине не устраивает писать struct Controls в обозначении типа (я не представляю ни одной такой причины, кроме использования для портирования слепой автозамены, не учитывающей контекст, которая не сможет понять, где Controls надо заменить на Controls, а где на struct Controls)?


В С++ это тоже работает, но в отличие от Си, можно даже не писать struct в обозначении типа:


Показать два примера кода на C++

Так работает:


struct Controls
{
    short Up;
    short Down;
    short Left;
    short Right;
    short Jump;
    short AltJump;
    short Run;
    short AltRun;
    short Drop;
    short Start;
};

Controls Controls;

А некоторые компиляторы позволяют даже так:


struct Controls
{
    short Up;
    short Down;
    short Left;
    short Right;
    short Jump;
    short AltJump;
    short Run;
    short AltRun;
    short Drop;
    short Start;
};
typedef struct Controls Controls;
Controls Controls;

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


В VisualBasic, как ни странно, в функциях и процедурах, аргументы передаются по принципу ссылок: их можно изменить непосредственно из кода функции:

Почему «как ни странно»?


Как ни странно, в VB аргументы могут передаваться хоть по значению, хоть по ссылке — в зависимости от того, как программисту нужно:


Public Sub addValue(ByVal number As Integer) ' void addValue(short number);
Public Sub addValue(ByRef number As Integer) ' void addValue(short &number);

И хорошим тоном является указывать способ передаче аргумента явно.


Ещё одна особенность, которая заключается в том, что VisualBasic 6 и C++ по разному обрабатывают логические выражения:

Нет, особенность состоит в не в том, что VB6 по другому обрабабывает логические выражения, а в том, что в VB операторы And, Or, Not, Xor и другие являются побитовыми, и им соответствуют не &&, ||, ! и !=, а &, |, ~ и ^. И тогда всё встаёт на свои места: правый операнд тоже вычисляется всегда, даже тогда, когда из результата вычисления левого операнда ясен результат всего выражения.


Зато разница состоит в другом: в том, что в C++ true это не полная противоположность false, а единичка. Единичка отличается от нуля не всеми битами, а только младшим битом. Поэтому использование булевых значений вперемешку с числовыми с использованием побитовых операций может иметь непредвиденный эффект:


nObjects = 32
If nObjects And (2 = 2) Then ' это сработает, потому что 32 & 0xFFFF ==> 32

nObjects = 32;
if(nObjects & (2 == 2)) // это не сработает, потому что 32 & 1 ===> 0

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


Но это лечится оборачиванием в cpp-исходнике всех «логических выражений» (типа сравнений) функцией, которая true превращает в ~0. Этой же функцией следует заменить VB-шную псевдофункцию CBool (не знаю, была ли она в оригинальном коде).


Не понятно, почему ситуация с (num > 0) && (array[num - 1]) названа болью логических выражений. Это при обратном портировании с С++ на VB создавало бы проблемы при механистическом переводе, а при портировании с VB на C++ это не должно давать никаких проблем.


Ни для кого не секрет, что в VisualBasic полностью отсутствовало полноценное понятие классов, а реализация классов,

Для меня секрет, что такое полноценное понятие классов. Вам так хотелось очернить VB, что у вас ни «полностью отсутствует понятие классов», ни «отсутствует полноценное понятие классов», а «полностью отсутствует полноценное». Даже не знаю, как это назвать. Двойная абсолютизация?


Что такое «полноценные классы»? Очевидно, что в каждом языке понятие классов — своё. В каждом языке классы не умеют чего-то, что умеют классы в других языках, но зато умеют что-то, чего не умеют классы ни в одном другом языке (или в каком-то из других языков). В каких-то языках мы наблюдаем вообще ООП без классов.


Классы в VB изначально ограничены тем, что им требуется быть полноценными классами с позиции COM и OLE Automation. VB вообще целиком зиждется на технологии COM.


Во многих ЯП с классами отсутствует множественное наследование, а в C++ оно есть. Значит ли это, что во всех этих языках полностью нет полноценных классов? У спорткара отсутствует ковш, поэтому с точки зрения водителя бульдозера Феррарри — неполноценное автотранспортное средство, а с точки зрения владельца спорткара все бульдозеры неполноценны, ведь они даже до 100 км/ч не разгоняются.


Часть вещей, которые умеют классы в С++, очевидно, в VB недоступны. Но зато в VB есть ряд вещей, которых нет в C++:


  • Нет понятия интерфейсов как таковых, хотя в COM понятие интерфейса и класса — это два раздельных понятия. В самом VB каждый класс в то же время является интерфейсом, и нельзя сделать интерфейс, не являющийся при этом классом, но зато в VB можно импортировать интерфейсы, описанные в TLB (созданной на свет каким угодно инструментом). Для примера, в PHP тоже есть раздельное понятие класса и интерфейса. В C++ в качестве интерфейса предполагается использовать абстрактный класс, но отсюда возникает ряд проблем.


  • Предлагаю подумать над ситуацией, когда из разных источников происходят интерфейсы IFoo и IBar, имеющие совершенно разные смыслы и предназначения, и по счастливому стечению обстоятельств оба имеют метод Reset(). И нужно иметь класс CObject, который имплементирует оба интерфейса, и значит реализуют метод Reset для каждого из интерфейсов (при этом для каждого из интерфейсов метод должен делать совершенно своё). Например один интерфейс отвечает за возможность перечислить дочерние подэлементы родительского объекта (родительский объект для этого имплементирует интерфейс энумерации) и метод Reset просто сбрасывает курсор перечисления на начало списка. А второй интерфейс отвечает за какое-нибудь соединение с чем-нибудь или какой-то длинный процесс и просто сбрасывает или отменяет этот длинный процесс. На VB это абсолютно беспроблемная ситуация:


    Implements IFoo
    Implements IBar
    Private Sub IFoo_Reset()
    '   реализация IFoo::Reset
    End Sub
    Private Sub IBar_Reset()
    '   реализация IBar::Reset
    End Sub

    Предлагаю подумать, как это будет выглядеть на C++? И ведь, что самое интересное, на С++ проблема решится пародоксально легко, если в одном из интерфейсов переименовать Reset в ResetThisObject. Спрашивается: почему проектанты разных интерфейсов, которые могут не знать друг друга, и которым не обязательно быть знакомым с тем, кто собирается имплементировать интерфейс, должен договариваться друг с другом с той целью, чтобы не возникло конфликта имён? Но корень проблемы концептуальный: что наследование и поддержка интерфейса — это два принципиально разных явления, и попытка сэмулировать концепцию поддержки интерфейсов через наследование абстрактного класса это дырявая абстракция.


  • В VB у классов есть концепция членов по умолчанию: это могут быть и свойства и методы. В C++ это кое-как можно получить только путём перегрузки операторов. Назначение какого-нибудь метода как члена-по-умолчанию позволяет в VB поиметь класс, экземпляры которого будут «прикидываться» функциеями. Ссылки на экземпляры таких классов внезапно становятся эквивалентными указателям на функции в C/C++, только это типо- и значение- безопасные указатели. Наличие возможности делать параметрические свойства сама по себе интересная, но одновременно с наличием возможности делать какое-то свойство свойством по умолчанию позволяет делать объекты, прикидывающиеся массивами или контейнерами любого толка. Легко делаются объекты, ведущие себя как PHP-массивы (а там это key-value словари по своей сути, причём key это число или словарь, а value — что угодно). Наличие синтаксиса foo!bar позволяет сделать класс, экземпляры которого будут вести себя как объекты в безклассовом JS (когда-то JS был таким): такие объекты можно будет во время исполнения наделять любыми нужными свойствами. Более того, вкупе с классами, которые могут обёртывать функции и претворяться функции, объекты можно будет наделять не только произвольными свойствами, но и произвольными методами.


    Dim blnk as BlankObject
    blnk!SubObject = New BlankObject
    blnk!SubObject!DoJob = AnotherObject.FlexibleMethod
    foo = blnk!SubObject!DoJob("test", IO_FOO, True)

  • Список можно продолжать.



VisualBasic 6 не умеет ничего, кроме локалезависимых ANSI-кодировок и очень ограниченной поддержки UTF16.

Это дезинформация. Начнём с того, что в VB всё-таки присутствует строковый тип и строки являются гражданами первого класса. В С++ встроенного типа нет и для строк предполагается использовать указатели на массивы целочисленных значений. Оператор сравнения == не будет корректно сравнивать две строки (потому что он будет сравнивать два указателя), оператор сложения не будет склеивать две строки, потому что он будет складывать два указателя, что не разрешено.


На вашем месте мне в связи с этим следовало бы заявить, что С++ не умеет ничего: ни локаленезависимые ANSI-кодировки, ни UTF-16. Но я не любитель таких громких заявлений.


Так вот, в VB имеет встроенный тип String, и, для вас это будет сюрпризом, за этим типом стоят исключительно юникодные строки. Все строки хранятся в памяти в юникоде, манипуляции с ними происходят в юникоде. При вызове методов объектов, включая внешние объекты, реализованные не на VB, а на чём угодно (на том же C++, например), строки по прежнему передаются и принимаются в юникоде. Потому что таковы правил технологии COM и тамошнего типа BSTR.


Другое дело, что помимо самого языка есть ещё набор стандартных функций, часть из которых вынужденна взаимодействовать с системой. Например функция MsgBox взаимодействует с системой (вызывает WinAPI-функцию MessageBox), функция Kill, удаляющая файл, тоже должна взаимодействовать с системой. Проблема в том, что VB4—VB6 должен был работать на Windows 9x сам по себе, и VB-программы в скомпилированном виде, должны были работать на Windows 9x, и в этих самых 9x-системах юникод не поддерживался. Большинство W-версий WinAPI-функций не могли работать.


Поэтому реализация той части «встроенных функций» VB, которые вынужденны взаимодействовать с ОС: вывод сообщений, встроенные контролы, работа с файлами — чтобы это хоть как-то работало под 9x, во всех местах взаимодействия VB с API операционной системы, юникод пробразуется в однобайтовую кодировку, а при обратном движении — наоборот.


Текст, сохраняемый или читаемый в/из файлы встроенными средствами языка вынужденно сохраняется в однобайтовой кодировке, потому что если бы он сохранялся как есть в юникоде — его бы открыли под какой-нибудь Windows 95 или 98 и ужаснулись бы: блокнот не показал бы непонятно что. Да и большинство текстовых файлов, существующих на дисках в тот момент, были не юникодными, так что VB-программы (если бы они ожидали прочитать юникод), читали бы что попало.


Стандартная библиотека Си тоже имеет функции printf() и strlen(), расчитанные на однобайтовые кодировки. Повод ли это говорить, что Си не умеет ничего? Это лишь говорит об стандартной библиотеки, но не об ограниченности самого языка. В случае VB никто не мешал использовать библиотеки, чья объектная модель предоставляла бы все нужные возможности, и которые взаимодействовали бы взаимодействовали с системой используя юникодные версии WinAPI-функций. Например, использовать библиотеку FSO для работы с файловой системой. Никто не мешал напрямую использовать W-версии WinAPI функций и работать с юникодом.


Никто, в конце-концов, не мешал сохранить в файл строку в юникоде, обернув её просто в байтовый массив:


Dim b() As Byte
Open "test.txt" For Binary as #1
b = "Привет, я люблю юникод"
Put #1, , b
Close #1

В таком виде строка в файле будет сохранена в юникоде (UTF-16 UCS-2) без BOM-а.


По крайней мере, если сишные strlen(), strstr() и substr() уж точно не поддерживают юникод, то VB-шные Len(), InStr() и Mid$() полноценно юникодные.


Я решил использовать в игре UTF8, поскольку эта кодировка является универсальной и повсеместной. Большинство операционных систем используют именно её в своих файловых системах. Отличается лишь Windows, которая предпочитает использовать локалезависимые ANSI-кодировки и UTF16. Из-за чего, в функциях взаимодействия с Windows я применил прямое преобразование между UTF8 и UTF16, чтобы продолжать использовать UTF8 внутри игры, и UTF16 при обращении к функциям самой Windows.

UTF-8 — это жуткий костыль. Это худшая из возможных кодировок для работы со строками, потому что для определения длины строки придётся просканировать всю строку от начала до конца. Потому что для одной строки вместо 1 теперь появляется 2 показателя: длина строки в символах и размер данных строки в байтах. Для хранения и передачи через сеть она, конечно, весьма оптимальная, особенно с позиции какого-нибудь американца, у которого текст почти полностью состоит из символов, умещающиеся в нижние 128 кодовых точек, и изредка содержащие какие-нибудь экзотические символы: перерасход место в таком случае получается почти что никакой по сравнению с той же UTF-16 или, упаси господи, UTF-32.


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


Между тем, Windows NT с самого своего появления была юникодной изнутри. Ядро Windows NT использует исключительно юникод для хранения всех строк. User-mode библиотеки Windows NT тоже используют юникод. ANSI-версии WinAPI-функций только и делают, что конвертируют ANSI в Юникод и передают это нормальным полноценным юникодным реализациям.


Технология COM тоже постулирует, что для строк используются юникодные строки, хранящиеся в кодировке UCS-2 и имеющие префикс, хранящий длину строки, а не нуль-терминацию, что позволяет не пробегаться по всей строке для подсчёта ей длины и позволяет хранить внутри строки символы с кодом 0.


То же самое делает и VB: его тип String этот тот же самый COM-овский тип BSTR — юникодная строка, 2 байта на символ, длина хранится перед строкой.

Вот вторая часть того, что я первоначально хотел сюда написать одним постом:
https://habr.com/ru/post/582566/#comment_23589108


Пришлось вырезать этот кусок из-за лимита на длину коммента, а затем текст и вовсе был утрачен и восстановлен склеиванием из кусков, оставшихся на правах мусора в файле подкачки.

Information

Rating
1,953-rd
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