Для того, чтобы в Oracle 11gR2 из PL/SQL вызвать функцию внешней динамической библиотеки необходимо:
-
создать динамически подгружаемую библиотеку с необходимой функцией, это
.so
- shared object - в ОС Unix и.dll
- dynamic link library - в ОС Windows, - поместить динамическую библиотеку в директорию на сервере, где работает экземпляр Oracle,
-
сконфигурировать процесс
exptproc
для того, чтобы он мог найти и загрузить динамическую библиотеку, - создать в БД Oracle объект LIBRARY, который присвоит имя полному пути к динамической библиотеке,
- создать в БД Oracle PL/SQL процедуру или функцию - обертку, указав объект LIBRARY и спецификацию вызова функции динамической библиотеки.
Я проделаю перечисленные шаги и продемонстрирую вызов функций внешней динамической библиотеки из PL/SQL.
Под пользователем oracle в ОС Linux создам директорию /home/oracle/test
и в ней - файл с исходным текстом программы на C:
[oracle@tsuki test]$ cat hello.c
#include
char result[4000];
char* hello(char* name) {
sprintf(result, "Hello, %s!", name);
return result;
}
void write(char* msg) {
FILE* file;
file = fopen("/home/oracle/test/hello.txt", "a");
fprintf(file, "%s\n", msg);
fclose(file);
}
Для функции hello
предстоит сделать PL/SQL функцию-обертку, для функции write
, не возвращающей значения, - PL/SQL процедуру-обертку.
Откомпилируем файл и построим динамическую библиотеку:
[oracle@tsuki test]$ gcc -shared -o libhello.so -fPIC hello.c
[oracle@tsuki test]$ ls -l
-rw-r--r-- 1 oracle oinstall 276 Jun 7 12:46 hello.c
-rwxr-xr-x 1 oracle oinstall 6545 Jun 7 12:46 libhello.so
Подробнее о том, как создать динамическую библиотеку в Linux при помощи инструментария gcc, можно прочитать в статье Shared libraries with GCC on Linux.
Прежде чем пытаться вызывать функции библиотеки из PL/SQL, проверю их работоспособность с помощью программы на C. Для этого, создам заголовочный файл для библиотеки libhello.so
и тестовую программку test.c
.
[oracle@tsuki test]$ cat hello.h
#ifndef hello_h_
#define hello_h_
extern char* hello(char* name);
extern void write(char* msg);
#endif // hello_h_
[oracle@tsuki test]$ cat test.c
#include
#include "hello.h"
int main(void) {
write(hello("world"));
return 0;
}
Создам исполняемый файл test
:
[oracle@tsuki test]$ gcc -Wall -L/home/oracle/test -o test test.c -lhello
[oracle@tsuki test]$ ls -l
-rw-r--r-- 1 oracle oinstall 276 Jun 7 12:46 hello.c
-rw-r--r-- 1 oracle oinstall 117 Jun 7 12:57 hello.h
-rwxr-xr-x 1 oracle oinstall 6545 Jun 7 12:46 libhello.so
-rwxr-xr-x 1 oracle oinstall 7072 Jun 7 13:01 test
-rw-r--r-- 1 oracle oinstall 99 Jun 7 12:59 test.c
Установлю переменную LD_LIBRARY_PATH
(для того, чтобы ОС нашла и подгрузила библиотеку libhello.so
) и выполню test
:
[oracle@tsuki test]$ ./test
./test: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory
[oracle@tsuki test]$ export LD_LIBRARY_PATH=/home/oracle/test:$LD_LIBRARY_PATH
[oracle@tsuki test]$ ./test
[oracle@tsuki test]$ ls -l hello.txt
-rw-r--r-- 1 oracle oinstall 14 Jun 7 13:05 hello.txt
[oracle@tsuki test]$ cat hello.txt
Hello, world!
[oracle@tsuki test]$ ./test
[oracle@tsuki test]$ cat hello.txt
Hello, world!
Hello, world!
Убедившись, что библиотека libhello.so
работоспособна, перехожу к конфигурированию процесса extproc
. Это процесс Oracle, который загружает внешние библиотеки при вызове функций этих библиотек из PL/SQL.
До версии Oracle 11gR2 для запуска процесса extproc
он должен был быть явно сконфигурирован в файлах listener.ora
и tnsnames.ora
. Однако, начиная с версии Oracle 11gR2 процесс extproc
не нуждается в явном конфигурировании, и изменять указанные файлы больше не требуется.
В файле extproc.ora
(расположенном в директории $ORACLE_HOME/hs/admin
в ОС Unix и в %ORACLE_HOME%\hs\admin
в ОС Windows) определяются переменные окружения для процесса extproc
. Приведу содержание этого файла с описанием переменной EXTPROC_DLLS
и ее установкой для работы с библиотекой libhello.so
:
[oracle@tsuki ~]$ cat $ORACLE_HOME/hs/admin/extproc.ora
#
# extproc.ora is used by extproc in the default Oracle configuration.
#
# This is a sample extproc init file that contains a name-value(s) pair which
# is same as the value of ENVS parameter in listener.ora file.
#
# Syntax: SET name=value (environment variable name and value)
#
# When specifying the EXTPROC_DLLS environment variable to restrict the DLLs
# that extproc is allowed to load, you may set EXTPROC_DLLS to one of the
# following values:
#
# * ONLY (maximum security)
#
# When EXTPROC_DLLS=ONLY:DLL[:DLL], only the specified DLL(s) can be loaded.
#
# Syntax: SET EXTPROC_DLLS=ONLY:DLL[:DLL]
#
# * NULL (the default value)
#
# When EXTPROC_DLLS=, only the DLL(s) in $ORACLE_HOME/bin and ORACLE_HOME/lib
# can be loaded.
#
# Syntax: SET EXTPROC_DLLS=
#
# * Colon-seperated list of the DLLs
#
# When EXTPROC_DLLS=DLL[:DLL], the specified DLL(s) and the DLLs in
# $ORACLE_HOME/bin and ORACLE_HOME/lib can be loaded.
#
# Syntax: SET EXTPROC_DLLS=DLL:DLL
#
# * ANY
# When EXTPROC_DLLS=ANY, DLL checking is disabled.
#
# Syntax: SET EXTPROC_DLLS=ANY
#
SET EXTPROC_DLLS=ONLY:/home/oracle/test/libhello.so
Следующий шаг - создать объект LIBRARY в БД Oracle:
SQL> create or replace library lib_hello as '/home/oracle/test/libhello.so';
Library created
Осталось создать PL/SQL функцию-обертку для библиотечной функции hello
и процедуру-обертку для библиотечной функции write
. Нам понадобится передавать внешним функциям строковые параметры и получать строкоый результат от функции hello
.
Создам PL/SQL функцию и процедуру для вызова внешних функций в пакете hello
:
SQL> create or replace package hello as
2
3 function hello(name varchar2) return varchar2
4 as language c
5 library lib_hello
6 name "hello"
7 ;
8
9 procedure write(msg varchar2)
10 as language c
11 library lib_hello
12 name "write"
13 parameters (msg string)
14 ;
15 end hello;
16 /
Package created
SQL> show error
No errors for PACKAGE AY.HELLO
Здесь для функции hello
не специфицированы типы данных параметра и возвращаемого значения - это значит, Oracle выполнит неявные преобразования к соответствующим типам данных по умолчанию. Типу данных PL/SQL VARCHAR2
соответствует по умолчанию тип спецификации STRING
(а ему - тип C char*
). А для процедуры write
тип данных параметра msg
специфицирован явно - чтобы продемонстрировать, как это делается.
Вызову функцию hello
и процедуру write
:
SQL> select hello.hello('мир') from dual;
HELLO.HELLO('МИР')
--------------------------------------------------------------------------------
Hello, мир!
SQL> call hello.write(hello.hello('everybody'));
Method called
Вернусь на сервер, в директорию /home/oracle/test
и проверю содержимое файла hello.txt
:
[oracle@tsuki test]$ cat hello.txt
Hello, world!
Hello, world!
Hello, everybody!
Итак, мы получили ожидаемый результат.
Ниже я приведу соответствия основных типов данных PL/SQL, типов данных для спецификации внешних процедур и типов данных С. Таблица подготовлена по данным Advanced Application Developer's Guide из официальной документации Oracle 11gR2:
PL/SQL | Тип данных спецификации | Тип данных C |
---|---|---|
BINARY_INTEGER
|
[UNSIGNED] CHAR
|
[unsigned] char [*]
|
FLOAT
|
FLOAT
|
float [*]
|
DOUBLE PRECISION
|
DOUBLE
|
double [*]
|
NUMBER
|
OCINUMBER
|
OCINumber *
|
CHAR
|
STRING
|
char *
|
LONG RAW
|
RAW
|
unsigned char *
|
BFILE
|
OCILOBLOCATOR
|
OCILobLocator *
|
DATE
|
OCIDATE
|
OCIDate *
|
TIMESTAMP
|
OCIDateTime
|
OCIDateTime [*]
|
INTERVAL DAY TO SECOND
|
OCIInterval
|
OCIInterval [*]
|
composite object types: collections (varrays, nested tables)
|
OCICOLL
|
OCIColl * or OCIArray * or OCITable *
|
При передаче значений из PL/SQL во внешние C функции и обратно возникают, в частности, следующие вопросы:
-
как передать значение
NULL
(то есть, отсутствие значения)? - как передать ограничения на размер данных (например, результирующая строка не должна быть длинее 100 символов)?
- как передать кодировку, в которой представлены строковые данные?
Разработчики Oracle позаботились дать ответы на эти вопросы с помощью параметров-свойств (properties), указываемых в части PARAMETERS
спецификации вызова наравне с обычными параметрами. В частности, определены следующие свойства:
-
INDICATOR
-
показывает, что другой параметр или возвращаемый результат имеет значение
NULL
. -
LENGTH
-
содержит текущую длину другого
IN
или[IN] OUT
параметра или возвращаемого значения. -
MAXLEN
-
содержит максимально допустимую длину другуого
[IN] OUT
параметра или возвращаемого значения. -
CHARSETID
-
содержит идентификатор набора символов передаваемой из PL/SQL строки (тот самый, что возвращает SQL функция
NLS_CHARSET_ID
). -
CHARSETFORM
- содержит 1 для набора символов БД (database charset, используемый с типами данных CHAR, VARCHAR2, CLOB) и 2 для национального набора символов (national charset, используемый с типами данных NCHAR, NVARCHAR2, NCLOB).
Подробнее о спецификации вызова внешних процедур и о работе с параметрами-свойствами можно прочитать в Database Advanced Application Developer's Guide.
В заключение, удалю из базы данных следы экспериментов:
SQL> drop package hello;
Package dropped
"объект LIBRARAY"
ОтветитьУдалитьвторая буква А лишняя.
Исправил, спасибо.
ОтветитьУдалить