Показаны сообщения с ярлыком Delphi 2010. Показать все сообщения
Показаны сообщения с ярлыком Delphi 2010. Показать все сообщения

27 апреля 2010

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

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

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 понравилась, и я буду её в будущем использовать.

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 :)

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 байта ;)

04 марта 2010

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

   В далекие школьные годы у нас в школе стоял компьютер ДВК-2М. По сравнению с общераспространенными БК - это было чудо техники, с нормальным монитором и даже с винчестером. На нем я писал свои первые программки. Однажды, когда я писал очередной "шедевр", в соседний кабинет вошла уборщица и включила свет. Этот щелчок включателя я запомнил надолго. При включении света, компьютер моргнул экраном и начал перезагружаться, унеся с собой больше часа моей работы. Это послужило для меня уроком. С тех пор, я регулярно сохраняюсь и, раз в несколько минут, мои руки автоматически жмут Ctrl+S.
   Когда я начал писать свою первую программу на Delphi 2010, я случайно промахнулся и нажал Ctrl+D вместо Ctrl+S. Каково было мое изумление, когда я заметил, что код программы стал выглядеть совсем не так, как я привык его форматировать за свои 14 лет работы с Delphi.