Spread the love

Лучшее в Delphi блогах за 2012 год

Все лучшие публикации по Delphi за 2012 год в одном месте. Радует, что качество публикаций растёт с каждым годом. 
Рад видеть среди новых авторов Александра Багеля (Rouse_), чьи советы и наработки опубликованные на форумах не раз мне помогали. Также, рад видеть новых авторов из Embarcadero (и особенно Сергея Рощина).
Отдельно рекомендую обратить внимание на: Перевод справочной системы Delphi (тем, кто не читает на английском) Серию постов Александра Багеля (изучаем отладчик) Разделы Инструменты и компоненты Статью “Разработка через тестирование” Материалы по Delphi Spring Все публикации и переводы Александра Алексеева

Подборки ссылок за предыдущие годы: Самые интересные публикации 2009 года Самое интересное в Delphi-блогах за 2010 год…

[[ This is a content summary only. Visit my website for full links, other content, and more! ]]

Читать на сайте автора.

Рисуем поверх TWinControl

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

Когда-то давным давно

http://alexander-bagel.blogspot.com/2013/03/draw-over-twincontrol.html

Нюансы использования Ribbon от DevExpress

Версия 1.0.3 

Однако ребята из Редмонда не перестают удивлять нас своими наработками в рамках пользовательского интерфейса. То у них модными были тулбары и плоские кнопки, то

http://alexander-bagel.blogspot.com/2013/02/ribbon-devexpress.html

Подводные камни при работе с исключениями в Delphi: Raise Exception, AcquireExceptionObject, исключения операционной системы

    Интересно, на сколько читатель уверен в своих знаниях подводных камней в работе с исключениями в Delphi.  Я решил подготовить три наиболее часто встречаемых ошибки в  рабочем проекте. Надо признаться, некоторые моменты меня удивили. В том числе и ответ на вопрос «в какой момент удаляются объекты исключений».
    Каждый может проверить свои познания подводных камней.  В листинге ниже приведены три метода Error#, каждый из которых ведет к определенной проблеме.
Пояснения я приведу вслед за кодом.

procedure  RaiseOSException;
var
res: integer;
zero: integer;
begin
zero := 0;
res := 5 div zero;
end;

function FormatException(E:Exception): Exception;
begin
if not (E is EMyException) then
result := EMyException.Create(E.Message)
else
begin
E.message := Format('Ошибка обработки исключения: %s', [E.Message]);
result := E;
endl
end;


procedure Error1;
begin
try
RaiseOSException;
except
on E: Exception do
begin
E.Message := Format('Заворачиваем в свой текст: %s', [E.Message]);
Raise;
end;
end;
end;

procedure Error2;
begin
try
raise EMyException.Create('Alarm!!111');
except
on E: Exception do
begin
Raise FormatException(E);
end;
end;
end;

procedure Error3;
begin
try
raise Exception.Create('Alarm!!111');;
except
Raise FormatException(AcquireExceptionObject);
end;
end;

Ответы:

  • Error1 — потеря изменений в сообщение исключения;
  • Error2 — Ошибка Access Violation
  • Error3 — Утечка памяти
Разберем каждый случай более детально:

Потеря сообщений

    В методе Error1 мы изменяем текст исходного сообщения и возбуждаем исключение вновь. Но в диалоге исключения мы не увидим текста с «Заворачиваем в свой текст: %s».
    При возбуждение внешнего исключения Delphi заворачивает его в обертку — собственное исключения и дает обработать в блоке except..end.  Далее, в вызове Raise вызывается System._RaiseAgain в котором происходит удаление объекта делфового исключения(в котором мы и сделали изменение данных поля Message), а дальше возбуждается исходное внешнее исключение  которое ничего не знает про наш новый текст сообщения. 
    Более подробно можно найти описание на stackoverflow.

