понедельник, 27 января 2014 г.

§7. О терпении

"...главное качество, впоследствии очень пригодившееся в моей карьере: терпение. Это абсолютно серьезно. Терпение обычно сильно недооценивается. Занимаясь всеми этими проектами с третьего класса по восьмой, я постепенно изучал одну область за другой и собирал все свои электрические устройства, практически не заглядывая ни в какие книги. Иногда я думаю: мне дико повезло. Судя по всему, судьба указала мне самый верный путь, и я пошел по нему. Я научился особо не беспокоиться по поводу конечного результата и сосредоточиваться именно на том этапе, на котором я в данный момент находился, и стараться сделать свою работу безупречно.
Не каждый сегодняшний специалист понимает это. На протяжении всей моей карьеры в Apple и в других компаниях мне всегда попадались множество чудиков, пытавшихся перескочить на следующий уровень знаний, при этом не усвоив как следует предыдущие. Но так ничего не получится. Так не бывает. Таковы законы когнитивного развития, тут все просто. Невозможно научить кого-то чему-то, перескакивая через этапы. И понимание этого очень помогало мне в обучении моих собственных детей, а также пятиклассников, с которыми я занимался уже потом. Я всегда повторял им, будто мантру: шаг за шагом. Всему свое время..."

Стив Возняк, Джина Смит Стив Джобс и я: подлинная история Apple


Цитата эта особенно актуальная для программистов, начинающих свою карьеру в LabVIEW - "невозможно научиться чему-либо, перескакивая через этапы". Простота LabVIEW довольно-таки иллюзорная. На самом деле это полноценный язык программирования, в котором всё подчиняется законам логики. К сожалению, многие начинающие программисты об этом забывают и сразу пытаются реализовать сложные системы без понимания основ программирования.

пятница, 24 мая 2013 г.

§ 6. О LabVIEW программистах и их проблемах

Однажды начинающий программист пришёл к Учителю.
"Я написал гениальную программу, — сказал юноша, — но вот не работает она". 
Вы не могли бы посмотреть, в чём моя проблема?
Учитель посмотрел на исходный код и призадумался...

    Просматривая форумы по LabVIEW я всё больше замечаю некую профессиональную деградацию программистов. Вообще программирование в LabVIEW — оно чем-то похоже на магию. Вы соединяете квадратики друг с другом, и чудесным образом программа начинает получать данные, производить некие измерения, выводить графики на экран... Проблемы начинаются тогда, когда что-то перестаёт работать. Начинают раздаваться крики - Виндовс глючит! Лабвью глючит! Вчера всё работало, а сегодня - сломалось! В таком случае мне всегда хочется сказать, печально глядя на нынешнее поколение: компьютер — он ведь тупой до безобразия, там только биты да байты, он делает ровно столько, сколько от него требуют (я не буду брать в расчёт ошибки аппаратные - они редки, да и проявляют себя как правило совсем иначе). Конечно, техника усложняется, и сегодня прикладному программисту, разрабатывающему приложения на высокоуровневом языке, нет особого смысла наизусть знать — сколько регистров у процессора, особенности работы кэш памяти, и так далее. Однако иметь общее представление об архитектуре компьютера, особенностях операционной системы, принципах функционирования приложений и взимодействия их с операционной системой, иметь представление о работе с памятью, программист просто обязан. В этом смысле прежде чем начинать работать на высокоуровневом функциональном языке имеет смысл научиться программировать на языке классическом. Лучше всего начать с Паскаля, затем изучить Си (что будет совсем не лишним для LabVIEW - хотя бы для уверенного использования Formula Node) и лишь затем переходить на следующий уровень. Кроме того имеет смысл воспитать в себе некую "пытливость ума" - ведь у любой проблемы, будь то не найденный файл, или падение программы, всегда есть причина. Сам процесс поиска проблем тоже может оказаться увлекательным путешествием в мир логики. И разобравшись, вы обнаружите, что нет никакой магии, а есть только биты и байты.

— "...Так в чём же моя проблема, Учитель?" — спросил программист?
— "Я полагаю, твоя проблема в хромосомах" — ответил старик.

понедельник, 28 июня 2010 г.

§ 5. О вызовах DLL из LabVIEW

