oracle调用外部C DLL

时间:2022-08-31 09:19:26

什么是外部程序?

一个外部程序是存储在一个动态链接库(DLL)中的第三代语言程序,使用PL / SQL注册,并通过你叫做特殊处理。该例程必须从C调用,但可以用任何语言编写。

在运行时,PL / SQL动态加载库,然后调用该程序就像是PL / SQL子程序一样。为了保护您的数据库,例程运行在单独的地址空间中。但是,它完全参与了当前的交易。此外,例程可以回调数据库进行SQL操作。

外部程序提高了可重用性,效率和模块化。可以从PL / SQL程序调用已经编写并可用于其他语言的DLL。DLL只在需要时加载,所以内存被保存。此外,可以增强DLL而不影响调用程序。

通常,外部程序用于与嵌入式系统接口,解决科学和工程问题,分析数据或控制实时设备和流程。例如,您可以使用外部程序向机器人发送指令,解决偏微分方程,过程信号,分析时间序列或在视频显示器上创建动画。

此外,外部程序使您能够

将计算绑定程序从客户端移动到服务器,由于更多的计算能力和更少的跨网络通信,它们将更快地执行
将数据库服务器与外部系统和数据源进行接口
扩展数据库服务器本身的功能
注意:

此功能仅在支持DLL或动态加载的共享库(如Solaris .so库)的平台上可用。

创建外部程序
要创建外部过程,您和您的DBA执行以下步骤:

1.建立环境

您的DBA通过向文件tnsnames.ora和listener.ora添加条目并通过启动专用于外部过程的侦听器进程来设置调用外部过程的环境。有关详细信息,请参阅Oracle8管理员指南。

2.识别DLL

在这种情况下,DLL是存储外部过程的任何可动态加载的操作系统文件。为了安全起见,您的DBA控制对DLL的访问。使用该CREATE LIBRARY语句,DBA创建一个名为别名库的模式对象,该库代表该DLL。然后,如果您是授权用户,DBA会授予您EXECUTE对别名库的权限。

如果DBA授予您CREATE ANY LIBRARY权限,则可以使用以下语法创建自己的别名库:

CREATE LIBRARY library_name {IS | AS}’file_path’;
您必须指定DLL的完整路径,因为链接器无法解析仅对DLL名称的引用。在以下示例中,您将创建c_utils代表DLL的别名库utils.so:

将库c_utils创建为“/DLLs/utils.so”;
3.指定外部程序

您可以找到或写入一个新例程,然后将其添加到DLL中,或者直接在DLL中指定一个例程。

4.注册外部程序

在您调用外部程序之前,必须先注册。也就是说,您必须告诉PL / SQL在哪里找到过程,如何调用它以及通过它。注册外部程序后,可以从任何PL / SQL程序中调用它。它以授予用户名的权限执行。

注册外部程序

您可以通过编写一种特殊类型的PL / SQL独立或打包子程序来实现,该子程序像外部过程的代理一样。(默认情况下,它们具有相同的名称。)

您以常规方式编写PL / SQL存储的子程序,除了在其正文中,而不是声明和BEGIN… END块,您编写该EXTERNAL子句。本节记录外部程序的信息,如位置,名称,编写语言以及编译语言。语法如下:
EXTERNAL LIBRARY library_name
[NAME external_procedure_name]
[LANGUAGE language_name]
[CALLING STANDARD {C | PASCAL}]
[WITH CONTEXT]
[PARAMETERS (external_parameter[, external_prameter]…)];
where external_parameter stands for

{  CONTEXT 
 | {parameter_name | RETURN} [property] [BY REF] [external_datatype]}
and property stands for

{INDICATOR | LENGTH | MAXLEN | CHARSETID | CHARSETFORM}

了解外部条款

该EXTERNAL子句是PL / SQL和外部过程之间的接口。以下小节告诉PL / SQL在哪里可以找到过程,如何调用它以及通过什么过程。(仅LIBRARY需要子条款。)

LIBRARY

指定本地别名库。(您不能使用数据库链接指定远程库。)库名称是PL / SQL标识符。所以,如果你用双引号括起来,就会区分大小写。(默认情况下,名称以大写形式存储。)您必须具有EXECUTE别名库的权限。

NAME