Ошибка Access Violation (Или «в какой момент удаляются объекты исключений»)


   В методе Error2 мы поймали исключение, хотим отформатировать его текст и пробросить дальше. Однако это приведет к проблеме.  
   Проблема  случая из метода Error2 заключается в том, что Delphi попытается дважды удалить один и тот же объект исключения. Первый раз Raise FormatException(E),  второй раз скорее всего где нибудь в TApplication, в завершения цикла обработки исключений. Ответ находится в System._HandleAnyException в которой каждый сможет найти комментарий:

 { we come here if an exception handler has thrown yet another exception }
        { we need to destroy the exception object and pop the raise list. }

   Синтаксис «Raise Argument: Exception;» подразумевает что мы пытаемся возбудить другой объект исключения, а не тот, что находится сейчас на вершине стека возбужденных исключений. Для этого вычищаем объект исключения, что находится на вершине стека и вталкиваем в стек его новый объект. Проблема заключается в том, что удаляемый и вталкиваемый объект являются одним и тем же объектом. 
  Единственное место, где в документации Embarcadero явно говориться, что так делать нельзя нашел в статье Handling exceptions in Delphi
   Правильно выполнить вызов raise без аргументов(«Raise;»), либо использовать метод AcquireExceptionObject. О нем ниже.

Утечка памяти


    В Error3 мы вместо пойманного исключения стандартного класса создаем собственное исключение, которое может более точно сообщить о проблеме при обработке в вызывающем коде. Нюанс заключается в том, что исходный  объект исключения получаем через AcquireExceptionObject. Данный метод может оказаться незаменим, например если мы хотим передать исключение из одного потока в другой. AcquireExceptionObject возлагает на нас ответственность за дальнейшее освобождения памяти полученного объекта исключения, а Delphi тем временем «умывает руки».
Справка Delphi сообщает, что за методом  AcquireExceptionObject должен следовать ReleaseExceptionObject который уменьшает счетчик ссылок на фрейм исключения(структура в rtl описывающая исключение). Получается, что  в Error3 мы забыли вызвать метод ReleaseExceptionObject? Нет. Вызов ReleaseExceptionObject нам ничем не поможет: объект  утечет в любом случае.
   В действительности счет ссылок и ReleaseExceptionObject  актуальны только для Linux. В модуле System есть объявление типа TRaiseFrame — фрейма исключений, в случае компиляции под Windows счетчик ссылок не предусмотрен. Вот текст метода:

function AcquireExceptionObject: Pointer;
begin
if RaiseListPtr <> nil then
begin
Result := PRaiseFrame(RaiseListPtr)^.ExceptObject;
PRaiseFrame(RaiseListPtr)^.ExceptObject := nil;
end
else
Result := nil;
end;

Метод AcquireExceptionObject «забывает» ссылку на объект исключения во фрейме исключения. А при стандартном попытке удаление исключения  ничего не произойдет, так как предусмотрена проверка на nil в деструкторе объекта:

procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;

Именно по этому вызов из проблемы которая описана выше не приведет к AV:
except

  E := AcquireExceptionObject;
Raise E; // В данном случае это правильный код
end;

А для метода test3 наиболее правильным будет решение отказаться от AcquireExceptionObject, и получать объект используя синтаксис On E: Exception do. Но все же, если удобнее использовать AcquireExceptionObject, то за ним должен следовать Raise Argument;  либо явный вызов деструктора:

procedure Error3;
var
e : Exception;
begin
try
raise Exception.Create('Alarm!!111');;
except
e := AcquireExceptionObject;
try
Raise FormatException(e);
finally
FreeAndNil(e);
end;
end;
end;

Более подробно, про то, что справка иногда обманывает qc.embarcadero.com

Подводя итог, как делать нельзя:

//AV

On E: Exception do
raise E;
//Утечка
E := AcquireExceptionObject;
raise EMyException.Create(E.Message);
//Утечка
E := AcquireExceptionObject;
try
Foo(E);
finally
ReleaseExceptionObject(E); //Вызов метода бессмысленнен
end;

Читать на сайте автора.

Реализация перехвата вызовов API

Версия 1.0.1 

Монитор. Отличное слово, правда, объединяющее в себе целую кучу понятий. Ну например впервые данное слово было применено в 1861 году к броненосцу «USS Monitor». Чуть позже

http://alexander-bagel.blogspot.com/2013/01/intercept.html

Revised Object Inspector для Delphi 2010-XE3 от Uwe Schuster. Обзор.

Этот пост является укороченным переводом описания первой версии Продвинутого Инспектора Объектов из блога Uwe.
Не так давно, коллега рассказал про расширение для Delphi замещающий стандартный инспектор объектов альтернативным, с поддержкой фильтра и функции любимых свойств. Пользуюсь этим расширением уже месяц в Delphi XE. Работает стабильно.
На данный момент Revised Object Inspector поддерживает Delphi 2010, Delphi XE, XE2 и XE3. На 21 сентября 2012 года последней версией является Beta 6.
Автор эксперта: Uwe Schuster. Uwe также является активным разработчиком Version Insight Plus, Modal Search Dialog expert, Platforms Expert, IDE Compiler Utils, а также Jedi Version Control System.
См. также: Страница загрузок в блоге Uwe. Все публикации об экспертах в блоге Uwe. …

[[ This is a content summary only. Visit my website for full links, other content, and more! ]]

Читать на сайте автора.

Delphi XE3 Professional и Client/Server

Перед выходом линейки XE3 продуктов Embarcadero (Delphi, C++Builder, RAD Studio), «в интернетах» произошел ужас — произошла утечка (намеренная ли нет, неизвестно) сведений, что новая лицензия для Professional не будет разрешать написание клиент-серверных приложений.

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

Так вот, в блогах шли одинаковые сообщения о грядущем апокалипсисе, одно за другим. Ленты delphifeeds (что com, что ru) содержали по нескольку таких сообщений подряд. На форумах развернулись баталии с обвинением Embarcadero в чудовищной жадности.

Если честно, у меня как у продавца, специфическое отношение к этому вопросу. С одной стороны, подобные изменения лицензии не радуют. С другой стороны, много людей, которые обвиняли Эмбаркадеро в данном грехе, скорее всего вообще никакую версию не покупали. Ну или покупали, но старую, и обновляться не собирались.

К счастью, в момент выхода релиза XE3 было объявлено, что лицензионное соглашение в данном плане осталось как и прежним, и даже вывесили экземпляр на всеобщее обозрение. И все успокоились.

Однако, присмотревшись к лицензии, я обнаружил, что все-таки в Professional писать клиент-серверные программы запрещено. Правда, только в отношении dbExpress:

ADDITIONAL LICENSE TERMS APPLICABLE TO RAD STUDIO, DELPHI AND C++BUILDER, PROFESSIONAL AND PROFESSIONAL ACADEMIC EDITIONS 
In the event Licensee has obtained a RAD Studio, Delphi or C++Builder Professional, or Professional Academic product license then the following terms apply. 
Subject to the terms and conditions of this Agreement, Licensor grants to Licensee as the licensed user of the Product the limited right to use that portion of the Product identified as «dbExpress», in executable form only, to access a local database installed on the same machine as the Work.  Licensee may not use that portion of the Product identified as «dbExpress» in association with a database located on a different machine other than the machine on which the Works are installed.

Предчувствуя недоброе, я открыл license.rtf от Delphi 2007. Нет такого пункта. Открыл от Delphi 2010 — ЕСТЬ, причем слово в слово.
Delphi 2009 у меня нет, возможно этот пункт появился там — не знаю. Но оказывается, он кочует по лицензионным соглашениям уже несколько лет, как минимум 2.5-3 года.
При этом я знаю несколько компаний, которые покупали Professional 2010/XE/XE2 и используют dbExpress именно для клиент-серверных приложений…

Читать на сайте автора.

Хранение массива в BLOB-поле — версия 2

    Комментарий Алексея Тимохина к моей заметке «Хранение массива в BLOB-поле» навел меня на мысль, что приведенный мной код можно значительно упростить, если выбросить из него использование TMemoryStream для временного хранения информации.
    Что бы загрузить массив в параметр query типа ftBlob можно использовать метод SetBlobData:

qInsertResearchData.Params[5].SetBlobData(aData, Length(aData) * SizeOf(aData[0]))

Чтение массива из поля типа TBlobField тоже можно записать коротко:

  // Установка размера динамического массива aData
  SetLength(aData, qResearchDATA.BlobSize div SizeOf(aData[0]));
  // Запись содержимого поля qResearchDATA типа TBlobField в массив aData
  qResearch.GetBlobFieldData(qResearchDATA.FieldNo, TBlobByteData(aData))

    Т.к. размер массива я устанавливаю равным размеру содержимого BLOB-поля, то для исключения лишних проверок в GetBlobFieldData я заменил вызов этого метода на свой код и теперь запись содержимого поля qResearchDATA типа TBlobField в массив aData выглядит так:

  With qResearch.CreateBlobStream(qResearchDATA, bmRead) do
    Try
      ReadBuffer(aData[0], qResearchDATA.BlobSize);
    Finally
      Free;
    End;

Читать на сайте автора.

Шифрование в InterBase — 2

