13 нояб. 2010 г.

Delphi Team обидела очередного приверженца Delphi

   Разработчики Delphi все больше ударов наносят по остаткам Delphi-сообщества: постоянные срывы roadmap, кучи багов, минимальное развитие от версии к версии, зашкаливающие цены на обновления... Всем своим поведением они стараются отталкнуть от себя людей. И это им удается!
   Например, в начале ноября 2007-го года Питер Моррис (Peter Morris), весьма известный человек в Delphi-сообществе (автор нескольких open-source библиотек (deAudio, deBold, DIB Controls и FastStrings); автор множества статей по Delphi, Bold и ECO; в феврале 2007-го анонсировал свою книгу, посвященную ECO...), поссорился с CodeGear (Borland) из-за лицензии на Delphi. Менеджеры CodeGear пожадничали, и в результате Питер Моррис закрыл свой сайт (Droopy Eyes Software), отказался от использования Delphi и прекратил поддержку своих open-source проектов.
   Сегодня другая известная личность в Delphi Community - Андреас Хаусладен (Andreas Hausladen) опубликовал на своем блоге заметку XE Update 1 the death of DDevExtensions. Андреас Хаусладен известен своими расширениями для Delphi IDE: DelphiSpeedUp (ускоряет загрузку IDE), DDevExtensions (делает более удобной работу в IDE), IDE Fix Pack (набор патчей для IDE исправляющих баги, до которых у R&D Team нет дела), dcc32Speed (ускоряет работу компилятора (dcc32.exe)) и прочие. Кроме этого он участвует в Project JEDI и разрабатывает собственные библиотеки (VCL Fix Pack, Midas Speed Fix, AsyncCalls и NvAPI). Проблема в том, что R&D Team сильно увлеклась защитой от пиратов, тем самым мешает работе разработчиков расширений для Delphi IDE. Пираты и так найдут, как украсть, а страдают, как всегда, честные люди. Delphi XE Update 1, если установлен DDevExtensions, случайным образом останавливает работу и отправляется на http://www.embarcadero.com/how-to-buy. Думаю, что после прочтения слов Андреаса "Это означает, что DDevExtensions для Delphi XE умер" многочисленные пользователи DDevExtensions по всему миру дружно сказали "фуцк".
   Год от года Delphi-сообщество уменьшается... Только, похоже, что это не волнует ее разработчиков и скоро Delphi, как и Borland, станет достоянием истории.

16 сент. 2010 г.

Экономия при покупке FastReport 5

   Компания FastReport уже включила в обновленный для партнеров прайс-лист цены на 5-ю версию генератора отчетов FastReport. Проанализируем стоимость single-лицензии на FastReport VCL (цены в российских рублях):

РедакцияFastReport 4FastReport 5Рост цены
 Basic 690 9901.43
 Standard259035901.39
 Professional 359049901.39
 Enterprise699097901.40

   Как видим, с выходом новой версии цена на FastReport вырастет почти в полтора раза! Для владельцев лицензий на FastReport 3 и FastReport 4 предусмотрены специальные цены на upgrade: владельцы лицензии FastReport 4 могут проапгрейдить ее до 5-й версии за полцены, а владельцы лицензии FastReport 3 за три четвертых от цены. Тем, кто планирует купить FastReport 5, но не имеет лицензии на FastReport 3 или FastReport 4, рост цен вообще не страшен (если конечно не тормозить) – существует законный способ существенно сэкономить, немного "надув" производителя.
   Рассказываю. У компании FastReport есть принцип – бесплатное полугодичное обновление продуктов на любую новую версию той же самой редакции (так было в 2006-м году, когда вышел FastReport 4). Для тех, кто не догадался, рассказываю дальше. Ранее было объявлено, что FastReport 5 будет выпущен осенью 2010 года (т.е. до релиза уже осталось меньше полугода). А это значит, что, купив FastReport 4 сейчас, можно бесплатно получить лицензию на FastReport 5. А это очень существенная экономия :-)
   P.S. Надеюсь, что рост цен на FastReport обусловлен хорошей реализацией нового функционала, а не тем, что "очень нужны деньги".

31 авг. 2010 г.

Новые пробники IDE от Embarcadero

   Компания Embarcadero выложила 30 дневные пробные версии Embarcadero RAD Studio XE Architect, Delphi XE Architect, Delphi Prism XE Enterprise, C++Builder XE Architect и RadPHP XE.
   Так же см. Release Notes for Delphi and C++Builder XE и Installation Notes for Delphi and C++Builder XE.

Дополню ссылкой на ISO-образ DVD с RAD Studio XE, который содержит Delphi XE и C++Builder XE (редакция (Professional, Enterprise или Architect) определяется серийным номером).

26 авг. 2010 г.

RAD Studio XE на халяву

   Компания Embarcadero проводит розыгрыш трех лицензий готовящейся к выпуску RAD Studio XE. Разыгрываются лицензии:
• Embarcadero RAD Studio XE Architect (примерная стоимость 4 299$);
• Embarcadero RAD Studio XE Enterprise (примерная стоимость 2 799$);
• Embarcadero RAD Studio XE Professional (примерная стоимость 1 399$).
   Победители получат лицензию на RAD Studio XE и ссылку для скачивания продукта еще до начала продаж. Для участия в розыгрыше необходимо до 29 августа 2010 года подать заявку. Подробнее см. "Правила участия в розыгрыше".

13 авг. 2010 г.

