Skip to Main Content
  • Questions
  • External Procedure -- data type declaration.

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Bala.

Asked: October 21, 2000 - 11:56 am UTC

Last updated: April 29, 2010 - 7:51 am UTC

Version: 8.0.5

Viewed 1000+ times

You Asked

Hi Tom,

I having problems in calling a 'C' ext proc in my plsql procedure.
The EXT proc requires char(10000) as one of the parmeter.
If i declare char(10000) in the calling plsql proc then
i am getting a 'unable to read memory error'

If i declare char(4000) then i am getting
'unable to write at memory' error.

I tried datatype long also,
getting 'unable to read at mem' error.

This is the sqlplus session........
SQL> drop library ssan3;

Library dropped.

SQL> create library ssan3 as
'f:\ssa\ssaname\myusa\n3sgus.dll';

Library created.

SQL>1 CREATE OR REPLACE PROCEDURE tcc_SSA_n3sgus(
2 P1 IN OUT CHAR,
3 P2 IN OUT CHAR,
4 P3 IN OUT CHAR,
5 P4 IN OUT CHAR,
6 P5 IN OUT CHAR,
7 P6 IN OUT CHAR,
8 P7 IN OUT CHAR,
9 P8 IN OUT CHAR,
10 P9 IN OUT CHAR,
11 P10 IN OUT CHAR, ----=====> CHAR(10000)
12 P11 IN OUT CHAR,
13 P12 IN OUT CHAR)
14 AS
15 EXTERNAL LIBRARY ssan3
16 NAME "n3sgusd"
17* LANGUAGE C;
SQL> /

Procedure created.

SQL> create or replace PROCEDURE SSA_GetKeyStack(
2 sSSANameIn IN VARCHAR2,
3 sSSAKeyString OUT VARCHAR2,
4 sSSAKeynumber OUT INTEGER)
5 IS
6 -- constants for key stack fields
7 KS_KEY_SZ CONSTANT INTEGER := 8;
8 KS_TYPE CONSTANT INTEGER := 2;
9 KS_ELEMENT_SZ CONSTANT INTEGER := (KS_TYPE + 2);
10
11 -- SSA n3sgus function call argument lists
12 SSA_Nameset_Service CHAR(8);
13 SSA_Response_Code CHAR(20);
14 SSA_Nameset_Function CHAR(32);
15 SSA_Name_In CHAR(75);
16 SSA_Name_Clean CHAR(75);
17 SSA_Words_Stack CHAR(258);
18 SSA_Keys_Stack CHAR(202);
19 SSA_Search_Table CHAR(806);
20 SSA_Categories CHAR(600);
21 SSA_Work_Area CHAR(10000); --===========> HERE
22 SSA_Dummy_Parm CHAR(1);
23
24 BEGIN
25 -- init variables for calling NAMESETP service
26 SSA_Nameset_Service := 'NAMESETP';
27 SSA_Response_Code := '9000000000' || '9000000000';
28 SSA_Nameset_Function:= '*NOSTAB*';
29 SSA_Name_In := sSSANameIn;
30 SSA_Name_Clean := ' ';
31 SSA_Words_Stack := ' ';
32 SSA_Keys_Stack := ' ';
33 SSA_Search_Table := ' ';
34 SSA_Categories := ' ';
35 SSA_Work_Area := ' ';
36 SSA_Dummy_Parm := ' ';
37
38 -- Call to SSA external procedure
39 tcc_SSA_n3sgus(
40 SSA_Nameset_Service,
41 SSA_Response_Code,
42 SSA_Nameset_Function,
43 SSA_Name_In,
44 SSA_Name_Clean,
45 SSA_Words_Stack,
46 SSA_Keys_Stack,
47 SSA_Search_Table,
48 SSA_Categories,
49 SSA_Work_Area,
50 SSA_Dummy_Parm,
51 SSA_Dummy_Parm
52 );
53
54 sSSAKeynumber := TO_NUMBER(SUBSTR(SSA_Keys_Stack, 1, 2));
55
56 sSSAKeyString :=' ';
57
58 FOR i IN 0..(sSSAKeynumber-1) LOOP
59 -- stripes out the key types, returning only a string with all keys
60 sSSAKeyString:=sSSAKeyString||SUBSTR(SSA_Keys_Stack, 3 + i * KS_ELEMENT_SZ,
61 KS_KEY_SZ) ;
62
63 END LOOP;
64
65 END SSA_GetKeyStack;
66 /

Procedure created.

SQL> declare
2 n INTEGER;
3 keys VARCHAR2(100);
4 Name VARCHAR2(70);
5
6 BEGIN
7
8 Name:='JHON SMITH';
9 n:=0;
10 keys:=' ';
11
12 SSA_GetKeyStack(Name,keys,n);
13 DBMS_OUTPUT.PUT_LINE('* Name = '||name);
14 DBMS_OUTPUT.PUT_LINE('*');
15 DBMS_OUTPUT.PUT_LINE('* Keys');
16 DBMS_OUTPUT.PUT_LINE('* --------');
17
18
19 FOR i IN 0..(n-1) LOOP
20
21 DBMS_OUTPUT.PUT_LINE(i || ' ' || SUBSTR(keys,1+i*8,8));
22
23 END LOOP;
24
25 END;
26 /

======> here i am getting extproc.exe application error
the instruction at "0x00000000c" referenced momory at
"0x00000000c". The memory could not be "read" .

If i try char(4000) then i am getting memory could be written error.

declare
*

ERROR at line 1:
ORA-28576: lost RPC connection to external procedure agent
ORA-06512: at "BIN.TCC_SSA_N3SGUS", line 0
ORA-06512: at "BIN.SSA_GETKEYSTACK", line 39
ORA-06512: at line 12
------------------------------------------

Please help.........

Thanks
Bala..


and Tom said...

The problem is in the C code somewhere, not in the PLSQL code.

Here is an example of how I would do this. I would *never* pass a CHAR string to C without passing the length and the maxlength of the buffer. Also, I would never consider passing anything out to C without a NULL indicator (you'll get no data found exceptions flung at the weirdest times if you don't).

I would prototype a PLSQL routine to take an IN or IN OUT char string as follows:

procedure pass( p_in in char,
p_out in out char )
as
language C
name "pass_str"
library demoPassing
with context
parameters ( CONTEXT,
p_in STRING,
p_in INDICATOR short,

p_out STRING,
p_out INDICATOR short,
p_out MAXLEN int );

The C prototype for that will be:

void pass_str
(
OCIExtProcContext *, /* 1 : With-Context */
char *, /* 2 : P_IN */
short , /* 3 : P_IN (Indicator) */
int , /* 4 : P_IN (Length) */
char *, /* 5 : P_OUT */
short *, /* 6 : P_OUT (Indicator) */
int *, /* 7 : P_OUT (Length) */
int * /* 8 : P_OUT (Maxlen) */
);


I always pass a context, always -- you need it to be able to transmit error messages back to the client via OCIExtProcRaiseExcpWithMsg.


Now, given that I have the above prototype, my C function looks like:

#ifdef WIN_NT
_declspec (dllexport)
#endif
void pass_str
( OCIExtProcContext * ctx /* CONTEXT */,

char * p_istr /* STRING */,
short p_istr_i /* INDICATOR short */,
int p_istr_l /* LENGTH int */,

char * p_ostr /* STRING */,
short * p_ostr_i /* INDICATOR short */,
int * p_ostr_l /* LENGTH int */,
int * p_ostr_ml /* MAXLEN int */
)
{
if ( p_istr_i == OCI_IND_NOTNULL )
{
if ( *p_ostr_ml >= p_istr_l )
{
memmove( p_ostr, p_istr, p_istr_l );
*p_ostr_l = p_istr_l;
*p_ostr_i = OCI_IND_NOTNULL;
}
else
{
OCIExtProcRaiseExcpWithMsg(ctx,20001,
"String too small",0);
}
}
}


And when I run:

tkyte@TKYTE816> declare
2 x char(10000) default '*';
3 y char(10000) default 'xxx';
4 begin
5 demo_passing_pkg.pass( x, y );
6
7 if ( x = y ) then
8 dbms_output.put_line( 'Success' );
9 else
10 dbms_output.put_line( 'Failed' );
11 end if;
12 end;
13 /
Success

PL/SQL procedure successfully completed.

tkyte@TKYTE816>
tkyte@TKYTE816>
tkyte@TKYTE816> declare
2 x char(10001) default '*';
3 y char(10000) default 'xxx';
4 begin
5 demo_passing_pkg.pass( x, y );
6
7 if ( x = y ) then
8 dbms_output.put_line( 'Success' );
9 else
10 dbms_output.put_line( 'Failed' );
11 end if;
12 end;
13 /
declare
*
ERROR at line 1:
ORA-20001: String too small
ORA-06512: at "TKYTE.DEMO_PASSING_PKG", line 0
ORA-06512: at line 5


It works as expected.

Can you try that small example and if you still have problems -- we would need to see the C code and the PLSQL mapping to the C code as that is where the bug is.


Rating

  (28 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

what am I missing?

George, June 25, 2002 - 5:08 pm UTC

Tom,

I am receiving the error "Lost RPC connection", and trying
to copy this example as closely as possible to get results... I thought perhaps it was something to do with
my C code, but when I add a main() that calls my sh()
function, the resulting executable works fine.. so I have
focussed on passing parameters.. I have had no luck :-(
Ultimately, I'd like to execute something... for now I'd
be happy returning a number without raising an error.
Any help you can lend is greatly appreciated:

my SQL:

CREATE OR REPLACE LIBRARY spi_unix_extproc is
   '/tmp_mnt/home/gliblick/oscommands/spi_unix_extproc.so' ;
/

CREATE OR REPLACE PACKAGE spi_unix_util IS

  FUNCTION oscommand( ps_cmd in varchar2 )
     RETURN NUMBER ;

end spi_unix_util;
/

CREATE OR REPLACE PACKAGE BODY spi_unix_util IS

  FUNCTION oscommand( ps_cmd in varchar2 ) RETURN NUMBER
  AS
  LANGUAGE C
  NAME "sh"
  LIBRARY spi_unix_extproc
  WITH CONTEXT
  PARAMETERS ( CONTEXT,
               ps_cmd          STRING,
               ps_cmd  LENGTH  SB4,
               RETURN  INDICATOR int );

END spi_unix_util;
/

my C code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <oci.h>

#ifdef WIN_NT
_declspec (dllexport)
#endif

int sh( OCIExtProcContext      *ctx,
        char                   *cmd,
        short                  cmd_i,
        short                  *return_indicator )
{
   int num = 199;
/*
   num = system(cmd);
*/

   return( num );
}


and when I test it from SQL*Plus:

SQL> @test_c
BEGIN :x := spi_unix_util.oscommand( 'touch /tmp_mount/home/gliblick/oscommand/z' ); END;

*
ERROR at line 1:
ORA-28576: lost RPC connection to external procedure agent
ORA-06512: at "CALSDBA.SPI_UNIX_UTIL", line 0
ORA-06512: at line 1

I have also had our DBA compile and run this test as
the Oracle UNIX user, in the $ORACLE_HOME directory.. but
no luck.  I am now at a loss for how to proceed...
 

Tom Kyte
June 25, 2002 - 7:07 pm UTC

That function maps to a C routine with a prototype like this:

Maps to a C function with the following prototype:
OCINumber *sh
(
OCIExtProcContext *, /* 1 : With-Context */
char *, /* 2 : PS_CMD */
sb4 , /* 3 : PS_CMD (Length) */
int * /* 4 : RETURN (Indicator) */
);

You have short, where you should have sb4 (4 bytes, not 2 bytes). Also, you have short * for return indicator, that is wrong as well.

You are not setting the return indicator at all -- thats wrong as well.

Lastly -- you are saying the function returns a NUMBER. Well, that would map to a C Datatype of "OCINumber *" -- that is where the grievous error is in this case! You are returning the number 199 which, when we dereference as a pointer, causes an immediate seg-fault!

If you have my book -- i spend about 70 pages on this topic, going over everything you need to know (if you are interested)

Your quick fix (after fixing the INPUT parameters!) is to return the plsql type BINARY_INTEGER. That is mapped to the C int type which is what you are returning....



bah humbug ;-)

George, June 26, 2002 - 5:06 pm UTC

with this advice I have made something work...
and bought your book (at full price, grumble, grumble)
Both you and your book have been very helpful and I will
soon understand enough to complete my requirements :-)
What happenned to the good old days when I just executed
a "host" command - you don't have to answer that ?!

thanks and good tidings, George :-)

ORA-30110

A reader, August 14, 2003 - 11:07 am UTC

Hi Tom,

We are on AIX 5.1 with Oracle 8.1.7 and are getting the following
error in a
External Procedure:

ORA-30110: syntax error at '%.*s'

Please set up your test case as:

CREATE OR REPLACE LIBRARY test AS '/usr/dk/test/test.so';
/
CREATE OR REPLACE FUNCTION GetDummy
RETURN VARCHAR2
AS LANGUAGE C
LIBRARY test
NAME "getDummy"
WITH CONTEXT
PARAMETERS (CONTEXT,RETURN INDICATOR short,RETURN LENGTH int,RETURN
STRING
);
/

The parameter file test.ini contains:

debugf = true

File test.c is:

#include <stdio.h>
#include <string.h>
#include <oci.h>

OCIEnv *envhp;
OCISvcCtx *svchp;
OCIError *errhp;

