Cadenas de conexión de Entity Framework… Cuidado!

Hola a todos!

Hoy voy a comentar algo bastante curioso con lo que me he topado trabajando con Entity Framework. A modo de preámbulo, os diré que durante la ejecución de una aplicación, cada vez que instanciábamos un objeto de una clase que heredase de ObjectContext se lanzaban una serie de excepciones, cuyo número iba creciendo. Es decir, que simplemente invocando al constructor, sin conectarse aún a ningún sitio, podíamos ver cómo se lanzaban 2 excepciones. A la siguiente invocación del constructor (de la misma clase!) podían lanzarse 3… Es decir, el número de excepciones iba «in crescendo». Y aunque iban aumentando el número de excepciones por invocación de constructor, no había un patrón para poder determinar por qué iba aumentando ese número. O al menos el patrón no era aparente del todo (tranquilos que al final desenmascararemos el misterio).

Por otro lado, para agravar más aún la situación, los ObjectContext tenían una vida cortísima, y se creaban y destruían continuamente (arquitectura empresarial, acceso a datos desde componentes sin estado… bueno, os hacéis a la idea, ¿verdad?), con lo que el número de excepciones lanzadas era… enorme.

Como a estas alturas ya sabréis, en las cadenas de conexión de Entity Framework hay que indicar varios parámetros:

  • El proveedor de acceso a datos a utilizar
  • La cadena de conexión a base de datos (de las de ADO.NET de toda la vida) que usa el proveedor del punto anterior.
  • Dónde están los ficheros de metadatos.

Para crear cadenas de conexión mediante código, se recomienda utilizar la clase EntityConnectionStringBuilder, tal y como se indica aquí.

Bien… Pues la causa de las excepciones era el cómo indicábamos dónde están los metadatos. Los ficheros de metadatos los tenemos dentro de los ensamblados en los que tenemos definidas las entidades. Por lo tanto, la ubicación de los metadatos la estábamos indicando de la siguiente manera:

res://*/MyEntities.csdl|res://*/MyEntities.ssdl|res://*/MyEntities.msl

Que quiere decir: Busca en los recursos del ensamblado «*» (es decir, en todos los ensamblados), un recurso que se llame «MyEntities.csdl» (modelo conceptual), otro que se llame «MyEntities.ssdl» (modelo de almacenamiento) y otro que se llame «MyEntities.msl» (mappings).

En casos normales, la búsqueda de esos metadatos no debería dar ningún problema. La búsqueda de los metadatos (según el MSDN) se lleva a cabo de la siguiente forma:

  1. Busca el recurso indicado en el ensamblado que realiza la llamada
  2. Busca el recurso indicado en los ensamblados referenciados desde el ensamblado del punto anterior
  3. Busca el recurso indicado en los ensamblados que se encuentran en la carpeta de trabajo de la aplicación

Esta información no es incorrecta, pero es incompleta. La búsqueda de los recursos se lleva a cabo utilizando la misma táctica que cuando se debe localizar un ensamblado concreto. Es decir, que el tercer paso sería más bien así:

  • Busco en los ensamblados cargados en el dominio de aplicación
  • Busco en los ensamblados de la carpeta de trabajo actual
  • Busco en los ensamblados de la Global Assembly Cache (GAC).

Esto creo que es así por la excepción que me lanzaban los constructores de las clases derivadas de ObjectContext. La excepción en concreto era la siguiente:

Ocurrió NotSupportedException: No se admite el miembro invocado en un ensamblado dinámico.

Y la pila de llamadas era la siguiente:

mscorlib.dll!System.Reflection.Emit.AssemblyBuilder.GetManifestResourceNames() + 0x39 bytes
System.Data.Entity.dll!System.Data.Metadata.Edm.MetadataArtifactLoaderCompositeResource.GetManifestResourceNamesForAssembly(System.Reflection.Assembly assembly) + 0x24 bytes
System.Data.Entity.dll!System.Data.Metadata.Edm.MetadataArtifactLoaderCompositeResource.AssemblyContainsResource(System.Reflection.Assembly assembly, ref string resourceName = «MyEntities.csdl») + 0x22 bytes
System.Data.Entity.dll!System.Data.Metadata.Edm.MetadataArtifactLoaderCompositeResource.LoadResources(string assemblyName, string resourceName, System.Collections.Generic.ICollection<string> uriRegistry = Count = 0, System.Data.Metadata.Edm.MetadataArtifactAssemblyResolver resolver = {System.Data.Metadata.Edm.DefaultAssemblyResolver}) + 0x91 bytes
System.Data.Entity.dll!System.Data.Metadata.Edm.MetadataArtifactLoaderCompositeResource.CreateResourceLoader(string path, System.Data.Metadata.Edm.MetadataArtifactLoader.ExtensionCheck extensionCheck, string validExtension, System.Collections.Generic.ICollection<string> uriRegistry, System.Data.Metadata.Edm.MetadataArtifactAssemblyResolver resolver) + 0xba bytes
System.Data.Entity.dll!System.Data.Metadata.Edm.MetadataArtifactLoader.Create(string path, System.Data.Metadata.Edm.MetadataArtifactLoader.ExtensionCheck extensionCheck, string validExtension, System.Collections.Generic.ICollection<string> uriRegistry, System.Data.Metadata.Edm.MetadataArtifactAssemblyResolver resolver) + 0x40 bytes
System.Data.Entity.dll!System.Data.EntityClient.EntityConnection.SplitPaths(string paths) + 0x294 bytes
System.Data.Entity.dll!System.Data.EntityClient.EntityConnection.GetMetadataWorkspace(bool initializeAllCollections = false) + 0x95 bytes
System.Data.Entity.dll!System.Data.Objects.ObjectContext.RetrieveMetadataWorkspaceFromConnection() + 0x1b bytes
System.Data.Entity.dll!System.Data.Objects.ObjectContext.ObjectContext(System.Data.EntityClient.EntityConnection connection, bool isConnectionConstructor = false) + 0xad bytes
System.Data.Entity.dll!System.Data.Objects.ObjectContext.ObjectContext(string connectionString, string defaultContainerName = «MyEntities») + 0x1c bytes

Es decir… que fallaba en el método GetManifestResourceNames, quejándose de que ese método no está soportado cuando se invoca sobre un ensamblado dinámico. El ensamblado llamador no era dinámico, de eso estoy seguro. Así como que estoy seguro de que no había ensamblados dinámicos ni como referencia del ensamblado que lo llamaba, ni en la carpeta bin. Pero sí que los había en el dominio de aplicación (AppDomain), con lo cual, a la hora de localizar los recursos, estaba buscando en los ensamblados ya cargados en el AppDomain, y lo estaba haciendo antes de mirar en los ensamblados del directorio de trabajo, ya que si no, habría localizado los recursos sin problemas.

El hecho de que el número de excepciones por constructor fuera aumentando quedaba resuelto de manera sencilla. Según se fuesen generando más ensamblados dinámicos y se fuesen cargando en el AppDomain, se lanzaba una excepción por cada ensamblado dinámico en el AppDomain en cada invocación del constructor.

Vale, ya sé que generar ensamblados dinámicamente (Reflection.Emit) no es algo que se haga todos los días. Pero me ha hecho pensar un poco en cómo escribir las cadenas de conexión para que .NET vaya directamente al ensamblado que tiene incrustados los recursos, y no se de un garbeo cargando todos los assemblies del lugar para buscar un recurso.

Corolario: Cuando especifiques una cadena de conexión de Entity Framework, si está incrustada como recurso en un ensamblado (que es la opción por defecto), indica siempre el nombre del ensamblado donde está ese recurso. ¡Huye de los asteriscos, no suelen ser buenos compañeros! (y si no, se lo preguntas al que hizo un rm -rf * sobre /).

Metadata=res://<assemblyFullName>/<resourceName>

P.D.: Aunque en la documentación diga que tienes que poner el nombre completo del ensamblado, en realidad no es así. No es necesario especificar la versión, ni la cultura, ni la clave pública del ensamblado…

Si sigo así, la próxima entrada será en el 2012… Saludos a todos!

Cuidado con los MemoryStream…

Hola a todos!

