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.