LabVIEW — довольно высокоуровневая среда разработки. Работая с LabVIEW, мы не задумываемся о колоссальной работе компилятора, остающейся "за кадром". Мы просто соединяем  элементы друг с другом, совершенно не заботясь о том, как резервируются области памяти, как предаются параметры, что происходит в регистрах, и так далее. Кто сегодня может сказать, сколько регистров у процессора? Что происходит со стеком, как располагаются структуры в памяти? Однако рано или поздно любого программиста настигают ситуации, когда подобное знание необходимо. Одна из таких ситуаций — вызов DLL из кода LabVIEW. Когда требуется подключать сторонние DLL? Существует несколько типичных ситуаций:
- производитель какого-либо устройства предоставил вам DLL для коммуникации
- требуется использовать WinAPI, например для работы с окнами
- ускорение критичных участков (LabVIEW довольно медленна сама по себе, как правило переписывание части кода на Си позволяет ускорить выполнение в несколько раз)

Для упражнений нам помимо LabVIEW понадобится какой - либо компилятор, позволяющий скомпилировать код в DLL. Я пользуюсь компилятором CVI, также подойдёт VisualStudio (бесплатная Express в том числе). Я не буду описывать процесс создания DLL и пошаговое руководство по подключению DLL к LabVIEW, об этом вы можете прочитать на сайте NI, например в статье Using Existing C Code or a DLL in LabVIEW.

Итак, начнём. Прежде всего надо заметить, что существуют два типа связи сторонних DLL с LabVIEW - статическая линковка и динамический вызов. В первом случае вы явно указываете имя и путь DLL в настройках. Во втором случае вы указываете путь к DLL на блок-диаграмме:



В основном используется статическая связь. Однако вы не сможете запустить VI, если требуемая библиотека будет отсутствовать. В подобных случаях можно подгружать библиотеки динамически, при этом на загрузку библиотеки и поиск соответствующих функций потребуется некоторое время — вот маленькое исследование на эту тему Call Library Function Node Calling Optimization.

 А вот с чем имеет смысл досконально разобраться, так это с соглашениями о вызовах. Вы, вероятно уже заметили, что при подключении внешнего кода требуется установить флажок "Calling conventions":



А чем, собственно, отличаются "stdcall (WINAPI)" и "С"? Существует несколько соглашений о вызовах (stdcall, cdecl, thiscall, fastcall). Нас сейчас интересуют первые два (thiscall используется в С++, а fastcall — довольно редко). Наиболее часто используется cdecl (этому соглашению соответствует опция C), а stdcall в основном используется фирмой Микрософт как основное соглашение для вызовов WinAPI. Как правило по умолчанию в компиляторах включён режим cdecl.

Давайте сделаем небольшую тестовую DLL, в которой исследуем то, каким образом передаются и возвращаются параметры, а также изучим различные соглашения о вызовах. Мы можем смело скомбинировать оба соглашения в рамках одной DLL, для этого напишем следующий код:

Параметры в DLL можно передавать по значению или по ссылке. В вышеприведённом примере параметр In передаётся по значению, а параметр Out передаётся по ссылке (то есть передаётся не значение переменной, а адрес, по которому она находится). Подключение библиотеки выполняется следующим образом:

Обратите внимание на строку, где показывается прототип вызываемой функции — используйте эту строку для контроля правильности описания параметров:

Что же происходит "за кадром"? Давайте заглянем внутрь наших функций:

Вот как выглядит функция, вызываемая как stdcall:


А вот как выглядит функция, декларированная как cdecl:


Итак, как мы видим, происходит следующее:
Параметры передаются через стек. Регистр esp - это и есть указатель на стек. Через стек передаются два аргумента. Параметр In смещён на четыре байта и его значение переносится из стека в регистр eax. Использование именно eax не случайно - это именно тот регистр, через который осуществляется возврат значения функцией (return In). Второй параметр Out смещён на восемь байт и его значение (при этом мы помним, что передали адрес) заносится в регистр edx. Третьей командой мы записываем значение eax по адресу, находящемуся в edx, затем возвращаемся в исходную программу. Вот здесь мы и видим отличие двух соглашений о вызовах. При вызове cdecl параметры заносятся в стек в обратном порядке, при этом указатель стека не изменяется (кстати, это даёт возможность вызывать функции с переменным количеством параметров, но не в LabVIEW). При вызове stdcall параметры передаются в прямом порядке, так как они объявлены в декларации, при этом указатель стека смещается на необходимое количество байт. В случае stdcall команда retn 8 выполняет коррекцию стека на 8 байт при выходе, а при использовании cdecl коррекция стека в теле функции не требуется. Что произойдёт в случае, если мы перепутаем соглашения? Мы получим неверный указатель стека при завешении функции, что в любом случае приведёт к проблемам. Дело даже не в возможной утечке памяти в стеке, а ещё и в том, что в стеке находятся другие данные, и неверный указатель практически моментально (или через некоторое время) приведёт к падению программы. Об этом также можно почитать в разделе помощи: Configuring the Call Library Function Node. И вот здесь мы натыкаемся на прелюбопытнейший эффект: программа продолжает работать как ни в чём не бывало даже в том случае, если мы неверно указали тип вызова! Оказывается, LabVIEW автоматически корректирует указатель стека даже в случае нашей ошибки. После нескольких экспериментов легко выяснить, что это происходит при установке флага Error Checking Level в Default (именно это значение и стоит по умолчанию):


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

Что произойдет в случае выбора других опций? Это легко выяснить:
Disabled - приводит к немедленному падению LabVIEW, что и следовало ожидать
Maximum - приводит к ошибке 1517:


Сообщение об ошибке в случае неверного типа функции  документировано, об этом можно почитать в статье Call Library Function Dialog Box. Таким образом следует обращать внимание на правильность указания соглашения о вызове. В случае неверного указания типа ошибку можно заметить не сразу, а например при изменении опции контроля ошибок.

Ещё один вопрос, на который мы можем ответить — что произойдёт в том случае, если мы укажем, например, то, что фукнция возвращает значение, хотя на самом деле тип функции void, либо наоборот. В данном случае фатальных последствий не будет, так как возвращаемое значение просто записывается в регистр eax, так что в одном случае мы просто получим значение этого регистра, а в другом случае просто проигнорируем его. Утечек памяти и исключений не возникнет (пытливый читатель может разобраться с тем, что происходит при возврате восьмибайтовых типов или строки самостоятельно).

Ещё одна из самых типичных ошибок — это неверное указание типа передаваемых параметров. В вышеприведённом примере наша функция имеет два параметра. Один из них (тот, который Out) передаётся по ссылке, и мы должны указать, что передаётся адрес переменной. Давайте намеренно совершим ошибку и передадим нулевое значение вместо адреса:

Запись по нулевому адресу немедленно вызывает исключение, а вот внешнее проявление опять-таки зависит от опции Error Checking. В случае Maximum или Default результатом будет ошибка 1097:


В случае, если мы выключим обработку ошибок вообще, то LabVIEW "упадёт". Таким образом, включение обработчика ошибок приводит также и к включению внутреннего обработчика исключений (примерно также, как это делается в C++ секциями try... catch).

Побаловавшись с простыми типами, можно перейти к массивам. В простейшем случае мы просто передаём в функцию указатель на массив. Давайте попробуем сделать простое поэлементное копирование массивов:

Здесь важно понимать то, что функция понятия не имеет о размере передаваемых массивов, так что количество копируемых элементов надо явно указать как параметр и передавать извне. Кроме того надо обратить внимание на то, что память под массив надо резервировать снаружи, в LabVIEW:


Типичная ошибка, которую можно совершить — выход за пределы массива. Как правило это приводит к исключению, либо нарушению работоспособности программы. Здесь снова может помочь включение режима отлова ошибок на максимум - в этом случае LabVIEW сообщить вам то, что вы выскочили за пределы массива (впрочем это не всегда спасает её от падения). Возникает логичный вопрос — а нельзя ли передать одновременно с массивом и количество элементов, а также зарезервировать память внутри DLL? Это сделать можно, но перед рассмотрением этого вопроса имеет смысл поэкспериментировать с простыми структурами, так как у LabVIEW есть одна небольшая тонкость.

Сделаем следующий пример:

Структурам в Си отвечает кластер в LabVIEW, но типичнейшая ошибка, которую как правило совершает начинающий программист, выглядит примерно вот так:




В лучшем случае результат далёк от ожидаемого, а в худшем код вызывает исключение.
Как же правильно передать структуру (читай - кластер) в DLL и обратно? Для этого нужно разобраться с тем, как хранятся данные в кластерах LabVIEW и в структурах Си. Основной документ, с которого имеет смысл начать — How LabVIEW Stores Data in Memory.

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

Прежде всего надо заметить, что массивы, строки и пути не хранятся непосредственно в кластере LabVIEW. Вместо них хранятся адреса (причём не на непосредственно данные, а на заголовки). Таким образом в приведённом примере передача массива или строки в составе кластера смысла не имеет. Поскольку массив в нашем примере хранится непосредственно в структуре, то и передавать его надо поэлементно. Здесь нас подстерегает ещё одна опасность. Каков размер структуры в Си (тот, что возвращает sizeof(TD_MyStruct))? Если вы думаете, что 13 байт (int + char + 2 x int = 4 + 1 + 2 x 4), то вы ошибётесь. Дело в том, что между членами char и int "вставлено" три дополнительных байта, которых вы не видите. Это сделано для исключения пенальти при обращении по адресам, невыровненным на границу 32х бит. Примеры выравнивания в структурах можно посмотреть в msdn: Structure Alignment Examples. По умолчанию установлено выравнивание на границу 4 байт, так что истинный размер структуры в нашем примере не 13, а 16 байт. А вот в LabVIEW элементы кластеров хранятся без выравнивания. Чтобы решить проблему выравнивания, существуют два пути - либо вставить недостающие "padding" элементы в кластер (это может быть использовано в том случае, если DLL не может быть перекомпилирована):