Los MemoryStream son una herramienta muy útil para ir construyendo un buffer en memoria, sobre todo cuando a priori no se sabe el tamaño que va a tener. Esta clase se utiliza como un Stream normal, de manera que puedes ir leyendo o escribiendo los datos que necesites. Además, te gestiona automáticamente el tamaño del buffer de memoria subyacente, de manera que no tengas que preocuparte de ir redimensionando el buffer si se te queda corto.

Sin embargo, si lo que necesitas no es un churro de bytes un buffer que contenga únicamente bytes, sino que quieres darlos un significado a posteriori (como por ejemplo, necesitas extraer unas cadenas de texto), y lo quieres obtener después de haber escrito los contenidos de ese MemoryStream poco a poco (por ejemplo, porque lo estás leyendo de otro Stream del que no conoces el tamaño inicialmente), hay que tener en cuenta un detalle importante:

Cuando obtengas el buffer subyacente del MemoryStream (es decir, llames al método GetBuffer), dicho buffer tendrá el tamaño total de lo que ha reservado automáticamente el MemoryStream.

¿Qué narices quiere decir eso? Pues básicamente, que si has escrito en el Stream un total de x bytes, y llamas al método GetBuffer, no obtendrás un buffer con esos x bytes que has escrito (a no ser que tengas mucha suerte), sino del tamaño que tenga reservado en ese momento el stream.

El método correcto al que debes llamar es ToArray(), que te devolverá un array de bytes. Si luego debes transformar dicho array, ten presente el encoding que utilice tu fuente de datos.

Bueno, pues nada más por hoy… Un saludo!

IDictionary no es serializable usando IXmlSerializable…

Hola!

Tengo una confesión hoy. Me encanta usar la clase genérica Dictionary. Cuando necesito organizar una caché que pueda ser accedida a la velocidad del rayo, y con claves no necesariamente sencillas, ¡Zas! Dictionary que te crió. Sin embargo, la clase genérica Dictionary implementa la interfaz IDictionary, y no se puede serializar usando IXmlSerializable.

Y… ¿a quién le importa esto? Pues… teniendo en cuenta que IXmlSerializable es la interfaz mediante la cual se serializan los objetos cuando se van a usar Web Services no-WCF (WCF utiliza DataContractSerializer), la cosa parece ya un poco más importante…

Si lo pensamos un momento, serializar un diccionario a XML no es más complicado que tomar las claves y los valores, y plantarlos en un XML. A la hora de deserializar, vas metiendo cada elemento con su clave en el diccionario y listos… Entonces… ¿por qué no se puede serializar usando IXmlSerializable?

La respuesta a esta pregunta la da Kim Hamilton (de Microsoft) en los foros. Básicamente dice que la clase Dictionary es parte de mscorlib.dll y la interfaz IXmlSerializable está definida en System.Xml.dll. Para evitar una referencia circular, el diccionario no se serializa, y listos…

Pues vaya, no?

Por suerte, la gente no es que esté muy contenta con esto, y hay alguna implementación de diccionarios que sí que son serializables, y son tan flexibles como el Dictionary que viene en el framework. Por ejemplo, ésta.

Si estás creando Web Services con WCF, entonces no tendrás problemas, ya que DataContractSerializer serializa sin problemas los diccionarios.

Saludos!

XML no es la solución universal…

…pero es un buen comienzo (aunque, como todo, depende de para qué).


Llevo varias semanas rumiando esta idea, y la verdad es que me ganaré las iras de más de uno al expresarla en público, pero da igual. Así además podré sondear las ideas de los cracks que por aquí suelen pasar.


Después de llevar ya un buen tiempo trabajando con Web Services, hay una idea que siempre pasa por mi cabeza de manera recurrente. ¿Por qué XML para el intercambio de datos?


Sé que SOA no es lo mismo que XML/XSD, pero en el 95% de los casos suelen ir de la mano para la comunicación de datos entre diversos sistemas. Y mi pregunta es ¿por qué?. Parte de las respuestas que me vienen a la cabeza son las siguientes:



  • XML es un lenguaje extensible, y muy flexible, y XSD nos permite definir cualquier estructura de datos que necesitemos.

  • Es fácil de comprender por los desarrolladores.

  • Es fácil de manipular por parte de los desarrolladores.

  • Al ser una representación textual de los datos, podemos examinar los contenidos de los datos de forma sencilla a la hora de depurar

  • Nos independizamos de las representaciones internas de los datos de cada uno de los sistemas.