Delphi. Трудный путь к 64 битам.

   С каждым годом 64-битных компьютеров становится все больше. Первую 64-х битную версию MS Windows компания Microsoft выпустила 28 марта 2003 (Windows XP 64-bit Edition). Следующий шаг был сделан 25 апреля 2005 – вышла Windows XP Professional x64 Edition. А что у нас с 64-битным компилятором Pascal под Windows? Разработчики FreePascal Compiler показали свою 64-битную программу "Hello world" под Windows еще в марте 2006 года. А что же разработчики Delphi?
   Представители разработчиков Delphi неоднократно говорили, о том, что 64-битная Delphi уже "в пути". Например, в первых числах января 2009-го года Nick Hodges опубликовал статью "The Future of the Delphi Compiler", в которой писал: "Мы ожидаем, что 64-битная версия Delphi будет готова в середине 2010. Поскольку компилятор – это первый шаг к полноценному 64-битному продукту, то мы планируем выпустить предварительную версию 64-битного компилятора в середине 2009 года". Уже середина 2010 года прошла, а мы не увидели даже предварительную версию 64-битного компилятора. В готовящемся релизе RAD Studio XE (результаты проекта "Fulcrum" – то, что должно было стать Delphi 2011) 64 битами только немного "пахнет" – для упрощения перехода на 64-битную архитектуру добавили типы данных NativeInt и NativeUInt.
   Сегодня ночью компания Embarcadero опять обновила "RAD Studio, Delphi and C++Builder Roadmap". В него добавили новый проект "Pulsar" и удалили заглядывающий глубоко в будущее проект "Chromium". Теперь у разработчиков RAD Studio четыре отчетные точки по пути к полной поддержке 64-бит:
1. 64 bit Compiler Preview – консольная версия 64-битного компилятора;
2. Pulsar – версия Delphi c 64-битным компилятором под Windows;
3. Wheelhouse – версия C++Builder c 64-битным компилятором под Windows;
4. Commodore – версии Delphi и C++Builder с 64-битными компиляторами под Windows, MacOS и Linux.
Опять обещания. А стоит ли им верить?
   С учетом того, что версия Delphi выходит раз в год, то Delphi XE2 можно ждать во второй половине 2011 года (а точнее в конце 2011 года). Вероятнее всего (я на это надеюсь) – это будут результаты проекта "Pulsar", что означает: ждать 64-битную Delphi осталось совсем не долго (чуть больше года). Есть надежда, что над проектами "Pulsar" и "Wheelhouse" параллельно будут работать (или уже работают) разные люди. В таком случае, если параллельно с "Pulsar" успеют закончить "Wheelhouse", то в следующем году компания Embarcadero представит полноценную 64-битную версию своей RAD Studio под Windows.

26 июн. 2010 г.

ImageEn станет бесплатной

   ImageEn - это одна из мощнейших библиотек компонент для обработки, просмотра и анализа картинок в программах на Delphi и C++Builder без использования дополнительных DLL или OCX. Она позволяет загружать и сохранять картинки множества форматов, получать их с TWain-сканеров, производить захват видео, применять фильтры, объединять картинки, выделять области изображения (поддерживается инструмент "Волшебная палочка") и многое другое. В 2004 году читатели журнала "Delphi Informant Magazine" выбрали ImageEn продуктом года в категории "Imaging Tool".
   В конце июля компания HiComponents планирует выпустить новую версию ImageEn (3.1.2), исходные тексты которой будут доступны бесплатно! Кроме того, осенью планируется выпустить новый бесплатный плагин для обнаружения объектов и лиц. Желающие уже могут взглянуть на новый дизайн сайта компании, который теперь будет иметь форум и систему регистрации ошибок и запросов о новых возможностях.

14 июн. 2010 г.

FastReport VCL 5: Объединение дублирующихся значений

   Компания Fast Reports обещает осенью 2010 года выпустить FastReport VCL 5. Из всех нововведений пятой версии меня наиболее заинтересовало "объединение дублирующихся значений". Это связано с тем, что за несколько дней до того, как я узнал о возможностях 5-й версии FastReport VCL, я делал отчет, в котором, если ФИО повторялось в соседних строках, то эти строки первого столбца должны были объединяться в одну строку. Думаю, многие делали, что-либо подобное:
   В текущей версии FastReport VCL это без скрипта сделать нельзя. У объекта "Текст" есть свойство " SuppressRepeated", но оно только позволяет скрывать повторяющиеся значения. При этом в FastReport.NET у объекта "Текст" есть свойство "Duplicates", которое может иметь одно из следующих значений:
• Показывать (Show) – показывать повторяющиеся значения;
• Прятать (Hide) – прятать объект с повторяющимся значением;
• Очищать (Clear) – очищать текст в объекте, но показывать сам объект;
• Объединять (Merge) – объединять объекты с одинаковыми значениями.
   Эта вопиющая несправедливость будет исправлена в FastReport VCL 5! В пятой версии, по аналогии с .NET, будет несколько режимов SuppressRepeated.

9 июн. 2010 г.

Проверяйте интервалы

   Недавно, когда я платил за электричество через инфокиоск Беларусбанка, я ошибочно ввел текущие показания счетчика на 100 единиц меньше, чем было оплачено в прошлый раз.
   Результат поразительный – я чуть не оказался должен оплатить 5 760$ за 99 900 киловатт!
   Спрашивается: где "защита от дурака"? Господа программисты, помните, что надпись "ПРОВЕРЯЙТЕ ПРАВИЛЬНОСТЬ ИНФОРМАЦИИ" ни к чему не обязывает пользователя, даже если вы её напишите большими жирными буквами! Лишняя, на первый взгляд, простенькая проверка введенной информации окажется совсем не лишней и спасет вас от подобного казуса.

11 мая 2010 г.

Обновился "RAD Studio, Delphi/C++Builder и Delphi Prism Roadmap"

   Компания Embarcadero обновила свой "RAD Studio, Delphi/C++Builder и Delphi Prism Roadmap". Основные направления работ R&D Team в проектах по развитию Delphi/С++Builder теперь такие:
  • Fulcrum – разработка кросс-платформенной VCL и компилятора для MacOS;
  • Wheelhouse – добавление поддержки Linux;
  • 64 bit Compiler Preview – консольная версия 64-битного компилятора;
  • Commodore – полноценная версия кросс-платформенного 64-битного компилятора, улучшенная поддержка разработки много-ядерных и много-поточных приложений, интеграция с социальными сетями;
  • Chromium – ускорение и упрощение процесса разработки за счет улучшения VCL и IDE, поддержка голосового ввода данных, поддержка процессора ARM.
   Мне кажется, компания Embarcadero повторяет ошибку Borland – погналась за кросс-платформенностью. Провал Kylix их так ни чему и не научил. Думаю, все же основным направлением развития Embarcadero RAD Studio должна была оставаться MS Windows, и приоритетной задачей должна была быть разработка 64-битного компилятора.
   Пока Embarcadero играет в кросс-платформенность, Microsoft во всю уже клепает 64-битные приложения. 64 bit Compiler Preview планируется к выпуску только в первой половине 2011 года. А значит Commodore выйдет еще позже, т.е. ближе к концу 2011 (или в первой половине 2012 года). Успеет ли Embarcadero выпустить свою IDE с полноценным 64-битным компилятором, пока еще есть программисты использующие Delphi?

RAD Studio, Delphi/C++Builder и Delphi Prism Roadmap

5 мая 2010 г.

Вред преждевременной оптимизации

   Некто из великих сказал "Преждевременная оптимизация - корень всех зол" ("Premature optimization is the root of all evil"). Точно автор этих мудрых слов не известен, но возможно это был Дональд Кнут или Энтони Хоар или Эдсгер Дейкстра. Подробнее о исследованиях на эту тему можно посмотреть тут или тут.
   Недавно я сам убедился в правдивости этой фразы. В одном из очень старых модулей на некоторую сумму денег хитрым образом начислялись проценты. На протяжении многих лет это начисление делалось первого числа каждого месяца (ежемесячно), но у руководства компании родилась идея начислять проценты раз в год (ежегодно). Теоретически, если проценты рассчитывать ежемесячно в течение года, то мы должны получить такую же сумму, как и при одном расчете сразу за весь год (из-за округлений можно допустить копеечную разницу). Практически же, попытка сделать ежегодное начисление на тестовом договоре, дала разницу в несколько тысяч рублей.
   Начал я разбираться. Сначала проверил заложенную в код логику расчета – все правильно. Потом стал смотреть код под отладчиком. Дошел до куска кода, в котором в цикле подсчитывается, сколько дней действовала каждая процентная ставка (для выбора дальнейшей формулы):

var
  iDayCount1, iDayCount2: Word;
  iDayRefCount: Byte;
  ........
  iDayCount1 := 0;
  iDayRefCount := 0;

  // начало цикла
  iDayCount2 := ...;
  If "правильное условие в этом тесте" then
    begin
      ...
      Inc(iDayRefCount, iDayCount2);
    end;
  Inc(iDayCount1, iDayCount2);
  // конец цикла
  ...

Первый проход цикла:
 • Ставка действовала 31 день (iDayCount2 = 31)
 • Начисление процентов сделано за 31 день (iDayRefCount = 31)
 • Общее количество обработанных дней в периоде – 31 (iDayCount1 = 31)
Второй проход цикла:
 • Ставка действовала 327 день (iDayCount2 = 327)
 • Начисление процентов сделано за 102 дня (iDayRefCount = 102)
 • Общее количество обработанных дней в периоде – 358 (iDayCount1 = 358)

   Стоп! При одинаковых значениях переменных результат выражения Inc(iDayRefCount, iDayCount2) – 102, а результат Inc(iDayCount1, iDayCount2) – 358. Чудеса? Нет! Переменная iDayRefCount объявлена, как Byte (unsigned 8-bit, 0..255), а переменная iDayCount1, как Word (unsigned 16-bit, 0..65535). Оказалось, что когда начисление делалось ежемесячно, размера переменной типа Byte хватало для хранения количества дней, за которые сделано начисление процентов (их было от 0 до 31). А ежегодный расчет вызвал искажение данных за счет её переполнения.
   Вот так сэкономленный много лет тому назад один байт памяти, мог привести компанию к большим убыткам.

30 апр. 2010 г.

