На форуме «offtop.ru» есть известный пользователь под ником «Vizael». На праздниках он связался с нами и предложил прислать известные ему проблемы и решения для версии CA-Visual Objects 2.8 SP1. Публиковать ли своё имя – он ещё не решил, но выразил согласие, что один из авторов этого сайта разместит присланный материал. Что мы с удовольствием и делаем.
Как рассказал Vizael, информацию об ошибках КаВо (версий 2.8 и др.) они собирали всем коллективом. Возможно, если тема получит продолжение, они продолжат посылку материалов или выступят(ит) самостоятельно. А пока – первая часть материалов. Она касается только ошибок КаВо 2.8 SP1.
Ошибка № 40.
Если в функцию передается параметр, состоящий из вложенных функций, то результат будет непредсказуем.
Пример:
Function( Func( Func1( Func2 ) ) ) // Не работает!!!
Решение:
LOCAL c AS …
c := Func1( Func2( ) )
? Func( c ) // Так – работает :))
Ошибка № 41.
Некорректная работа с переменными типа WORD.
Пример:
LOCAL j := 1 AS WORD
LOCAL x AS WORD
x
:= j
* 11 // В отладчике выдает неправильную информацию
Решение: использовать переменные типа DWORD.
Ошибка № 42.
Проблема при блокировке .dbf-файла с помощью FLOCK().
После перемещения до конца файла (EoF), блокировка FLOCK() может самопроизвольно сбрасываться.
Решение: принудительно ставить RLOCK
() (блокировку записи) при повторном проходе по файлу.
Для серверов, если установлен режим (mode) компиляции ссOptimistic, а .dbf-файл заблокирован с помощью FLOCK() – при выполнении метода FIELDPUT() проверяется только блокировка записи (RLOCK), а запись не корректируется.
Решение: всегда ставить режим (mode) ссNone или явно блокировать RLOCK().
Ошибка № 43.
На класс инструмпентальной панели (ToolBar) не распространяется тип выравнивание контрола OA_PX_PY_PWIDTH_PHEIGHT.
Пример:
SELF:ToolBar:OwnerAlignment := OA_PX_PY_PWIDTH_PHEIGHT // Не работает :((
Решение: не использовать.
Ошибка № 45.
Иногда сложные логические выражения, содержащие множественные функции, приводят к неверной интерпретации и работе программы (нет вхождения в некоторые функции).
Решение: вынести эти функции из выражения, упрощая его и обрабатывать отдельно.
* * *
Как автору-публикатору, разрешите сделать несколько своих замечаний.
Об ошибке № 40.
В КаВо есть известные проблемы при работе с функциями и методами классов, если в качестве их аргументов выступают другие функции, методы или операции с динамическими переменными. Раньше (не знаю, как сейчас), это могло вызывать проблемы с высвобождением (очисткой) памяти. С тех пор, никогда не передаю в качестве аргументов какую-либо операцию с динамическими переменными. Естественно, код резко удлиняется, меня за это критикуют, но я это делаю, ибо всегда позволяет избежать ошибок.
Примеры:
Вместо ErrorBox{, cText}:Show()
Пишу:
oEB := ErrorBox{, cText}
oEB:Show()
Вместо Func( Func1( Func2( … ) ) )
Пишу:
xValue2 := Func2( … )
xValue1 := Func1( xValue2 )
xValue := Func( xValue1 )
Об ошибке № 41.
Случаев, когда в КаВо надо использовать именно переменные типа WORD – почти не осталось. Разве что, используются какие-то структуры. Дело в том, что WORD-переменные – это «рудимент» 16-ти разрядной среды (Win3.xx, Win95-98, WinME). В среде Windows 2000 и новее лучше всегда использовать DWORD-переменные. Да, они «кушают» больше памяти, но программа будет работать быстрее. В интернете можно найти статьи об этом. Поэтому, я уже давно не использую переменные типа WORD и данную проблему не встречал.
Об ошибке № 45.
Такую ошибку не встречал (или я её неправильно понял). Но, встречал ситуации, когда сложные (длинные) условия неверно отрабатывались в программе. И здесь всегда спасали скобки. Т.е., вроде бы как скобки в некоторых случаях не нужны, но лучше их ставить.
Пример:
WHILE .NOT. oServer:EoF .AND. условие1 .AND. условие2 … ит.д.
Будет лучше написать:
WHILE ( .NOT. oServer:EoF ) .AND. ( условие1 ) .AND. ( условие2 ) … ит.д.
Почему-то в таком случае КаВо работает лучше… Хотя, если условия короткие и чёткие – скобки не использую.
27.02.2013 г.
Наступило летнее затишье. Автор, начавший эту тему, написал, что сейчас сильно занят. И, чтобы заполнить образовавшийся промежуток, предлагаю своё продолжение темы.
Окна, отображение и закрытие.
Обычно, всю «начинку» окна я рисую в методе PostInit() или Init(). Иногда, при постепенной обработке данных для «рисования» выясняется, что возникла ошибка, из-за которой само окно уже рисовать нельзя. Как же прервать рисование окна?
Стандартно – это добавить две строки:
IF < ошибка >
// Если надо – выводим пользователям какое-то сообщение
SELF:EndWindow() // или EndDialog() – для диалогового окна
RETURN SELF // или NIL в PostInit
ENDIF
Отлично работает. Но, в КаВо 2.5, в этом случае, появляется некий «артефакт» (мелькающее окно). Его хорошо видно на медленных машинах. Это происходит потому, что окно начинает рисоваться (внимание: закрытие окна не приводит к обнулению объекта), потом программа начинает «понимать», что есть ошибка и по нашему алгоритму – его надо закрыть. И с удовольствием это выполняет.
Как я вышел из этой ситуации (пользователям мельтешение не нравится)? Почти у всех объектов в КаВо есть такой замечательный метод, как Destroy(). Т.е., вместо EndWindow (EndDialog) я поставил Destroy(). Что он делает – рассказывать не надо. Мельтешение исчезает!
Но, после этого в программе (обычно в больших программах) иногда начинают появляться странные ошибки, в частности и «любимая» 5333. Как же так? А тоже оказывается – очень просто: Destroy() объект-то «прикрывает», но криво. И когда начинает выполняться метод Show() данного окна – то КаВо в некоторых случаях что-то упорно пытается делать…
Чтобы этого избежать, я сделал свои классы типовых окон. Например:
CLASS msDataWindow INHERIT DATAWINDOW
METHOD Show( kShowState ) CLASS msDataWindow
IF .NOT. SELF:Handle() == NULL_PTR
SUPER:Show( kShowState )
ENDIF
// RETURN NIL // ДляКаВо25
RETURN SELF // Для КаВо28
Дело в том, что Destroy() всегда обнуляет указатель (handle) окна. А раз его нет, то и окна нет. Остались ли эти проблемы в более новых версиях КаВо – мне было не интересно. Кто хочет – пусть проверяет. Естественно, в своих классах окон я ещё много чего «нарисовал». Так что, создание своих классов – очень полезное занятие.
Низкоуровневая работа с DBF-фалами – присвоение данных.
С самого начала КаВо – разработчики не прекращают свои попытки «усовершенствовать» свой механизм работы с базами данных (здесь я чуток коснусь работы с DBF). Изначально, разработчики честно пытались сделать прямую кальку Клипперовских функций для DBF. Надо признать, это им как-то удавалось. Параллельно, они делали свой класс DbServer. Это вещь более удобная, но работает заведомо медленнее, чем работа через функции. Поэтому, где нужна скорость – я работал, как в Клиппере, а где – удобство программирования – через DbServer.
Замечательно. В версии КаВо2.5 они стали совершенствовать «Клипперовские» функции, делая их более типизированными и быстрыми. Такие функции получили приставку «VO» (DbDelete() => VoDbDelete() и т.д.). В версии КаВо 2.7 они серьёзно переделали свой DbServer-класс, значительно его ускорив. При этом заявили, что функции они пока оставили, но работать через них – не быстрее, чем через класс и всякую работу через поля напрямую – КаВо отныне сводит к функциям. Т.е, всем пора начинать забывать эти функции о Клипперовский синтаксис (типа, работайте с помощью класса DbServer!).
Что получилось? Раньше самый быстрый код был подобен Клипперовскому:
Select( < что надо > ) // или VoDbSelect
_Field->( < такому-то > ) := < требуемое значение >
и т.д.
И фактически он свёлся к такому:
Select( < что надо > ) // или VoDbSelect
FieldPut( < такому-то >, < требуемое значение > ) // или VoDbFieldPut
и т.д.
Испытания показали, что так оно и оказалось. Ну и ладно. Главное, что программа понимала и новый и старый синтаксис и никаких проблем это не вызывало. До появления КаВо2.8 (SP3?).
В версии КаВо2.8 SP3 в помощи по-прежнему можно увидеть, что при присвоении данных полям возможно использование макроподстановок:
cName := «Field1»
_Field->&cName := 1
Но, реально, это не работает… Ошибка не возникает, но полю реально ничего не присваивается. Когда это появилось и будет ли исправлено – я не знаю. Выход один – использовать функцию:
FieldPut( < такому-то >, < требуемое значение > ) // или VoDbFieldPut
Только так…
Комментарии
Всем привет. Понравился пост,
Всем привет. Понравился пост, ставлю 5 баллов.
Your mustang-soft.com - great resource
A bug or not a bug? That is the question...
Всем привет!
Как-то по ошибке вставил DTOC вместо NTRIM в следующую конструкцию:
CToD("01.01."+NTrim(Year(Today())))
CToD("01.01."+dtoc(Year(Today())))
Соответственно, если в первом варианте я получал дату начала текущего года,
то во втором на выходе имел NULL_DATE. Особенность ситуации в том, что в обоих случаях
компилятор не выдавал ни предупреждений, ни ошибок. Мы обсудили это дел с уважаемым
Карандашом и пришли к выводу, что коль основой типа данных "дата" в VO является unsigned
int, то в целом такая конструкция могла бы работать. Но если рассматривать этот вопрос
с точки зрения проверки компилятором типа передаваемых данных, то это баг, т.к. для
DTOC'a тип данных один - это DATE, а фактически передавался DWORD. Соответственно, если
логика дальнейшей работы функции/метода/приложения основывалась на этом значении, то очевидно была бы ошибка.
Так что в этом смысле это баг.
Кстати, если вместо DTOC вставить LTOC, то компилятор ошибку выдаст.
Вероятно, в случае с DTOC'ом неверно определяется тип передаваемых данных.