Bueno, como podéis ver, la quinta razón no es del todo cierta, ya que si por ejemplo queréis consumir un servicio web creado con otra tecnología que no sea .NET desde una aplicación .NET veremos que cuando determinados tipos de datos aparecen en escena (por ejemplo, fechas), es posible que nos encontremos con algún problema.


Y hay algo más que salta a la vista tras ver las razones expuestas (si bien es una visión algo sesgada): El foco está puesto en el desarrollador, no en los sistemas que tienen que intercambiar los datos entre sí.


La duda que se me plantea es: ¿Por qué usamos un formato de datos comprensible por los humanos cuando los que tienen que manipular esos datos son sistemas informáticos? (es decir, máquinas). Los datos en XML llevan asociados una merma de rendimiento derivada del procesamiento que hay que hacer antes de poderlos utilizar nada despreciable. Y también se usa más espacio del que se podría utilizar, incrementando el gasto de ancho de banda para las comunicaciones.


Creo que sería más interesante algo como lo siguiente: Al igual que tenemos los XSD para definir los XML, ¿no podríamos definir un lenguaje del estilo de XSD que lo que defina es un formato binario de datos de forma sencilla? Es decir, el lenguaje que define los datos a intercambiar debería ser fácil de comprender por los desarrolladores, pero las estructuras de datos que representa deberían ser mucho más compactas y eficientes. Y sin dejar huecos para las imprecisiones. Cada tipo de dato «simple» – fechas, cadenas de texto, datos numéricos – debería tener definida su representación en binario. Por ejemplo, podríamos definir que las cadenas de texto están representadas mediante Unicode, ANSI Z, etc. O si queremos que los datos utilicen big-endian o little endian…


Por supuesto, debería tener la posibilidad de hacer includes, y referencias a objetos, de manera que cada tipo de datos «complejo» pueda ser definido una única vez, y cada instancia pueda ser serializada también de manera única.


Esto requiriría un apoyo por parte de las herramientas de desarrollo, de manera que a la hora de depurar tomasen la estructura de datos a examinar, y utilizando el lenguaje de definición mostrase al desarrollador los datos de una manera sencilla de comprender y manipular.


Quizá con algo así, podríamos tener un «Biztalk» que no necesitase tanto maquinón para ejecutarse… ^_^U


¿Qué opináis al respecto?

Una chorradita, pero que queda muy molona…

Buenas!

En este artículo vamos a crear un formulario que muestre un mensaje de espera, por si tenemos que hacer una tarea que requiera tiempo, para distraer un poco al usuario. Además vamos a realizar un fundido del formulario, para que aparezca poco a poco.

En este ejemplo vamos a utilizar multithreading, al estilo de lo comentado en este artículo, (no me preguntéis dónde está el código fuente de este artículo, porque pese a que los adjuntos están en el servidor – no sé dónde – no se muestran en los artículos – ayudaaa!).

Antes de entrar en la chicha, unos pocos preparativos:

  1. Vamos a crear un nuevo formulario en nuestro proyecto. Le llamaremos FrmEspera.
  2. A este formulario le quitamos el borde (FormBorderStyle=None).
  3. Además, le quitamos el texto (Text=»»). Así conseguiremos que se oculte completamente el borde del mismo, quedando como un panel flotante.
  4. Por último, establecemos la propiedad StartPosition=CenterScreen, para que cuando mostremos el formulario aparezca siempre en el centro de la pantalla.
  5. Para que quede bonito, le ponemos una imagen de fondo al formulario, un control PictureBox para mostrar un icono, y una etiqueta (control Label).

Ahora, vamos al grano: Sería interesante poder modificar el texto a mostrar en el formulario (para usarlo en diferentes situaciones), y la imagen a mostrar. Para esto, en el código del formulario declaramos un par de propiedades:

Public Property ImagenAMostrar() As Image
   Get
      Return pictureBox1.Image
   End Get
   Set(ByVal value As Image)
      pictureBox1.Image = value
   End Set
End Property
 