Продолжаем. Я не случайно остановился после установки SEP, поскольку дальнейшие действия возможны в двух «опциях». InterBase поддерживает два алгоритма шифрования. DES и AES. DES можно использовать по умолчанию, в том числе в редакциях Trial и Developer, а вот AES можно включить только в полной (купленной) редакции. Впрочем, в использовании DES или AES нет никакой разницы, кроме, разумеется, факта, что AES является более сильным алгоритмом, и пришел на смену DES (для вскрытия которого несколько лет назад были даже созданы специализированные микросхемы). У DES длина ключа 56 байт, у AES — 128-256 байт.

Тем не менее, для проверки будет достаточно DES. Напомню, что шифровать можно как базу целиком, так и отдельные столбцы, поэтому я на всякий случай создам 3 разных ключа шифрования. Логинимся под SYSDSO, выполняем команды

CREATE ENCRYPTION db_des for DES 
CREATE ENCRYPTION kname_des for DES 
CREATE ENCRYPTION sname_des for DES

все созданные ключи хранятся в таблице RDB$ENCRYPTIONS. Вообще команда CREATE ENCRYPTION сложнее:

create encryption key-name [as default] [for {AES | DES}]
[with length number-ofbits [bits]]
[password {‘user-password’ | system encryption password}]
[init_vector {NULL | random}] [pad {NULL | random}]
[description ‘some user description’]

Length — можно использовать только для AES, указывая длину ключа 128, 192 или 256 бит. 128 по умолчанию для AES.
Password — только для ключей, которыми шифруют столбцы. Можно использовать для дополнительной аутентификации при расшифровке зашифрованных столбцов.
Init-vector — random включает Ciper Block Changing, при которой для одинаковых значений генерируется разный зашифрованный текст. При null для этого используется Electronic Codebook (в DataDef.pdf на странице 214 это указано как Electronic Cookbook). Null по умолчанию.
Pad — при random padding одинаковые значения могут давать разный результат шифрования. Null по умолчанию.

! включение init-vector random или pad random делают невозможным создание индекса по зашифрованному столбцу, при попытке проиндексировать такой столбец сервер выдаст ошибку. Нужно отметить, что и без init-vector и pad с поиском по индексированным зашифрованным столбцам не все хорошо, об этом будет дальше.

Чтобы пользователи могли зашифровать базу или столбцы, SYSDSO должен дать им гранты на использование ключей. Я даю гранты SYSDBA

GRANT ENCRYPT ON ENCRYPTION db_des to SYSDBA; 
GRANT ENCRYPT ON ENCRYPTION kname_des to SYSDBA; 
GRANT ENCRYPT ON ENCRYPTION sname_des to SYSDBA;

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

Продолжение следует.

Читать на сайте автора.

Хранение массива в BLOB-поле

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

Var
  aData: Array of Integer;

Процедура записи динамического массива в базу данных получилась примерно такая:

Var
  ms: TMemoryStream;
  …
begin
  …
  Try
    ms := TMemoryStream.Create;
    // Запись содержимого массива aData в поток ms
    ms.WriteBuffer(aData[0], Length(aData) * SizeOf(aData[0]));
    // Запись содержимого потока ms в параметр query для вставки данных
    qInsertResearch.Params[5].LoadFromStream(ms, ftBlob);
  Finally
    ms.Free;
  End;
  …

А процедура чтения массива из базы данных не сложнее процедуры записи:

Var
  ms: TMemoryStream;
  …
begin
  …
  Try
    ms := TMemoryStream.Create;
    // Запись содержимого поля qResearchDATA типа TBlobField в поток ms
    qResearchDATA.SaveToStream(ms);
    // Установка размера динамического массива aData
    SetLength(aData, ms.Size div SizeOf(aData[0]));
    ms.Position := 0;
    // Запись содержимого потока ms в массив aData
    ms.ReadBuffer(aData[0], ms.Size);
  Finally
    ms.Free;
  End;
  …

Теперь на запись/чтение нескольких миллионов записей моя программа тратит секунду. Вместо размера первого элемента массива SizeOf(aData[0]) вы можете использовать размер типа элементов массива (для приведенного мной примера — SizeOf(Integer)). Но такой вариант кода будет менее универсальным, т.к. будет зависеть от используемого типа данных.

P.S. При использовании такого способа хранения данных не забывайте, что размер некоторых типов данных зависит от операционной системы, для которой скомпилирована программа. Иначе информация, которую прочтет из базы данных ваша 64-битная программа перекомпилированная из 32-битной, вас неприятно удивит.

Продолжение…

Читать на сайте автора.