Создание InterBase UDF на Delphi для Linux (strings)

Posted by on in Blogs

В предыдущем посте Создание InterBase UDF на Delphi для Linux был представлен пример того, как на Delphi создается двоичный модуль UDF для СУБД InterBase, предназначенный для работы в Linux. В том примере был собран двоичный модуль - загружаемая библиотека Linux - в которой находится точка входа простейшей функции, складывающей два числа и возвращающей числовой результат 'по значению'. Для 4-8 байтовых чисел это допустимое решение использования памяти. А как быть, если требуется возвратить в InterBase значительно большие объемы данных, например, текстовые данные?

В этом случае функции потребуется выделять область памяти, размещать результат в этой области и возвращать ее адрес, чтобы передать результат в InterBase. Казалось бы, разумным решением было бы использование статической, один раз выделенной глобальной, в пределах библиотеки, области памяти, но в многопользовательском/многопоточном режиме работы сервера InterBase отдельные потоки-пользователи будут мешать друг другу, затирая собственными результатами вычисления других. Поэтому глобальные переменные в многопотоковых функциях использовать нельзя. Другие корректные варианты возврата данных подробно описаны в статье Д.Кузьменко Правила написания thread-safe UDF. Кстати, демонстрационный пример для проверки взят из нее же.

Цитата из этой статьи:

Правила написания thread-safe UDF

Основная задача при написании потоко-безопасных UDF – обеспечить корректное обращение к возвращаемым параметрам при многопользовательской работе. Особенности архитектуры SuperServer полностью исключают использование глобальных переменных в DLL (shared library) для возврата результата функции, как это было принято в архитектуре Classic (IB 4.0).

Сначала ознакомимся с исходными текстами демонстрационного примера. Исходный текст примера библиотеки выглядит практически так же, как и в предыдущем посте.

library safeudf;

uses
  SysUtils,
  Classes
  ,POSIX.Base;

const
  libib_util = 'libib_util.so';
  //
  //  ib_util; // ib_util can be found in interbase/include.
  //
  function ib_malloc(l: integer): pointer; cdecl; external 'libib_util.so' name 'ib_util_malloc';

// затем следуют собственно UDF

а в конце - имена экспортируемых из библиотеки функций

exports
  function1,
  function2,
  function3;
begin
// эта строка обязательна для IB 4.2, 5.x и 6.0 и выше // this is for multithreading. Needed to use udfs with IB 4.2, 5.x and 6.0 IsMultiThread := True;
end;

В статье выделены три основных  варианта передачи и возврата строковых параметров.

Первым рассмотрим возврат строки при помощи входного параметра - RETURNS PARAMETER. В реальности для этого варианта надо создавать не функцию, а процедуру, поскольку результат возвращается в той же строке, которая передается, как второй параметр. Для Delphi строки передаются, как указатели PChar. За выделение места в памяти и заполнение его данными отвечает вызывающая сторона - в данном случае InterBase.  В UDF строки могут быть представлены тремя типами: CString(n), VarChar(n) и Char(n). Наиболее близкий к встроенным в Delphi типам данных - CString, поэтому мы используем именно его. Функция ниже просто копирует первую строку во вторую и возвращает ее. Если длина второго параметра  будет меньше, чем первая строка, результат будет сокращен соответственно. 

//  процедура, возвращающая результат через второй входной параметр.
//  Эта функция работает быстрее function3 и function2.
//  Автор - Кузьменко Дмитрий, This email address is being protected from spambots. You need JavaScript enabled to view it. (c) 1998-2003.

procedure function1(P1, P2: PChar); cdecl; export;
begin
  P2 := StrCopy(P2, P1);
end;

Этой процедуре на Delphi будет соответствовать такой код DDL в InterBase для Windows

DECLARE EXTERNAL FUNCTION function1
  CSTRING(80), CSTRING(80)
  RETURNS PARAMETER 2
  ENTRY_POINT 'function1'  MODULE_NAME 'safeudf'

Второй вариант: Почему бы не управлять выделением памяти для результата в самой UDF? Это возможно, но следует учитывать, что в запросе, который возвращает многие тысячи записей, выделение и освобождение памяти будет происходить при обработке каждой записи, что может существенно замедлить запрос. С другой стороны, скорость таких операций намного выше, чем скорость получения записей с диска, поэтому влияние на скорость обработки данных будет минимальным.

