Accediendo a rutinas C y C++ desde Oracle
Creación: 09-04-2007
1. Introducción
En este tutorial os quiero mostrar
como llamar a rutinas desarrolladas en c o cpp desde un procedimiento
almacenado de ORACLE.
2. Entorno
El tutorial está escrito
usando el siguiente entorno:
-
Hardware: HP COMPAQ Presario
V6000 (Centrino Duo 1.66GHz, 2048 MB RAM, 100 GB HD) -
Sistema Operativo: Windows XP
Home Edition -
Oracle 9.2
-
MinGW 5 para windows.
-
Eclipse como editor de
ficheros
3. Creando el .c y el .cpp
Para comenzar vamos a crear dos
pequeños ficheros, uno escrito en c, y otro escrito en cpp.
Crearemos una función que escribirá en un fichero una
pequeña frase. A continuación os muestro el código
de nuestros archivos:
helloWord.c
#include
<stdlib.h>
#include
<stdio.h>
#include
<windows.h>
#define
EXPORT _declspec(dllexport)
EXPORT
void
HelloWord(char*
cadena, int*
numero);
EXPORT
void
HelloWord(char*
cadena, int*
numero)
{
FILE
* f = fopen(«c:\\HelloWordC.log»,
«aw»);
fprintf(f,
«Hola desde la libreria
HelloWordC.dll:\n»);
fprintf(f,
«Datos antes de modificarse
\nCadena: %s \nNumero: %d\n»,
cadena, *numero);
strcpy(cadena,«Modificada
por helloWordC.dll»);
*numero
= 10;
fprintf(f,
«Despues\nCadena: %s \nNumero:
%d\n», cadena, *numero);
fflush(f);
fclose(f);
}
Al llamar a la función
HelloWord, se creará un fichero, si no existe ya, con el
nombre HelloWordC.log en c:\ y se le añadirá la frase
«Hola desde la librería HelloWordC.dll». Se imprimirán
los parámetros antes y despúes de ser modificados, para
ver que realmente los datos son obtenidos y modificados
correctamente.
helloWord.cpp
#include
<stdlib.h>
#include
<stdio.h>
#include
<windows.h>
#define
EXPORT extern
«C»
_declspec(dllexport)
EXPORT
void
HelloWord(char*
cadena, int*
numero);
EXPORT
void
HelloWord(char*
cadena, int*
numero)
{
FILE
* f = fopen(«c:\\HelloWordCpp.log»,
«aw»);
fprintf(f,
«Hola desde la libreria
HelloWordCpp.dll:\n»);
fprintf(f,
«Datos antes de modificarse
\nCadena: %s \nNumero: %d\n»,
cadena, *numero);
strcpy(cadena,«Modificada
por helloWordCpp.dll»);
*numero
= 20;
fprintf(f,
«Despues\nCadena: %s \nNumero:
%d\n», cadena, *numero);
fflush(f);
fclose(f);
}
La única diferencia entre
las dos implementaciónes está en la definición
de EXPORT. En cpp se antepone extern «C» a
_declspec(dllexport) .
4. Creando las librerías
Vamos a crear un fichero makefile
para facilitarnos la compilación de los ficheros y la creación
de la librería. El contenido es el siguiente:
JDK_HOME=$(JAVA_HOME)
WIN32_CPP=g++
WIN32_C=gcc
CPPFILE=helloWord.cpp
CFILE=helloWord.c
CPPLIBNAMEWIN32=helloWordCpp.dll
CLIBNAMEWIN32=helloWordC.dll
CFLAGSWIN32=-DLOG
-I$(JDK_HOME)\include -I$(JDK_HOME)\include\win32 -shared
-Wl,–kill-at -Wall
all:
@echo
«Uso: make {win32 | help}»
win32:
$(FILE)
$(JNIFILE)
$(WIN32_CPP)
$(CFLAGSWIN32)
-o $(CPPLIBNAMEWIN32)
$(CPPFILE)
$(WIN32_C)
$(CFLAGSWIN32)
-o $(CLIBNAMEWIN32)
$(CFILE)
help:
@echo
«Uso: make win32»
Se
crearán dos librerías. Una llamada helloWordCpp.dll
generada a partir del fuente helloWord.cpp y otra llamada
helloWordC.dll, generada a partir del fuente helloWord.c
Estas
dos librerías son las que invocaremos desde Oracle.
5. Configurando Oracle para poder acceder a librerías
externas
Debemos editar el listener de
oracle (archivo listener.ora), situado en
%oracle_dir%\ora92\network\admin\listener.ora, y añadir a cada
entrada de SID_DESC la opción (ENVS = «EXTPROC_DLLS=ANY»).
A continuación os muestro un ejemplo:
[…]
SID_LIST_LISTENER =
(SID_LIST
=
(SID_DESC
=
(SID_NAME
= PLSExtProc)
(ORACLE_HOME
= C:\oracle\ora92)
(PROGRAM
= extproc)
(ENVS
= «EXTPROC_DLLS=ANY»)
)
(SID_DESC
=
(GLOBAL_DBNAME
= WOODY)
(ORACLE_HOME
= C:\oracle\ora92)
(SID_NAME
= WOODY)
(ENVS
= «EXTPROC_DLLS=ANY»)
)
)
[…]
Una vez editado el
fichero, debemos reiniciar el servicio del listener de Oracle.
6. Llamada a las librerías
Lo primero que debemos
hacer es crear un objeto LIBRARY asociado a la librería a la
que queremos acceder. Lanzamos en la consola de Oracle:
CREATE
OR REPLACE LIBRARY helloWordC AS ‘C:\helloWordC.dll’; /
CREATE
OR REPLACE LIBRARY helloWordCpp AS ‘C:\helloWordCpp.dll’; /
Como vemos, las
librerías se encuentran en C:\.
Oracle no comprueba que la librería es correcta (el path, el
código, etc.) hasta el momento de la ejecución, por lo
tanto no sabríamos si lo hemos hecho bien hasta que no
lanzaramos la prueba.
Ahora
vamos a asociar dos procedimientos almacenados con las dos funciones
HelloWord de las librerías. Lanzamos en la consola de Oracle:
CREATE
OR REPLACE PROCEDURE helloC(cadena IN OUT VARCHAR, numero IN OUT
PLS_INTEGER) AS LANGUAGE C LIBRARY helloWordC name «HelloWord»;
/
CREATE
OR REPLACE PROCEDURE helloCpp(cadena IN OUT VARCHAR, numero IN OUT
PLS_INTEGER) AS LANGUAGE C LIBRARY helloWordCpp name «HelloWord»;
/
Hemos asociado un
procedimiento helloC al procedimiento HelloWord() de la librería
de C, y un procedimiento helloCpp asociado al HelloWord() de la
librería de Cpp. Para mapear los parámetros podemos
consultar el capítulo 2 del manual de oracle
(http://www.unix.org.ua/orelly/oracle/prog2/ch21_04.htm).
Nos indica que VARCHAR mapea a un tipo char* de C, y PLS_INTEGER
mapea a un tipo de dato int* de C.
Vamos a crear otro
procedimiento llamado test para poder probar nuestras librerías:
CREATE OR
REPLACE PROCEDURE test
AS
cadena
VARCHAR(50);
numero
PLS_INTEGER;
begin
cadena
:= ‘Hola mundo’;
numero
:= 0;
dbms_output.put_line(‘Valores
iniciales’);
dbms_output.put_line(‘Cadena:’
|| cadena);
dbms_output.put_line(‘Numero:’
|| to_char(numero));
helloC(cadena,
numero);
dbms_output.put_line(‘Valores
modificados con helloWordC.dll’);
dbms_output.put_line(‘Cadena:’
|| cadena);
dbms_output.put_line(‘Numero:’
|| to_char(numero));
helloCpp(cadena,
numero);
dbms_output.put_line(‘Valores
modificados con helloWordCpp.dll’);
dbms_output.put_line(‘Cadena:’
|| cadena);
dbms_output.put_line(‘Numero:’
|| to_char(numero));
end; /
Se imprimen los valores
iniciales de las variables y se realiza las llamadas a las funciones,
imprimiéndose los parámetros para comprobar que se modifican
correctamente.
Ahora lanzamos nuestro
test. Primero hay que activar el serveroutput de oracle para que se
impriman las variables:
set
serveroutput on;
Lanzamos el test:
exec
test;
Comprobamos que los
cambios se realizan correctamente, viendo el resultado por pantalla
en la consola de Oracle. Comprobamos también que se generan
los dos archivos de texto en C:\
con el contenido descrito anteriormente.
7. El código fuente
Aquí
adjunto el fuente y las dll de windows.
8. Sobre el autor
José Carlos López
Díaz, Ingeniero en Informática
Autentia Real Business Solutions
S.L – «Soporte a Desarrollo»