Public Property MensajeAMostrar() As String
   Get
      Return label1.Text
   End Get
   Set(ByVal value As String)
      label1.Text = value
   End Set
End Property

Como vamos a utilizar varios hilos, vamos a crear un tipo delegado que nos va a permitir invocar desde el hilo en el que vamos a controlar el cambio de la opacidad del formulario el método que realmente cambiará la opacidad del mismo. Esto es necesario para evitar excepciones derivadas del hecho de cambiar una propiedad de un formulario desde un hilo diferente a aquél en el que se creó.

Public Delegate Sub SetOpacityDelegate(ByVal op As Double)
Private dlg As New SetOpacityDelegate(AddressOf Me.SetOpacity)
 
Private Sub SetOpacity(ByVal op As Double)
   Me.BringToFront()
   Me.Opacity = op
   ' Esto es feo, pero necesitamos que se procesen los eventos pendientes...
   Application.DoEvents()
   If op = 0.0F Then
      Me.Close()
   End If
End Sub

Por último, vamos a crear los métodos que usaremos para mostrar y ocultar el formulario. Les vamos a llamar FadeIn y FadeOut:

Public Sub FadeIn()
   Me.Opacity = 0.0F
   Me.Show()
   Dim dlg As New System.Threading.ThreadStart(AddressOf Me.doFadeIn)
   dlg.Invoke()
End Sub
 
Public Sub FadeOut()
   Dim dlg As New System.Threading.ThreadStart(AddressOf Me.doFadeOut)
   dlg.Invoke()
End Sub
 
Private Sub doFadeIn()
   For op As Double = 0.1F To 1.0F Step 0.1F
      If InvokeRequired Then
         Me.Invoke(dlg, op)
      Else
         Me.SetOpacity(op)
      End If
      System.Threading.Thread.Sleep(50)
   Next
   Me.Invoke(dlg, 1.0F)
End Sub
 
Private Sub doFadeOut()
   For op As Double = 1.0F To 0.1F Step -0.1F
      If InvokeRequired Then
         Me.Invoke(dlg, op)
      Else
         Me.SetOpacity(op)
      End If
      System.Threading.Thread.Sleep(50)
   Next
   Me.Invoke(dlg, 0.0F)
End Sub

Como podemos ver, en el método FadeIn y en el método FadeOut, hacemos prácticamente lo mismo: Inicializamos un hilo que llamará al método que se encarga de realizar un bucle variando la opacidad del formulario. Y en este método hacemos un bucle en el que se comprueba si estamos en el hilo de la interfaz de usuario o en otro (llamando a InvokeRequired), si bien no es necesario hacerlo, ya que esa condición siempre será verdadera por cómo llamamos a los métodos doFadeIn y doFadeOut, y se llama al método que cambia la opacidad a través de un delegado si estamos en un hilo diferente, o directamente si estamos en el hilo de la interfaz de usuario.

¿Cómo usaríamos este formulario? Pues de la siguiente manera:

Dim frmMsg As New FrmEspera
frmMsg.MensajeAmostrar = "Por favor, espere. Estoy haciendo algo MUY importante"
frmMsg.ImagenAMostrar = Me.ImageList1.Images("ImagenEspera")
frmMsg.FadeIn()
 
' Hacemos la tarea pesada aquí...
 
frmMsg.FadeOut()

Bueno… Pues eso es todo por hoy.

Saludos!

Hagamos recuento…

Hola,

Hace ya poco más de un año, este blog se inauguró con este post. Desde entonces se han recogido 36.232 visitas. Con un total de 46 artículos, se han producido 214 comentarios, una media de 4,7 comentarios por artículo (si bien esto no es lo habitual). Esta media es superior a lo que debería haber sido, principalmente a causa de un par de artículos que fueron en su día polémicos (éste y éste).

De todas maneras, ha sido un año complicado, que se ha notado en la cantidad de artículos publicados por mes, pero haciendo balance, creo que ha sido positivo.

Muchas gracias a todos los que participáis, bien sea leyendo o haciendo comentarios.

Saludos!

III Conferencia de Usuarios de Sharepoint

La empresa para la que trabajo (Informática El Corte Inglés) va a participar como patrocinador Oro en la III Conferencia de Usuarios de Sharepoint, que se celebrará el 30 de octubre en el Hotel Meliá Castilla, sito en la calle Capitán Haya, 43, en Madrid.

En esta conferencia se hará una presentación de un proyecto bastante interesante, con el que se ha alcanzado la certificación oficial «AA» de Accesibilidad.

Podéis consultar la agenda del evento aquí:

http://www.microsoft.com/spain/office/ceus/agenda/default.mspx

Y podéis inscribiros aquí:

http://www.microsoft.com/spain/office/ceus/default.mspx

Saludos!

Mi aplicación en C++ (VC++ 2005 Express) no se ejecuta en otros equipos!!! (solución)

Hola a todos!

Como ya he comentado en otra ocasión, estoy usando el Visual C++ 2005 Express para desarrollar un jueguecillo con OpenGL y SDL. Cuando empecé, lo estaba desarrollando con el Visual C++ 2003, pero me comentaron que la STL había mejorado en la versión 2005, y como había una versión gratuita, decidí actualizarme.

El caso es que el otro día decidí liberar una alpha para los betatesters. Y de repente algunos (no todos) me comentaron que no funcionaba. Que les daba el siguiente error:

No se pudo iniciar la aplicacion porque su configuracion es incorrecta. Reinstalar la aplicacion puede solucionar el problema.

Hmmm… Al principio pensé que debía haber algún problema con mi código. Así que empece a meter trazas y pedir logs. Tras comentarme los betatesters que no había ningún log, me di cuenta de que la aplicación, como dice el error claramente, no llegaba a iniciarse. ^_^U

Entonces me di cuenta de un pequeño detalle. El runtime de C de esta versión de C++ probablemente no lo tenían todos los usuarios. Googleando un poco, ví que los betatesters se podían instalar el paquete de distribución del runtime de C++… Pero me pareció que iba a ser un rollo para ellos tener que andar instalando nada para probar una alpha.

¿Qué hago? Si no quiero que se instalen el paquete de distribución, tampoco me voy a poner a generar un msi con la alpha del juego. Habría sido la mejor solución, pero generalmente la gente prefiere descomprimir el archivo, y ejecutar directamente, sobre todo cuando algo es no definitivo…

Finalmente, encontré la solución en los foros de microsoft. La traduzco a continuación:

1) En la máquina en la que tienes instalado Visual C++ Express, crea la siguiente carpeta y subcarpetas colgando de %PROGRAMFILES%Microsoft Visual Studio 8VC

redistx86Microsoft.VC80.CRT

2) Copia msvcr80.dll, msvcp80.dll, msvcm80.dll de:

%WINDIR%winsxsx86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.42_x-ww_0de06acd

a:

%PROGRAMFILES%Microsoft Visual Studio 8VCredistx86Microsoft.VC80.CRT

3) En la carpeta Microsoft.VC80.CRT, crea un nuevo fichero llamado:

Microsoft.VC80.CRT.manifest

4) Dicho fichero deberá tener el siguiente contenido:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Copyright © 1981-2001 Microsoft Corporation -->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <noInheritable/>
  <assemblyIdentity
      type="win32"
      name="Microsoft.VC80.CRT"
      version="8.0.50608.0"
      processorArchitecture="x86"
      publicKeyToken="1fc8b3b9a1e18e3b"
    />
  <file name="msvcr80.dll"/>
  <file name="msvcp80.dll"/>
  <file name="msvcm80.dll"/>
</assembly>

Con esto hemos creado un nuevo directorio en tu instalación de Visual C++ Express que puede ser reutilizado siempre que lo necesites.

Para distribuir estos ficheros, simplemente copia la carpeta Microsoft.VC80.CRT en la carpeta de tu programa. Y eso es todo. Así que, por ejemplo, si el ejecutable de tu aplicación está en la ruta:

%PROGRAMFILES%MiAplicación

tendrás una carpeta llamada:

%PROGRAMFILES%MiAplicaciónMicrosoft.VC80.CRT

que contendrá los cuatro ficheros mencionados anteriormente (3 DLLs, y el manifiesto que has creado a mano).

No hace falta instalar nada más. Simplemente arranca tu aplicación, y se ejecutará sin problemas. No hace falta ninguna instalación específica. Simplemente basta con que tu programa de instalación cree esa subcarpeta junto con el resto de archivos que tenga tu aplicación.

NOTA:

%WINDIR% es el directorio en el que está instalado Windows. Es una variable que se puede utilizar en el cmd.exe. Normalmente es C:WINDOWS

%PROGRAMFILES% es similar, salvo que apunta a la carpeta Archivos de Programa. Lo bueno es que es independiente de idioma.

Saludos!

Aprieta, aprieta, que entra más…

No os llevéis a confusión con el título… Este artículo no es más que un mini-tutorial sobre la clase System.IO.Compression.GZipStream, que nos proporciona la funcionalidad necesaria para comprimir datos utilizando el estándar «de facto» gzip.

La verdad es que el uso de esta clase no debería ser complicado, ya que básicamente lo que hacemos es leer y escribir datos de un stream, como si fuese un fichero o un trozo de memoria más. Pero tiene una pequeña particularidad: Para que funcione correctamente nuestro código tenemos que tener cuidado de indicar en el constructor que queremos que el stream subyacente a nuestro GZipStream se quede abierto cuando terminemos de utilizarlo. Sobre todo cuando lo que queremos es comprimir en memoria, para posteriormente, sin crear ficheros temporales en el disco duro, enviarlo por red.

Veamos el ejemplo, sencillo, sencillo:

Para comprimir:

   Dim memStream As New System.IO.MemoryStream
   ' Aquí está el quid de la cuestión: Indicarle en el tercer parámetro
   ' que cuando cierre el stream de compresión, no elimine el stream
   ' subyacente.
   Dim gzip As New System.IO.Compression.GZipStream(memStream, _
                   IO.Compression.CompressionMode.Compress, True)
   Dim fileBytes() As Byte
   Dim compressedFileBytes() As Byte = {}
 
   ' Leemos el contenido del fichero a comprimir
   fileBytes = System.IO.File.ReadAllBytes(filePath)
   ' Escribimos en el stream compresor
   gzip.Write(fileBytes, 0, fileBytes.Length)
   ' Si no cerramos el stream, no podremos leer correctamente el 
   ' resultado de la compresión.
   gzip.Close()
 
   ' Vamos a leer los datos comprimidos
   Array.Resize(compressedFileBytes, memStream.Position)
   ' Colocamos el stream de memoria al inicio, para leer todo.
   memStream.Position = 0
   ' Ya tenemos todos los datos en el array compressedFileBytes.
   memStream.Read(compressedFileBytes, 0, compressedFileBytes.Length)
 
   ' Hacer lo que queramos con los bytes comprimidos...
 
   ' Y finalmente, limpiar un poquito.
   memStream.Close()
   gzip.Dispose()

Para descomprimir:

  ' fileBytes es un array de bytes que contiene el fichero comprimido
  Dim memStream As New System.IO.MemoryStream(fileBytes)
  ' Creamos el GZipStream basándonos en el stream de memoria.
  Dim gzip As New System.IO.Compression.GZipStream(memStream, _
                   IO.Compression.CompressionMode.Decompress)
  ' Si sabemos a priori el tamaño del fichero sin comprimir, 
  ' inicializamos el array de bytes que contendrá el fichero
  ' descomprimido.
  Dim realFileBytes(fileSize - 1) As Byte
 
  memStream.Position = 0
  
  ' Leemos el fichero.
  gzip.Read(realFileBytes, 0, fileSize)
  ' Cerramos...
  gzip.Close()
  gzip.Dispose()
 
  ' Usamos el fichero para lo que queramos, está en el memoryStream...
 
  ' Cerramos el MemoryStream.
  memStream.Close()

Saludos!

Una técnica interesante para "ajustar" imágenes

Iba a poner «para redimensionar imágenes», pero realmente no es eso lo que hace. Lo interesante de esta técnica es que cuando reduces la imagen, lo que hace es eliminar la parte de la imagen que no nos proporciona información interesante. Y funciona bastante bien.

A esta técnica la llaman «Seam Carving».

Un vídeo de demostración de esta técnica que podéis ver en YouTube: