Scripting al Servicio de Visual Basic
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).
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.
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).
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.
'-----------------------------------------------------------
' 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.
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.
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.
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