Создание InterBase UDF на Delphi для Linux (strings)
В предыдущем посте Создание 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.

Comments
-
Please login first in order for you to submit comments