lunes, 9 de marzo de 2009

Un programa simple de descarga de archivos usando API’s de Windows en Visual Basic

Hola a todos, bueno en esta oportunidad les contare algo del uso de APIs desde nuestros programas hechos en Visual Basic.
Para los que no saben que es un API… un API(Application Programming Interface) o en castellano “Interfaz de Programación de Aplicaciones” es [gracias Wikipedia :o)] :

Un conjunto de funciones residentes en bibliotecas (generalmente dinámicas, también llamadas DLLs por sus siglas en inglés, término usado para referirse a éstas en Windows) que permiten que una aplicación corra bajo un determinado sistema operativo. En este caso se refiere a las aplicaciones Windows. Debido a su estrecha relación con el desarrollo de software, los programas en sus especificaciones generalmente explicitan la versión de la API del sistema operativo, mediante diversas nomenclaturas tales como la versión específica del sistema operativo (para Windows 98, por ejemplo), o explicitando la versión del conjunto de bibliotecas (Plataforma Win32, etc.). Las funciones API se dividen en varias categorías:
  • Depuración y manejo de errores
  • E/S de dispositivos
  • DLLs, procesos e hilos
  • Comunicación entre procesos
  • Manejo de la memoria
  • Monitoreo del desempeño
  • Manejo de energía
  • Almacenamiento
  • Información del sistema
  • GDI (interfaz para dispositivos gráficos) de Windows (tales como impresoras)
  • Interfaz de usuario de Windows

Bueno a lo nuestro.. “y entonces José para que es todo eso, si VB (Visual Basic) tiene un montón de clases, funciones y procedimientos que nos permiten hacer cosas con el sistema operativo” , es cierto pero no todo se puede hacer con lo nativo de Visual Basic, veamos un ejemplo especifico; se requiere hacer un programa en VB de descarga de archivos de Internet, donde nos muestre una barra con el avance de la descarga y obviamente antes nos habra mostrado el tamaño total de la descarga.

Como lo resolvemos??? Existe en VB6 el control Inet y en Vb.Net la clase Net.WebClient, pero con ninguno de ellos podremos hacer una barra de progreso real.

Asi que para eso usaremos el API “WinInet” que como su nombre lo indica esta en el archivo “WinInet.dll” dentro de la carpeta de Windows\system32.

Ahora si, a resolver el problema..

  1. Hagamos la Interfaz usando el diseñador de formularios en VB2005 de tal forma que nos quede algo asi:

Ok, ahora agregamos un modulo que se llame WinInet.bas y declaramos las funciones de la API que usaremos:

Option Explicit
Public Const INTERNET_OPEN_TYPE_PRECONFIG = 0
Public Const INTERNET_OPEN_TYPE_DIRECT = 1
Public Const INTERNET_OPEN_TYPE_PROXY = 3
Public Const scUserAgent = "VB OpenUrl"
Public Const INTERNET_FLAG_RELOAD = &H80000000

Public Const HTTP_QUERY_CONTENT_LENGTH = 5
Public Const HTTP_QUERY_LAST_MODIFIED = 11

Public Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" _
(ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, _
ByVal sProxyBypass As String, ByVal lFlags As Long) As Long

Public Declare Function InternetOpenUrl Lib "wininet.dll" Alias "InternetOpenUrlA" _
(ByVal hOpen As Long, ByVal sUrl As String, ByVal sHeaders As String, _
ByVal lLength As Long, ByVal lFlags As Long, ByVal lContext As Long) As Long

Public Declare Function InternetReadFile Lib "wininet.dll" _
(ByVal hFile As Long, ByVal sBuffer As String, ByVal lNumBytesToRead As Long, _
lNumberOfBytesRead As Long) As Integer

Public Declare Function InternetCloseHandle Lib "wininet.dll" _
(ByVal hInet As Long) As Integer

Public Declare Function HttpQueryInfo Lib "wininet.dll" Alias "HttpQueryInfoA" _
(ByVal hHttpRequest As Long, ByVal lInfoLevel As Long, ByRef sBuffer As Any, _
ByRef lBufferLength As Long, ByRef lIndex As Long) As Boolean

Una explicacion rápida (el artículo se volvería demasiado extenso si explico a profundidad cada uno de ellos):

InternetOpen: Abre la conexión a Internet.
InternetOpenUrl:
Abre la dirección desde donde descargaremos el archivo.
InternetReadFile:
Lee o descarga el archivo desde la dirección abierta.
HttpQueryInfo: Lee o extrae algun tipo de información de una dirección en Internet.
InternetCloseHandle: Cierra la conexión abierta a Internet