либо явно указать компилятору что наша структура не требует выравнивания (это достигается при помощи #pragma pack(1) непосредственно перед объявлением структуры). Обратите внимание, что #pragma pack(1) будет распространяться на все структуры до момента пока не встретится следующая #pragma pack.

В заключение - маленький совет. Поскольку порядок следования элементов в кластере весьма важен, то для того, чтобы не проверять каждый раз порядок через меню "Reorder Elements in Cluster" просто установите опцию Arrange Vertically (доступно в контекстном меню) — в этом случае расположение элементов в кластере всегда будет отражать реальный порядок следования.

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

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

вторник, 2 февраля 2010 г.

§ 4. LabVIEW против С или гонка за лидером

Здравствуйте, коллеги!

Сегодня ещё несколько слов о быстродействии LabVIEW программ. В предыдущем параграфе было показано то, что генерируемый LabVIEW код довольно "рыхлый". Как это отражается на быстродействии и есть ли резервы? Давайте разберёмся.

В качестве простенького примера займёмся вычислением центра масс восьмибитной картинки. (если вы забыли, что такое центр масс или центроид, то загляните в Википедию: Centroid, Center of Mass) Для теста возьмём картинку Лены в четверть мегапиксела. В качестве референсного примера я воспользуюсь IMAQ Centroid из библиотеки IMAQ Vision:


Этот пример даст мне значение центра масс в точке (265.19, 247.43):



Классическая схема вычисления примерно такая:

Заметьте, что использование типа I64 в данном случае более чем оправдано, иначе возникнет переполнение.

Сколько времени займёт вычисление? Приготовим простенький бенчмарк:



На моём компьютере получается 8 миллисекунд. Неплохо.

Пример хорош тем, что даёт богатую пищу для размышлений. Что произойдёт, если перейти от целочисленого типа к DBL? Давайте проверим:



Получилось 5.5 миллисекунд. Уже лучше.

Немного потеряв в точности, можно перейти к типу SGL:


Теперь 3,75 миллисекунды.

Хороший программист сразу предложит вариант улучшения, позволяющий избавиться от вложенного цикла, например такой:



Теперь получилось 3,25 миллисекунды. Что ж, на этом, пожалуй, остановимся. Строго говоря, можно попытаться совсем избавиться от арифметики с плавающей точкой и перейти к целочисленному типу, оставаясь при этом в рамках 32 бит, но это вызовет значительное усложнение кода (а выигрыш будет невелик).

Давайте кардинально изменим подход и перепишем этот участок кода на чистом Си. К вопросу подойдём "в лоб", взяв за основу самый первый пример:


Это замечательное упражнение также помогает попрактиковаться в передаче массива и кластера в DLL:


Технически я пользуюсь средой CVI, на которую навешан Интеловский компилятор.

Что же получается?



0,84 миллисекунды! Наш код работает вчетверо быстрее аналогичного LabVIEW кода, что и требовалось доказать. (Пытливый читатель может сравнить ассемблерные листинги обоих примеров).

Достигли ли мы предела производительности? Конечно нет, ведь можно использовать факт наличия двух или более ядер (наш код пока что однопоточный), или использовать SIMD комманды. Однако несмотря на очевидный выигрыш, конечно, не стоит бросаться и тут же переписывать куски кода на Си. Отлаживать такой код на порядок сложнее, да и при современном быстродействии компьютеров как правило в этом нет нужды. Рассматривайте эту возможность как резерв, который всегда есть, и который можно и нужно использовать при необходимости. А «ранняя оптимизация» — она, как правило, вредна и порой приводит к неоправданному увеличению сложности. Сначала закончите и отладьте функциональность кода, затем определите «узкие места», попытайтесь оптимизировать LabVIEW код, и лишь потом используйте «тяжёлую артиллерию».

До следующих встреч,

Андрей.

CentroidLVvsC.zip (5MB)

четверг, 28 января 2010 г.

§ 3. Интерпретатор или компилятор?

Здравствуйте, коллеги!

Сегодня — несколько слов о внутреннем устройстве файлов LabVIEW, плюс парочка "хакерских" упражнений.

Много лет назад, только начиная работать с LabVIEW, я был абсолютно уверен в том, что LabVIEW — интерпретатор, в чём-то отчасти похожий на Basic. Блок-диаграмма наверняка представлена в виде некоего дерева, которое интерпретируется средой выполнения. В пользу этого также говорило наличие увесистой Run-Time Engine, необходимой для запуска "скомпилированного" приложения, возможность "подсветки" кода при выполнении, наличие файлов VI "как есть" внутри скомпилированного приложения, ну и малая скорость выполнения по сравнению с компиляторами типа С или Delphi (впрочем и сегодня оставляющая желать много лучшего). Однако моя уверенность значительно пошатнулась после прочтения любопытной статьи, в которой был продемонстрирован ассемблерный листинг простенького цикла:


Но даже увидев реальный код, я был уверен, что код этот — результат работы классного интерпретатора, выдернутый из памяти приложения. Я даже попытался сбросить память LabVIEW-программы в дамп и деассемблировать его, да ничего хорошего из этого не вышло.
Желающим повозиться самостоятельно скажу сразу, что пытаться деассемблировать скомпилированную LabVIEW программу "в лоб", равно как и отдельные SubVI смысла не имеет.
Однако как любому ребёнку хочется заглянуть внутрь любимой игрушки, так и мне никак не давал покоя вопрос — как же всё-таки устроен VI и где же код?

Что ж, давайте набросаем простенький код, например тот, который был приведён в статье выше:



и заглянем внутрь обычным hex-просмотрщиком (я использую обыкновенный Far):


среди двоичного мусора можно легко заметить осмысленные четырёхбуквенные теги: LVIN, VIDS, BDHP, FPHP, ICON, CONP и так далее. Нетрудно догадаться что BD и FP - отвечают за блок-диаграмму и переднюю панель соответственно. На самом деле структура файла VI практически идентична ресурсам Макинтоша (более подробно можно прочитать в Википедии: Resource fork). Оно и понятно — ведь самые первые версии LabVIEW выпускались исключительно для Mac, и хранить ресурсы было удобнее в формате самой операционной системы.

Уже морально приготовившись к написанию парсера, я наткнулся на роскошную функцию LabVIEW:REdLoadResFile, с помощью которой можно не только получить список ресурсов из VI, но и вытащить двоичные данные для каждого ресурса:

Кстати, возьмите на заметку — эта функция может вытащить ресурсы не только из *.vi, но и из *.ctl файлов; кроме того её можно вызывать как из LabVIEW, так и из Run-Time Engine.


Пользоваться этой функцией можно примерно так:



(Сниппет не выкладываю ввиду известных багов LabVIEW с кластерами и property node в сниппетах - ну да мы к багам в LabVIEW привычные)

А получится вот что:


Тут мы видим, что в нашем простеньком VI находится порядка трёх десятков ресурсов. Пытливый читатель может попытаться проанализировать двоичные данные самостоятельно, нас же сейчас интересует один единственный ресурс с тегом VICD. Это и есть скомпилированный код. Небольшая тонкость заключается в том, что код (как и некоторые другие ресурсы) упакован алгоритмом zip (сигнатура начала архива хорошо видна после чётвёртого байта).
Давайте извлечём и распакуем его:


ZLIB Inflate взят из OpenG. На вышеприведённой диаграмме код извлекается из VI и сохраняется в *.bin файле.
Ну вот, теперь похоже на некое подобие кода:


Дальше собственно дело техники - нам потребуется подходящий дизассемблер. Кто-то предпочитает HIEW, а мне нравится IDA, тем более что старенькая версия абсолютно бесплатна. Скачать можно вот отсюда: Free IDA Disassembler.

При открытии файла нас спросят про детали - отвечаем что файл двоичный:


Затем вопрос про режим деассемблирования:

Разумеется соглашаемся с предложением 32-битного режима.

Ещё маленькое напоминание о том, что точку входа найти не удалось и её придётся указать вручную:

Вот почти и всё. Когда файл откроется в IDA его надо промотать немного, скажем до адреса 20, и нажать клавишу "С" для запуска анализатора:


После этого получится вот что:


Между адресами 119...16A находится тело цикла. Сравнение и увеличение счётчика показаны на скриншоте. Напомню как выглядел исходный VI:

Нельзя сказать что сгенерированный код оптимален, но в принципе неплохо.

Давайте сделаем маленький эксперимент. Возьмём простенький VI с увеличением счётчика:


LabVIEW генерирует вот такой код:

Всё просто и логично.
Любопытно взглянуть что получится если перейти к типу I64:


А получится вот что:

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


Ну и наконец двойной инкремент:


LabVIEW накомпилирует следующую конструкцию:




Сравните этот листинг с листингом для одинарного инкремента I32. Хорошо видно, как компилятор работает что называется "в лоб", вставляя совершенно ненужные пересылки из памяти в регистры (три операции mov между инкрементами абсолютно не нужны).

Что можно извлечь из всего написанного?
Прежде всего — LabVIEW действтельно компилятор, причём компилятор совершенно фантастический — ведь генерация кода происходит практически "на лету". Вы нажимаете кнопку Run - и программа немедленно запускается. Процесс компиляции прозрачен и незаметен. Такого, пожалуй, нет ни у одного самого продвинутого компилятора. Мало того, что скомпилированный код также вставляется в исходные файлы (он сохраняется в VI вместе с блок-диаграммой), так он ещё и упаковывается при этом! При запуске приложения код распаковывается "на лету" средствами Run-Time Engine.
Важно также понимать, что сохранив VI без блок-диаграммы мы оставляем в файле только скомпилированный код, который превратить обратно в блок-диаграмму уже невозможно.
Сам по себе скомпилированный код нельзя назвать оптимальным, и именно поэтому в большинстве случаев аналогичный по функциональности участок диаграммы, переписанный на Cи и скомпилированный в DLL будет работать быстрее "нативного" LabVIEW кода.

В заключение - пара VI, использованных в статье:



пятница, 22 января 2010 г.

§ 2. Об автоматической обработке ошибок

В LabVIEW есть пара опций, которые обычно отключают (хотя они включены по умолчанию):

Суть автоматической обработки ошибок заключается в том, что при возникновении ошибки на выходе VI исполнение программы может быть остановлено (за диалог отвечает как раз вторая опция):


В вышеприведённом примере мы создаём новую папку, но такая папка уже существует, что и вызывает ошибку 10 (если у вас нет папки Windows на диске с:\ просто запустите этот пример дважды). Поскольку выход примитива CreateFolder не подсоединён, то возникающая ошибка вызывает прерывание выполнения программы.

В следующем примере это дилоговое окно не появится:


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

Однако на заключительном этапе имеет смысл включить эту опцию и вычистить участки кода,  вызывающие "оборванные" ошибки (ну или как минимум обратить внимание на такие места).

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

§ 1. Что отличает профессионала от любителя

Запомните простую вещь — профессионал никогда не напишет «Labview» или «LabView» или как-либо ещё.

Единственно верное написание — «LabVIEW». Только так и никак иначе.

Тем не менее, даже если вы пишете «LabVIEW» правильно, то это ещё не делает вас профессионалом.