Кроме того, выделение памяти следует выполнять только при помощи специальной библиотечной функции ib_util_malloc из поставляемой с InterBase библиотеки libib_util.so Именно поэтому, в тексте программы присутствует строка описания внешней функции.

  function ib_malloc(l: integer): pointer; cdecl; external 'libib_util.so' name 'ib_util_malloc';

и нам придется "подцепить" эту дополнительную внешнюю библиотеку при сборке.

В DDL такой функции обязательно указание выходного формата с длиной и спецификатора FREE_IT, который предписывает серверу самостоятельно освободить использованную память. Обязательным является соответствие количества выделяемой памяти внутри функции, и длина строки в объявлении функции в DDL. 

//  функция, возвращающая результат с FREE_IT. ,
//  обратите внимание на вызов ib_util_malloc - обычный
//  malloc или другие аллокаторы памяти здесь использовать нельзя.
//  Эта функция работает медленнее function1 и function3.
{ DECLARE EXTERNAL FUNCTION function2
  CSTRING(80)
  RETURNS CSTRING(80) FREE_IT
  ENTRY_POINT 'function2'  MODULE_NAME 'safeudf' }
function function2(P1: PChar): PChar; cdecl; export;
var
  P2: PChar;
begin
  P2 := ib_malloc(80);
  StrCopy(P2, P1);
  Result := P2;
end;

И третий вариант: 

//  функция, не выделяющая память для возврата. В качестве
//  куска возвращаемой памяти используется память входного параметра.
//  В данном случае нет потерь памяти, поскольку IB самостоятельно
//  освобождает память, занимаемую входным параметром, а память для
//  выходного параметра не выделяется (поэтому входной и выходной параметры должны
//  быть объявлены одной и той-же длины).
//  Эта функция работает чуть медленнее function1, но быстрее function2.
function function3(P1: PChar): PChar; cdecl; export;
begin
  P1 := StrCopy(P1, P1);
  Result := P1;
end;

В 1 и 3 вариантах память для передачи и получения результата UDF выделяется InterBase один раз в контексте клиентского соединения. 

Далее все делается так же, как и в предыдущем посте, но для успешной сборки проекта для Linux необходимо расширить используемый соединением Linux SDK. Для этого требуется вызвать по правой кнопке мыши на Linux-target пункт меню Edit SDK и добавить туда путь к нужной библиотеке 'libib_util.so', которая по умолчанию находится в директории /opt/interbase/lib.

 

Для создания описаний этих UDF на сервере InterBase можно воспользоваться IBConsole или любым другим средством выполнения SQL в InterBase. Как видно на заглавной картинке, я воспользовался Embarcadero Rapid SQL из состава RAD Studio 10.2 Architect. 

Тестирование выполнялось по методике из статьи Д.Кузьменко:  "Тестировалась каждая функция следующим образом: из 3-х отдельных окон SQL Query одновременно выполнялся запрос

SELECT FUNCTION#(строка) FROM EMPLOYEE, EMPLOYEE
WHERE FUNCTION#(строка) <> строка

где строка – уникальная строка для каждого экземпляра Database Explorer, но одинаковой длины (10 символов). В том случае, если условие FUNCTION#(строка) <> строка по каким-то причинам нарушалось, ответ на запрос содержал бы минимум 1 строку. Это могло произойти только если результат функции перекрывался бы другим результатом функции. Такого у thread-safe функций произойти не может в принципе." Тест показал абсолютную безопасность всех трех способов при работе в многопользовательских средах. Сравнение временных характеристик вы можете выполнить самостоятельно.

Для успеха, как и в прошлый раз, всего лишь потребовалось указать Linux64 вместо Windows как target-платформу - остальное Delphi сделал автоматически, но главное в этот раз - мы научились "подцеплять" дополнительные внешние библиотеки Linux для сборки приложений в Delphi. 



About
Gold User, Rank: 11, Points: 295
SC at Embarcadero. DB Tools expert Delphi practitioner

Comments

Check out more tips and tricks in this development video: