7 de noviembre de 2015

Creando un servidor COM de subproceso múltiple (Parte 2 de 3)

Segunda parte del artículo "Creando un servidor COM de subproceso múltiple" escrito por Antonio Muñoz de Burgos y Caravaca (eMans).


Parte 2 de 3

Tenemos algunos comandos que tienen un funcionamiento diferente cuando se ejecuta en servidores de automatización y su funcionamiento fuera de ellos, es decir trabajando normalmente.

SET DEFAULT

El directorio de trabajo de un servidor se controla mediante el Sistema Operativo, por lo tanto si variamos de posición todos los objetos se verán afectados.

Si al iniciar nuestra aplicación establecemos el directorio de trabajo con los comandos SET DEFAULT o CD, como es lógico y deseado afecta a todo el proceso, donde tendríamos que tener cuidad,o es si creamos instancias y en una ellas utilizamos algunos de estos comandos, ya que afectará al proceso donde estén cargados los objetos y a los subprocesos.

SET PROCEDURE

El comando SET PROCEDURE es exclusivo de cada subproceso, pero predeterminada Visual FoxPro ejecuta implícitamente dicho comando cuando se realiza la instancia de un servidor COM (.exe o.dll) de esta forma se consigue tener acceso a cualquiera de los procedimientos que tengamos en nuestro servidor.

Por lo tanto debemos tener mucho cuidado si algunos de nuestros subprocesos ejecutamos el comando SET PROCEDURE TO, ya que estaríamos produciendo una ruptura dentro de la capacidad de acceso a procedimientos del servidor, ya que estamos estableciendo al ruta a ninguna, si bien cierto, que si se crea un nuevo objeto en el mismo subproceso se restablece automáticamente.

SET CLASS

Este comando tiene un tratamiento similar que SET PROCEDURE, ya que también se guarda en un almacén local de subprocesos, debemos de tener cuidado si hemos inicializado librerías de clases con todo el conjunto de bibliotecas que hay en el servidor COM (.exe o .dll), y que en algún subproceso ejecutemos el SET CLASSLIB TO, para eliminar clases de forma independiente podemos usar el comando RELEASE CLASSLIB.


Los ejemplos mostrados son orientativos, para entender el funcionamiento y la forma de como aplicarlos, aunque los ejemplos son la base de inicio, el código debe estructurarse en función al tipo de desarrollo y objetivo deseado.

Obteniendo valores para la página Web .ASP.

Podemos obtener los valores de nuestro componente, de distintas formas:

[* 1] Por medio de propiedades tipo ARRAY
[* 2] Retornando directamente código HTML, JavaSript, XML, etc.
[3] Generando ficheros.

Definición de la clase: MiComponenteWeb.prg
*
DEFINE CLASS MiComponenteWeb AS Session OLEPUBLIC
*
****************************************************
* LA DEFINICIÓN DE ESTA PROPIEDAD, NOS SERVIRÁ PARA
* RECOGER VALORES DESDE LA PÁGINA ASP.
* ESTA SERÍA UNA DE LAS FORMAS.
****************************************************
*
DIMENSION paValores[1,1]
*
FUNCTION Init( )
*
******************
* INICIALIZACIÓN.
******************
*
PUBLIC ARRAY gaValores[1,1]
*
STORE SPACE( 0 ) TO THIS.paValores, gaValores
*
*************************************************
* AQUÍ EN INIT PODEMOS DEFINIR LOS COMANDOS SET
* DE LA FORMA DESEADA PARA EL COMPORTAMIENTO
* DE LOS MISMOS DENTRO DE TODO EL PROCESO.
*************************************************
*
RETURN DODEFAULT( )
*
*******************************************************
* SI ES NECESARIO, AQUÍ PUEDES UTILIZAR LOS OTROS
* EVENTOS, MÉTODOS y PROPIEDADES DE LA CLASE SESSION.
*******************************************************
*
FUNCTION Ejecutar( tcComando, tcFicheroLibreria )
*
**************************************************
* NUESTRA FUNCIÓN EJECUTAR, SERÁ LA INTERMEDIARIA
* PARA LA EJECUCIÓN DE NUESTRO CÓDIGO.
**************************************************
*
* PARÁMETROS:
*
* tcComando:
*
* Comando directo o función que deseamos ejecutar y parámetros si corresponde.
*
* tcFicheroLibreria:
*
* Es nuestra librería, que contiene la función que deseamos ejecutar.
* El nombre de la función a ejecutar vendrá dado en el parámetro tcComando.
*
******************************************
* DEFINICIÓN e INICIALIZACIÓN DE VARIABLES
* LOCALES, UTILIZADAS EN ESTA FUNCIÓN.
******************************************
*
LOCAL lnIndi, lnColumna, luRetVal
*
STORE 0 TO lnIndi, lnColumna
STORE SPACE( 0 ) TO luRetVal
*
IF PARAMETERS( ) <> 0
*
IF .NOT. EMPTY( tcFicheroLibreria )
*
***************************************************
*
[* A] ESTABLECEMOS EL DIRECTORIO POR DEFECTO,
* DEPENDIENDO DE LA POSICIÓN DESEADA,
* EN NUESTRO CASO SIEMPRE PARTIMOS DE LA
* RAÍZ DEL SERVIDOR WEB.
*
*
[* B] Y A PARTIR DEL DIRECTORIO POR DEFECTO,
* ASIGNAMOS LAS RUTAS RELATIVAS.
* DE TAL FORMA QUE A PARTIR DE ESTAS ASIGNACIONES
* EN TODO EL PROCESO UTILIZAMOS RUTAS RELATIVAS.
* LO ESTABLECEMOS CON EL COMANDO SET PATH.
*
*
Nota: También se puede realizar esta asignación
* en la FUNCION Init( ) del componente.
*
***************************************************
*
*
[* A] DIRECTORIO POR DEFECTO [RAÍZ]
*
SET DEFAULT TO
( SUBSTR( JUSTPATH( tcFicheroLibreria ), 1, RAT( "", JUSTPATH( tcFicheroLibreria ), 2 ) ) )
*
*
[* B] PATH RELATIVOS AL RAÍZ.
*
SET PATH TO datos, modulos
*
***************************************************
* ABRIMOS NUESTRO FICHERO DE PROCEDIMIENTOS.
* LA LIBRERÍA CON LA FUNCIÓN, QUE VAMOS A EJECUTAR.
***************************************************
*
SET PROCEDURE TO &tcFicheroLibreria
*
ENDIF
*
*********************************************************
* EJECUTAMOS EL COMANDO o LA FUNCIÓN.
*
* ASIGNADA EN : tcComando
* y CONTENIDA EN : tcFicheroLibreria
*
* AQUÍ ES DONDE SE PROCEDE A EJECUTAR NUESTRO CÓDIGO.
*********************************************************
*
* SI LA FUNCIÓN EJECUTADA RETORNA ALGÚN TIPO DE CÓDIGO
* SE REALIZA LA ASIGNACIÓN A
luRetVal
* ESTA SERÍA OTRA FORMA DE RETORNAR CÓDIGO A LA PÁGINA .ASP
*
luRetVal = &tcComando
*
************************************************************
* UNA VEZ QUE HEMOS EJECUTADO EL CÓDIGO DESEADO.
* RESTAURAMOS EL ENTORNO, EN RELACIÓN A LOS PROCEDIMIENTOS.
************************************************************
*
IF .NOT. EMPTY( tcFichPrograma )
*
CLEAR PROGRAM & tcFicheroLibreria
*
ENDIF
*
***********************************************
*
[* 1] OBTENIENDO VALORES POR MEDIO DE
* PROPIEDADES.
*
* ACTUALIZAMOS LAS PROPIEDADES DEL OBJETO.
* QUE SE UTILIZAN PARA INTERCAMBIO DE DATOS.
*
*
Nota: Podemos crearnos tantas propiedades,
* según las necesidades y casos.
***********************************************
*
DECLARE THIS.paValores[ ALEN( gaValores, 1 ), ALEN( gaValores, 2 ) ]
*
FOR lnIndi = 1 TO ALEN( gaValores, 1 )
*
FOR lnColumna = 1 TO ALEN( gaValores, 2 )
*
THIS.paValores[ lnIndi, lnColumna ] = gaValores[ lnIndi, lnColumna ]
*
ENDFOR
*
ENDFOR
*
ENDIF
*
************************************************************
*
[* 2] RETORNANDO CÓDIGO EN DISTINTOS FORMATOS.
*
* CON LA VARIABLE luRetVal, NUESTRA FUNCIÓN PUEDE RETORNAR
* VALORES y/o CÓDIGO QUE PUEDA SER APLICADO POR ASP o QUE
* SEA ENTENDIBLE DE FORMA DIRECTA POR EL NAVEGADOR WEB.
************************************************************
*
RETURN luRetVal
*
ENDDEFINE
*

Una vez que tenemos el código de nuestro componente, procedemos a compilarlo (ver formas de compilación), y podemos ver las entradas que se producen en el registro del Sistema.

Cuando realizamos la compilación, nos encontraremos con los siguientes ficheros:

Un fichero de biblioteca de tipos (.TLB)

La biblioteca .TBL es un fichero binario que contiene una relación de todas las clases publicadas en nuestro componente (Servidor de automatización), con todos sus eventos, métodos y propiedades.

Un fichero de registro (.VBR)

En el fichero de registro podemos ver todos los identificadores globales que se han generado al compilar nuestro componente, este tipo de ficheros es igual que los ficheros de registro.REG, lo único que se excluye es la ruta de acceso al código.


Nota: Estos ficheros se incluyen por compatibilidad o para NT 4, ya que la información necesaria se incluye en el componente, esto es válido e interpretados por Sistemas Windows 2000 o superiores, por lo que no son necesarios.


Llamada de la DLL desde una página .ASP


En la Web (www.emans.com) tenemos una opción que es ver todas las noticias de PortalFox.

Una forma de realizar dicha opción en .ASP, hubiera sido; el que todo programador de .ASP realiza, es decir escribiría todo o casi toda la codificación en .ASP, utilizando su lenguaje VBA (Visual Basic Script), la conexión y manipulación de la base de datos (Visual FoxPro) se tendría que realizar por medio de ADO y utilizando VBA o por múltiples .DLL

Pues bien, la creación de esta .DLL en Visual FoxPro es justamente para evitar todo eso, de forma que todo lo codifiquemos en Visual FoxPro y sin necesidad de tener amplios conocimientos de .ASP y/o VBA.

El único código que tenemos que tener en .ASP es la instancia del componente y ejecutar nuestra función en Visual Foxpro que es la que se encargará de hacer todo el proceso de recuperación de datos y en otros casos la manipulación de la información, actualizaciones, inserciones, borrados, etc.

Incluso si utilizamos la versión 8 de Visual FoxPro, podríamos utilizar el CursorAdapter, para acceder a otros motores de bases de datos una forma sencilla y simple, se da por entendido que también se puede acceder a datos Visual FoxPro de forma nativa, también por medio de OLEDB.

Si utilizas la versión 8 como es lógico, lo que corresponde es generarse librerías de clases bases para el acceso a datos, basándonos en el CursorAdapter, trabajar con proveedores OLEDB, de forma tal que tengamos una capa para cambiar fácilmente al tipo de motor de base de datos a tratar.

Si realizas una buena abstracción de cada una de las capas, podrás reutilizar y sin cambiar la codificación, las clases tanto para este tipo de aplicaciones o para aplicaciones de escritorio en entorno multiusuarios, realmente esta es una de las grandezas de las nuevas clases que aporta la nueva versión 8 de Visual FoxPro.

Nota: Si realmente lo que deseas es llegar a crear un sistema lo más escalable posible, utiliza el proveedor OLEDB de Visual FoxPro, no trabajes de forma nativa con datos vFox, ya que en un futuro un cambio sería más traumático y costoso, lo que en resumen implica que tu desarrollo no es escalable, de esta forma empezaras a obviar cierto tipo de código y formas de estructuras que no se corresponderán a los aplicativos puramente cliente/servidor.
Esto es un comentario que debes tenerlo en cuenta, que no se puede detallar en unas líneas, pero de momento no se corresponde de forma directa al tema que estamos tratando.


Definición de la página: TodasNoticiasPortalFox.asp

Estructura base de una página .html, puedes utilizar cualquier editor web como puede ser el FrontPage o simplemente utilizar el bloc de notas.

Nota: Este ejemplo es como explotamos la información, no es el código de la captura de la información desde PortalFox, por medio del backend.php existente en PortalFox, este código lo puedes ver directamente desde dicha opción, pulsado el icono de mostrar código fuente del proceso de la opción

<html>
<head>
<meta name="eMans" content="Antonio Muñoz de Burgos y Caravaca">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>PortalFox Noticias</title>
</head>
<body>
<%
'
'----------------------------------------------------------------
' TODO LO QUE ESTE ENCERRADO ENTRE <% %> SERÁ
' PROCESADO EN EL SERVIDOR.
' ES EL IDENTIFICADOR DE ASP.
'
'----------------------------------------------------------------
' INICIO ASP VER TODAS LAS NOTICIAS
' REGISTRADAS EN PORTAL_FOX.
'----------------------------------------------------------------
'
'----------------------------------------------------------------
' DECLARACIÓN DE VARIABLES.
'----------------------------------------------------------------
'
DIM lcRetorno
'
lcRetorno = ""
'
' CREAMOS EL OBJETO BASADO EN NUESTRO COMPONENTE.
'
SET oPasarelaVFP =Server.CREATEOBJECT( "pasarela_ea.pasarela" )
'
' La función Ejecutar: tiene dos parámetros:
'
' (1) Función que deseamos ejecutar.
' (2) En que librería se encuentra ubicada, fisicamente.
'
'----------------------------------------------------------------
' EJECUTAMOS LA FUNCIÓN:
NoticiasPortalFox
' SI NUESTRA FUNCIÓN UTILIZARA PARÁMETROS, SOLAMENTE
' TENDRÍAMOS QUE INDICARLA ENTRO LOS PARÉNTESIS DE
' LA MISMA
'----------------------------------------------------------------
'
lcRetorno=oPasarelaVFP.Ejecutar("NoticiasPortalFox()", "c:ejemplosprgfuncionesWeb.prg" )
'
' LA FUNCIÓN DEVUELVE CÓDIGO HTML.
'
Response.Write( lcRetorno )
'
SET oPasarelaVFP = Nothing
'
%>
</body>
</html>


Definición de la librería de funciones: funcionesWeb.prg

Aquí, en este fichero es donde definimos las funciones de nuestra Web.

Nota:
se pueden utilizar tantas librerías de funciones como se deseen, que se a su vez tengan definiciones de clases, etc.

La función NoticiasPortalFox, es código totalmente vFoxPro, por lo tanto este código no es nada extraño, ya que los pasos que se realiza son los de cualquier función, con la excepción del retorno de datos; que en este caso se realiza generando el código .HTML correspondiente, pero podríamos retornar .XML, DHTML, JavaScript, un objeto para sea analizado y procesado desde .ASP o una mezcla de diferentes lenguajes y objetos.

FUNCTION NoticiasPortalFox( ) contenida en el fichero funcionesWeb.prg

(1)
Declaración de variables locales de la función.

(2) Llamada a otras librerías en el caso de ser necesaria.

SET PROCEDURE TO misFuncionesGenerales, etc. ADDITIVE.

(3) Llamada a INCLUDES en caso de ser necesaria.

#INCLUDE "miIncludeWeb.h"

Entendemos en esto casos que no es necesario utilizar su ruta,
ya que en el módulo ejecutar hemos establecido el SET PATH.

(4) Apertura de la base de datos.

IF DBUSED( "miBaseDatos" )
SET DATABASE TO "miBaseDatos"
ELSE
OPEN DATABASE "miBaseDatos.dbc" SHARED NOUPDATE
ENDIF

(5) Apertura de la tabla en donde se guardan los datos de las noticias,
en este caso dicha tabla almacena el título y el link a portalFox con
la noticia correspondiente.

USE "miTablaNoticias" IN 0 SHARED

(6) Generamos una página .HTML que es la que retornamos a la página .ASP

SELECT( "miTablaNoticias" )
SCAN ALL
TEXT TO lcRetVal TEXTMERGE NOSHOW ADDITIVE
Código HTML en base al diseño que deseamos retornar.
Para insertar valores de variables, en este caso valores
de los campos de nuestra tabla.
<<MiCampo1>> ETC.
ENDTEXT
ENDSCAN

(7) Cerramos la tabla.

USE IN ( "miTablaNoticias" )

(8) Cerramos la base de datos.

SET DATABASE TO "miBaseDatos"
CLOSE DATABASES

(9) Quitamos las librerías cargadas.

RELEASE PROCEDURE "misFuncionesGenerales"

(10) Retornamos los datos.

RETURN lcRetVal


Validando en el lado del Servidor o en el Cliente.

Todo dependerá de cada caso, no existe una regla fija, pero si hay casos que deben de estar claros, con el sistema mostrado podríamos tener la intención de realizar validaciones en el lado del servidor, como el controlar los campos obligatorios de una ficha de entrada, por ejemplo si se encuentran vacíos.

Eso sería tan simple, como pasar los valores a nuestra .DLL y por medio de una función en Visual FoxPro, comprobar su valores y retornar lo que corresponda en caso de no ser correcto.