指定要调用的外部过程。如果将过程名称括在双引号中,则它将区分大小写。(默认情况下,名称以大写形式存储。)如果省略此子句,则过程名称默认为PL / SQL子程序的大写名称。

LANGUAGE

指定写入外部程序的第三代语言。目前,只允许使用C语言。如果省略此子句,则语言名称默认为C.

CALLING STANDARD

指定编译外部过程的Windows NT调用标准(C或Pascal)。(在Pascal调用标准下,参数在堆栈中颠倒,被调用的函数必须弹出堆栈。)如果省略此子句,则调用标准默认为C.

WITH CONTEXT

指定一个上下文指针将被传递给外部过程。上下文数据结构对于外部过程是不透明的,但可用于由外部过程调用的服务例程。

PARAMETERS

指定传递给外部过程的参数的位置和数据类型。它还可以指定参数属性,如当前长度和最大长度,以及首选参数传递方法(按值或引用)。

一个例子

假设c_gcd找到两个数字的最大公约数的C例程存储在DLL中utils.so,并且您具有EXECUTE别名库的权限c_utils。C原型c_gcd如下:

int c_gcd(int x_val,int y_val);
在以下示例中,您编写一个名为“ gcd寄存器C”例程的PL / SQL独立函数c_gcd作为外部函数:

CREATE FUNCTION gcd (
-- find greatest common divisor of x and y
   x BINARY_INTEGER, 
   y BINARY_INTEGER) 
RETURN BINARY_INTEGER AS EXTERNAL
   LIBRARY c_utils
   NAME "c_gcd"  -- quotes preserve lower case
   LANGUAGE C;

调用外部程序

你不直接调用外部程序。而是调用注册外部过程的PL / SQL子程序。这样的调用,你以常规方式编码,可以出现在

匿名块
独立和打包的子程序
对象类型的方法
数据库触发器
SQL语句(只调用打包函数)
注意:

要从SQL语句调用打包的函数,必须使用pragma RESTRICT_REFERENCES,它会声明函数的纯度级别(函数的程度无副作用)。PL / SQL无法检查相应外部程序的纯度级别。所以,请确保例行程序不会违反规范。否则,可能会产生意想不到的结果。

在服务器端或客户端上执行的任何PL / SQL块或子程序(例如,在Developer / 2000工具(如Oracle Forms)中)都可以调用外部过程。唯一的要求是从C代码调用外部过程。

在服务器端,外部过程在单独的进程地址空间中运行,从而保护您的数据库。图10-1显示了Oracle8和外部过程如何交互。

图10-1 Oracle8与外部过程的交互
oracle调用外部C DLL

一个例子

在最后一个例子中,您编写了PL / SQL函数gcd,其中注册了外部过程c_gcd,如下所示:
CREATE FUNCTION gcd (
– find greatest common divisor of x and y
x BINARY_INTEGER,
y BINARY_INTEGER)
RETURN BINARY_INTEGER AS EXTERNAL
LIBRARY c_utils
NAME “c_gcd” – quotes preserve lower case
LANGUAGE C;

在下面的示例中,您可以gcd从匿名块中调用PL / SQL函数。PL / SQL将两个整数参数传递给外部函数 c_gcd,该函数返回最大的公约数。

DECLARE
   g BINARY_INTEGER;
   a BINARY_INTEGER;
   b BINARY_INTEGER;
   ...
BEGIN
   ...
   g := gcd(a, b);  -- call function
   IF g IN (2,4,8) THEN ... 

PL / SQL如何调用外部过程

要调用外部过程,PL / SQL必须知道它驻留在哪个DLL中。因此,PL / SQL在EXTERNAL注册外部过程的子程序的子句中查找别名库,然后Oracle会在数据字典中查找DLL。

接下来,PL / SQL警报一个监听器进程,这进程会产生(启动)一个名为extproc的特定于会话的代理。然后,监听器将连接传递给extproc。PL / SQL传递给extproc DLL的名称,外部过程的名称以及任何参数。

然后,extproc加载DLL并运行外部过程。此外,extproc处理服务调用(例如引发异常)和回调到Oracle服务器。最后,extproc将向外部过程返回的任何值传递给PL / SQL。图10-2显示了控制流程。

图10-2如何调用外部程序
oracle调用外部C DLL

在外部过程完成后,Extproc在整个Oracle会话中保持活动状态。(当您注销时,extproc将被杀死。)所以,无论您打了多少个电话,您只需要产生一个extproc的费用一次。但是,只有计算好处大于成本时,才应该调用外部程序


注意:

侦听器必须在运行Oracle服务器的计算机上启动extproc。不支持在不同的机器上启动extproc。

环境变量

监听器设置了几个必需的环境变量(例如ORACLE_HOME,ORACLE_SID和LD_LIBRARY_PATH)为EXTPROC。否则,它为extproc提供了一个“干净”的环境。为extproc设置的环境变量与为客户端,服务器和监听器设置的环境变量无关。因此,在extproc进程中运行的外部过程无法读取为客户端,服务器或侦听器进程设置的环境变量。

将参数传递给外部过程

将参数传递给外部过程在几种情况下变得复杂:

一组PL / SQL数据类型与一组C数据类型不一一对应。
PL / SQL参数可以NULL,而C参数则不能。(与C不同,PL / SQL包括无效的RDBMS概念。)
外部过程可能需要当前长度或最大长度CHAR,LONG RAW,RAW,和VARCHAR2参数。
外部程序可能需要有关字符集信息CHAR,VARCHAR2以及CLOB参数。
PL / SQL可能需要外部过程返回的值的当前长度,最大长度或空状态。
在以下部分中,您将学习如何指定处理这些情况的参数列表。

指定数据类型

您不直接将参数传递给外部过程。而是将其传递给注册外部过程的PL / SQL子程序。因此,您必须为参数指定PL / SQL数据类型。有关指导,请参见表10-1。每个PL / SQL数据类型映射到默认的外部数据类型。(反过来,每个外部数据类型映射到C数据类型。)PL / SQL为您执行所有数据类型转换。

表10-1参数数据类型映射

PL/SQL Type Supported External Types Default External Type
BINARY_INTEGER [UNSIGNED] CHAR INT
BOOLEAN [UNSIGNED] SHORT
PLS_INTEGER [UNSIGNED] INT
[UNSIGNED] LONG
SB1, SB2, SB4
UB1, UB2, UB4
SIZE_T
NATURAL [UNSIGNED] CHAR UNSIGNED INT
NATURALN [UNSIGNED] SHORT
POSITIVE [UNSIGNED] INT
POSITIVEN [UNSIGNED] LONG
SIGNTYPE SB1, SB2, SB4
UB1, UB2, UB4
SIZE_T
FLOAT FLOAT FLOAT
REAL
DOUBLE PRECISION DOUBLE DOUBLE
CHAR STRING STRING
CHARACTER
LONG
NCHAR
NVARCHAR2
ROWID
VARCHAR
VARCHAR2
LONG RAW RAW RAW
RAW
BFILE OCILOBLOCATOR OCILOBLOCATOR
BLOB
CLOB
NCLOB

备注:MarkDown本身不支持跨行单元格、此表格为多对多表格。

在某些情况下,您可以使用该PARAMETERS子句覆盖默认的数据类型映射。例如,您可以将PL / SQL数据类型BOOLEAN从外部数据类型重新映射INT到外部数据类型CHAR。

为了在声明C原型参数时避免错误,请参见表10-2,该表显示了为给定的外部数据类型和PL / SQL参数模式指定的C数据类型。例如,如果OUT参数的外部数据类型是STRING,请在C原型中指定数据类型char *。

表10-2外部数据类型映射

External Datatype IN, RETURN IN by Ref, RETURN by Ref IN OUT, OUT
CHAR char char * char *
UNSIGNED CHAR unsigned char unsigned char * unsigned char *
SHORT short short * short *
UNSIGNED SHORT unsigned short unsigned short * unsigned short *
INT int int * int *
UNSIGNED INT unsigned int unsigned int * unsigned int *
LONG long long * long *
UNSIGNED LONG unsigned long unsigned long * unsigned long *
SIZE_T size_t size_t * size_t *
SB1 sb1 sb1 * sb1 *
UB1 ub1 ub1 * ub1 *
SB2 sb2 sb2 * sb2 *
UB2 ub2 ub2 * ub2 *
SB4 sb4 sb4 * sb4 *
UB4 ub4 ub4 * ub4 *
FLOAT float float * float *
DOUBLE double double * double *
STRING char * char * char *
RAW unsigned char * unsigned char * unsigned char *
OCILOBLOCATOR OCILobLocator * OCILobLocator * OCILobLocator **

使用参数条款

通常,注册外部过程的PL / SQL子程序声明了一个形式参数的列表,如下例所示:
CREATE FUNCTION interp (
– find the value of y at x degrees using Lagrange interpolation
x IN FLOAT,
y IN FLOAT)
RETURN FLOAT AS EXTERNAL
NAME “interp”
LIBRARY mathlib
LANGUAGE C;
每个形式参数声明指定名称,参数模式和PL / SQL数据类型(映射到默认外部数据类型)。这可能是外部程序需要的所有信息。如果没有,您可以使用该PARAMETERS子句提供更多信息,这可以让您指定

非默认外部数据类型
参数的当前和/或最大长度
参数的null / not null指示符
字符集ID和表单
列表中的参数位置
如何IN传递参数(按值或引用)

对于每个形式参数,PARAMETERS子句中必须有相应的参数。如果包含WITH CONTEXT子句,则必须指定参数CONTEXT,该参数显示参数列表中上下文指针的位置。另外,如果外部例程是一个函数,那么必须RETURN在最后一个位置指定参数。

指定属性

您还可以使用该PARAMETERS子句将关于PL / SQL形式参数和函数结果的附加信息传递给外部过程。您可以通过指定以下属性来执行此操作:

INDICATOR
LENGTH
MAXLEN
CHARSETID
CHARSETFORM

表10-3显示给定属性允许的外部数据类型,PL / SQL数据类型和PL / SQL参数模式。请注意,MAXLEN不能应用于IN参数

表10-3属性数据类型映射

Property C Parameter PL/SQL Parameter
Allowed External Types Default External Type Allowed Types Allowed Modes
INDICATOR SHORT SHORT all scalars IN
INT IN OUT
LONG OUT
RETURN
LENGTH [UNSIGNED] SHORT INT CHAR IN
[UNSIGNED] INT LONG RAW IN OUT
[UNSIGNED] LONG RAW OUT
VARCHAR2 RETURN
MAXLEN [UNSIGNED] SHORT INT CHAR IN OUT
[UNSIGNED] INT LONG RAW OUT
[UNSIGNED] LONG RAW RETURN
VARCHAR2
CHARSETID UNSIGNED SHORT UNSIGNED INT CHAR IN
CHARSETFORM UNSIGNED INT CLOB IN OUT
UNSIGNED LONG VARCHAR2 OUT
RETURN

在下面的示例中,我们使用该PARAMETERS子句来指定PL / SQL形式参数和函数结果的属性:

CREATE FUNCTION parse (
   x IN BINARY_INTEGER,
   Y IN OUT CHAR) 
RETURN CHAR AS EXTERNAL
   LIBRARY c_utils 
   NAME "c_parse" 
   LANGUAGE C
   CALLING STANDARD PASCAL
   PARAMETERS (
      x,            -- stores value of x
      x INDICATOR,  -- stores null status of x
      y,            -- stores value of y
      y LENGTH,     -- stores current length of y
      y MAXLEN,     -- stores maximum length of y
      RETURN INDICATOR,
      RETURN);

有了这个PARAMETERS条款,C原型就变成了

char * c_parse(int x,short x_ind,char * y,int * y_len,
int * y_maxlen,short * retind);
在C原型中的附加参数对应INDICATOR,LENGTH和MAXLEN中参数PARAMETERS子句。该参数RETURN对应于存储结果值的C函数标识符。

使用INDICATOR

一个指示器是一个参数,其值“表示”另一参数是否为空。PL / SQL不需要指示符,因为RDBMS的无效概念被内置到该语言中。但是,外部过程可能需要知道参数或函数结果是否为空。另外,一个外部程序可能需要向服务器通知返回的“值”实际上是一个null,并且应该被相应地对待。

在这种情况下,您可以使用属性INDICATOR将指标与形式参数相关联。如果PL / SQL子程序是一个函数,您还可以将指标与函数结果相关联。

要检查指标的值,可以使用常数OCI_IND_NULL和OCI_IND_NOTNULL。如果指示符相等OCI_IND_NULL,相关参数或函数结果为空。如果指示符相等OCI_IND_NOTNULL,则参数或函数结果不为空。

对于IN参数,INDICATOR通过值传递(除非您指定BY REF),并且是只读(即使指定BY REF)。For OUT,IN OUT和RETURN参数INDICATOR通过引用传递。

使用LENGTH和MAXLEN

在PL / SQL中,没有标准方法来指示原始或字符串参数的长度。但是,在许多情况下,您希望将参数的长度传递给外部过程。使用性质LENGTH和MAXLEN,你可以指定存储当前长度和形式参数的最大长度参数。

注意:

使用类型的参数,RAW或者LONG RAW必须使用该属性LENGTH。

对于IN参数,LENGTH通过值传递(除非您指定BY REF),并且是只读(即使指定BY REF)。For OUT,IN OUT和RETURN参数LENGTH通过引用传递。

MAXLEN不适用于IN参数。For OUT,IN OUT和RETURN参数MAXLEN通过引用传递,但是是只读的。

使用CHARSETID和CHARSETFORM

Oracle提供国家语言支持,可让您处理单字节和多字节字符数据并在字符集之间进行转换。它还允许您的应用程序在不同的语言环境中运行。

属性CHARSETID并CHARSETFORM识别形成要传递的字符数据的非默认字符集。使用CHAR,CLOB和VARCHAR2参数,可以使用CHARSETID并将CHARSETFORM字符集ID和表单传递给外部过程。

对于IN参数,CHARSETID并CHARSETFORM通过值传递(除非您指定BY REF),并且是只读(即使指定BY REF)。For OUT,,IN OUT和RETURN参数,CHARSETID并CHARSETFORM通过引用传递但是只读。

这些属性的OCI属性名称为OCI_ATTR_CHARSET_ID和OCI_ATTR_CHARSET_FORM。有关在OCI中使用NLS数据的更多信息,请参阅Oracle Call Interface Programmer’s Guide。

Repositioning Parameters

记住,每个形式参数必须在PARAMETERS子句中具有相应的参数。他们的位置可能不同,因为PL / SQL通过名称而不是位置来关联它们。但是,PARAMETERS子句和外部过程的C原型必须具有相同数量的参数。

Passing Parameters by Reference

在C中,您可以通过值(IN参数的值传递)或引用传递标量参数(传递给该值的指针)。当外部程序期望一个指向标量的指针时,指定BY REF短语通过引用传递参数。下面是一个例子:
CREATE PROCEDURE find_root (
x IN REAL, …)
AS EXTERNAL
LIBRARY c_utils
NAME “c_find_root”
PARAMETERS (
x BY REF, …);

在这种情况下,C原型将是

void c_find_root(float * x,…);
而不是默认

void c_find_root(float x,…);

Using the WITH CONTEXT Clause
通过包含该WITH CONTEXT子句,您可以给外部程序访问有关参数,异常,内存分配和用户环境的信息。该WITH CONTEXT子句指定一个上下文指针将被传递给外部过程。例如,如果您编写以下PL / SQL函数

CREATE FUNCTION get_num (
   x IN REAL) 
RETURN BINARY_INTEGER AS EXTERNAL
   LIBRARY c_utils
   NAME "c_get_num"
   LANGUAGE C 
   WITH CONTEXT
   PARAMETERS (
      CONTEXT,
      x BY REF,
      RETURN INDICATOR);

那么C原型就是

int c_get_num(
OCIExtProcContext *with_context,
float *x,
short *retind);
上下文数据结构对于外部过程是不透明的,但可用于由外部过程调用的服务例程。

如果还包括PARAMETERS子句,则必须指定参数CONTEXT,该参数显示参数列表中上下文指针的位置。如果省略PARAMETERS子句,则上下文指针是传递给外部过程的第一个参数。

使用服务程序

当从外部过程调用时,服务程序可以引发异常,分配内存,并获取OCI(Oracle Call Interface)句柄来回调服务器。要使用这些函数,必须指定该WITH CONTEXT子句,这样可以将上下文结构传递给外部过程。上下文结构在头文件中声明ociextp.h如下:

typedef struct OCIExtProcContext OCIExtProcContext;
现在,让我们看看服务程序如何使用上下文信息。

OCIExtProcAllocCallMemory

该服务例程在外部过程调用的持续时间内分配n个字节的存储器。一旦控制返回到PL / SQL,该函数分配的任何内存就会自动释放。

注意:

外部程序不需要(也不应该)调用C函数free()来释放此服务程序分配的内存。

该功能的C原型如下:
dvoid *OCIExtProcAllocCallMemory(
OCIExtProcContext *with_context,
size_t amount);
参数with_context和amount是上下文指针和字节数来分配,分别。该函数返回一个无类型指针到分配的内存。返回值为零表示失败。

在SQL * Plus中,假设您注册外部函数concat,如下所示:

SQL> CREATE FUNCTION concat (
  2     str1 IN VARCHAR2, 
  3     str2 IN VARCHAR2) 
  4  RETURN VARCHAR2 AS EXTERNAL
  5     NAME "concat"
  6     LIBRARY stringlib
  7     WITH CONTEXT
  8     PARAMETERS (
  9        CONTEXT, 
 10        str1   STRING, 
 11        str1   INDICATOR short, 
 12        str2   STRING, 
 13        str2   INDICATOR short, 
 14        RETURN INDICATOR short, 
 15        RETURN LENGTH short, 
 16        RETURN STRING);

调用时,concat连接两个字符串,然后返回结果。如果任一字符串为空,结果也为空。如下面的示例示出了,concat使用OCIExtProcAllocCallMemory用于结果字符串分配内存:

char *concat(ctx, str1, str1_i, str2, str2_i, ret_i, ret_l)
OCIExtProcContext *ctx;
char   *str1;
short  str1_i;
char   *str2;
short  str2_i;
short  *ret_i;
short  *ret_l;
{
  char *tmp;
  short len;
  /* Check for null inputs. */
  if ((str1_i == OCI_IND_NULL) || (str2_i == OCI_IND_NULL))
  {
      *ret_i = (short)OCI_IND_NULL;
      /* PL/SQL has no notion of a null ptr, so
         return a zero-byte string. */ 
      tmp = OCIExtProcAllocCallMemory(ctx, 1); 
      tmp[0] = '\0'; 
      return(tmp); 
  }
  /* Allocate memory for result string, including null terminator. */
  len = strlen(str1) + strlen(str2);
  tmp = OCIExtProcAllocCallMemory(ctx, len + 1);

  strcpy(tmp, str1);
  strcat(tmp, str2);

  /* Set null indicator and length. */
  *ret_i = (short)OCI_IND_NOTNULL;
  *ret_l = len;

  /* Return pointer, which PL/SQL frees later. */
  return(tmp);
}

OCIExtProcRaiseExcp
此服务程序引发预定义的异常,它必须具有范围为1 … 32767的有效的Oracle错误编号。执行任何必要的清除后,外部过程必须立即返回。(没有赋值OUT或IN OUT参数。)此函数的C原型如下:

int OCIExtProcRaiseExcp(
   OCIExtProcContext *with_context, 
   size_t error_number);

参数with_context和error_number 是上下文指针和Oracle错误号。返回值OCIEXTPROC_SUCCESS并OCIEXTPROC_ERROR指示成功或失败。

在SQL * Plus中,假设您注册外部过程divide,如下所示:

SQL> CREATE PROCEDURE divide (
  2     dividend IN BINARY_INTEGER, 
  3     divisor  IN BINARY_INTEGER, 
  4     result   OUT FLOAT) 
  5  AS EXTERNAL
  6     NAME "divide"
  7     LIBRARY mathlib
  8     WITH CONTEXT
  9     PARAMETERS (
 10        CONTEXT, 
 11        dividend int, 
 12        divisor  int, 
 13        result   float);

当被调用时,divide找到两个数字的商。如下例所示,如果除数为零,则divide用于OCIExtProcRaiseExcp提高预定义异常ZERO_DIVIDE:

void divide (ctx, dividend, divisor, result)
OCIExtProcContext *ctx;
int    dividend;
int    divisor;
float  *result;
{
  /* Check for zero divisor. */
  if (divisor == (int)0) 
  {
    /* Raise exception ZERO_DIVIDE, which is Oracle error 1476. */
    if (OCIExtProcRaiseExcp(ctx, (int)1476) == OCIEXTPROC_SUCCESS)
    {
      return;
    }
    else
    {
      /* Incorrect parameters were passed. */
      assert(0);
    }
  }
  *result = (float)dividend / (float)divisor;
}

OCIExtProcRaiseExcpWithMsg
此服务例程引发用户定义的异常并返回用户定义的错误消息。该功能的C原型如下:

int OCIExtProcRaiseExcpWithMsg(
   OCIExtProcContext *with_context, 
   size_t error_number,
   text   *error_message, 
   size_t  len);

参数with_context,error_number以及error_message上下文指针,Oracle错误编号和错误消息文本。该参数len存储错误消息的长度。如果消息是以空值终止的字符串,len则为零。返回值OCIEXTPROC_SUCCESS并OCIEXTPROC_ERROR指示成功或失败。

在上一个示例中,您注册了外部过程divide,如下所示:

SQL> CREATE PROCEDURE divide (
  2     dividend IN BINARY_INTEGER, 
  3     divisor  IN BINARY_INTEGER, 
  4     result   OUT FLOAT) 
  5  AS EXTERNAL
  6     NAME "divide"
  7     LIBRARY mathlib
  8     WITH CONTEXT
  9     PARAMETERS (
 10        CONTEXT, 
 11        dividend int, 
 12        divisor  int, 
 13        result   float);

在下面的示例中,您使用不同的版本divide。使用此版本,如果除数为零,则divide用于OCIExtProcRaiseExcpWithMsg引发用户定义的异常:

void divide (ctx, dividend, divisor, result)
OCIExtProcContext *ctx;
int    dividend;
int    divisor;
float  *result;
  /* Check for zero divisor. */
  if (divisor == (int)0) 
  {
    /* Raise a user-defined exception, which is Oracle error 20100,
       and return a null-terminated error message. */
    if (OCIExtProcRaiseExcpWithMsg(ctx, (int)20100, 
          "divisor is zero", 0) == OCIEXTPROC_SUCCESS)
    {
      return;
    }
    else
    {
      /*  Incorrect parameters were passed. */
      assert(0);
    }
  }
  *result = dividend / divisor;
 }

OCIExtProcGetEnv
此服务例程在外部过程调用期间启用OCI回调数据库。使用此函数获取的OCI句柄仅用于回调。如果将它们用于标准OCI调用,则句柄将建立与数据库的新连接,不能用于同一事务中的回调。换句话说,在外部过程调用期间,您可以使用OCI句柄进行回调或新连接,但不能同时使用。

该功能的C原型如下:

sword OCIExtProcGetEnv(
   OCIExtProcContext *with_context, 
   OCIEnv    **envh, 
   OCISvcCtx **svch, 
   OCIError  **errh);

该参数with_context是上下文指针,和参数envh,svch以及errh是OCI环境,服务,和错误把手,分别。返回值OCIEXTPROC_SUCCESS并OCIEXTPROC_ERROR指示成功或失败。

Doing Callbacks

在Oracle服务器上执行的外部过程可以调用服务例程来获取OCI环境和服务句柄。使用OCI,您可以使用回调来执行SQL语句和PL / SQL子程序,获取数据和操作LOB。此外,回调和外部过程在相同的用户会话和事务上下文中操作。所以他们有相同的用户权限。

在SQL * Plus中,假设您运行以下脚本:

CREATE TABLE emptab (empno NUMBER(10))
/
CREATE PROCEDURE insert_emptab (
   empno BINARY_INTEGER)
AS EXTERNAL
   NAME "insert_emptab"
   LIBRARY insert_lib
   WITH CONTEXT
   PARAMETERS (
      CONTEXT, 
      empno LONG)
/

之后,您可以OCIExtProcGetEnv从外部程序调用服务程序insert_emptab,如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <oratypes.h>
#include <oci.h>
...
void insert_emptab (ctx, empno) 
OCIExtProcContext *ctx; 
long empno; 
{ 
  OCIEnv    *envhp; 
  OCISvcCtx *svchp; 
  OCIError  *errhp; 
  int        err; 
  ... 
  err = OCIExtProcGetEnv(ctx, &envhp, &svchp, &errhp); 
  ... 
}

回调限制

使用回调,不支持以下SQL命令和OCI例程:

事务控制命令如 COMMIT
数据定义命令如 CREATE
面向对象的OCI例程如 OCIRefClear
轮询模式OCI例程,如 OCIGetPieceInfo
所有这些OCI例程:
OCIEnvInit
OCIInitialize
OCIPasswordChange
OCIServerAttach
OCIServerDetach
OCISessionBegin
OCISessionEnd
OCISvcCtxToLda
OCITransCommit
OCITransDetach
OCITransRollback
OCITransStart

另外,使用OCI例程OCIHandleAlloc,不支持以下句柄类型:

OCI_HTYPE_SERVER
OCI_HTYPE_SESSION
OCI_HTYPE_SVCCTX
OCI_HTYPE_TRANS

调试外部程序

通常情况下,外部程序失败时,其原型机有故障。也就是说,原型与PL / SQL内部生成的原型不符。如果指定不兼容的C数据类型,则可能会发生这种情况。例如,要传递一个OUT类型的参数REAL,你必须指定float 。指定float,double 或任何其他C数据类型将导致不匹配。

在这种情况下,您可能会收到与外部过程代理错误的丢失的RPC连接,这意味着代理extproc异常终止,因为外部过程导致了核心转储。为了在声明C原型参数时避免错误,请参见表10-2。

使用软件包DEBUG_EXTPROC

为了帮助您调试外部程序,PL / SQL提供了实用程序包DEBUG_EXTPROC。要安装软件包,请运行脚本dbgextp.sql,您可以在PL / SQL演示目录中找到该脚本。(有关目录的位置,请参阅Oracle安装或用户指南。)

要使用包装,请按照说明进行操作dbgextp.sql。您的Oracle帐户必须具有EXECUTE包和CREATE LIBRARY权限的权限。

注意:

DEBUG_EXTPROC 仅适用于可附加到正在运行的进程的调试器的平台上。

演示程序

在PL / SQL演示目录中也是脚本extproc.sql,它演示了外部过程的调用。伴随文件extproc.c包含外部过程的C源代码。

要运行演示,请按照说明进行操作extproc.sql。您必须使用SCOTT/TIGER必须具有CREATE LIBRARY权限的帐户。

外部程序指南

在未来的版本中,extproc可能是一个多线程进程。所以,请务必编写线程安全的外部程序。这样,如果extproc变为多线程,它们将继续正常运行。特别是避免使用静态变量,这些变量可以由在单独的线程中运行的例程共享。否则,可能会产生意想不到的结果。

有关创建动态链接库的帮助,请查看RDBMS子目录/public,找到模板makefile。

调用外部程序时,不要写入IN参数或溢出参数的容量OUT。(PL / SQL不会对这些错误条件进行运行时检查。)同样,从不读取OUT参数或函数结果。另外,总是分配一个值IN OUT和OUT参数和函数结果。否则,您的外部过程将不会成功返回。

如果包含WITH CONTEXT和PARAMETERS子句,则必须指定参数CONTEXT,该参数显示参数列表中上下文指针的位置。如果省略PARAMETERS子句,则上下文指针是传递给外部过程的第一个参数。

如果包含PARAMETERS子句,外部例程是一个函数,则必须在最后一个位置指定参数RETURN(非RETURN 属性)。

对于每个形式参数,PARAMETERS子句中必须有相应的参数。另外,确保PARAMETERS子句中的参数的数据类型与C原型中的参数的数据类型相兼容,因为没有进行隐式转换。

使用类型的参数,RAW或者LONG RAW必须使用该属性LENGTH。此外,如果该参数为IN OUT或或为OUT空,则必须将相应C参数的长度设置为零。

对外部程序的限制

目前,以下限制适用于外部程序:

此功能仅在支持DLL的平台上可用。
只支持从C代码(不是C ++代码)调用的例程。
您不能将PL / SQL游标变量,记录,集合或对象类型的实例传递到外部过程。
在LIBRARY子条款中,您不能使用数据库链接来指定远程库。
侦听器必须在运行Oracle服务器的计算机上启动代理extproc。不支持在其他计算机上启动extrproc。
可以传递给C外部过程的参数的最大数量为128.但是,如果通过值传递float或double参数,则最大值小于128.少量取决于此类参数和操作系统的数量。要得到一个粗略的估计,计数每个浮点数或双重值传递为两个参数。