Потребовалось мне разработать UDF для Firebird. которая возвращала бы значение типа BLOB. Причём, бинарный, а не текстовый. Сделал по образу и подобию. Получение входных данных, их преобразование и сохранение результирующих данных в буфер проблем не вызвало. А вот с переносом данных в выходной параметр возникли проблему.
где BufPtr — указатель на данные, BufLen — размер буфера, а Blob — указатель на структуру BLOBCALLBACK.
Всё работает нормально за исключением пустяка: в выходной BLOB попадают данные только до первого нулевого байта. То есть получается, что данные воспринимаются как строка с терминальным символом. Как запихать в выходной BLOB все данные из буфера, ориентируясь на размер данных из BufLen?
Вопрос не критичный, так как проблему решили другим способом. Но если кто знает ответ, поделитесь, плиз. А то осталась заноза в голове.
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
04-10-2024 07:03 | Комментарий к предыдущим ответам
>>>Обычная строка, состоящая исключительно из печатных однобайтных символов. Скажем так — #$21..#$FE
У нас с вами, здесь произошла небольшая терминологическая путаница.
Функция get_code(), предназначена для вызова из sql запросов и объявляется как UDF (с помощью ddl). Собственно, на такие функции и налагаются жёсткие ограничения по типам параметров и механизму их передачи.
Но здесь, вы всё правильно сделали. Передали строку посредством параметра типа CString.
А вот "вспомогательная функция", для которой строка из первой функции "подаётся на вход другой функции, которая выделяет память и загоняет туда некий бинарник, полученный на основании этой строки" - хоть и находится в той же dll, предназначенной для FB, но уже UDF являться на может (поскольку не предполагает вызова из sql). Поэтому, для неё вы вольны использовать параметры любого типа (доступные в Delphi). И в общем-то, даже можно в параметрах передавать вместо PBlob, указатель на буфер обрабатываемых данных, предназначенных для PutSegment(). Другими словами, это обычная функция Delphi (и даже не cdecl, и не export).
Я полагал, что у вас возникла ошибка именно в функции предназначенной для UDF.
Обычная строка, состоящая исключительно из печатных однобайтных символов. Скажем так — #$21..#$FE
Даже пробелов нет. А вот потом эта строка подаётся на вход другой функции, которая выделяет память и загоняет туда некий бинарник, полученный на основании этой строки. И вот там уже могут быть все 255 возможных байт.
Не заморачивайтесь особо, пытаясь угадать, как именно я достиг такого "феноменального" результата :-) Возможно, я чего-то вообще напутал в процессе экспериментов. Тем более, что последний из тех "экспериментов" вообще ничего не возвращает. А что там было на каждом из предыдущих шагов, теперь уже не узнать.
>>> В вашем вопросе топика, функция была объявлена в библиотеке dll Delphi так
Ничуть того ни бывало. Это была вспомогательная процедура. А экспортировалась примерно такая процедура
procedure get_code(S: PAnsiChar; Blob: PBlob);
var
BufPtr: Pointer;
BufLen: Integer;
begin
// здесь некоторый код, который для строки S
// создаёт некоторый код, который загоняет в
// выделенную память размера BufLen, на которую
// указывает указатель BufPtr.
try
BufToBlob(BufPtr, BufLen, Blob);
finally
FreeMem(BufPtr);
end;
end;
Сейчас я сваял тестовый пример по образу и подобию, только результирующий код не рассчитывается, а уже находится в константном массиве.
Какой точно был код раньше, и почему он загонял в BLOB только данные до первого нулевого байта, теперь уже установить не получится, так как в процессе экспериментов код многократно переписан. Главное — я всё же достиг нужного результата. Хотя бы для 32 разрядов
>>>Про объявление понятно: я ж говорю, что у меня работает, только данные не все переносит, если есть нулевые байты.
Если я правильно вас понял, то данные у вас передаются через параметр функции.
Поэтому повторю.
UDF - это функция, в терминологии Firebird. Вы создаёте библиотеку dll в Delphi. В этой dll создаёте экспортируемую функцию (или процедуру). Параметры этой функции записываются согласно синтаксису и типам языка Delphi. Затем, вы в FB выполняете скрипт ddl в котором создаёте метаданные в БД для этой функции (уже UDF). Параметры этой функции записываются согласно синтаксису и типам доступным в FB. (Таблица соответствия типов FB и Delphi приведена по ссылке в Обзорной статье).
При использовании созданной библиотеки, она грузится в адресное пространство FB, как обычная dll. Но при вызове функции (UDF) из запроса, происходит вызов кода функции из dll не напрямую, а через некоторый промежуточный механизм. В частности, "типы данных Delphi" интерпретируются как "типы данных FB". А перед тем, как выполнить UDF, FB создаёт копии входных переменных в памяти, и передаёт указатели именно на эти копии.
В вашем вопросе топика, функция была объявлена в библиотеке dll Delphi так:
Соответствующее, предполагаемое описание этой UDF на ddl FB:
declare external function Buf2Blob
cstring(254),
integer,
blob
returns parameter 3
При таком описании, первый параметр BufPtr, будет интерпретирован в UDF как строка с нулевым окончанием. И видимо, поэтому и "попадают данные только до первого нулевого байта".
Попробуйте использовать вот такое описание:
procedure Buf2Blob(BlobSrc: PBlob; var BufLen: Integer; BlobDest: PBlob); cdecl; export;
Соответствующее, описание этой UDF на ddl FB:
declare external function Buf2Blob
blob,
integer,
blob
returns parameter 3
Про объявление понятно: я ж говорю, что у меня работает, только данные не все переносит, если есть нулевые байты.
Понял, что ничего не понял :-) Почему-то если экспортируемую функцию объявить в dpr-файлу, то не работает. А вот если вынести в отдельный юнит, то работает. Сделал тестовую UDF с тестовыми данными (похожими на настоящие) на Delphi-7 под 32-разрядный FireBird 2.5.
Работает корректно, возвращает все 12 байт. Проверил на вот таком запросе SELECT getcode(2) FROM rdb$database
Считаем вопрос решённым. Нужно просто очень аккуратно определять типы, выбирать правильную платформу и не забывать всюду совать CDECL (в FireDuck callback-функции определены с моделью вызова stdcall).
Предыдущий некорректно работающий пример привести не могу, так как я с ним экспериментировал до состояния полной неработоспособности :)
>>>Получение входных данных, их преобразование и сохранение результирующих данных в буфер проблем не вызвало.
Предполагается, что функции UDF будут вызываться внутри FB сервера. Поэтому кроме описания функции в исходном коде (*.pas), требуется ещё и её объявление на sql (declare external function ...). В "sql объявлении" допускаются только те типы, которые известны FB. Ожидается, что параметры в "исходном коде" функции и в её "sql объявлении" будут сопоставимы. Поскольку такой текст "sql объявления" вы не привели, предположу:
- поскольку у вас первый аргумент функции имеет тип pointer, которого нет среди типов FB, он был преобразован к одному из строковых типов.
- что в "sql объявлении", тип первого аргумента указан как CString. А CString – это строка символов, оканчивающаяся нулевым символом.
>>>Сделал по образу и подобию.
Если не сложно, поделитесь ссылкой.
>>>Как запихать в выходной BLOB все данные из буфера, ориентируясь на размер данных из BufLen?
1. Обзорная статья "об UDF на Delphi". В конце статьи, есть пример функции загрузки blob из файла: http://www.ibase.ru/udf_ok/
2. Библиотека FreeUDFLib. В файле BlobFncs.pas можно посмотреть примеры функций, использующих как входные, так и выходные параметры типа blob: http://www.ibase.ru/d_udf/
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.