script.gif (1713 bytes)

ActiveX Scripting - Guiones de ejecución

José Manuel Abad Hernández
Introducción

Las aplicaciones que implementan capacidades de lenguajes macro o lenguajes interpretados en tiempo de ejecución, suministran una funcionalidad extraordinaria a los usuarios. Un ejemplo muy conocido es el editor de documentos Microsoft Word.

Este artículo muestra una forma de incorporar lenguajes interpretados en tiempo de ejecución a las aplicaciones, mediante la tecnología ActiveX Script de Microsoft, utilizando como plataforma de construcción a Delphi versión 3 o posterior.

ActiveX Script es una especificación de interfaces de objetos COM (Common Object Model), que posibilitan a los desarrolladores, incorporar en sus aplicaciones los motores de ejecución de lenguajes interpretados de una manera fácil.

Por desarrolladores en este trabajo, entenderemos las empresas o personas cuyo producto es un programa o paquete especifico, por ejemplo un editor de texto, un sistema financiero contable, un navegador de Internet, etc, para distinguirlo de aquel que se especializa en desarrollar motores o maquinas interpretación de guiones de ejecución, que llamaremos proveedor.

La definición del lenguaje del guión de ejecución (el script en si mismo), la sintaxis, el formato de almacenamiento, el modelo de ejecución y otras temas relacionadas, son responsabilidad de los proveedores de motores ActiveX Script. El hospedar o soportar un motor de interprete de guiones de ejecución, es responsabilidad del desarrollador de la aplicacion.

El guión de ejecución (el script) en general es un texto, escrito con las reglas del lenguaje que soporta el motor en el cual se ejecuta.

Los pasos básicos

Las componentes de ActiveX Script caen en dos categorías: Los hospederos (HOST) de guiones de ejecución y los motores de interpretación (SCRIPT ENGINE).

Los hospederos son las aplicaciones que crean los guiones y llaman al motor de interpretación adecuado para ejecutar el guión. Un ejemplo de aplicación hospedera es el Microsoft Internet Explorer, tan utilizado para navegar por las páginas WEB.

Los motores de interpretación se pueden desarrollar para cualquier lenguaje y ambiente de ejecución, por ejemplo el Microsoft Visual Basic Scripting Edition (VBScript), utilizado en el Microsoft Internet Explorer.

Los pasos involucrados en la secuencia básica para la ejecución de un guión es la siguiente (algunos elementos utilizados de momento, serán explicados mas adelante):

El hospedero

La aplicación (hospedero) que soporte guiones de ejecución ActiveX Script tiene que contener al menos una instancia de un objeto que exponga la interfaz IActiveScriptSite. Este objeto, es el punto de interacción del hospedero con el motor de interpretación. Usualmente este objeto es el contenedor de todos los otros objetos que son visibles para el guión que ejecuta el hospedero. La figura 1 muestra el código pascal de definición de dicha interfaz.

A continuación veamos una breve descripción de cada uno de los miembros de la interfaz IActiveScriptSite

GetLCID Esta función permite al hospedero indicar las constantes de localización para la interacción del motor de interpretación y el usuario de la aplicación.
GetItemInfo Esta función es llamada por el motor de interpretación para buscar los objetos nominalizados dentro de la aplicación.
GetDocVersionString Esta función es llamada por el motor de interpretación para obtener una el número de versión textualizado del documento vigente. Esta cadena puede ser usada para validar que cualquier estado congelado en el motor de ejecución pueda ser salvado consistentemente en el documento vigente.
OnScriptTerminate Esta función es llamada cuando el motor de interpretación términa. En muchos motores, esta función no es llamada y es posible utilizar el guión interpretado para generar eventos en la aplicación hospedera
OnStateChange Esta función es llamada cuando el motor de intérprete cambia de estado tanto explicitamente mediante la función SetScriptState o implícitamente por los eventos que se generan en el guión de ejecución.
OnScriptError Esta función es llamada cuando durante el análisis sintáctico del guión o durante la ejecución del mismo, se encuentra un error. El motor de interpretación suministra una implementación de la interfaz IActiveScriptError que describe el error en tiempo de ejecución en términos de una estructura EXCEPINFO, en adición a la indicación de la localización del error en el texto original del guión.
OnEnterScript Esta función es llamada por el motor de ejecución para indicar el comienzo de una unidad de trabajo.
OnLeaveScript Esta función es llamada por el motor de ejecución para indicar la terminacion de una unidad de trabajo.
Type
IActiveScriptSite = Interface( IUnknown )
['{DB01A1E3-A42B-11CF-8F20-00805F2CD064}']
function GetLCID( out Lcid: TLCID ): HRESULT; stdcall;
function GetItemInfo( const pstrName: POleStr;
dwReturnMask: DWORD;
out ppiunkItem: IUnknown;
out Info: ITypeInfo): HRESULT; stdcall;
function GetDocVersionString( out Version: TBSTR): HRESULT; stdcall;
function OnScriptTerminate( const pvarResult: OleVariant;
const pexcepinfo: TExcepInfo): HRESULT; stdcall;
function OnStateChange( ScriptState: TScriptState ): HRESULT; stdcall;
function OnScriptError( const pscripterror: IActiveScriptError ): HRESULT; stdcall;
function OnEnterScript: HRESULT; stdcall;
function OnLeaveScript: HRESULT; stdcall;

end;

figura 1. Definición de la interfaz IActiveScriptSite

El motor de interpretación

El motor de interpretación ActiveX Script, tiene que implementar la interfaz IActiveScript. Cuando el hospedero crea una instancia del motor de interpretación, llama a la función SetScriptSite de esta interfaz, para establecer el puente de interacción hospedero-motor de interpretación.

A continuación veamos una breve descripción de cada uno de los miembros de la interfaz IActiveScript

SetScriptSite Informa al motor de lenguaje del objeto establecido en el hospedero que expone la interfaz IActiveScriptSite
GetScriptSite Puede ser llamada para obtener el hospedero del motor de interpretación
SetScriptState Pone el motor de interpretación en un estado dado
GetScriptState Obtiene el estado del motor de interpretación
Close Causa que el motor de interpretación abandone cualquier guión cargado, pierda el estado en que se encuentra, librere los objetos internos y punteros a las interfaces de otros objetos y entre en el estado closed
AddNamedItem Adiciona un nombre en el nivel primario del espacio nominal del motor de interpretación
AddTypedLib Adiciona una librería de tipo en el espacio nominal del motor de interpretación
GetScriptDispath Obtiene un puntero a la interfaz IDispatch para los métodos y propiedades asociadas con el guión que se está ejecutando
GetCurrentScriptThread Obtiene el identificador definido del motor de interpretación para el hilo de ejecución (thread) vigente
GetScriptThreadID Obtiene el identificador de hilo de ejecución (thread) asociado en el motor de interpretación asociado con el hilo de ejecución dado en Microsoft WIN32
GetScriptThreadState Obtiene el estado vigente en el hilo de ejecución (thread) del guión
InterrupScriptThread Interrumpe la ejecución del guión que se esta ejecutando
Clone Sintetiza una copia gemela del motor de interpretación (excepto cualquier estado de ejecución), retornando una nueva instancia del motor de interpretación cargado sin asociación al hospedero en el hilo de ejecución vigente (thread)

El motor de interpretación permite controlar la funcionalidad básica de la ejecucion del guion; pero si soporta guiones textuales y evaluación de expresiones textuales compatibles con el lenguaje, entonces tiene que implementar la interfaz IActiveScriptParse.

La interfaz IActiveScriptParse constituye el analizador sintáctico del lenguaje interpretado y el ejecutor de las instrucciones del guión del motor de interpretación. El puntero a esta interfaz se obtiene llamando el metodo QueryInterface de IActiveScript. Los miembros fundamentales de la misma son:

InitNew Inicializa el motor de interpretacion
AddScriptlet Adiciona un fragmento de codigo a un guion
ParseScriptText Efectúa el analisis sintactico de un guion, adicionando las declaraciones dentro del espacion nominal del motor y evaluando apropiadamente el codigo

La función ParseScriptText ejecuta el analisis sintáctico de acuerdo a las banderas de control que se establezcan en su llamada. Esta función tiene la siguiente sinopsis:

HRESULT ParseScriptText (
LPCOLESTR pstrCode, // Buffer con el texto del guion
LPCOLESTR pstrItemName, // Nombre de item que suministra el contexto
IUnknown* punkContext, // Contexto de depuracion
LPCOLESTR pstrEndDelimiter, // Delimitador de fin del guion
DWORD dwFlags, // Banderas de control
VARIANT* pvarResult, // Retorno de los resultados de la ejecucion
EXCEPINFO* pexcepinfo); // Buffer para retornar los errores

El parámetro dwFlags, determina el procesamiento y puede ser cualquiera o una combinacion de las siguientes constantes:

SCRIPTTEXT_ISEXPRESSION Si la distinción entre una expresion computacional y una sentencia es importante pero sintacticamente ambigua en el lenguaje, esta bandera especifica si el texto debe ser interpretado como una expresion o como una lista de sentencias. Implicitamente se asume que son sentencias
SCRIPTTEXT_ISPERSISTENT Indica que el codigo adicionado durante la llamada a esta funcion debe ser salvado en el motor de interpretacion o si éste debe retornar a su condicion en el estado de inicializado
SCRIPTTEXT_ISVISIBLE Indica si el texto del guion debe ser visible (y por tanto, accesable por nombre) como un metodo global del espacio nominal del guion

El parámetro pexcepinfo apunta a una estructura para recibir la informacion de excepcion, que llenada si la funcion ParseScriptText retorna el valor DISP_E_EXCEPTION.

Aplicacion de ejemplo

Se puede construir una aplicación rudimentaria que actúe de hospedero de los motores de interpretación. El código que acompaña este artículo, tienen las fuentes completas del ejemplo que explicamos a continuación. Para crear el hospedero, creemos una nueva aplicacion en Delphi. Incorporemos un componente Memo1 del tipo TMemo, para la edición de los guiones. Adicionemos un componente TMenu con al menos un elemento TMenuItem para ejecutar el guión editado asociado al siguiente codigo:

procedure TForm1.mnEjecutar1Click(Sender: TObject);
var
    ActiveScriptParse : IActiveScriptParse;
    ActiveScript : IActiveScript;
    ExcepInfo : TExcepInfo;
    AppScriptSite : TScriptSiteWindow;
    EngineID : TCLSID;
begin
    // En esta aplicacion, se utilizara el motor de interpretacion de Microsoft
    // para Visual Basic (VBScript).
    EngineID := ClSID_VBScript;
    if Memo1.Lines.Text = '' then Exit;
    FResult := UnAssigned;
    // Crear el objeto que implementa la interfase IActiveScriptSite del hospedero
    AppScriptSite := TScriptSiteWindow.Create;
    AppScriptSite.WindowHandle := Application.Handle;
    // Inicializar las variables que sostendran las interfases que suministrara
    // el motor de interpretacion. El moldeo a Pointer es requerido, para
    // evitar la manipulacion automatica de Delphi del contador de referencias
    // de la interfase IUnknown.
    Pointer(ActiveScript) := nil;
    Pointer(ActiveScriptParse) := nil;
    // Crear una instancia del motor de interpretacion y obtener el puntero a su
    // interfase IActiveScript.
    // Si hay error en la creacion de la instancia del objeto, OleCheck genera una
    // excepcion y la instancia AppScriptSite creada anteriormente será liberada
    // automaticamente al salir del marco.
    OleCheck( CoCreateInstance(
        EngineID, // Indentificador del motor de interpretacion.
        nil,
        ClsCtx_InProc_Server, // El motor tiene que estar implementado
        // en una biblioteca de enlace dinamico
        IID_IActiveScript, // Identificador de la interfase implementada
        // en el objeto que se quiere obtener
        ActiveScript) // Puntero para recibir el retorno la
        // interfase solicitada.
        );
    // Obtener el puntero a la interface IActiveScriptParse del motor de
    // interpretacion.
    ActiveScript.QueryInterface(IID_IActiveScriptParse, ActiveScriptParse);
    // Registrar con el motor de interpretacion, el hospedero del mismo.
    OleCheck( ActiveScript.SetScriptSite( AppScriptSite ));
    try
        // Comenzar la ejecucion de un nuevo guion en el motor de interpretacion
        if SUCCEEDED( ActiveScriptParse.InitNew ) then
        begin
        // Para crear los objetos COM globales del hospedero que pueden ser
        // accesados por el motor de interpretacion se indica en comentario
        // el codigo a ejecutar.
        // Los objetos globales se adicionan en un listado
        // El primer caso es un objeto COM no registrado que es parte de la
        // aplicacion. El segundo adiciona un objeto registrado en la PC donde se
        // corre esta aplicacion. El primer parametro de procedimiento AddGlobal,
        // es el nombre con que el objeto va a ser referenciado en los guiones
        // de ejecucion.
        // AppScriptSite.AddGlobal('AppObj', TObjetoDeAplicacion.Create);
        // AppScriptSite.AddGlobal('PCO', CreateOleObject('PCOBJDLL.PCObjeto') );
        // Adicionados los objetos globales accesables desde el guion, cuando el
        // motor de interpretacion necesita obtener informacion de cualquier objeto
        // llamará a la funcion GetItemInfo de la interfase IActiveScriptSite.
        // Adicionemos pues los seudonimos de los objetos, al espacio global de
        // nombres de hospedero, donde hara sus busquedas el motor de interpretacion.
        { ActiveScript.AddNamedItem('AppObj',
        SCRIPTITEM_ISVISIBLE
        or SCRIPTITEM_GLOBALMEMBERS //Permitir al guion usar los miembros del
        //objeto sin referenciar directamente el objeto
        or SCRIPTITEM_ISPERSISTENT //Permitir que el objeto sea salvado
        //directamente por el motor de interpretacion
        );
        ActiveScript.AddNamedItem('PCO', SCRIPTITEM_ISVISIBLE or
        SCRIPTITEM_GLOBALMEMBERS or
        SCRIPTITEM_ISPERSISTENT
        );
        }
    // Activar el motor de interpretacion
    OleCheck( ActiveScript.SetScriptState(SCRIPTSTATE_CONNECTED) );
    // Analizar y ejecutar el guion
    OleCheck( ActiveScriptParse.ParseScriptText(
        POleStr(WideString(Memo1.Lines.Text)), // Texto del guion de ejecucion
        nil, // Nombre del elemento que da contexto al guion a ejecutar
        nil, // Contexto de depuracion (debugger)
        nil, // Delimitador del fin del guion de ejecucion
        0, // Contexto fuente del Cookie
        0, // Linea de comienzo de guion
        SCRIPTTEXT_ISPERSISTENT or SCRIPTTEXT_ISVISIBLE, // Banderas
        FResult, // Buffer para retornar el resultado de la ejecucion del guion
        ExcepInfo // Buffer para recibir la informacion de error
        ));
    end;
    except // Si algo fue mal, mostrar el mensaje de error
        On e:Exception do
            ShowMessage( e.Message );
    end;
    // Finalmente es interesante notar que
    // El compilador de Delphi (version 3 o posterior), genera automaticamente el
    // codigo para liberar (llamada a procedimiento _Release del objeto) los
    // objetos creados que implementan la interfaz IUnknown cuando estos se van
    // fuera de su marco validez.
end;

El código ha sido abundantemente comentado para permitir identificar claramente los pasos explicados para la implementación de la tecnología ActiveX Script en una aplicación.

ActiveX Script en Windows 98, vuelven los comandos

Microsoft(r) Windows(r) Scripting Host es un hospedero de guiones de ejecución independiente del lenguaje para motores de interpretación ActiveX™ en plataformas Windows de 32 bits.

Las plataformas de Microsoft Windows(r) 98, Windows NT(r) Workstation versión 5.0 y Windows NT Server versión 5.0 soportan este hospedero de guiones de ejecución. La propia compañía ya ha incluído los motores Microsoft(r) Visual Basic(r) Scripting Edition (VBScript) y Microsoft(r) JScript™. Otras compañías de proporcionarán otros motores ActiveX™ para lenguajes como Perl, TCL, REXX, Python y otros.

Este hospedero presenta baja ocupación de memoria durante la ejecución directa de los guiones, las secuencias de comandos no necesitan estar incrustadas en un documento HTML y es ideal para implementar comandos no interactivos que realicen tareas administrativas y de inicio de sesiones.

Windows Scripting Host utiliza la extensión del archivo de comandos para determinar qué motor de secuencias de comandos debe utilizar. Como resultado, quien escribe secuencias de comandos no necesita obtener el ID de motor de secuencias de comandos. El hospedero mantiene por sí solo, una asignación de extensiones de secuencias de comandos con ID de los motores y utiliza el modelo de asociación de Windows para iniciar el motor apropiado para la secuencia de comandos dada.

Windows Scripting Host expone nueve objetos globales de los cuales sólo tres están disponibles para los guiones de ejecución. Estos son WScript, WshArguments y WshShell. Las propiedades y metodos de los mismos quedan fuera del alcance de este articulo. El lector interesado, puede encontrar la documentacion de referencia en la ayuda del Personal Web Server de Windows 98.

En Windows 98 puede correrse desde la consola un guión de ejecución con el utilitario CSCRIPT.EXE. En la carpeta SAMPLES\WHS dentro del directorio de instalación de Windows 98, encontrará el archivo SHOWVAR.VBS, que es un guión de ejecucion en VBScript. Revise su contenido con el utilitario NOTEPAD.EXE. Para correrlo, haga doble CLICK sobre el mismo o ejecute desde la opcion EJECUTAR del menú de inicio la siguiente linea de comando,

CSCRIPT SHOWVAR.VBS


Ir a los Artículos