char *getDummy( OCIExtProcContext *ctx, short *return_i, int
*return_l )
{

char *dummy = OCIExtProcAllocCallMemory(ctx,50);

ub1 false = 0, debugf_flag;
text *errbuf = OCIExtProcAllocCallMemory(ctx,512);
sb4 errcode = 0;

if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
{
OCIExtProcRaiseExcpWithMsg(ctx,20001,"Error",0);
return NULL;
}

if ( OCIExtractInit( envhp, errhp ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}

if ( OCIExtractSetNumKeys( envhp, errhp, 1 ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}

if ( OCIExtractSetKey( envhp, errhp, (text *) "debugf",
OCI_EXTRACT_TYPE_BOOLEAN, 0, &false, NULL, NULL ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}

if ( OCIExtractFromFile( envhp, errhp, 0, (text *)
"/tmp/test.ini" ) !=
OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
if ( OCIExtractToBool( envhp, errhp, (text *) "debugf",
0, &debugf_flag
) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,20002,"here1",0);
return NULL;
}
if ( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,20003,"here2",0);
return NULL;
}

strcpy(dummy,"Success.");
*return_l = strlen(dummy);
*return_i = OCI_IND_NOTNULL;
return dummy;
}

We compile using $ make -f $ORACLE_HOME/rdbms/demo/demo_rdbms.mk
extproc_with_context SHARED_LIBNAME=test.so OBJS="test.o"
Please help
Regards,


Tom Kyte
August 14, 2003 - 11:40 am UTC

how are you calling it?

i created a /test/temp.ini with:


[tkyte@localhost test2]$ cat /tmp/test.ini
debugf = true

[tkyte@localhost test2]$ od -c /tmp/test.ini
0000000   d   e   b   u   g   f       =       t   r   u   e  \n  \n
0000017


and then:

ops$tkyte@ORA920LAP> select getdummy from dual;

GETDUMMY
---------------------------------------------------
Success.



worked first time...
 

Thanks

A reader, August 14, 2003 - 1:42 pm UTC

Dear Tom,
I provided this test case to Oracle Metalink, they opened the bug with the number:3056017 initially. The bug diagnostics concentrated the test case to the memory allocation at the end, and bypassed the Cartridge Services routines. They have later on closed the bug, even when I've requested them to have a look at the Cartridge Sevices routines.
They now doubt if the above test case is valid, and are advising me to test the above in SYS or SYSTEM account.
The whole story is in TAR# 3163706.996 and TAR#2735554.1 (extension of the previous TAR).
I've waited endlessly for you in your forum for confirmation on this test case, and am extremely indebted on your prompt response on this.
Oracle Metalink on the other hand, promised us the resolution by 15th Aug (1 month after it was reported) after diagnosing this as a Bug, then denied it as a Bug, it is now not reporducible at Metalink and have asked to test it with SYS/SYSTEM account.

Extremely sorry for boring you with all this.
I've tried the od -c command it looks exactly the same as you have shown me. I have also tried to know the return code from OCIExtractFromFile it is OCI_ERROR, and error is ORA-30110, which says it could not find the occurance of debugf in the test.ini
Is there something I can do to find the solution?
Extremely thankful for your help.
Best regards,

Tom Kyte
August 14, 2003 - 2:08 pm UTC

well, they closed the bug because of a bug in the test case:

char *getDummy( OCIExtProcContext *ctx, short *return_i, int *return_l )
{
int *myCtx;
char dummy[50];
....

}
strcpy(dummy,"Success.");
*return_l = strlen(dummy);
*return_i = OCI_IND_NOTNULL;
return dummy;
}

you are returning a pointer to a stack variable!!!

that is why that bug was "not a bug"


but -- can you put a couple of

fopen( 'a')
fprintf ( 'im on line %d', __LINENO__ )
fclose

debug lines in there to see which call exactly is failing?


Thanks

A reader, August 14, 2003 - 2:34 pm UTC

Dear Tom,
Thanks very much for your response.
The code now looks like this:
#include <stdio.h>
#include <string.h>
#include <oci.h>

OCIEnv *envhp;
OCISvcCtx *svchp;
OCIError *errhp;
FILE *fp;

char *getDummy( OCIExtProcContext *ctx, short *return_i, int
*return_l )
{

char *dummy = OCIExtProcAllocCallMemory(ctx,50);

ub1 false = 0, debugf_flag;
text *errbuf = OCIExtProcAllocCallMemory(ctx,512);
sb4 errcode = 0;

if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
{
OCIExtProcRaiseExcpWithMsg(ctx,20001,(text
*)"Error",0);
return NULL;
}


if ( OCIExtractInit( envhp, errhp ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}

if ( OCIExtractSetNumKeys( envhp, errhp, 1 ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}

if ( OCIExtractSetKey( envhp, errhp, (text *) "debugf",
OCI_EXTRACT_TYPE_BOOLEAN,
0, &false, NULL, NULL ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}

if ( OCIExtractFromFile( envhp, errhp, 0,
(text *) "/tmp/test.ini" )
== OCI_ERROR )
{
fp = fopen("/tmp/a","w");
fprintf(fp,"im on line %d",__LINE__);
fclose(fp);
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
if ( OCIExtractToBool( envhp, errhp, (text *) "debugf",
0,
&debugf_flag ) == OCI_NO_DATA )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,20222,"here1",0);
return NULL;
}
if ( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,20003,"here2",0);
return NULL;
}

strcpy(dummy,"Success.");
*return_l = strlen(dummy);
*return_i = OCI_IND_NOTNULL;
return dummy;
}

The error in /tmp/a is
im on line 56

The line 56 is fprintf line (there is only one in the code).

Thanks very much for your time and effort.
Kind regards,


Tom Kyte
August 14, 2003 - 2:38 pm UTC

would you mind fopening /tmp/test.ini , freading the first 1k of it and fwriting what you get into the debug? just to see what we see?

i'll have to try later on 817 (only 9ir2 right now) but it'll be solaris

Thanks

A reader, August 14, 2003 - 2:57 pm UTC

Thanks very much Tom for your time and effort. I do not have the words for appreciation here.

I tried the following code:
#include <stdio.h>

FILE *fi,*fo;
char buffer[1024];

main()
{

fi = fopen("/tmp/test.ini","r");
fo = fopen("/tmp/debuf","w");
fread(buffer,sizeof(char),1024,fi);
fwrite(buffer,sizeof(char),1024,fo);
fclose(fi);
fclose(fo);
}

$ cc -o dk dk.c
$ dk
$ ll /tmp/debuf
-rw-rw-r-- 1 eve eve 1024 Aug 14 19:47 /tmp/debuf
$ cat /tmp/debuf
debugf = true

$ cat
/tmp/test.ini
debugf = true

$ ll /tmp/test.ini
-rwxrwxrwx 1 eve eve 15 Aug 14 18:19 /tmp/test.ini
$

I feel (based on the response of the first Support Analyst) that the problem is with the AIX machines, it is not with the solaris.
Thanks once again.
Kind regards

#include <stdio.h>

FILE *fi,*fo;
char buffer[1024];

main()
{

fi = fopen("/tmp/test.ini","r");
fo = fopen("/tmp/debuf","w");
fread(buffer,sizeof(char),1024,fi);
fwrite(buffer,sizeof(char),1024,fo);
fclose(fi);
fclose(fo);
}


Tom Kyte
August 14, 2003 - 3:00 pm UTC

you need to

n = fread()
fwrite( ..., n, ... )

and do it in the EXTPROC -- i wanna see what it sees

i'll try later.

Thanks

A reader, August 15, 2003 - 6:30 am UTC

Hi Tom,
Sorry I misunderstood.
#include <stdio.h>
#include <string.h>
#include <oci.h>

OCIEnv *envhp;
OCISvcCtx *svchp;
OCIError *errhp;

char *getDummy( OCIExtProcContext *ctx, short *return_i, int
*return_l )
{
FILE *fi,*fo;
char buffer[1024];
int n;
char *dummy;
dummy=OCIExtProcAllocCallMemory(ctx,50);

if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
{

}
fi = fopen("/tmp/test.ini","r");
fo = fopen("/tmp/debuf","w");
n = fread(buffer,sizeof(char),1024,fi);
fwrite(buffer,sizeof(char),n,fo);
fclose(fi);
fclose(fo);
strcpy(dummy,"Success.");
*return_l = strlen(dummy);
*return_i = OCI_IND_NOTNULL;
return dummy;
}

$ ll /tmp/debuf
-rw-r--r-- 1 oracle dba 15 Aug 15 08:59 /tmp/debuf
$ cat /tmp/debuf
debugf = true

$ cat /tmp/test.ini
debugf = true

$ ll /tmp/test.ini
-rwxrwxrwx 1 eve eve 15 Aug 14 18:19 /tmp/test.ini

$ od -c /tmp/test.ini
0000000 d e b u g f = t r u e \n \n
0000017

$ od -c /tmp/debuf
0000000 d e b u g f = t r u e \n \n
0000017
$


Tom Kyte
August 15, 2003 - 10:04 am UTC

Ok, lets try this piece of code. this is a generic template I use for all of my external procedures (if you have my book expert one on one Oracle -- this is from there)



include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#include <oci.h>

#ifdef WIN_NT
#define INI_FILE_NAME "c:\\temp\\extproc.ini"
#else
#define INI_FILE_NAME "/tmp/extproc.ini"
#endif

#define strupr(a) {char * cp; for(cp=a;*cp;*cp=toupper(*cp), cp++);}

/*
* This is our "context". It holds what normally might be global variables
* in a typical program. We cannot (should not) use global variables in an
* external procedure. Also, since static data will be reinitialized
* between calls -- globals would not work correct anyway. We will use the
* OCI context management API calls to get and set a global context for
* our extproc. You would add any state variables you needed to preserve
* from call to call in this structure below.
*
* the debugf_* variables are initialized from a parameter file. In our
* init() routine, we will read the parameter file and set these values.
* you may add your own parameters to this structure as well and set them
* in init() as I do.
*
* curr_lineno and curr_filename are set by the debugf macro below so when
* we create a trace file, I can tell you what line number and source code
* file a message was generated from
*
*/

typedef struct myCtx
{
OCIExtProcContext * ctx; /* Context passed to all external procs */
OCIEnv * envhp; /* OCI environment handle */
OCISvcCtx * svchp; /* OCI Service handle */
OCIError * errhp; /* OCI Error handle */

int curr_lineno;
char * curr_filename;

ub1 debugf_flag;
char debugf_path[255];
char debugf_filename[50];

/* add your own state variables here... */
}
myCtxStruct;


/*
* Here is the implementation of our "debugf" routine. It is used to
* instrument the code and aid in debugging runtime issues.
*
* It works much like the C printf function (well, exactly like it)
* in that you send a C format and then a varying number of arguments
* to be formatted and printed. We add to it the Year, Month, Day,
* Hour, Minute, Second in GMT to each trace record as well as the name
* of the C source code file and the line number.
*
* Note that in order to be as OS independent as possible, we use the
* OCI API's to write to files. The File API's are expected to have
* been initialized in the init() routine alread and will be closed out
* in the term() routine.
*/

void _debugf( myCtxStruct * myCtx, char * fmt, ... )
{
va_list ap;
OCIFileObject * fp;
time_t theTime = time(NULL);
char msg[8192];
ub4 bytes;

if ( OCIFileOpen( myCtx->envhp, myCtx->errhp, &fp,
myCtx->debugf_filename,
myCtx->debugf_path,
OCI_FILE_WRITE_ONLY, OCI_FILE_APPEND|OCI_FILE_CREATE,
OCI_FILE_TEXT ) != OCI_SUCCESS ) return;

strftime( msg, sizeof(msg),
"%y%m%d %H%M%S GMT ", gmtime(&theTime) );
OCIFileWrite( myCtx->envhp, myCtx->errhp, fp, msg, strlen(msg), &bytes );

va_start(ap,fmt);
vsprintf( msg, fmt, ap );
va_end(ap);
strcat( msg,"\n");

OCIFileWrite( myCtx->envhp, myCtx->errhp, fp, msg, strlen(msg), &bytes );
OCIFileClose( myCtx->envhp, myCtx->errhp, fp );
}

/*
* this macro is a more convienent way to use debugf. Instead of have
* to pass the __LINE__, __FILE__ each time we call -- we just code:
*
* debugf( myCtx, "This is some format %s", some_string );
*
* and this macro will set them in our context and then call _debugf for us.
*
*/
void _debugf( myCtxStruct * myCtx, char * fmt, ... );
#define debugf \
if ((myCtx!=NULL) && (myCtx->debugf_flag)) \
myCtx->curr_lineno = __LINE__, \
myCtx->curr_filename = __FILE__, \
_debugf


/*
* This routine works like the PLSQL builtin by the same name. It
* sets an application defined error message and error code. It also
* works like the C printf routine in that you pass to it a C format
* string and then a varying number of arguments
*
* The resulting string is limited to 8k in size. Note the call
* to debugf. Debugf is the macro above, we will use it throughout
* the code to 'instrument' it.
*/

static int raise_application_error( myCtxStruct * myCtx,
int errCode,
char * errMsg, ...)
{
char msg[8192];
va_list ap;

va_start(ap,errMsg);
vsprintf( msg, errMsg, ap );
va_end(ap);

debugf( myCtx, "raise application error( %d, %s )", errCode, msg );
if ( OCIExtProcRaiseExcpWithMsg(myCtx->ctx,errCode,msg,0) ==
OCIEXTPROC_ERROR )
{
debugf( myCtx, "Unable to raise exception" );
}
return -1;
}



/*
* This is a convienence routine to allocate storage for an error message
* and return it. Note that the type of storage we allocate is CALL based,
* hence, when we return from the extproc, OCI will automatically free it
* for us.
*
* This routine removes the trailing newline from the error message as well
*/
static char * lastOciError( myCtxStruct * myCtx )
{
sb4 errcode;
char * errbuf = (char*)OCIExtProcAllocCallMemory( myCtx->ctx, 256 );

strcpy( errbuf, "unable to retrieve message\n" );
OCIErrorGet( myCtx->errhp, 1, NULL, &errcode, errbuf,
255, OCI_HTYPE_ERROR );
errbuf[strlen(errbuf)-1] = 0;
return errbuf;
}

/*
* Here is the init routine. This routine must be called very early
* in the execution of any external procedure using this template. It
* will
* o retrieve the OCI handles and set them in the context for us
* o retrieve our CONTEXT, if the context has not yet been allocated,
* it will allocate storage for our context and save it. The memory
* is allocated to last as long as our process lasts.
* o it will read and retrieve the values from the parameter file if
* they have not been. You can add more parameters of your own by:
* - adding elements the the myCtxStruct above
* - increment the counter sent into ExtractSetNumKeys appropriately
* - adding a call to ExtractSetKey
* - adding a call to ExtractTo* (string, bool, int, etc)
* o it will initialize the FILE apis via OCIFileInit(). This is crucial
* for the successful operation of debugf above.
*
* You may add other "init" type of calls. for example, if you choose to
* use the String Formatting Interface (similar to vsprintf() but in OCI)
* You could add a call to OCIFormatInit() here. You would add the
* corresponding OCIFormatTerm() call to term() below.
*
*/
static myCtxStruct * init( OCIExtProcContext * ctx )
{
ub1 false = 0;
myCtxStruct *myCtx = NULL;
OCIEnv *envhp;
OCISvcCtx *svchp;
OCIError *errhp;
ub4 key = 1;


if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS )
{
OCIExtProcRaiseExcpWithMsg(ctx,20000,
"failed to get OCI Connection",0);
return NULL;
}
if ( OCIContextGetValue( envhp, errhp, (ub1*)&key, sizeof(key),
(dvoid**)&myCtx ) != OCI_SUCCESS )
{
OCIExtProcRaiseExcpWithMsg(ctx,20000,"failed to get OCI Context",0);
return NULL;
}

if ( myCtx == NULL )
{
if ( OCIMemoryAlloc( envhp, errhp, (dvoid**)&myCtx,
OCI_DURATION_PROCESS,
sizeof(myCtxStruct),
OCI_MEMORY_CLEARED ) != OCI_SUCCESS )
{
OCIExtProcRaiseExcpWithMsg(ctx,20000,
"failed to get OCI Memory",0);
return NULL;
}
myCtx->ctx = ctx;

myCtx->envhp = envhp;
myCtx->svchp = svchp;
myCtx->errhp = errhp;
if ( OCIContextSetValue( envhp, errhp,
OCI_DURATION_SESSION, (ub1*)&key,
sizeof(key), myCtx ) != OCI_SUCCESS )
{
raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
return NULL;
}

if (( OCIExtractInit( envhp, errhp ) != OCI_SUCCESS ) ||
( OCIExtractSetNumKeys( envhp, errhp, 3 ) != OCI_SUCCESS ) ||
( OCIExtractSetKey( envhp, errhp, "debugf",
OCI_EXTRACT_TYPE_BOOLEAN,
0, &false, NULL, NULL ) != OCI_SUCCESS ) ||
( OCIExtractSetKey( envhp, errhp, "debugf_filename",
OCI_EXTRACT_TYPE_STRING,
0, "extproc.log",
NULL, NULL ) != OCI_SUCCESS ) ||
( OCIExtractSetKey( envhp, errhp, "debugf_path",
OCI_EXTRACT_TYPE_STRING,
0, "", NULL, NULL ) != OCI_SUCCESS ) ||
( OCIExtractFromFile( envhp, errhp, 0,
INI_FILE_NAME ) != OCI_SUCCESS ) ||
( OCIExtractToBool( envhp, errhp, "debugf", 0,
&myCtx->debugf_flag ) != OCI_SUCCESS ) ||
( OCIExtractToStr( envhp, errhp, "debugf_filename", 0,
myCtx->debugf_filename,
sizeof(myCtx->debugf_filename ) )
!= OCI_SUCCESS ) ||
( OCIExtractToStr( envhp, errhp, "debugf_path",
0, myCtx->debugf_path,
sizeof(myCtx->debugf_path ) )
!= OCI_SUCCESS ) ||
( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS ))
{
raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
return NULL;
}
}
else
{
myCtx->ctx = ctx;
myCtx->envhp = envhp;
myCtx->svchp = svchp;
myCtx->errhp = errhp;
}
if ( OCIFileInit( myCtx->envhp, myCtx->errhp ) != OCI_SUCCESS )
{
raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
return NULL;
}
return myCtx;
}

/*
* This must be called after any successful call to init() above. It
* should be the last thing you call in your routine before returning
* from C to SQL
*/
static void term( myCtxStruct * myCtx )
{
OCIFileTerm( myCtx->envhp, myCtx->errhp );
}


/*
* everthing above this is BOILERPLATE!!!
*/




/*
* With the string (the varchar) we'll use 2 parameters -- the
* indicator variable and the LENGTH field. This time, much like an
* OUT parameter, we set the length field to let the caller know how
* long the returned string is.
*
* Many of the same considerations apply for returning strings as above
* -- we'll allocate storage, set the indicator, supply the value and
* return it...
*/
#ifdef WIN_NT
_declspec (dllexport)
#endif
char * return_string
( OCIExtProcContext * ctx,
short * return_i,
int * return_l )
{
char * data_we_want_to_return = "Hello World!";

char * return_value;
myCtxStruct*myCtx;

if ( (myCtx = init( ctx )) == NULL ) return NULL;
debugf( myCtx, "Enter return String" );

return_value = (char *)OCIExtProcAllocCallMemory(ctx,
strlen(data_we_want_to_return)+1 );

if( return_value == NULL )
{
raise_application_error( myCtx, -20001, "%s", "no memory" );
}
else
{
*return_i = OCI_IND_NULL;
strcpy( return_value, data_we_want_to_return );
*return_l = strlen(return_value);
*return_i = OCI_IND_NOTNULL;
}
term(myCtx);
return return_value;
}



The Makefile I use is:

MAKEFILE= $(ORACLE_HOME)/rdbms/demo/demo_rdbms.mk

INCLUDE= -I$(ORACLE_HOME)/rdbms/demo \
-I$(ORACLE_HOME)/rdbms/public \
-I$(ORACLE_HOME)/plsql/public \
-I$(ORACLE_HOME)/network/public

TGTDLL= extproc.so
OBJS = extproc.o

all: extproc.so

clean:
rm *.o

extproc.so: $(OBJS)
$(MAKE) -f $(MAKEFILE) extproc_callback \
SHARED_LIBNAME=$(TGTDLL) OBJS=$(OBJS)

CC=cc
CFLAGS= -g -I. $(INCLUDE) -Wall


but your command line should work as well... Then test.sql is:

create or replace library demoPassing
as '/usr/oracle/ora920/bin/extproc.so'
/

create or replace
function return_string return varchar2
as
language C name "return_string" library demoPassing
with context parameters
( CONTEXT, RETURN INDICATOR short, RETURN LENGTH int, RETURN STRING );

-- char *return_string
-- (
-- OCIExtProcContext *, /* 1 : With-Context */
-- short *, /* 2 : RETURN (Indicator) */
-- int * /* 3 : RETURN (Length) */
-- );
/


select return_string from dual;




i could not reproduce your issue on solaris with your code - could be a bug in the code -- if this code works, we'll have to see what is different about them



Thanks!

A reader, August 15, 2003 - 11:57 am UTC

Dear Tom,
I made two changes to your code:
1. removed the -Wall from the compiler options (not available on  AIX)
2. put the return_string in quotes
The code fails with the message:
SQL> ed
Wrote file afiedt.buf

  1  create or replace
  2  function return_string return varchar2
  3  as
  4  language C name
  5  "return_string"
  6  library demoPassing
  7  with context parameters ( CONTEXT,
  8                            RETURN INDICATOR short,
  9                            RETURN LENGTH int,
 10*                           RETURN STRING );
SQL> 
SQL> /

Function created.
SQL> select return_string from dual
  2  /
select return_string from dual
*
ERROR at line 1:
ORA-20000:


SQL> /

RETURN_STRING
----------------------------------------------------------------------------

----
Hello World!

SQL>

Runs well on the next run.
Please note that if I add the -G compiler option it fails with ORA-21500. The message I reported in the TAR. Also, as before the second run is okay.

SQL> select return_string from dual;
select return_string from dual
                          *
ERROR at line 1:
ORA-20000: ORA-21500:T?


SQL> /

RETURN_STRING
----------------------------------------------------------------------------

----
Hello World!

SQL>

Tom, the test case I submitted to Metalink has drawn heavily on your code (I had your book, but lost it. So planning to buy another). I only goofed up in the memory allocation at the end that returned crap on AIX (the only excuse I have for myself is that worked on the IBM/ Dynix NUMA Oracle 8.1.5 very well - so didn't figure out the problem). So if you would allow me to relate this to the analysis by bug diagnostics at Metalink, in the Reproducibility section 8.1.7.4 AIX 32 bit ORA-21500 is returned. 
May be this problem would be reproducible after all. I've the management after me to report on the status as it is 15th Aug.
Many thanks 

Tom Kyte
August 15, 2003 - 12:47 pm UTC

I would say this is a bug on AIX -- to do with parsing that file. That my code fails and then runs is expected cause I only parse that file once per session.


I do not have an AIX box anywhere to play with -- so my suggestion is

simplify the test case -- function returning an integer, just send the context, no indicators, minimal code.


something they can easily reproduce in house. If you like, you can put it here before filing with them and we'll eyeball it to make sure it is OK first.

NO GLOBALS -- they always cause suspicion.



Thanks

A reader, August 18, 2003 - 4:16 am UTC

Dear Tom,

Please see if this is okay?
test.c file:

#include <oci.h>

OCIEnv    *envhp;
OCISvcCtx *svchp;
OCIError  *errhp;

int getDummy( OCIExtProcContext *ctx )
{
  ub1   false = 0, debugf_flag;
  text  *errbuf = OCIExtProcAllocCallMemory(ctx,512);
  sb4   errcode = 0;

  if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
  {
     OCIExtProcRaiseExcpWithMsg(ctx,20001,(text
*)"Error",0);
     return NULL;
  }
  if (( OCIExtractInit( envhp, errhp )   != OCI_SUCCESS ) ||
      ( OCIExtractSetNumKeys( envhp, errhp, 1 ) != OCI_SUCCESS ) ||
      ( OCIExtractSetKey( envhp, errhp, (text *) "debugf",
                            OCI_EXTRACT_TYPE_BOOLEAN,
                0, &false, NULL, NULL ) != OCI_SUCCESS ) 
||
      ( OCIExtractFromFile( envhp, errhp, 0,
                   (text *) "/tmp/test.ini" )
!= OCI_SUCCESS )
||
      ( OCIExtractToBool( envhp, errhp,  (text *) "debugf",
0,
                        &debugf_flag ) != OCI_SUCCESS )  ||
      ( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS ))
  {
     OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
            errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
     OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
     return NULL;
  }
  return ((int) 0);
}

CREATE OR REPLACE FUNCTION GetDummy
RETURN binary_integer
AS LANGUAGE C
LIBRARY test
NAME "getDummy"
WITH CONTEXT
PARAMETERS (CONTEXT, RETURN int);
/

Error that is returned (instead of 0 expected):
SQL> /
select getdummy from dual
*
ERROR at line 1:
ORA-30110: syntax error at '%.*s'

Thanks
 

Tom Kyte
August 18, 2003 - 7:26 am UTC

OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);

shouldn't sizeof(errbuf) be 511? since you allocated 512 bytes?

but otherwise -- looks good.

Thanks

A reader, August 18, 2003 - 11:18 am UTC

Dear Tom,
All the examples that Oracle have provided use sizeof(errbuf) and not sizeof(errbuf) -1.

Tom Kyte
August 18, 2003 - 2:57 pm UTC

that works if errbuf was defined as

char errbuf[255];


sizeof(errbuf) would be 255 -- telling us "255 bytes of storage for the error message"

if you however:

char * errbuf = call_to_allocate_memory();


sizeof( errbuf ) is now 4 (generally), even if you allocated 255 bytes.

Thanks

A reader, August 19, 2003 - 3:48 pm UTC

Thanks Tom,
I have forwarded the test case to metalink.
Also for above, it's because sizeof is determined at compile time and not run time.
Thanks again

Can you please help I am getting the same error

lou, June 22, 2004 - 4:37 pm UTC

select * from table(prepids.party_match('john'))
ERROR at line 1:
ORA-28576: lost RPC connection to external procedure agent
ORA-06512: at "SSA_QA.SSA_IDS", line 149
ORA-06512: at "SSA_QA.SSA_IDS", line 180
ORA-06512: at "SSA_QA.PREPIDS", line 79
ORA-06512: at "SSA_QA.PREPIDS", line 539
ORA-06512: at "SSA_QA.PREPIDS", line 565
ORA-06512: at "SSA_QA.PREPIDS", line 644
ORA-06512: at line 1

====================================
FUNCTION PARTY_MATCH(
name in VARCHAR2,
pty_typ_cd in VARCHAR2 DEFAULT NULL,
matchTolerance in VARCHAR2 := 'Typical')
-- RETURN VARCHAR2 IS
RETURN pty_srch_lst PIPELINED IS

i BINARY_INTEGER;
sockh BINARY_INTEGER;
tmp_str CHAR (200);
systemName VARCHAR2 (15);
search VARCHAR2 (30);
search_comment VARCHAR2 (100);
IDT VARCHAR2 (256);
memSize BINARY_INTEGER;
IDTLength BINARY_INTEGER;
numFields BINARY_INTEGER;
parameters LONG RAW;
sa ssa_ids.ids_StringArray;
searchRec LONG RAW;
recs BINARY_INTEGER;
records LONG RAW;
score BINARY_INTEGER;
PTY_NAME VARCHAR2(105) ;
PTY_ID NUMBER (9) ;
PTY_TYP_CDE VARCHAR2(5) ;
PTY_NA_ID NUMBER (15) ;
pty_lst pty_srch_lst;
pty pty_srch;
BEGIN
--libCheck('SSA_SSASE');
systemName:='Party';
memSize := 100;
recs := 0;

-- dbms_output.enable(1000000);

sockh := getConnection(systemName);

initializeParamsForPartyMatch(name,pty_typ_cd,search,parameters);

search_idt_get_ids (sockh,search,IDT,memSize);

search_fields_count_ids (sockh, search, numFields);
dbms_output.put_line (' numFields = ' || numFields);

reportSearchFields (sockh, search, numFields);

-- Initialize the searchRec and records
idt_length_get_ids (sockh, IDT, IDTLength);
searchRec := ssa_ids_util.chartoraw (' ', IDTLength);
records := ssa_ids_util.chartoraw (' ', 1);

dbms_output.put_line('Name: '||name||', Type: '||pty_typ_cd||', Parameters: '||ssa_ids_util.rawtochar(parameters));

search_start_ids (sockh, search, 'Typical', matchTolerance,
parameters, numFields, searchRec,
'', recs, records, 0);

dbms_output.put_line ('Records found: '||recs);

--result:='';
tmp_str:='';

FOR i IN 1..recs LOOP
simple_search_get_ids (sockh, search, searchRec,
IDTLength, score);
-- dbms_output.put_line (' score = ' || score);

tmp_str:=substr (ssa_ids_util.rawtochar (searchRec), 1, 135);

-- dbms_output.put_line (' searchRec = ' || tmp_str);

PTY_NAME := substr(tmp_str, 1 ,105) ;
PTY_NA_ID := substr(tmp_str, 106 , 15) ;
PTY_ID := substr(tmp_str, 121 , 9) ;
PTY_TYP_CDE := substr(tmp_str, 130 , 5) ;

pty := pty_srch( score ,
PTY_ID ,
PTY_NAME ,
PTY_TYP_CDE ,
PTY_NA_ID );

PIPE ROW(pty);
END LOOP;

--FOR i IN 1..recs LOOP
--dbms_output.put_line (' ID('||i||'): '|| result(i));

--END LOOP;

search_finish_ids (sockh, search);
shutdown (sockh);

return;
END PARTY_MATCH;

FUNCTION TITLE_MATCH(
TITLE IN VARCHAR2,
PTY_ID IN NUMBER,
IP_NAME IN VARCHAR2,
PTY_NA_ID IN NUMBER,
ROLE IN VARCHAR2,
MATCHTOLERANCE IN VARCHAR2 := 'Typical')
-- RETURN VARCHAR2 IS
RETURN title_srch_lst PIPELINED IS

i BINARY_INTEGER;
sockh BINARY_INTEGER;
tmp_str CHAR (400);
systemName VARCHAR2 (15);
search VARCHAR2 (30);
-- search_comment VARCHAR2 (100);
IDT VARCHAR2 (256);
memSize BINARY_INTEGER;
IDTLength BINARY_INTEGER;
numFields BINARY_INTEGER;
sa ssa_ids.ids_StringArray;
parameters LONG RAW;
searchRec LONG RAW;
recs BINARY_INTEGER;
records LONG RAW;
score BINARY_INTEGER;
IDS_WRK_TTL_ID NUMBER(15) ;
IDS_TTL VARCHAR2(150);
IDS_WRK_ID NUMBER(11) ;
IDS_IDS_ENTMT_ID NUMBER(15) ;
IDS_IDS_REC_PFR_ID VARCHAR2(15) ;
IDS_IDS_PTY_NA_ID NUMBER(15) ;
IDS_IDS_ROL_TYP_CDE VARCHAR2(2) ;
IDS_NAME VARCHAR2(100);
IDS_PTY_ID NUMBER(9) ;

-- titleSrchLst title_srch_lst;
titleSrch title_srch;
mtch_tolerance VARCHAR2(50) ;
BEGIN
--libCheck('SSA_SSASE');
systemName:='Title';
search := 'Title';
memSize := 100;
recs := 0;

-- dbms_output.enable(100000);

sockh := getConnection(systemName);


-- initialize parameters.
initializeParamsForTitleMatch(
TITLE ,
PTY_ID ,
IP_NAME ,
PTY_NA_ID ,
ROLE ,
search ,
parameters );

search_idt_get_ids (sockh,search,IDT,memSize);

search_fields_count_ids (sockh, search, numFields);
dbms_output.put_line (' numFields = ' || numFields);

reportSearchFields (sockh, search, numFields);

-- Initialize the searchRec and records
idt_length_get_ids (sockh, IDT, IDTLength);
searchRec := ssa_ids_util.chartoraw (' ', IDTLength);
records := ssa_ids_util.chartoraw (' ', 1);

--parameters is split in 2 parts due to limitiation in dbms_output.
dbms_output.put_line('Parameters: '||
SUBSTR(ssa_ids_util.rawtochar(parameters), 1, 200));
-- dbms_output.put_line('Parameters: '||
-- SUBSTR(ssa_ids_util.rawtochar(parameters),201,
-- LENGTH(ssa_ids_util.rawtochar(parameters))));


mtch_tolerance := matchTolerance;
IF mtch_tolerance is NULL
THEN
mtch_tolerance := 'Typical';
END IF;

search_start_ids (sockh, search, 'Exhaustive', 'Loose',
parameters, numFields, searchRec,
'', recs, records, 0);

dbms_output.put_line ('Records found: '||recs);

--result:='';
tmp_str:='';

FOR i IN 1..recs LOOP
simple_search_get_ids (sockh, search, searchRec,
IDTLength, score);
-- dbms_output.put_line (' score = ' || score);

tmp_str:=substr (ssa_ids_util.rawtochar (searchRec), 1, 400);

-- dbms_output.put_line (' searchRec = ' || tmp_str);

IDS_WRK_TTL_ID := substr(tmp_str, 1 , 15 ) ;
IDS_TTL := substr(tmp_str, 16 , 150 ) ;
IDS_WRK_ID := substr(tmp_str, 166, 11 ) ;
IDS_IDS_ENTMT_ID := substr(tmp_str, 177, 15 ) ;
IDS_IDS_REC_PFR_ID := substr(tmp_str, 192, 15 ) ;
IDS_IDS_PTY_NA_ID := substr(tmp_str, 207, 15 ) ;
IDS_IDS_ROL_TYP_CDE := substr(tmp_str, 222, 2 ) ;
IDS_NAME := substr(tmp_str, 224, 100 ) ;
IDS_PTY_ID := substr(tmp_str, 324, 9 ) ;

titleSrch := title_srch( score ,
IDS_WRK_TTL_ID ,
IDS_TTL ,
IDS_WRK_ID ,
IDS_IDS_ENTMT_ID ,
IDS_IDS_REC_PFR_ID ,
IDS_IDS_PTY_NA_ID ,
IDS_IDS_ROL_TYP_CDE ,
IDS_NAME ,
IDS_PTY_ID );

PIPE ROW(titleSrch);
END LOOP;

--FOR i IN 1..recs LOOP
--dbms_output.put_line (' ID('||i||'): '|| result(i));

--END LOOP;

search_finish_ids (sockh, search);
shutdown (sockh);

return;
END TITLE_MATCH;

END PREPIDS;
/


Tom Kyte
June 22, 2004 - 10:16 pm UTC

your C code is "crashing".

(as stated in the original answer.... problem in your C code, not in plsql or anything else)

Corollary EXTPROC Tactical Question

Robert, July 03, 2004 - 10:43 am UTC

Tom,

We have a very simple extproc program which our Developers use to issue unix calls from pl/sql using the system() function. We are wanting to spiff up this procedure to pass the 'stdout' output back to the calling pl/sql procedure.

In trying to figure out the best way to do this I have arrived at these possibilities. I would greatly value your advise and comments in this matter.

(1) Pass "> /tmp/stdout$$.txt" along with unix command to be run, and then use utl_file to read this file after extproc command returns. -->This is what we are doing now.
(2) Pass stdout back as an in/out variable
(3) Pass stdout back as the function return code
(4) Pass stdout back via a pl/sql pipe which calling program can read.
(5) Have extproc insert stdout into a database table so the calling pl/sql program can query it.
(6) Any other ideas?

Thanks,

Robert.

Tom Kyte
July 03, 2004 - 11:56 am UTC

</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:952229840241 <code>

use a java stored procedure.... option 6

Security Risk Calling Host Commands from Java

Robert, July 03, 2004 - 11:08 pm UTC

Tom,

But what about the security risk of calling host commands from java routines... since the commands are executed as 'oracle'?
By using extproc running as a lesser privileged user, you can limit the permissions and lock down security.

What am I missing?

Thank You!

Robert.

Tom Kyte
July 04, 2004 - 11:09 am UTC

you have the ability to very very very finely control which programs are callable and not -- something you don't have the capability to do with extprocs.

you can specify "this guy can run programs A and B" with java

with an extproc, you can specify the user extprocs run as -- by running a listener as that user, but you cannot limit the programs they may execute.

We have a "unique" situation...

Robert, July 05, 2004 - 11:03 am UTC

Tom,

Thanks for your expert help!

But our Developers are creating *dynamic* shell scripts from within their pl/sql programs, mainly dealing with ftp'ing files and processing them, and we are on 8.1.7.4 so we cannot use utl_smtp (?) to ftp binary files.

That is why we went with controlling security by extproc and unix userid.

So unless we could possibly standardize various shell script templates into which Developers could pass parameters (e.g. filenames, etc.). I think we will have to stick with the more complicated extproc and OCI type stuff.

Any further practical or philosophical words on this subject would be greatly appriciated!

Thanks,

Robert.

Tom Kyte
July 05, 2004 - 11:33 am UTC

java can easily ftp files.

heck, plsql can. (smtp is for email).

</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:6379798216275#19264891198142 <code>


you can ftp files in/out of the database directly.




Does ftp.pk[sb] work for *binary* ftp in 8i ?

Robert, July 06, 2004 - 10:41 am UTC

Tom,

Please forgive my denseness...
But the example notes for the ftp.pk[sb] package you referred to says the following...

<quote>
The implementation of binary transfers relies on UTL_FILE features only available in Oracle9i Release 2.
<quote>

I take it from this that I *cannot* use this to ftp *binary* files with 8i database.
Am I missing something?

Thanks,

Robert.

Tom Kyte
July 06, 2004 - 11:19 am UTC

to save a binary file without using ftp they would be needed... to ftp a binary file in or out - it does not need it.

the example I had there was "saving a blob to disk" -- everything we need is there, the utl_file stuff isn't needed to get a file into the database via ftp (nor out actually). I showed that a binary interface...

Got it!

A reader, July 06, 2004 - 11:52 am UTC

Tom,

I get it now...I think.

You use the '[get|put]_remote_binary_data' procedures in the 'ftp' package.

...It's a two-step process:

DECLARE
l_conn UTL_TCP.connection;
l_blob blob;
BEGIN
-------------------------------------
-- Step 1: GET BINARY file from a server
-------------------------------------
l_conn := ftp.login('xxxx-xxx-xxx', '21', 'ftpuser', 'xxxxxxx');
ftp.binary(p_conn => l_conn);
l_blob := ftp.get_remote_binary_data
(p_conn => l_conn,
p_file => '/tmp/oracleO');
ftp.logout(l_conn);
utl_tcp.close_all_connections;
-------------------------------------
-- Step 2: PUT BINARY file to another server
-------------------------------------
l_conn := ftp.login('yyyyy-yyy-yyyy', '21', 'ftpuser', 'yyyyyyy');
ftp.binary(p_conn => l_conn);
ftp.put_remote_binary_data
(p_conn => l_conn,
p_file => '/tmp/new_oracleO',
p_data => l_blob);
ftp.logout(l_conn);
utl_tcp.close_all_connections;
END;
/

.......Am I correct, sir?

Thanks!

Robert.

Tom Kyte
July 06, 2004 - 1:58 pm UTC

yes, as long as you stick with blobs and clobs and use ftp to get/put to the file system -- it'll be just fine in 8i.

How to pass a PL/SQL *record* to/from a 'C' *struct*

Robert, July 27, 2004 - 3:42 pm UTC

Tom,

I have been studying your Chapter 18 on 'C-Based External Procedures' and have successfully created non-OCI pl/sql external procedures, passing several parameters of number and varchar2.
But is there a way to pass a pl/sql record to a 'c' external procedure and have the 'c' program 'map' that record as a struct?

If so, could you provide a simple example of how to 'map' these together.

Thanks,

Robert.

Tom Kyte
July 27, 2004 - 4:01 pm UTC

plsql records cannot be bound to 3gl structs/recs/whatever.

the only thing that can be bound to a plqsl record is another plsql records.


You can use object types "create type foo as object ( x int, y date );" and use those, but not a plsql record type

We can pass pl/sql objects to external proc?

Robert, July 27, 2004 - 5:25 pm UTC

Tom,

Do I understand you correctly that you can pass a pl/sql object (e.g. 'foo' in your example) to a 'c' external procedure?
If so, could you show a small example?

Thanks,

Robert

Tom Kyte
July 27, 2004 - 7:42 pm UTC

you can pass a foo - i only have demo of collections of simple types like collections of numbers, dates, and strings -- objects, I find them too "complicated" from the 3gl perspective -- but there are examples of binding to object types in the OCI guides -- that is all they are -- "object types"

you want to see about "ott"

</code> http://otn.oracle.com/pls/db92/db92.drilldown?remark=&word=ott&book=&preference= <code>

the object type translator as well.

Difference

IK, January 24, 2006 - 8:26 pm UTC

Tom,

Apologies if iam digressing here.

Could you please explain in simple terms, conceptually what the difference is between ext proc and data cartridges.

Thanks,





Tom Kyte
January 25, 2006 - 12:54 pm UTC

Data cartridges are like an "application"
</code> http://docs.oracle.com/docs/cd/B19306_01/appdev.102/b14289/dciwhatis.htm#sthref16 <code>

they are an implementation. They extend the functionality of the server by adding say a new datatype.

A data cartridge might well use an external procedure in it's implementation. You might choose to implement your data cartridge code in say the "C" language. The way to do that would be to code the "C" routines as external procedures which are now callable via the server.

So, a data cartridge is an implementation, that might use the database feature "external procedures"..

Thanks

IK, January 25, 2006 - 3:02 pm UTC

Thanks very much.

So, if i add a new datatype using C. Say, a struct. Can i populate it with values and store it in the database using my external proc? And, it would be stored as a BLOB, i guess.

Could you please point me (URL) to a full example if you can, please?

Iam having a mental block in understanding the concepts.

Thanks,

Tom Kyte
January 25, 2006 - 5:07 pm UTC

you would either

a) map it to scalars in a table, insert the components of the struct (my choice)
b) map it to a complex object type, hence create your own sql type.
c) store it in a blob - something I would never recommend, demonstrate, or even suggest.


A) would be my choice.

Datatype?

A reader, January 27, 2006 - 1:20 pm UTC

So, when you say "They extend the functionality of the server by adding say a new datatype." - do you mean an object type?

Could you clarify that part please? I dont seem to get how we could add a new datatype.

Thanks-

Tom Kyte
January 28, 2006 - 12:31 pm UTC

an object type coupled with functionality. like spatial does, like text does, like image does.....




A reader, June 10, 2008 - 5:31 am UTC

Hi Tom,

I need to call an external C library with fuction that accept parameter of pointer to array. What is the correct syntax for the "create function".

The specs for the C function is,

long FAID_size (long start_date,
long roster_count,
long* roster_from, -- array of start time
long* roster_to, -- array of end time
long* roster_id, -- array of Roster ID
long* roster_id_count
)

The pl/sql wrapper is,

CREATE OR REPLACE PACKAGE FAID_DLL_PKG AS
TYPE int_VarType IS VARRAY(50) OF PLS_INTEGER;
FUNCTION FAID_Size (
p_start_date in PLS_INTEGER,
p_roster_count in PLS_INTEGER,
p_roster_from in out int_VarType,
p_roster_to in out int_VarType,
p_roster_id in out int_VarType,
p_roster_id_cnt in out PLS_INTEGER
)
RETURN PLS_INTEGER;
end;

CREATE OR REPLACE PACKAGE BODY FAID_DLL_PKG AS
FUNCTION FAID_Size (
p_start_date in PLS_INTEGER,
p_roster_count in PLS_INTEGER,
p_roster_from in out int_VarType,
p_roster_to in out int_VarType,
p_roster_id in out int_VarType,
p_roster_id_cnt in out PLS_INTEGER
)
RETURN PLS_INTEGER
AS EXTERNAL
LANGUAGE C
NAME FAID_Size
LIBRARY FAID_DLL
PARAMETERS (
CONTEXT,
p_start_date long,
p_roster_count long,
p_roster_from BY REFERENCE ,
p_roster_to BY REFERENCE ,
p_roster_id BY REFERENCE ,
p_roster_id_cnt BY REFERENCE ,
RETURN long
);
end;

Thanks very much for your help.

Tom Kyte
June 10, 2008 - 8:07 am UTC

You'll need a different C function call - you cannot call any arbitrary function - it has to have datatypes we can map to, using SQL types.


If you have access to Expert One on One Oracle - in the chapter on external procedures, you'll find a full example, but in short, it would look like this:

tkyte@TKYTE816> create or replace type numArray as table of number
  2  /
Type created.


...
 12
 13      procedure pass( p_in in numArray, p_out out numArray );
 14
.....

100      procedure pass( p_in in numArray, p_out out numArray )
101      as
102      language C name "pass_numArray" library demoPassing
103      with context parameters
104      ( CONTEXT,
105        p_in  OCIColl, p_in  INDICATOR short,
106        p_out OCIColl, p_out INDICATOR short );
107
108       -- void pass_numArray
109       --   (
110       --  OCIExtProcContext  *,  /*   1 : With-Context  */
111       --            OCIColl  *,  /*   2 : P_IN  */
112       --              short   ,  /*   3 : P_IN (Indicator) */
113       --            OCIColl **,  /*   4 : P_OUT  */
114       --              short  *   /*   5 : P_OUT (Indicator) */
115       --   );
116
......
#ifdef WIN_NT
_declspec (dllexport)
#endif
void pass_numArray
 ( OCIExtProcContext * ctx        /* CONTEXT */,
   OCIColl *           p_in       /* OCICOL  */,
   short               p_in_i     /* INDICATOR short */,
   OCIColl **          p_out      /* OCICOL  */,
   short *             p_out_i    /* INDICATOR short */
 )
{
ub4        arraySize;
double     tmp_dbl;
boolean    exists;
OCINumber *ocinum;
int        i;
myCtxStruct*myCtx;

    if ( (myCtx = init( ctx )) == NULL ) return;
    debugf( myCtx,  "Enter Pass numArray" );

    if ( p_in_i == OCI_IND_NULL )
    {
        raise_application_error( myCtx, ERROR_ARRAY_NULL,
                                       "Input array was NULL" );
    }
    else

    if ( OCICollSize( myCtx->envhp, myCtx->errhp,
                      p_in, &arraySize ) != OCI_SUCCESS )
    {
        raise_application_error(myCtx,ERROR_OCI_ERROR,
                                "%s",lastOciError(myCtx));
    }
    else
    {
        debugf( myCtx,  "IN Array is %d elements long", arraySize );

        for( i = 0; i < arraySize; i++ )
        {
            if (OCICollGetElem( myCtx->envhp, myCtx->errhp, p_in, i,
                              &exists, (dvoid*)&ocinum, 0 ) != OCI_SUCCESS )
            {
                raise_application_error(myCtx,ERROR_OCI_ERROR,
                                        "%s",lastOciError(myCtx));
                break;
            }
            if (OCINumberToReal( myCtx->errhp, ocinum,
                                 sizeof(tmp_dbl), &tmp_dbl ) != OCI_SUCCESS )
            {
                raise_application_error(myCtx,ERROR_OCI_ERROR,"%s",
                                        lastOciError(myCtx));
                break;
            }
            debugf( myCtx,  "p_in[%d] = %g", i, tmp_dbl );
            if ( OCICollAppend( myCtx->envhp, myCtx->errhp, ocinum, 0,
                                *p_out ) != OCI_SUCCESS )
            {
                raise_application_error(myCtx,ERROR_OCI_ERROR,
                                        "%s",lastOciError(myCtx));
                break;
            }
            debugf( myCtx,  "Appended to end of other array" );
        }
        *p_out_i = OCI_IND_NOTNULL;
    }
    term(myCtx);
}



You'll be passing a collection - you will need to write a wrapper C function that takes the collection, allocates a long array, fills it, invokes existing C function and then repackages the answer back into the collection.



A reader, June 10, 2008 - 10:59 pm UTC

I've got a copy of your book at the previous company I worked, but not here. I am ordering a copy of your book through amazon. Might take a week for that.

I am not a C programmer at all. That might take a while for me to digest this.

From my understanding, the alternative to accessing external function is using ORA_FFI. But I though accessing via pl/sql in database would be easier.

Could you tell me which is the better method to access external function in my case. Thanks very much.
Tom Kyte
June 11, 2008 - 8:02 am UTC

ora_ffi - the "foreign function interface" is for forms/reports and the like - in the middle tier.

I haven't ever used it - but it'll have similar issues - the C concept of an array with long * is a bit different then the plsql representation of a plsql index by table, collection or varray.

What I'm trying to say is that not just any C function is callable from PLSQL, you might have to implement an intermediate C function that marshalls the data between PLSQL and C.

Configure listener and tnsnames

A reader, June 11, 2008 - 11:29 pm UTC

Hi Tom,
You have been most helpfull with my problem. Still another question.

I am getting error (ORA-28575:unable to open RPC connection to external procedure agent ) when testing the connection calling "GetDiskFreeSpaceA" in "kernel32.dll".

Have we got the correct configuration for the connection. Thanks.

Listener.ora

LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = IPC)(KEY=EXTKEY))
(ADDRESS = (PROTOCOL = TCP)(HOST =)(PORT = 1521))
)
)
)

tnsnames.ora

crsdevlthebe.intranet =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = IPC)(KEY=EXTKEY))
(ADDRESS = (PROTOCOL = TCP) (Host = thebe.intranet) (Port = 1521))
(CONNECT_DATA = (SERVICE_NAME = crsdevl.intranet))
)

external procedure

A reader, July 15, 2008 - 4:58 am UTC

Hi Tom,

If my database server runs on Unix, does that mean I can only create association to Unix specific library ( library with .so extension) using 'create library'.

Can I call a VB DLL?

Thanks very much.

Tu-Oanh
Tom Kyte
July 15, 2008 - 9:47 am UTC

you would need the Oracle software installed on the remote machine - the listener, the database and all.

A reader, July 15, 2008 - 5:17 am UTC

PS. How do I call a window library written in VB from PL/SQL or Forms.

I know you are not a Forms person, but when I try ORA_FFI from Forms; I could not register the function. The library loaded fine.
Tom Kyte
July 15, 2008 - 9:47 am UTC

otn.oracle.com has a discussion forum all about forms and using forms.... try there

N3sgus

Joseph Boniface, April 29, 2010 - 6:41 am UTC

Can u provide me with some more info on n3sgus dll

We are working on a project which involves matching data like name, address. We have been using "n3sgus.dll" for this purpose.
This project was first started in 2000 in VB platform. Now we need to re-engineer the project to J2EE and for that, we need to re-engineer the methods used in n3sgus.dll. We are not sure in which language this dll is built and there are not Java equivalent available for the same.
We would like to know the alternate methods of using this DLL in J2EE application. Is it recommended to call DLL from Java program

Tom Kyte
April 29, 2010 - 7:51 am UTC

"U" isn't available, "U" is dead as far as I know. Look it up, it is true.

http://en.wikipedia.org/wiki/U_of_Goryeo


I have no clue what n3sgus.dll is, does, or where it came from. That would be something for you to figure out.


More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library