Listo, ahora que tenemos las funciones hay que usarlas. En el formulario que creamos anteriormente lo que nos interesa es que al presionar el boton etiquetado “Iniciar” descargue el archivo ingresado en la caja de texto al lado de la etiqueta “Ruta(URL):”; ademas deberemos cambiar el nombre de algunos objetos como sigue:

La barra de progreso : pgDownload
La Etiqueta de Tamaño del archivo: lblFileSize
La Etiqueta de Bytes descargados: lblBytesDownloaded
La etiqueta de Estado: LblEstado

entonces el código para ello seria el siguiente:

Private Sub Command1_Click()
DownloadFile Text1.text, “c:\Downloads\archivo.txt”
End Sub

Public Function DownloadFile(FileOrigen As String, FileDestino As String) As Boolean
'On Error GoTo errhandle
Dim hOpen As Long
Dim HOpenURL As Long
Dim sUrl As String
Dim bDoLoop As Boolean
Dim bRet As Boolean
Dim sReadBuffer As String * 2048
Dim lNumberOfBytesRead As Long
Dim sBuffer As String
Dim percent As Integer
Dim file As String
Dim flen As Long

pgDownload.Value = 0
lblFileSize.Caption = 0
lblBytesDownloaded.Caption = 0
'MsgBox "voy a conectar" + vbCrLf + FileOrigen + vbCrLf + FileDestino

sUrl = FileOrigen 'url
hOpen = InternetOpen(scUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)
HOpenURL = InternetOpenUrl(hOpen, sUrl, vbNullString, 0, INTERNET_FLAG_RELOAD, 0)

'MsgBox "conectado!!"

DownloadFile = False
Me.LblEstado.Caption = "Descargando.."
Me.LblEstado.Refresh
bDoLoop = True

Dim cnt As Long
cnt = 0
DownloadFile = True

While bDoLoop
sReadBuffer = vbNullString
bRet = InternetReadFile(HOpenURL, sReadBuffer, Len(sReadBuffer), lNumberOfBytesRead)
sBuffer = sBuffer & Left$(sReadBuffer, lNumberOfBytesRead)
'percent = Int((Len(sBuffer) / flen) * 100)
cnt = cnt + lNumberOfBytesRead
If (cnt > 10000) Then
lblBytesDownloaded.Caption = CStr(Len(sBuffer))
lblBytesDownloaded.Refresh
cnt = 0
End If
pgDownload.Value = lblBytesDownloaded.Caption
If Not CBool(lNumberOfBytesRead) Then
bDoLoop = False
End If
Wend
'Kill FileDestino
Open FileDestino For Binary Access Write As #2
Put #2, , sBuffer
Close #2
pgDownload.Value = 100
lblBytesDownloaded.Caption = file
lblBytesDownloaded.Refresh

If HOpenURL <> 0 Then InternetCloseHandle (HOpenURL)
If hOpen <> 0 Then InternetCloseHandle (hOpen)

End Function

Con eso seria suficiente, espero a alguien le sirva este programita, a mi me sirvio mucho; le hice algunas modificaciones para que me sirva de un actualizador de EXE's en mi red corporativa, si alguien desea que le explique como lo hice e avisan para un siguiente post jeje.

Referencias:
http://www.wikilearning.com/tutorial/los_rincones_del_api_win32_el_cache_de_wininet-enumerar_las_entradas_almacenadas_en_el_cache/3846-6#verOpiniones
http://www.experts-exchange.com/Programming/Languages/Visual_Basic/Q_20577961.html
http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3BQ175179
http://en.allexperts.com/q/Visual-Basic-1048/using-wininet-dll.htm
http://www.vbforums.com/showthread.php?t=543107

2 comentarios:

  1. Caramba!!! Jose, en otras palabras (corrigeme si me equivoco) es (un tipo de) pariente/primo/lo-que-seqa (con un GUI) del WGET de los sistemas *nix? Bueno a mi me lo parece :)

    Bueno.. sea lo que sea esta genial!!!

    Gracias por compartir tu conocimiento con nosotros =P

    (P.D. Gracias tambien por tus experiencias migrando de MSSQL a PostgreSQL, mira que me han caido como anillo al dedo por que en la escuela tenemos un proyecto que trata (casualmente) de montar/comparar/etc... un RDBMS diferente a MSSQL y mi equipo y yo elegimos Postgres,,, Gracias!!! Muchas gracias)

    ResponderEliminar
  2. Jose buenos dias me gustaria saber un poco mas acerca de esto pues intenté implementar lo que expusiste aca pero no entendí como, si es posible dejame tu correo para escribirte o escribeme al mio jhonlagosx@hotmail.com

    ResponderEliminar