Pero estaríamos cargando el Servidor sin tener una verdadera razón, ya que esto se podría procesar de forma simple desde el cliente, recordemos que el cliente esta utilizando un navegador para este tipo de aplicación, pues tenemos alternativas muy sencillas como puede ser el escribir una función genérica en JavaScript, que se encargue de realizar este tipo de validaciones siempre que corresponda.

El proceso sería el siguiente;

Realizar la validación en el lado del cliente, por medio de JavaScript.

Si todo es correcto; ejecutar la acción correspondiente
por medio de la .DLL por ejemplo la inserción de registros en la tabla,
donde podemos utilizar el sistema de buffer para mantener la integridad
de nuestros datos para inserciones en más de una tabla en cascada.

Sino es correcto, damos el mensaje directamente en el Cliente por
medio de la función JavaScritp, y no ejecutamos el proceso deinsersión
de datos.


El código valido para este tipo de comprobación sería el siguiente y podríamos aplicarlo a todos los formularios de entrada de datos.

Para estos casos, lo lógico sería tener una librería de funciones en JavaScript, que se cargue en el inicio de nuestra página .HTML y/o .ASP.

La forma de realizar la llamada de dicha librería sería de la siguiente forma;

<html>
<head>
<SCRIPT LANGUAJE="JavaScript1.2" src="miLibreriaBasesEnJavaScript.js" type="text/javascript"></SCRIPT>
</head>
...


Definiendo un función en nuestra librería miLibreriaBasesEnJavaScript.js

Pasemos a crear una función genérica para validar la entrada de datos, en este caso la función se encargará de comprobar que los campos no pueden tener valor vacío.

Nota: Esta función se ejecuta en el lado del Cliente (en el navegador)

//
// ***********************************
// [INICIO] Chequea campos obligatorios.
// ***********************************
//
// PARÁMETROS: toForm : Objeto formulario
//
// Pasamos el objeto formulario, de forma que
// chequeamos los campos que hemos establecido
// como obligatorios en la propiedad objOBLID
//
// Nota: JavaScript distingue entre mayúsculas y minúsculas.
//
function chequeaCamposOBL(toForm)
{
//
// VARIABLE QUE INFORMARÁ DE LOS ERRORES.
//
var lcMsg = ""
var lnIndice = 0
//
// TOTAL DE OBJETOS DENTRO DEL FORMULARIO.
//
for ( lnIndice = 0; lnIndice <= (toForm.elements.length-1); lnIndice++)
{
//
// SOLAMENTE CHEQUEAMOS AQUELLOS CAMPOS QUE
// HEMOS ESTABLECIDO COMO OBLIGATORIOS.
//
if ( toForm.elements[lnIndice].objOBLID != null &&
toForm.elements[lnIndice].objOBLID != "" &&
toForm.elements[lnIndice].objOBLID != "undefined" )
{
//
// CHEQUEAMOS EL VALOR DEL OBJETO.
//
if ( toForm.elements[lnIndice].value == "" || toForm.elements[lnIndice].value == null )
{
//
// FORMAMOS LA EXPRESIÓN DEL MENSAJE.
//
lcMsg = lcMsg +
"El campo [" +
toForm.elements[lnIndice].objOBLID +
"] no puede estar vacio." +
"r" + "n"
}
//
}
}
//
// COMPROBACIÓN PARA RETORNO DE VALOR.
//
if ( lcMsg != "" )
{
//
// CAMPOS OBLIGATORIOS NO RELLENADOS.
//
lcMsg = "ATENCIÓN : Campos obligatorios" + "r" + "n" + "n" + lcMsg
//
alert( lcMsg )
//
return false
}
else
{
//
// SECUENCIA DE CAMPOS OBLIGATORIOS CORRECTA.
//
return true
}
}
//
// ********************************
// [FIN] Chequea campos obligatorios.
// ********************************
//

En el caso de no rellenar los datos, en el cliente se obtendría el mensaje informándole de todos los campos que son obligatorios y que no están rellenos:

Parte 2 de 3


Bibliografía y/o documentación adicional:

Biblioteca MSDN & Resource KIT.
Manual de Referencia de Visual FoxPro.


Antonio Muñoz de Burgos y Caravaca
www.emans.com (Web realizada en vFoxPro)
Sevilla - España
Manifestando el apoyo a la comunidad de desarrolladores de MS Visual FoxPro.

Todas las marcas aquí mencionadas, están registradas por sus respectivos fabricantes.

No hay comentarios. :

Publicar un comentario