Scripting al Servicio de Visual Basic

 

Un Ejecutable de Visual Basic Puede Hacer Llamadas a Scripts Para Llevar a Cabo Tareas Asincronas

 

Las Secuencias de Comandos, o Scripts, tienen ciertas ventajas sobre el codigo binario que dan su razón de ser.  Son simples, emplean muy poca memoria, y flexibles para el programador. Las desventajas son por supuesto el bajo rendimiento comparado con el código binario, un lenguaje relativamente reducido y, en general, la carencia de un entorno de desarrollo aceptable. Se trata de la misma filosofía de los antiguos archivos por lotes del DOS (Batch Files).

 

Introducción

 

Alguna vez se tuve la siguiente idea: “si pudiera modificar algo de código sin tocar el ejecutable”, - y un suspiro. ¿Usted no?. Hay un chance, y de eso se trata este articulo. Por supuesto dependemos de los avances tecnológicos de los Script, en otras palabras, viva Microsoft Scripting Host. Para colocar esta tecnología al servicio de Visual Basic, básicamente hacemos una llamada Shell a un archivo de Script  y esperamos una respuesta asincrona (no es un requerimiento). Es claro que el uso de la técnica sugiere llevar a cabo alguna tarea puntual, y no procedimientos complejos, es decir, no se trata de cambiar Scripts por el potente código binario.

 

Realmente la tecnología del Scripting ha avanzado bastante, por ejemplo la literatura encontrada de componentes como Microsoft Scripting Host (MSH) tiene un volumen considerable y hace pensar que los Scripts tienen posos limites. La tarea de Windows Scripting Host es permitir la ejecución de secuencia de comandos de los lenguajes VBScript y JScript dentro del sistema operativo. Para validar este articulo, el lector debe tener este componente. Los usuarios de Windows 95 lo pueden bajar del sitio http://www.msdn.microsoft.com/scripting. El Host  es WSCRIPT.EXE y normalmente se instala en el directorio de Windows.

 

 

Ejemplo Paso a Paso

 

El ejemplo que expondré a continuación hace una llamada a un a archivo de secuencia de comandos y espera una respuesta asincrona del mismo (he aquí la parte menos simple).

 

1. El Script

 

Cualquier editor de texto sirve, por ejemplo NOTEPAD. Agregue la siguiente secuencia de comandos. Luego guarde como TAREA.VBS (recomiendo una carpeta nueva en donde tambien irá la aplicación de ejemplo):

 

'------------------------------------------------------------

' FILE NAME    : TAREA.VBP

' TYPE         : VBScript

' PROJECT      : Scripting Studio

' AUTHOR       : Harvey T.

' DESCRIPTION  : Ejemplo del articulo “Scripting al Servicio

'                de Visual Basic”

' UPDATE       : 24-11-2000

'------------------------------------------------------------

Option Explicit

 

'// La ejecución del Script comienza y termina aqui:

Dim O

Set O = New CTarea

O.Tarea

Set O = Nothing

 

'------------------------------------------------------------

' CLASS       : CTarea

' DESCRIPTION : Plantilla de una tarea en Script

'------------------------------------------------------------

Class Ctarea

'// Este objeto se emplea para gererar el evento que indica

'// cuando haya finalizado la tarea.

Private msg

 

    Private Sub Class_Initialize()

        Set msg = CreateObject("Messages.CMessage")

    End Sub

 

    Private Sub Class_Terminate()

        Set msg = Nothing

    End Sub

 

    Public Sub Tarea()

         '// Escriba la terea...

         '//

         '// Fin de tarea.

         msg.Message = "El Script ha terminado la tarea..."

    End Sub

End Class

 

Se trata de una secuencia de comandos de VBScript en donde uso Class para crear un objeto dentro del Script. Esto es importante ya que permite programar el Script en términos de objetos (la directiva Class requiere de la versión 5 o superior de VBScript). Realmente programar un Script en estos términos es una formalidad mas que un requerimiento.

 

Note que dentro de esta secuencia de comando se inicia y termina el código en la sección conocida por programadores Visual Basic como Declaraciones. Esto se asemeja a un procedimiento Main de un modulo estándar.

 

Tambien, dentro de el Script encuentra la creación del objeto externo Messages.CMessage, el cual es una componente fuera de proceso EXE ActiveX escrito por mi, y cuya función es establecer una comunicación entre programas, aquí se empea solo para dar el aviso asincrono de que la tarea a terminado. Un componente como este es idóneo también para retornar datos desde el Script a su cliente. Los detalles de este componentes se encuentran en el Anexo 1 de este articulo.

 

 

2. El Proyecto de Visual Basic

 

Se trata de un proyecto EXE normal:

 

1.       En el menú Archivo, haga clic en Nuevo proyecto.

2.       En el cuadro de diálogo Nuevo proyecto, haga doble clic en el icono EXE Estándar. Daremos al proyecto el nombre de Scripting.

3.       Al formulario predeterminado demos el nombre de frmScripting.

  1. Agreguemos un CommandButton (Name= cmdJob, Caption = Llamar a TEST.VBS) y un Label (Name= lblPrompt).
  2. Agreguemos la referencia al componente Message (Ver Anexo 1): Desde el Menú Proyecto, Opción: Referencias. Ubique el archivo Message.EXE (puede ser mas cómodo hacerlo desde el botón Examinar).
  3. Agregue el siguiente código al formulario frmScripting:

 

'-----------------------------------------------------------

' NAME         : frmScripting

' TYPE         : FORM

' PROJECT      : Scripting.vbp

' AUTHOR       : Harvey T.

' DESCRIPTION  : Ejemplo del articulo "Scripting al Servicio

'                de Visual Basic"

' UPDATE       : 24-11-2000

'------------------------------------------------------------

Option Explicit

 

'// Este objeto se emplea para esperar el evento que generará

'// el Script TEST.VBS cuando haya finalizado la tarea

Private WithEvents msg As Messages.CMessage

 

Private Sub Form_Load()

    Set msg = New Messages.CMessage

    Prompt vbNullString

End Sub

 

Private Sub Form_Unload(Cancel As Integer)

    Set msg = Nothing

End Sub

 

Private Sub cmdJob_Click()

    Shell "C:\WINDOWS\WSCRIPT.EXE " & BeetQM(App.Path & "\TAREA.VBS")

End Sub

 

Private Function BeetQM(s)

    BeetQM = """" & s & """"

End Function

 

Private Sub msg_MessageChange()

    Prompt msg.Message

End Sub

 

Private Sub Prompt(s As String)

    lblPrompt.Caption = s

    lblPrompt.Refresh

End Sub

 

Note los siguientes detalles del anterior código: (1) Creamos un objeto de Message, este objeto se emplea para esperar el evento que generará el Script TEST.VBS cuando haya finalizado la tarea. (2) Hice la llamada al Script TAREA.VBS usando la instrucción Shell, realmente prefiero usar la API ShellExecute, ya que no requiere de ningún C:\WINDOWS\WSCRIPT.EXE, para detalles ver Anexo 2.

 

Así de simple es la cuestión. Ahora puede ejecutar el proyecto, y de clic sobre el unico botón. Si todo fue bien, debería obtener una pantalla final como esta:

 

 

Durante la ejecución de clic sobre el botón “Llamar a TEST.VBS” sucedió lo siguiente: (1) Se cargo y ejecuto el Host WSCRIPT.EXE, luego este llamo al motor de VBScript para ejecutar la secuencia de comandos. Aquí se crea el componente fuera de proceso Message, que luego de haber terminado la tarea, dispara el evento MessageChange el cual es leído en el cliente. De esta manera sabemos que el Script ha terminado. Todo se produce de manera asincrona.

 

Resumen

 

La técnica anteriormente expuesta permite la ejecución de código VBScript o JScript (virtualmente JavaScript también, ya que podemos guarda código JavaScript en archivos JS sin ningún inconveniente) para ejecutar tareas asincronas. El empleo de un componente fuera de proceso como Message permite un dialogo entre el cliente y el Script. Este componente también puede ser usado para retornar datos al cliente a través de propiedades publicas y un evento como MessageChange. Es decir en sus manos quedan los mil y un servicios que le puede  brindar esta idea.

 

 

Anexo 1. El Comonente Message

 

Reutilizo un componente que cree y explique en el articulo pasado llamado “Comunicación Entre Ejecutables”. Este articulo esta disponible en el sitio Web de ALGORITMO VIRTUAL de EIDOS (www.eidos.es). Si no dispone de este articulo, a continuación se explica como crear Message.EXE.

 

1.       En el menú Archivo, haga clic en Nuevo proyecto.

2.       En el cuadro de diálogo Nuevo proyecto, haga doble clic en el icono EXE ActiveX. Asigne Name=Message.

3.       A la clase que fue creada como predeterminada daremos el nombre de CMessage  (Name=CMessage). Agregamos el código:

 

'------------------------------------------------

' NAME         : CMessage

' TYPE         : Class, Instancing=MultiUse

' PROJECT      : Messages, EXE ActiveX

' AUTHOR       : Harvey T.

' DESCRIPTION  : Keeps shared class CMessageStore

' UPDATE       : -

'------------------------------------------------

Option Explicit

 

Public Event MessageChange()

 

Private WithEvents MessageStore As CMessageStore

 

Private Sub Class_Initialize()

    If gCount = 0 Then

       Set gMessageStore = New CMessageStore

    End If

    gCount = gCount + 1

    Set MessageStore = gMessageStore

End Sub

 

Private Sub Class_Terminate()

    If gCount - 1 = 0 Then

       Set gMessageStore = Nothing

    End If

    gCount = gCount - 1

    Set MessageStore = Nothing

End Sub

 

Private Sub MessageStore_MessageChange()

    RaiseEvent MessageChange

End Sub

 

Public Property Get Message() As String

    Message = MessageStore.Message

End Property

 

Public Property Let Message(RHS As String)

    MessageStore.Message = RHS

End Property

 

'//Code Test

Public Property Get Count() As Long

    Count = gCount

End Property

 

 

4.       Cree un modulo estándar y de el valor Shared a la propiedad Name. El código de este modulo es el siguiente:

 

'------------------------------------------------

' NAME         : Shared

' TYPE         : Standard Module

' PROJECT      : Messages, EXE ActiveX

' AUTHOR       : Harvey T.

' DESCRIPTION  : Keeps shared data

' UPDATE       : -

'------------------------------------------------

Option Explicit

 

Public gCount As Long

Public gMessageStore As CmessageStore

 

5.       Agregamos una nueva clase de nombre CMessageStore, de instancia privada. El código es el siguiente:

 

'------------------------------------------------

' NAME         : CMessageStore

' TYPE         : Class, Instancing=Private

' PROJECT      : Messages, EXE ActiveX

' AUTHOR       : Harvey T.

' DESCRIPTION  : This is the shared class

' UPDATE       : -

'------------------------------------------------

Option Explicit

 

Public Event MessageChange()

 

Private m_Message As String

 

Public Property Get Message() As String

    Message = m_Message

End Property

 

Public Property Let Message(RHS As String)

    m_Message = RHS

    RaiseEvent MessageChange

End Property

 

6.       Por ultimo compile a Message.EXE.

 

 
Anexo 2. ShellExecute

 

La ventaja de la API ShellExecute sobre el Shell de Visual Basic es que no requiere del nombre de la aplicación para cargar un documento. Por ejemplo la llamada a un archivo de extensión DOC, cargara Microsoft Word con el documento. De la misma manera los Scripts de extensión VBS o JS se cargan naturalmente. Normalmente es conveniente empaquetar al código API en clases, así pues uso la clase CShellExecute que contiene el siguiente código:

 

'-----------------------------------------------------------

' NAME         : CShellExecute

' TYPE         : CLASS

' PROJECT      : -

' AUTHOR       : -

' DESCRIPTION  : KB Visual Basic:

' HOWTO: Use ShellExecute to Launch Associated File (32-bit)

' ShellExecute() starts the application associated with a

' given document extension, without knowing the name of the

' associated application.

'-----------------------------------------------------------

Option Explicit

 

Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" ( _

    ByVal hwnd As Long, _

    ByVal lpszOp As String, _

    ByVal lpszFile As String, _

    ByVal lpszParams As String, _

    ByVal lpszDir As String, _

    ByVal FsShowCmd As Long) As Long

Private Declare Function GetDesktopWindow Lib "user32" () As Long

 

Private Const SW_SHOWNORMAL          As Long = 1

Private Const SE_ERR_FNF             As Long = 2

Private Const SE_ERR_PNF             As Long = 3

Private Const SE_ERR_ACCESSDENIED    As Long = 5

Private Const SE_ERR_OOM             As Long = 8

Private Const SE_ERR_DLLNOTFOUND     As Long = 32

Private Const SE_ERR_SHARE           As Long = 26

Private Const SE_ERR_ASSOCINCOMPLETE As Long = 27

Private Const SE_ERR_DDETIMEOUT      As Long = 28

Private Const SE_ERR_DDEFAIL         As Long = 29

Private Const SE_ERR_DDEBUSY         As Long = 30

Private Const SE_ERR_NOASSOC         As Long = 31

Private Const ERROR_BAD_FORMAT       As Long = 11

 

Public Sub StartDoc( _

    Document As String, _

    Optional ShowError As Boolean = False _

    )

    Dim Scr_hDC As Long

    Dim rtn     As Long

   

    Scr_hDC = GetDesktopWindow()

    rtn = ShellExecute(Scr_hDC, "OPEN", Document, "", "C:\", SW_SHOWNORMAL)

   

    If ShowError Then

       ShowErrorMessage rtn

    End If

End Sub

 

Private Sub ShowErrorMessage(r As Long)

    Dim s As String

   

    If r <= 32 Then

        'There was an error

        Select Case r

            Case SE_ERR_FNF

                s = "File not found"

            Case SE_ERR_PNF

                s = "Path not found"

            Case SE_ERR_ACCESSDENIED

                s = "Access denied"

            Case SE_ERR_OOM

                s = "Out of memory"

            Case SE_ERR_DLLNOTFOUND

                s = "DLL not found"

            Case SE_ERR_SHARE

                s = "A sharing violation occurred"

            Case SE_ERR_ASSOCINCOMPLETE

                s = "Incomplete or invalid file association"

            Case SE_ERR_DDETIMEOUT

                s = "DDE Time out"

            Case SE_ERR_DDEFAIL

                s = "DDE transaction failed"

            Case SE_ERR_DDEBUSY

                s = "DDE busy"

            Case SE_ERR_NOASSOC

                s = "No association for file extension"

            Case ERROR_BAD_FORMAT

                s = "Invalid EXE file or error in EXE image"

            Case Else

                s = "Unknown error"

        End Select

        MsgBox s, vbInformation

    End If

End Sub

 

Para emplear la clase, la adicionamos al proyecto del ejemplo y cambiamos el código del evento del botón cmdJob por el siguiente:

 

Private Sub cmdJob_Click()

    Dim O As CShellExecute

   

    cmdJob.Enabled = False

    Prompt "Llamando al Script..."

   

    Set O = New CShellExecute

    O.StartDoc App.Path & "\TAREA.VBS"

    Set O = Nothing

End Sub

 

Autor: Harvey Triana

MVP Visual Basic Developer

www.eidos.es/VeXPERT

htriana@eidos.es

Derechos Reservados

Ultima Actualización: 09-12-2000