Шутки округления

   Для IP-телефонии я пользуюсь JustVoip (один из сервисов Betamax). Этот сервис отличается низкими ценами (в 2-3 раза ниже, чем у Skype) и наличием бесплатных звонков во многие страны на стационарный телефон.
   Так вот, как-то надо было мне позвонить другу в Германию. Запускаю JustVoip и звоню ему на домашний телефон – не снимает. Набираю ему на мобильный, а программа мне пишет: "Стоимость минуты 0.08€, у вас не хватает средств". Смотрю, точно на счету – 0.07€. Захожу на сайт сервиса, чтобы пополнить счет, а на сайте написано: "На счету 0.08€". А ведь это ровно стоимость минуты!
   Вот наглядный пример, когда округление суммы на счете до центов реализовано разными алгоритмами. Система одна, счет один, а клиентские программы разные и в результате различный баланс :(
   Итак, авторы программ задумайтесь над тем, чтобы алгоритмы обработки данных в рамках одной системы были одинаковыми!

27 апр. 2010 г.

Delphi 2010: Ловим ошибки отсроченной загрузки DLL

   Как я писал раньше, Delphi 2010 научилась вызывать функций из DLL новым способом – с помощью отсроченной загрузки DLL.
   Одним из недостатков этого способа является то, что если при вызове функции произойдет ошибка, то пользователь не узнает ее причину. В случае если DLL не существует, так же как и если в DLL нет нужной функции, то сообщение в программе будет однотипным:
   События, которые происходят при отложенной загрузке DLL и вызове её функций, можно перехватить и обработать самому. Установить свой обработчик на эти события позволяет функция SetDliNotifyHook. Эта функция работает с параметрами процедурного типа DelayedLoadHook описанного в юните System:

DelayedLoadHook = function (dliNotify: dliNotification; pdli: PDelayLoadInfo): Pointer; stdcall;

где:
• dliNotify – это событие которое происходит с DLL:
  - dliNoteStartProcessing – намёк, что будет работа по загрузке DLL;
  – dliNotePreLoadLibrary – идёт загрузка DLL;
  – dliNotePreGetProcAddress – идёт попытка получения адреса вызываемой функции;
  – dliNoteEndProcessing – окончание работы по загрузке функции из DLL;
  – dliFailLoadLibrary – ошибка при загрузке DLL;
  – dliFailGetProcAddress – ошибка при получении адреса вызываемой функции.
• pdli – указатель на структуру типа TDelayLoadInfo с полями:
  – cb – размер структуры;
  – pidd – данные в необработанной форме;
  – ppfn – указатель на адрес функции, которая загружена;
  – szDll – название DLL;
  – dlp – название или номер загружаемой функции;
  – hmodCur – информация о DLL (запись типа TDelayLoadProc);
  – pfnCur – указатель на адрес функции, которая будет вызвана;
  – dwLastError – номер полученной ошибки.

   Более подробно о функции SetDliNotifyHook и связанных с ее работой типах вы можете почитать в справке Delphi 2010. А мы приступим к написанию собственного обработчика ошибок при отложенной загрузке DLL и вызове её функций.
   Т.к. нас интересует только перехват и обработка ошибок, то воспользуемся урезанной версией функции SetDliNotifyHook – SetDliFailureHook, которая устанавливает обработчик событий только на события возникающие при ошибке: dliFailLoadLibrary и dliFailGetProcAddress. Чтобы не писать перехватчик в каждом новом проекте, вынесем его код в отдельную юниту.

Unit dliHandler;

Interface

Uses
  SysUtils;

Type
  // Ошибка при работе с отсроченной загрузкой DLL
  EDliFailure = class(Exception)
                    ErrorCode: Integer;
                    constructor Create(const sMessage: String;
                                              const iErrorCode: Integer);
  end;

Implementation

constructor EDliFailure.Create(const sMessage: String; const iErrorCode: Integer);
begin
  inherited Create(sMessage);
  ErrorCode := iErrorCode;
end;

// Функция возвращает имя или номер импортируемой функции
function ImportName(const AProc: TDelayLoadProc): String;
begin
  if AProc.fImportByName
    then Result := AProc.szProcName
    else Result := '#' + IntToStr(AProc.dwOrdinal);
end;

// Обработчик ошибок генерирующий "красивую" ошибку
function DelayedLoadHook(dliNotify: dliNotification; pdli: PDelayLoadInfo): Pointer; stdcall;
begin
  If dliNotify = dliFailGetProcAddress
    then Raise EDliFailure.Create('В ' + pdli.szDll + ' не найдена функция ' + ImportName(pdli.dlp), 2)
    else Raise EDliFailure.Create('Ошибка при загрузке ' + pdli.szDll, 1); //dliFailLoadLibrary}
end;

Var
  LOldFailureHook: TDelayedLoadHook;

Initialization
  // Устанавливаем свой обработчик
  LOldFailureHook := SetDliFailureHook(DelayedLoadHook);
Finalization
  // На всякий случай вернем старый обработчик
  SetDliFailureHook(LOldFailureHook);
end.

Добавляем dliHandler в любой проект и наслаждаемся автоматической обработкой ошибок при работе с отсроченной загрузкой DLL во всем проекте. Теперь вместо непонятного сообщения "External exception" пользователь увидит:
• если не удалось загрузить DLL
• если не удалось получить адрес функции
   При необходимости, можно обработать ошибку и для каждого конкретного случая вызова функции (специально для этого в dliHandler я определил класс EDliFailure):

Try
  ...
  fFee := CalcFee(1234)
  ...
  Except
    on E: EDliFailure do
      Case E.ErrorCode of
        1: HandleDliFailLoadLibrary;
        2: HandleDliFailGetProcAddress;
      End
    else Raise
End;

где, HandleDliFailLoadLibrary и HandleDliFailGetProcAddress – специфические для данного конкретного случая обработчики ошибок.
   Как видите, научить программу выводить понятные пользователю сообщения об ошибке легко.

21 апр. 2010 г.

Fast Reports переманивает клиентов у конкурентов

   Когда-то давно, на заре человечества, единственным и неповторимым генератором отчетов для Delphi был QuickReport (про ReportSmith даже вспоминать не будем). Он много лет входил в поставку Delphi и его изначально использовали все. Поработав с QuickReport и устав регулярно вспоминать "матерей его разработчиков" программисты стали искать ему альтернативу. Кто то пытался сам улучшить QuickReport, кто то стал генерировать отчеты в MS Word/Excel (например, я), кто то, в надежде на лучшее, украл или купил другие генераторы Report Builder или Rave Reports (или даже монстра по имени Crystal Reports), а кто то стал писать свой генератор отчетов. Одним из тех, кого не устроил QuickReport и кто начал писать свой генератор, был Александр Цыганенко – автор FastReport. Думал ли Александр в далеком 1998 году, что его генератор станет лидером рынка?
   За последние десять лет ситуация изменилась – генераторов отчетов, которые могут быть использованы в Delphi, написано уже много и на любой вкус. Поэтому рынок генераторов отчетов уже насытился и теперь начинается новая стадия борьбы за старых клиентов. Стадия, в которой главная роль отводится не программистам, а маркетологам. Компания Fast Reports, опередив своих конкурентов по функциональным возможностям их генераторов отчетов, решила дожать тех, кто сомневался в его покупке и начала акцию по переманиванию пользователей других коммерческих генераторов отчетов на FastReport. В обмен на лицензию конкурента они предлагают скидку в 20% на FastReport 4 VCL, FastReport VCL OLAP Pack, FastReport.NET и FastReport Studio. VCL-версию FastReport можно получить в обмен на Report Builder, Rave Reports, Crystal Reports и ActiveReports.
   Обидно за QuickReport. Его, классика жанра, вообще обделили вниманием. Но, думаю – это "заслужено", ни кто на просторах бывшего СССР не покупал это убожество программисткой мысли.

14 апр. 2010 г.

Delphi 2010: Отсроченная загрузка DLL

Долгие годы программы на Delphi умели загружать DLL двумя способами: статически и динамически. В Delphi 2010 появился третий способ – отсроченная загрузка.

Сделаем простенькую DLL, которая экспортирует некую функцию, например, CalcFee:

library TestDLL;
Function CalcFee(const iID: Integer): Currency;
  begin
    Result := ...;
  end;
Exports
  CalcFee;
end.

и рассмотрим все три способа.

1. Статическая загрузка.
Это самый простой способ вызвать функцию из DLL. Для его реализации необходимо всего лишь описать внешнюю функцию:

Function CalcFee (const iID: Integer): Currency; external 'TestDLL.dll';

И после этого её можно вызывать:

fFee := CalcFee(1234)

Наша DLL загружается при запуске программы и остается загруженной до завершения ее работы.
За этой простотой скрывается большая проблема – если DLL удалить или испортить, то программа просто не запустится.


2. Динамическая загрузка.
Этот способ – более продвинутый, но требует написания значительно большего количества кода:

Var
  CalcFee: function(const iID: Integer): Currency;
  hDLL: THandle;
begin
  hDLL := LoadLibrary('TestDLL.dll');
  If hDLL <> 0
    then try
      @CalcFee := GetProcAddress(hDLL, 'CalcFee');
      If @CalcFee = nil
        then ShowMessage('В TestDLL.dll не найдена функция CalcFee')
      else fFee := CalcFee(1234);
    finally
      FreeLibrary(hDLL);
    end
    else ShowMessage('Ошибка при загрузке TestDLL.dll')

В этом коде используются функции Win32 API из Windows.pas:
  - LoadLibrary – загружаем DLL;
  - GetProcAddress – получаем адрес функции по её имени;
  - FreeLibrary – выгружаем DLL из памяти.

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

3. Отсроченная загрузка
В Delphi 2010 появился третий способ вызова функций из DLL – отсроченная загрузка. Как и динамическая загрузка, она позволяет загружать DLL только при необходимости (например, для экономии ресурсов), но также как при статической загрузке, функцию достаточно лишь описать, добавив директиву delayed:

Function CalcFee(const iID: Integer): Currency; external 'TestDLL.dll' delayed;

Недостатки этого способа – это то, что DLL уже нельзя выгрузить и то, что если при вызове функции произойдет ошибка, то пользователь не узнает ее причину. В справке написано "Trying to call a delayed routine that cannot be resolved results in a run-time error (or an exception, if the SysUtils unit is loaded)". Если DLL удалить или если в DLL нет нужной функции, сообщение будет однотипным:
Если функция будет использована в консольной программе, то сообщение об ошибке будет тоже не информативным:

"Runtime error 255 at 7C812AFB "

Но спасение есть. При желании ошибки, возникающие во время загрузки DLL или вызове её функций можно перехватить и обработать с помощью процедур SetDliNotifyHook и SetDliFailureHook из юниты System.

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

8 апр. 2010 г.

TIOBE: Delphi удерживает позиции

   Вчера компания TIOBE Software опубликовала апрельский индекс популярности языков программирования - TIOBE Programming Community Index. Согласно ему, язык программирования Delphi уверенно удерживает занятое в феврале 2010-го года 9-е место. По сравнению с апрелем прошлого года он поднялся с 11-го на 9-е место. При этом, начиная с 2001-го года, пики популярности Delphi были в 2004-м году: 12-е место в январе и 7-е в августе.
   По сравнению с апрелем прошлого года рейтинг языка Pascal, опустился с 15-го на 16-е место. При этом в сентябре 2009-го он входил в дюжину лидеров и занимал 12-е место. Жалко прародителя :(
   "TIOBE Programming Community Index" обновляется один раз в месяц и основан на количестве квалифицированных инженеров, курсов и разработчиков дополнительного программного обеспечения. Для сбора этой информации используются поисковые машины (Google, MSN, Yahoo!, Wikipedia и YouTube). Для языка Delphi отслеживаются: Delphi, Kylix, Object Pascal, Free Pascal, Chrome (исключая "Google Chrome"), Oxygene, Delphi.NET и Delphi Prism.

1 апр. 2010 г.

Embarcadero Technologies покупает бренд "Borland"

   По сообщению BusinessWeek компания Embarcadero Technologies, крупнейший производитель и поставщик платформенно-независимых инструментов для разработки, управления и оптимизации приложений и баз данных, подписала соглашение с Micro Focus International plc о приобретении эксклюзивного права на использование торговой марки "Borland" в названии продуктов линейки "Rapid Application, Web and Java™ Development Tools".
   "Мы возрождаем славное имя Borland! Когда-то "Borland" входил в тройку самых известных в мире брендов. В какой-то степени, использование "Borland" в названии наших продуктов – это возвращение к нашим корням. Borland – это бренд, в будущее которого верят многие ИТ-профессионалы!" – сказал вице-президент по связям с разработчиками и главный евангелист Embarcadero Technologies Дэвид Интерсаймон ("David I").
   С первого апреля 2010 года, слово "Borland" будет использоваться в названии всех новых версий инструментальных средств для разработки приложений компании Embarcadero Technologies: Borland® RAD Studio, Borland® Delphi, Borland® C++Builder, Borland® Delphi Prism, Borland® JBuilder, Borland® Delphi for PHP.

24 мар. 2010 г.

Delphi 2010: TDataSet.FreeBookmark – рудимент

   Часто бывает полезно отметить текущее положение курсора в DataSet'е так, чтобы позже можно было быстро возвратиться к этому месту. Delphi обеспечивает эту функциональную возможность с помощью закладок (Bookmark), для работы с которыми используются процедуры:
  • GetBookmark – устанавливает закладку на текущую запись;
  • BookmarkValid – проверяет, существует ли запись, на которую ссылается закладка;
  • GotoBookmark – позиционирует курсор на запись, на которую ссылается закладка;
  • FreeBookmark – освобождает системные ресурсы, используемые методом GetBookmark.
Думаю это знакомая многим конструкция:

Var
  q: TSDQuery;
  bm: TBookmark;
begin
  Try
    bm := q.GetBookmark; // делаем закладку
    // обрабатываем ds
  Finally
    If bm <> nil then
      begin
        If q.BookmarkValid(bm) then
          q.GotoBookmark(bm);
        q.FreeBookmark(bm);
      end;
  End;
end;

В Delphi 7 код FreeBookmark выглядит так:

procedure TDataSet.FreeBookmark(Bookmark: TBookmark);
begin
  FreeMem(Bookmark);
end;

В Delphi 2010 он – просто заглушка:

procedure TDataSet.FreeBookmark(Bookmark: TBookmark);
begin
  // No longer need to free bookmark since it's a TBytes now.
end;

   Т.е. FreeBookmark в Delphi 2010 – это уже пережиток прошлого и вызывать его больше не нужно. Таким образом, исходный код становится проще:

Var
  ds: TSDQuery;
  bm: TBookmark;
begin
  Try
    bm := q.GetBookmark; // делаем закладку
    // обрабатываем ds
  Finally
    If (bm <> nil) and q.BookmarkValid(bm) then
      q.GotoBookmark(bm);
  End;
end;

   P.S. А вот справочную систему Delphi 2010 подправить, как всегда, забыли. В ней есть раздел "DB.TDataSet.FreeBookmark" с описанием процедуры и рекомендацией ее использования. А раздел "Marking and Returning to Records" содержит строку "FreeBookmark frees the memory allocated for a specified bookmark when you no longer need it. You should also call DB.TDataSet.FreeBookmark before reusing an existing bookmark." и пример, где используется FreeBookmark :)

21 мар. 2010 г.

Разработчики Delphi собрали 26 000$ для помощи Гаити

   Anders Ohlsson, сотрудник Embarcadero Technologies (бывший сотрудник компании Borland), организовал на eBay благотворительный аукцион для помощи населению пострадавшего от землетрясения Гаити.
   На аукцион были выставлены старые вещи, связанные с компанией Borland. За 2 025$ был продан Compaq II – компьютер, на котором Anders Hejlsberg разрабатывал Turbo Pascal. За 99$ продали наклейку на бампер "Delphi Developers Do It Faster". С молотка ушли подписанные авторами книги "Delphi 2 Developer's Guide" (Steve Teixeira) и "Delphi Component Design" (Danny Thorpe), старые релизы продуктов в оригинальных упаковках, фирменные футболки компании, эксклюзивная спортивная куртка с большой буквой "B" и прочее. Обед с David Intersimone ("David I", вице-президент по связям с разработчиками и главный евангелист Embarcadero Technologies) оценили в 480$.
   Всего поклонниками компании Borland со всех континентов было куплено 177 предметов общей стоимостью 26 000$. Некто то, из Австрии, потратил на этом аукционе почти 4 200$!
   Собранные деньги будут переданы фонду "Clinton Bush Haiti Fund".
По информации Santa Cruz Sentinel

16 мар. 2010 г.

Delphi 2010: TValue - "тормоз"!

Перевод. Оригинал "TValue is very slow" (© TURBU Tech) дополнен моими тестами и комментариями.

   Справочная система Delphi 2010 описывает тип TValue, используемый модулем RTTI для хранения значений произвольных типов, как "облегченная версия типа Variant". Увидев это, я задался вопросом, насколько он легковеснее? Как быстро работает TValue?
   К счастью, среди известных мне новых возможностей Delphi 2010 – модуль диагностики (Diagnostics), который предоставляет нам объект TStopwatch – простой таймер позволяющий засечь время выполнения операций и тем самым облегчить написание простого теста скорости.
   Я ожидал, что скорость работы TValue будет сравнима со скоростью Variant, или возможно немного больше. Для проверки, я написал следующую программку:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, rtti, diagnostics;

const
  HUNDRED_MILLION = 100000000;

procedure tryTValue;
var
  i: integer;
  j: TValue;
  value: integer;
begin
  for I := 1 to HUNDRED_MILLION do
    begin
      j := i;
      value := j.AsInteger;
    end;
end;

procedure tryVariants;
var
  i: integer;
  j: variant;
  value: integer;
begin
  for I := 1 to HUNDRED_MILLION do
    begin
      j := i;
      value := j;
    end;
end;

var
  stopwatch: TStopWatch;
begin
  try
    stopwatch := TStopWatch.StartNew;
    tryVariants;
    stopwatch.Stop;
    writeln('Variants: ', stopwatch.ElapsedMilliseconds);

    stopwatch := TStopWatch.StartNew;
    tryTValue;
    stopwatch.Stop;
    writeln('TValues: ', stopwatch.ElapsedMilliseconds);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  readln;
end.

   Конечно, этот тест - не исчерпывающая проверка возможностей TValue, но результаты поучительны. Когда я запустил его на своем рабочем компьютере (высокопроизводительный ноутбук Alienware), тест Variant выполнился почти мгновенно, а тест TValue выполнялся так долго, что я решил, что он завис и остановил его.
   Затем я запустил тест снова и получил следующие результаты (в миллисекундах):
Variants: 717
TValues: 31131
   По крайней мере, для этой конкретной операции, TValue в 43.52167832167832 раза медленнее, чем Variant!

От меня.

   Вообще то 31131 поделить на 717 будет равно 43.41841004 ;)
   Что бы проверить результат, я запустил несколько тестов на своей домашней "dev machine".

Тест #1. Запустил исходный тест и получил такую же разницу скорости - в 43.17657992 раза:
Variants: 538
TValues: 23229.
Тест #2. Переставил местами вызов tryTValue и tryVariants и снова получил 43.376404494:
TValues: 23163
Variants: 534.
Тест #3. Закомментировав обратное присвоение TValue и Variant целой переменной ("value := j.AsInteger" и "value := j") я получил для TValue более "веселый" результат:
Variants: 535
TValues: 5168
Присвоение целого значения TValue медленнее присвоения целого значения Variant всего в 9.65981308 раза. А значит основное падение скорости вызвано AsInteger.

Тест #4. В процедуре tryTValue я заменил "value := j.AsInteger" на "value := j.AsOrdinal"
Variants: 536
TValues: 5862
В результате общее падение скорости всего в 10.93656716 раза!

Тест #5. AsOrdinal возвращает значение типа Int64, поэтому в процедуре tryTValue я заменил "value := j.AsInteger" на "value := j.AsInt64" и получил падение скорости в 54,25981308 раза!!!
Variants: 535
TValues: 29029
Вывод: "value := j.AsOrdinal" у TValue работает почти так же быстро, как и "value := j" для Variant. А методы AsInteger и AsInt64 – лучше не использовать. Но все равно, главный вывод: TValue – "тормоз"!

   Напоследок, я проверил с помощью функции SizeOf число байт, которые занимали переменные: переменная типа Variant занимала – 24 байта, а TValue – всего 16. Может в этом проявляется "облегченность" типа TValue? Тогда, храните числа в integer – они будут занимать 4 байта ;)

15 мар. 2010 г.

Использование полей и закладок при работе с MS Word из Delphi

   В предыдущей заметке "Поиск и замена текста в документе MS Word из Delphi" я рассказывал, как дорабатывал старый модуль, который генерирует клиентам компании письма в формате MS Word с помощью поиска и замены текста. Сдав модуль заказчикам, я в свободное от работы время, переделал его. Вместо поиска и замены использовал поля с переменными (DocVariable).
   В шаблон письма с помощью макроса добавил переменные

Sub AddFields()
  ThisDocument.Variables.Add "FIO", "FIO"
  ThisDocument.Variables.Add "ADDRESS", "ADDRESS"
  ...
End Sub

и расставил поля по тексту шаблона. В макросе у метода Add первый параметр – название переменной, а второй – ее значение. Я специально сделал их одинаковыми, чтобы пользователям было проще и нагляднее редактировать шаблоны.
   Затем внес изменения в методы модуля работающие с MS Word. Если опустить все детали и различную логику, то упрощенно работа с MS Word выглядит так:

Var
  wa: WordApplication;
  ovDotName, ovFileName: OleVariant;
  i: Integer;
  q: TSDQuery;
  ...
begin
  ...
  wa := CreateComObject(CLASS_WordApplication) as _Application;
  ovDotName := 'какой то шаблон.dot';
  wa.Documents.Add(ovDotName, EmptyParam, EmptyParam, EmptyParam);

  For i := 0 to q.FieldCount-1 do
    MSWordSetVariable(q.Fields[i].FieldName, q.Fields[i].AsString);
  ...
  ovFileName := 'письмо любимому клиенту.doc';
  wa.ActiveDocument.SaveAs(ovFileName, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
  ...

Где MSWordSetVariable – процедура, которая присваивает значение переменной в документе MS Word.

procedure MSWordSetVariable(ovVariable: OleVariant; const sValue: String);
begin
  If sValue = ''
    then wa.ActiveDocument.Variables.Item(ovVariable).Value := ' '
    else wa.ActiveDocument.Variables.Item(ovVariable).Value := sValue
end;

   Если переменной MS Word присвоить пустую строчку, то в поле выводится текст 'Ошибка! Переменная документа не указана.', поэтому вместо пустого значения я присваиваю ей пробел (так же делает и сам MS Word). Это связано со странной работой MS Word с коллекцией переменных. Если переменной присвоить пустую строку, то количество переменных в коллекции (Variables.Count) уменьшается на единицу, а попытка удалить переменную (Variables.Item(ovVariable).Delete) дает ошибку 'Объект был удален', т.е. присвоение пустой строки равносильно удалению. При этом после удаления переменной из коллекции переменных, присвоение ей непустого значения выполняется успешно и количество переменных в коллекции увеличивается на единицу, т.е. присвоение непустого значения равносильно вызову метода добавления переменной в коллекцию переменных.
   Продемонстрирую вышесказанное примером кода, по которому видно как изменяется количество переменных (iCount):

iCount := wa.ActiveDocument.Variables.Count; // iCount = 3
wa.ActiveDocument.Variables.Item(ovVariable).Value := '';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 2
wa.ActiveDocument.Variables.Item(ovVariable).Value := 'не пусто';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 3
wa.ActiveDocument.Variables.Item(ovVariable).Delete;
iCount := wa.ActiveDocument.Variables.Count; // iCount = 2
wa.ActiveDocument.Variables.Item(ovVariable).Value := 'не пусто';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 3
wa.ActiveDocument.Variables.Item(ovVariable).Value := '';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 2
wa.ActiveDocument.Variables.Item(ovVariable).Delete; // ошибка 'Объект был удален'

   Вот такая логика у индусов, писавших этот кусок MS Word.

   После присвоения значений всем переменным, осталось только дать команду полям обновиться новыми значениями переменных. Для этого в VBA у коллекции объектов полей (Fields) есть метод Update:

wa.ActiveDocument.Fields.Update

А для того, чтобы избежать дальнейшего обновления полей, избавимся от их связи с переменными:

wa.ActiveDocument.Fields.Unlink

   У каждого структурного элемента документа (заметки, колонтитула, сноски и т.д.) своя коллекция объектов полей, поэтому, если документ, как у меня, состоит из разных структурных элементов, то методы Update и Unlink необходимо вызвать для каждого из этих элементов. Для этого перебираем все элементы коллекции StoryRanges.

procedure MSWordUpdateStoryRanges;
  Var
    StoryRanges: Word2000.StoryRanges;
    StoryRange: Word2000.Range;
    iStoryIndex: integer;
Begin
  StoryRanges := wa.ActiveDocument.StoryRanges;
  For iStoryIndex := wdMainTextStory to wdFirstPageFooterStory do
    Try
      StoryRange := StoryRanges.Item(iStoryIndex);
      If StoryRange <> nil then
        begin
          StoryRange.Fields.Update;
          StoryRange.Fields.Unlink;

          While (StoryRange.NextStoryRange <> nil) do
            begin
              StoryRange := StoryRange.NextStoryRange;
              StoryRange.Fields.Update;
              StoryRange.Fields.Unlink;
            end;
          end;
    Except
      StoryRange := nil;
    End;
End;

   Т.к. в моем шаблоне есть только основной текст и одна заметка, то вместо процедуры, которая перебирает все элементы коллекции StoryRanges, я сделал процедуру, которая работает только с одним ее элементом:

procedure MSWordUpdateStoryRange(const iStoryIndex: integer);
  Var
    StoryRange: Word2000.Range;
begin
  Try
    StoryRange := wa.ActiveDocument.StoryRanges.Item(iStoryIndex);
    If StoryRange <> nil then
      begin
        StoryRange.Fields.Update;
        StoryRange.Fields.Unlink;
      end;
  Except
  End;
end;

И вызываю ее перед сохранением документа.

MSWordUpdateStoryRange(wdMainTextStory);
MSWordUpdateStoryRange(wdTextFrameStory);

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

Var
  ovBookmarkName, ovWhat: OLEVariant;
begin
  ovWhat := wdGoToBookmark;
  ovBookmarkName := 'писать текст сюда';
  Try
    If wa.Selection.GoTo_(ovWhat, EmptyParam, EmptyParam, ovBookmarkName) <> nil then
      begin
        wa.Selection.TypeText('просто текст ');
        wa.Selection.Font.Bold := 1;
        wa.Selection.TypeText('жирный текст');
        wa.Selection.Font.Bold := 0;
        wa.Selection.TypeText(' просто текст');
      end;
  Except
  End;

   Вот и все :)
   Раннее связывание и использование полей/закладок дало существенный рост скорости генерации писем. Если на 500-х различных документах этого было почти не заметно, то при генерации 15 000 документов, прирост скорости составил 30% (специально проверил несколько раз на одних и тех же исходных данных).

P.S. При работе с ранним связыванием мне не нравится только одно – многие параметры в методах объявлены, как VAR (даже индекс элемента коллекции!), поэтому приходится заводить для них специальную переменную типа OleVariant.

9 мар. 2010 г.

Поиск и замена текста в документе MS Word из Delphi

   Попросили меня доработать старый модуль, который генерирует клиентам компании письма в формате MS Word. Пользователи создают шаблоны, в которых расставляют названия полей, заключенные в служебные символы (например, #CONTRACT_NUM#, #FIO#, #ADDRESS#...). Программа по заданным критериям выбирает информацию о клиентах из базы и генерирует письма, находя в тексте шаблонов названия полей и заменяя их фактическими значениями.
   Если опустить все детали и различную логику, то упрощенно это выглядит так:

Var
  MSWord: OleVariant;
  i: Integer;
  q: TSDQuery;
  ...
begin
  ...
  MSWord := CreateOleObject('Word.Application');
  MSWord.Documents.Add('какой то шаблон.dot');
  For i := 0 to q.FieldCount-1 do
    MSWord.Selection.Find.Execute(FindText := '#' + q.Fields[i].FieldName + '#', ReplaceWith := q.Fields[i].AsString);
  ...
  MSWord.ActiveDocument.SaveAs('письмо любимому клиенту.doc');
  ...

   Первое, с чем я столкнулся, это то, что длина значения параметра ReplaceWith не должна превышать 255 символов. Но это я обошел легко, заменив "поиск и замену текста" на "поиск и вывод текста":

If MSWord.Selection.Find.Execute(FindText := '#' + q.Fields[i].FieldName + '#') then
  MSWord.Selection.TypeText(q.Fields[i].AsString);

   Вторая задачка оказалась сложнее. Внизу листа в фиксированное место необходимо было вывести фамилию и адрес получателя. Сначала я думал поместить его в нижний колонтитул, но оказалось, что письмо может быть на двух листах. Тогда ничего не оставалось, как использовать объект "заметка". Вставил "заметку". Красота! Документ генерируется, текст сдвигается, заметка остается на месте... Но радость была недолгой, т.к. поля #FIO# и #ADDRESS#, помещенные в заметку, так и остались незамененными :(
   Оказалось, что MSWord.Selection.Find.Execute ищет текст только в основной части документа, а в документе, состоящем из разных структурных элементов (заметок, колонтитулов, сносок и т.д.), поиск необходимо производить отдельно в каждом из этих элементов. Все эти структурные элементы документа являются элементами коллекции StoryRanges. Т.к. дело было к ночи, а модуль должен был быть готов к утру, я не стал разбираться, как работать со StoryRanges через OLE из Delphi, и просто добавил в тестовый шаблон письма макрос на VBA, в котором перебираются все структурные элементы активного документа, в которых ведется поиск:

Sub ReplaceText(sFindText As String, sReplaceText As String)
  Dim rngStory As Range
  For Each rngStory In ActiveDocument.StoryRanges
    With rngStory.Find
      .Text = sFindText
      .Replacement.Text = sReplaceText
      .Wrap = wdFindContinue
      .Execute Replace:=wdReplaceAll
    End With
  Next rngStory
End Sub

А в программе я только написал вызов макроса:

MSWord.Application.Run('ReplaceText', '#FIO#', 'Иванов Иван Иванович');

Работает как часы :)

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

MSWord.Quit;
MSWord := UnAssigned;

P.P.S. Для подобной задачи генерации писем в формат MS Word больше подходит не поиск и замена текста, использование полей с переменными (DocVariable) и закладок (Bookmark). А как это сделать, я расскажу в следующий раз.

4 мар. 2010 г.

Delphi 2010: Сносим назойливый Code Formatter

   В далекие школьные годы у нас в школе стоял компьютер ДВК-2М. По сравнению с общераспространенными БК - это было чудо техники, с нормальным монитором и даже с винчестером. На нем я писал свои первые программки. Однажды, когда я писал очередной "шедевр", в соседний кабинет вошла уборщица и включила свет. Этот щелчок включателя я запомнил надолго. При включении света, компьютер моргнул экраном и начал перезагружаться, унеся с собой больше часа моей работы. Это послужило для меня уроком. С тех пор, я регулярно сохраняюсь и, раз в несколько минут, мои руки автоматически жмут Ctrl+S.
   Когда я начал писать свою первую программу на Delphi 2010, я случайно промахнулся и нажал Ctrl+D вместо Ctrl+S. Каково было мое изумление, когда я заметил, что код программы стал выглядеть совсем не так, как я привык его форматировать за свои 14 лет работы с Delphi.
   Я вспомнил, что одним из нововведений Delphi 2010 является "Code Formatter" - встроенный модуль для форматирования исходного кода. Больше часа я бился над его настройкой, но все равно не смог настроить его под себя - он упорно переносил строчки не так как мне нужно. Решив его больше не трогать, я продолжил работать, пока снова, случайно не нажал Ctrl+D...
   Поиски настройки, отключающей это чудо программисткой мысли Embarcadero, ничего не дали и, как я понял, мирным способом договориться с Delphi IDE не форматировать мои исходники не получится.
   Значит, мы пойдем другим путем! Возможности Delphi IDE расширяется за счет отдельных модулей, поэтому существует большая вероятность того, что этот форматер вынесен в отдельную BPL или DLL. Подумав так, я не ошибся - в каталоге "RAD Studio\7.0\bin" я нашел Embarcadero.Modeling.Formatter.dll, в свойствах которой было написано "Embarcadero RAD Studio Formatter". В реестре среди "Known IDE Packages" форматера я не заметил, поэтому, выбрав Embarcadero.Modeling.Formatter.dll, я безжалостно нажал Shift+Delete. С тех пор форматер кода меня больше в Delphi 2010 не беспокоит :)