Mostrando entradas con la etiqueta VFP. Mostrar todas las entradas
Mostrando entradas con la etiqueta VFP. Mostrar todas las entradas

jueves, enero 25, 2018

Visual FoxPro 9: Cómo arreglar ciertos tipos de corrupción en librerías VCX (_Memberdata)

En Visual FoxPro las librerías pueden funcionar con ciertos tipos de corrupción sin que nos demos cuenta y sin errores visibles o funcionales, pero cuando se trabaja con Control de Código fuente y se usan las vistas de texto, como la generada por FoxBin2Prg, a veces estos errores se hacen evidentes y siempre es mejor solucionarlos.

En este artículo veremos el caso de _Memberdata y cómo arreglarlo.



_Memberdata

_Memberdata es una propiedad que guarda la capitalización de las propiedades y métodos creados. Por defecto VFP crea todas las propiedades y métodos en minúsculas, pero con la propiedad _Memberdata VFP puede recordar la capitalización que le demos a la propiedad (Ver más información en la ayuda de VFP o en la web de Microsoft sobre MemberData Extensibility)

Por ejemplo, si creamos la propiedad "ValorMaximo" con el PEM Editor (de VFPx), la propiedad _Memberdada tendrá información con esta estructura:

<VFPData>
   <memberdata name="valormaximo" display="ValorMaximo"/>
</VFPData>


Donde:
  • name contiene el nombre original en minúsculas
  • type (opcionalmente) contiene el tipo de elemento ("Property" o "Method" habitualmente)
  • display contiene el nombre capitalizado a mostrar

Propiedad "ValorMaximo" creada con el PEM Editor


A medida que se van agregando propiedades capitalizadas, se irán agregando también elementos <memberdata name="xx" display="Xx">, y llegado un momento dado este campo puede llegar a ser bastante largo (puede tener hasta unos 8 KiB de longitud)

Este es un ejemplo de propiedad _Memberdata corrupta, visto con el Notepad++ desde la vista texto generada por FoxBin2Prg de la librería foxcharts.vcx en GitHub:



Pueden observarse algunos problemas evidentes:

  1. VFPData aparece varias veces, cuando solo debería aparecer 2 veces
  2. Siendo un miembro codificado en XML, debería tener un tag <VFPData> de inicio y un tag </VFPData> final, el cual no se ve
  3. El tag <VFPData> de inicio está anidado en sí mismo varias veces
Bueno, sabemos que esto está mal y queremos solucionarlo, ¿cómo se puede verificar la lista de propiedades que debería ir? Esa parte es fácil, solamente hay que echar un vistazo en la cabecera VC2 de la clase para ver qué propiedades y métodos se han definido:

*<DefinedPropArrayMethod>
    *m: caption_assign
    *m: reset        && Resets the legend GDI+ objects
    *m: rotation_assign
    *m: _drawstring
    *m: _setup
    *m: _updatemeasures
    *m: _value_assign
    *p: backcoloralpha
    *p: forecoloralpha
    *p: format        && Specifies the input and ...
    *p: format2
    *p: isparent
    *p: ogfx
    *p: rotationcenter
    *p: _forceformat
    *p: _height
    *p: _initialized
    *p: _memberdata        && XML Metadata ...
    *p: _obrush
    *p: _ofont
    *p: _orectangle
    *p: _ostringformat
    *p: _transfcaption
    *p: _value
    *p: _vartype
    *p: _width
*</DefinedPropArrayMethod>



Mirando nuevamente la propiedad _Memberdata y su contenido, puede verse un patrón de repetición de varias propiedades, donde básicamente se repite esta parte:

_memberdata = <VFPData>
   <memberdata name="autosize" ... display="AutoSize"/>
   <memberdata name="whatsthishelpid" ... display="WhatsThisHelpID"/>
   <memberdata name="_setup" ...display="_Setup"/>
   <memberdata name="forecoloralpha" ... display="ForeColorAlpha"/>
   <memberdata name="backcoloralpha" ... display="BackColorAlpha"/>
   <memberdata name="width" ... display="Width"/>
   <memberdata name="oledragpicture" ... display="OLEDragPicture"/>

   ...
</VFPData>        && XML Metadata for customizable properties
con lo que recortando cuidadosamente el resto de repeticiones hasta el tag de finalización </VFPData>, quedaría resuelta esta parte.

Nota: Revisando esta librería encontré que tiene más secciones <VFPData> con problemas más adelante, con lo que una vez detectado el problema siempre es conveniente buscar y revisar todas las estructuras <VFPData> para asegurarse de que están correctas.


Puede pasar que alguna de las propiedades descriptas en los miembros <memberdata> esté incompleta o cortada, por ejemplo podría contener solamente la parte del "name" pero no la parte del "display".

Nuevamente, conociendo que la estructura mínima de un miembro debe contener el name y el display, simplemente se puede crear manualmente la propiedad display y ponerle un valor que seguramente va a ser bastante obvio: el nombre de la propiedad capitalizada que querramos que se muestre en el IDE de VFP.



Resumen


Una vez resueltos todos los problemas, se regenera el binario desde esta vista texto (click-derecho sobre el archivo VC2, elegir "Enviar a" -> FoxBin2Prg)

Como se ve, realmente no es difícil solucionar varios de estos problemas, pero sí es necesario dedicar un rato para analizar el problema, conocer qué es lo que estamos modificando, cuál es la estructura correcta e implementar la solución.


Hasta la próxima! :D

jueves, diciembre 28, 2017

Cannot find entry point (DLL name) in the DLL with Visual FoxPro

Este error puede ocurrir por dos motivos, hasta donde sé, actualmente.

El motivo oficial está explicado en la MSDN:
Cannot find entry point (DLL name) in the DLL with Visual FoxPro

Básicamente dice cuando se define una una función DLL se debe respetar estrictamente la capitalización de los nombres (mayúsculas/minúsculas) y los tipos de los parámetros (long/string/etc, y si son por referencia "@" o por valor)

Voy a usar el ejemplo con la función GetUserName que usa la MSDN.

Declaración original en C (siempre buscar la original):

   BOOL GetUserName(

      LPTSTR  lpBuffer,// address of name buffer
      LPDWORD  nSize // address of size of name buffer
     );
   Parameters


Declaración en Visual FoxPro:

   DECLARE GetUserName in Win32API ;
      String @mybuffer, Integer @mybuffersize


Si la función se declara de la siguiente forma, generará el error del artículo:

   DECLARE GetuserName in Win32API ;
      String @mybuffer, Integer @mybuffersize



Ahora bien, hay otro motivo que puede causar este error, y que me ha costado unas cuántas horas encontrar. Es un poco enrevesado para reproducir, pero en resumen puede pasar cuando se tiene una DLL (ej: MiLibreria.dll) en el System32 para que se puede acceder públicamente desde cualquier directorio, y una copia de la misma DLL en otro directorio dentro del ámbito del PATH de Visual FoxPro, que se active con posterioridad.

Ejemplo del caso de uso:
- Se declara una función DLL (ej: DECLARE long MiFuncion in MiLibreria.dll long p1, string @p2 )
- Se usa la función (y funciona bien)
- Se activa un PATH donde hay una copia de la misma (ej: SET PATH TO \proj\dlls ADDITIVE)
- Se intenta usar nuevamente la función => Aquí es donde puede fallar!


Algo importante a saber es que, a diferencia de los componentes tradicionales de Visual FoxPro, como forms, clases y programas, cuando se declara una función de DLL la librería se busca automáticamente en el System32 (o SysWow64), por lo cual no sirve hacer la típica prueba de ? FILE('MiLibreria.dll') para saber si VFP encuentra la librería, porque FILE() no busca en el directorio System32 (o SysWow64) a menos que explícitamente se haga un SET PATH con dicho directorio.

Como digo, esto "puede" pasar, aunque no tiene por qué hacerlo, pero lo importante es tener en cuenta este caso para que no les pase lo mismo.


Hasta la próxima! :D

domingo, julio 24, 2016

Nueva versión v1.19.48 de FoxBin2Prg (arreglos) y Extensiones para PlasticSCM v2.5.48


Parte 1: FoxBin2Prg / Parte 2: Extensiones para Plastic

Parte 1: FoxBin2Prg


FoxBin2Prg es un programa pensado para sustituir a SccText/X y TwoFox y mejorar sus funcionalidades, generando versiones de texto estilo-PRG que pueden ser modificadas y permiten recrear el binario original. Puede ser utilizado con herramientas SCM (Administradores de Control de Código Fuente, como VSS, CVS, SVN) y herramientas DVCS (como Git, Mercurial, Plastic, and others), o como programa independiente, para hacer operaciones de Diff (ver diferencias) y Merge (mezclar cambios).


La lista de cambios es la siguiente:

* Bug Fix: Mensaje de error 'variable tcOutputFile not found' (german version) (Andy Kasper)
 

   Este error solo se muestra en la versión traducida al Alemán por el uso de un nombre de variable incorrecto en un mensaje de estado. (Fix enviado por Andy Kasper / Alemania).

* Bug Fix: Posición de menú BEFORE siempre cambiada a AFTER al convertir (Andy Kasper)
 

   Cuando se convierten menús de texto a binario y el menú está configurado como BEFORE, se estaba cambiando a AFTER por un error de comparación. (Fix enviado por Andy Kasper / Alemania).

* Bug Fix: No se respetan algunas restricciones de conversión para DBFs cuando se usan CFGs particulares por tabla (Nathan Brown)
 

   Cuando se usa el nuevo valor DBF_Conversion_Support: 8 en un config de DBF (filename.dbf.cfg), ciertas restricciones no son respetadas, permitiendo que otros DBFs que no tienen esa configuración también sean regenerados desde las verisones texto.

* Bug Fix db2: Cuando se lee un memo multilínea de un db2 con datos antiguo, se produce un error de índice fuera de rango

   Hasta la versión v1.19.46 los memos de los DBFs se exportaban en una única línea, reemplazando los retornos de carro por su equivalente &#13 y &#10, pero desde la v1.19.47 los memos se exportan como multi-línea. El problema era que al importar los DB2 no se estaban teniendo en cuenta los antiguos memos de línea única anteriores a la posibilidad de importarlos con DBF_Conversion_Support: 8.

* Bug Fix db2: Cuando se usa ExcludeDBFAutoincNextval: 1 en FoxBin2Prg.cfg y a la vez la importación de datos de una tabla con campo AutoInc, se produce el error "Error 2088, Field <FIELD> is read-only" (Nathan Brown)

   La opción de ExcludeDBFAutoincNextVal: 1 estaba usando un armado de SQL distinto al que se usa para importar datos, lo que provocaba ese error. Se ha cambiado para usar el mismo armado SQL que se usa en la nueva conversión.

* Bug Fix pj2: Cuando se regenera el binario de un PJ2 con archivos en una ruta con paréntesis y espacios, se genera un error "Error 36, Command contains unrecognized phrase/keyword" (Nathan Brown)

   Este error solo ocurre en las condiciones descriptas y fue descubierto al convertir las clases de ejemplo de VFP (Solution, FFC) en un Windows de 64 bits. (Bug Fix enviado por Nathan Brown / EEUU).

* Bug Fix frx: Los ControlSource de objetos OLE que contienen comillas se generan mal (Nathan Brown)

   Cuando en un reporte se usa un objeto OLE cuyo Control Source se define como "Expression or variable name" y se forma con una expresión del tipo ["] + nombre_var + ["], el motro de parser interpretaba un fin anticipado de expresión, cortándola en el primer ["]. Se ha agregado codificación/decodificación XML para este caso.

Como actualizar el FoxBin2Prg existente:Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de VFPx


Link de descarga de GitHub



Parte 2: Extensiones para Plastic



Las extensiones para Plastic son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM (herramienta de control de versiones) para poder invocar a FoxBin2Prg desde dentro de la interfaz de Plastic.


La lista de cambios es la siguiente:

> Actualización de FoxBin2Prg: Versión sincronizada con la última versión de FoxBin2Prg



El README.txt explica como se configura en Inglés y Español, y también está explicado en esta nota: Cómo configurar las Herramientas de VFP 9 para Plastic


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de las Extensiones FoxBin2Prg para Plastic de GitHub



Thank you for your support!



Artículos relacionados:

FoxBin2Prg, el sucesor mejorado del Scctext

FoxBin2Prg: Detalle de vistas, Datos de uso, Configuraciones y más

FoxBin2Prg: Guía rápida de uso y configuración


 Saludos!

jueves, junio 09, 2016

Nueva versión v1.19.47 de FoxBin2Prg (arreglos y mejoras) y Extensiones para PlasticSCM v2.5.47


Parte 1: FoxBin2Prg / Parte 2: Extensiones para Plastic

Parte 1: FoxBin2Prg


FoxBin2Prg es un programa pensado para sustituir a SccText/X y TwoFox y mejorar sus funcionalidades, generando versiones de texto estilo-PRG que pueden ser modificadas y permiten recrear el binario original. Puede ser utilizado con herramientas SCM (Administradores de Control de Código Fuente, como VSS, CVS, SVN) y herramientas DVCS (como Git, Mercurial, Plastic, and others), o como programa independiente, para hacer operaciones de Diff (ver diferencias) y Merge (mezclar cambios).


La lista de cambios es la siguiente:

* Bug Fix: Cuando se indica como nombre de archivo "*" y como tipo "*", se regeneran automáticamente todos los archivos binarios desde los archivos de texto (Alejandro Sosa)
 

   Al usar FoxBin2Prg desde la línea de comandos de VFP e indicar los primeros 2 parámetros como "*", se regeneraban todos los binarios, siendo que es necesario indicar una extensión. Ahora está corregido y se comprueba este parámetro.

* Mejora: Agregados los fuentes de Filename_Caps.exe
 

   El programa Filename_Caps.exe se encarga de normalizar la capitalización de los archivos convertidos, pero como era un programa desarrollado aparte, solo estaba incluido el EXE. Ahora se incluyen los fuentes en el directorio Filename_Caps.

* Mejora: Permitir importar los datos exportados de los DBF (Walter Nichols)
 

   Desde esta versión se pueden importar los datos exportados de los DBF usando el nuevo valor DBF_Conversion_Support: 8 en un config de DBF (filename.dbf.cfg).

* Bug Fix vbs scripts: Algunos arreglos en los scripts vbs de conversión (Doug Hennig)
 

* Mejora: Nuevos métodos API de bajo nivel para automatización

   - get_DBF_Configuration: Permite obtener un objeto CFG con la configuración de la tabla indicada (tabla.dbf.cfg).
   - conversionSupportType: Permite devolver el código de soporte del archivo binario o texto indicado (0,1,2,4,8)



Como actualizar el FoxBin2Prg existente:Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de VFPx


Link de descarga de GitHub



Parte 2: Extensiones para Plastic



Las extensiones para Plastic son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM (herramienta de control de versiones) para poder invocar a FoxBin2Prg desde dentro de la interfaz de Plastic.


La lista de cambios es la siguiente:

> Actualización de FoxBin2Prg: Versión sincronizada con la última versión de FoxBin2Prg



El README.txt explica como se configura en Inglés y Español, y también está explicado en esta nota: Cómo configurar las Herramientas de VFP 9 para Plastic


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de las Extensiones FoxBin2Prg para Plastic de GitHub




Artículos relacionados:

FoxBin2Prg, el sucesor mejorado del Scctext

FoxBin2Prg: Detalle de vistas, Datos de uso, Configuraciones y más

FoxBin2Prg: Guía rápida de uso y configuración


 Saludos!

viernes, febrero 12, 2016

Nueva versión v1.19.46 de FoxBin2Prg (arreglos y mejoras) y Extensiones para PlasticSCM v2.5.46


Parte 1: FoxBin2Prg / Parte 2: Extensiones para Plastic

Parte 1: FoxBin2Prg


FoxBin2Prg es un programa pensado para sustituir a SccText/X y TwoFox y mejorar sus funcionalidades, generando versiones de texto estilo-PRG que pueden ser modificadas y permiten recrear el binario original. Puede ser utilizado con herramientas SCM (Administradores de Control de Código Fuente, como VSS, CVS, SVN) y herramientas DVCS (como Git, Mercurial, Plastic, and others), o como programa independiente, para hacer operaciones de Diff (ver diferencias) y Merge (mezclar cambios).


La lista de cambios es la siguiente:

* Bug Fix: Arreglo de bug en método set_UserValue() cuando se intenta obtener información de un error que no puede abrir la tabla (por ej, porque el memo está corrupto)
 

   Cuando ocurre o se detecta algún error se intenta obtener más información de contexto dentro del memo (vcx/scx) para mostrarlo. Si el memo está corrupto se interrumpe la recolección de datos y se queda a medias. Ahora se puede continuar adelante hasta el final, aunque no se pueda leer el memo.

* Mejora: Agregado soporte interno para consulta de información de cfg de directorio, mediante nuevo parámetro opcional, para los métodos API que lo requieren (por ej: get_Ext2FromExt, hasSupport*)
 

   Las funciones API get_Ext2FromExt y hasSupport* permiten indicar en un nuevo parámetro el directorio donde sobre el que se desea obtener dicha información, de modo que pueda evaluarse antes cualquier archivo de configuración que pueda afectar a ese contexto.

* Bug Fix: Cuando se procesa un directorio o un proyecto con todos los archivos, a veces puede ocurrir el error "Alias already in use" (Dave Crozier)
 

   Un problema de garbage collection estaba causando que en determinadas situaciones de error al procesar múltiples archivos una tabla se quede abierta y luego al intentar analizar la siguiente se estaba reabriendo la misma tabla anterior.

* Bug Fix mnx: Cuando se usa '&&' en los textos de las opciones, se corrompe el binario del menú al regenerarlo (Walter Nichols)
 

   Esto pasaba porque se confundían estos símbolos con los comentarios in-line. Se agregó un algoritmo específico para detectar este caso.

* Mejora: El objeto WSscript.Shell da problemas en algunos entornos o bajo ciertas condiciones, por lo que se reemplaza por llamadas Win32 nativas (Aurélien Dellieux)
 

   En algunos equipos WScritp.Shell no mostraba siempre el reporte de errores, por lo que se sustituyó por funcionalidad equivalente basada en funciones API de Windows.

* Bug Fix Frx/Lbx : El ordenamiento de registros de los reportes cambia el orden Z de los objetos próximos que se solapan, pudiendo causar que se visualicen mal (Ryan Harris)
 

   Hasta ahora se venían ordenando los registros internos de los reportes con un orden que estaba provocando problemas para determinados ordenamientos manuales del usuario, haciendo que se pierda el orden correcto de objeto encima/debajo. Se ha quitado dicho ordenamiento para mantener el indicado por el usuario manualmente.

* Bug Frx/Lbx: Cuando se regeneran reportes o etiquetas con textos multilinea alineados al centro o a la derecha, la alineación no es completamente correcta (Ryan Harris)
 

   Esto ocurria porque internamente se estaban guardando los textos con CR+LF en vez de solamente CR, lo que hacía que el parser de VFP interprete la alineación de forma distinta.

* Bug Frx/Lbx: Cuando se agrupan controles en diseño y se convierte a texto, al regenerar se pierden las agrupaciones (Lutz Scheffler)
 

   Esto ocurría porque se estaban ordenando los objetos (ObjType 1,25,26) alfabéticamente en vez de mantener el orden original.

* Bug Fix Pjx: Los archivos SPR y MPR no estan bien representados en la información del proyecto (Ralf Wagner)
 

   Estos archivos no tenían especificado el tipo correcto para ser detectados como archivos de texto. Code Fix aportado por Ralf Wagner.

* Bug Fix Pj2: Se genera un error al regenerar un PJX desde un PJ2 donde algún archivo contiene paréntesis (EddieC)
 

   Se ha corregido la búsqueda de los nombres de archivo para contemplar estos casos.

* Mejora dbf: Nuevo parámetro ExcludeDBFAutoincNextval para evitar diferencias por este dato (edyshor)
 

   Este nuevo parámetro permite que al generar el archivo DB2 con la información de estructura de un DBF con campo autonumérico, no se guarde el dato del siguiente valor a tomar, lo que evita diferencias producidas por el mismo.

* Bug Fix: Cuando se procesa un archivo en el directorio raiz, se genera un error 2062 (Aurélien Dellieux)

   Se ha corregido el algoritmo de recorrido de directorios para contemplar este caso.

* Nuevo: Se a agregado una nueva herramienta fb2p_diff

   Esta nueva herramienta está pensada para quienes solamente quieren hacer una comparación rápida de binarios para ver las diferencias:





Como actualizar el FoxBin2Prg existente:Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de VFPx


Link de descarga de GitHub



Parte 2: Extensiones para Plastic



Las extensiones para Plastic son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM (herramienta de control de versiones) para poder invocar a FoxBin2Prg desde dentro de la interfaz de Plastic.


La lista de cambios es la siguiente:

> Actualización de FoxBin2Prg: Versión sincronizada con la última versión de FoxBin2Prg



El README.txt explica como se configura en Inglés y Español, y también está explicado en esta nota: Cómo configurar las Herramientas de VFP 9 para Plastic


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de las Extensiones FoxBin2Prg para Plastic de GitHub




Artículos relacionados:

FoxBin2Prg, el sucesor mejorado del Scctext

FoxBin2Prg: Detalle de vistas, Datos de uso, Configuraciones y más

FoxBin2Prg: Guía rápida de uso y configuración


 Saludos!

martes, octubre 20, 2015

SouthwestFox 2015 - Un premio por FoxBin2Prg! :-)

Extracto de la presentación de Southwest Fox 2015 (en inglés), donde me han otorgado el premio VFPx 2015 por mi proyecto Open Source FoxBin2Prg (proyecto: http://vfpx.codeplex.com/releases/view/116407 y también en https://github.com/fdbozzo/foxbin2prg).

 VFPx está alojado en GitHub (https://vfpx.github.io/, anteriormente en CodePlex), y es la continuación de varios proyectos open-source que han sido aprobados por un grupo de Administradores y gurus de Visual FoxPro que han aportado mucho a la Comunidad (artículos, código, sesiones de formación,  libros, etc), que además es el sitio que aloja todos los fuentes de los ejemplos que acompañan al producto en su directorio de instalación y del proyecto Sedna, que es un grupo de proyectos open-source para extender este lenguaje.

Southwest Fox es una conferencia Internacional de desarrolladores Visual FoxPro que se viene realizando desde hace muchos años en Gilbert, Arizona (EEUU), y que cuenta con unos excelentes oradores de la Comunidad FoxPro y de algunas empresas relacionadas.


¡Qué emoción esta noticia! Fue algo realmente inesperado.
Lo dedico a la Comunidad FoxPro, que fue muy importante tanto en las pruebas como en las sugerencias :)







Gracias! :D

domingo, junio 21, 2015

Nueva versión v1.19.45 de FoxBin2Prg (arreglos y mejoras) y Extensiones para PlasticSCM v2.5.45


Parte 1: FoxBin2Prg / Parte 2: Extensiones para Plastic

Parte 1: FoxBin2Prg


FoxBin2Prg es un programa pensado para sustituir a SccText/X y TwoFox y mejorar sus funcionalidades, generando versiones de texto estilo-PRG que pueden ser modificadas y permiten recrear el binario original. Puede ser utilizado con herramientas SCM (Administradores de Control de Código Fuente, como VSS, CVS, SVN) y herramientas DVCS (como Git, Mercurial, Plastic, and others), o como programa independiente, para hacer operaciones de Diff (ver diferencias) y Merge (mezclar cambios).

La lista de cambios es la siguiente:

> Bug Fix Mnx: Cuando se exporta a texto un menu que usa comillas simples o una expresión en el mensaje de las opciones, al regenerar el binario se recortan partes del mensaje de esas opciones (Mike Potjer)

   Por ejemplo, una opción que tenga su texto 'de esta forma' o "de esta" + "forma", no era bien reconocida en las versiones anteriores.

Bug Fix: Cuando se procesan múltiples archivos PJ2, puede ocurrir un error de "variable llError no definida" (Lutz Scheffler)

   Al procesar múltiples archivos, si se cancelaba el proceso a alguno de los archivos no tenía soporte de conversión, se generaba un error de "variable llError no definida"

> Bug Fix pjx/pj2: Los proyectos PJX/PJ2 que referencian archivos de otras unidades de disco causan errores ne esos archivos al procesar con las opciones "*" o "*-" (Matt Slay)

   Esto ocurría porque porque se concatenaba el path del proyecto y el path relativo del archivo, lo que para archivos no-locales generaba un path erróneo, como c:\proyecto\g:\ruta\archivo

> Bug Fix: Cuando se procesan múltiples archivos, a veces los errores no se muestran

   Dependiendo del tipo de error, podía ocurrir que al finalizar un proceso múltiple no fuera reportado ninguno.

> Mejora API-PJX: Nuevo método loadModule() que devuelve el objeto Project interno de FoxBin2Prg cuando se pasa un nombre de proyecto existente

   Al usar la API de FoxBin2Prg (en modo objeto), un nuevo método loadModule() devuelve la referencia interna del objeto del archivo PJX indicado, lo que permite realizar ajustes en sus propiedades antes de la conversión a texto o consultar información del mismo

> Mejora API-PJX: Nuevo método getFilesNotFound() para el objeto Project de FoxBin2Prg, que devuelve la cantidad de archivos del proyecto que no se encuentran y un array con los estados de existencia de cada archivo

  Con la referencia interna del objeto de Proyecto correspondiente al archivo PJX indicado, se puede consultar los archivos no existentes en disco que darían error si se abriera el proyecto con MODIFY PROJECT. El nuevo método getFilesNotFound() devuelve la cantidad de archivos no econtrados en el disco, así como un array con todos los nombres de los archivos del proyecto y sus estados de existencia en disco


Como actualizar el FoxBin2Prg existente:Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de VFPx


Link de descarga de GitHub



Parte 2: Extensiones para Plastic



Las extensiones para Plastic son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM (herramienta de control de versiones) para poder invocar a FoxBin2Prg desde dentro de la interfaz de Plastic.


La lista de cambios es la siguiente:

> Cambio de codificación de versión: Versión sincronizada con la última versión de FoxBin2Prg

   Desde esta versión, la última parte de la versión va sincronizada en ambos desarrollos vXX.YY.ZZ, para saber a qué versión de FoxBin2Prg corresponden los scripts. Por eso, a la versión v2.5.45 de las Extensiones para Plastic, se corresponde con la versión v1.19.45 de FoxBin2Prg, y de esta forma se puede saber a qué versión de fuentes se corresponde el binario (EXE)
> Bug Fix: Arreglos en los scripts para mostrar algunos errores que no se reportaban


   El mismo arreglo hecho en FoxBin2Prg, implicó hacer algunas adaptaciones en los scripts de Plastic


El README.txt explica como se configura en Inglés y Español, y también está explicado en esta nota: Cómo configurar las Herramientas de VFP 9 para Plastic


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de las Extensiones FoxBin2Prg para Plastic de GitHub




Artículos relacionados:

FoxBin2Prg, el sucesor mejorado del Scctext

FoxBin2Prg: Detalle de vistas, Datos de uso, Configuraciones y más

FoxBin2Prg: Guía rápida de uso y configuración


 Saludos!

domingo, mayo 31, 2015

Nueva versión v2.5.11 de las herramientas Visual FoxPro 9 para PlasticSCM (Incluye FoxBin2Prg.exe v1.19.44)


Estas herramientas son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM (herramienta de control de versiones) para poder invocar a FoxBin2Prg (incluye solo el EXE) desde dentro de la interfaz de Plastic.


Está liberada la versión v2.5.11 de las herramientas Visual FoxPro 9 para PlasticSCM, con los siguientes cambios:

  • Actualizados los scripts para usar la nueva propiedad l_Errors de FoxBin2Prg para conultar errores de sesión.




El README.txt explica como se configura en Inglés y Español, y también está explicado en esta nota: Cómo configurar las Herramientas de VFP 9 para Plastic


Nota: Los fuentes del proyecto FoxBin2Prg y el historial de ambios, están en CodePlex, en este link.


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.


Link de descarga de GitHub:
https://github.com/fdbozzo/plasticscm-tools-for-visual-foxpro-9


Saludos!

Nueva versión v1.19.44 de FoxBin2Prg (arreglos y mejoras)

FoxBin2Prg es un programa pensado para sustituir a SccText(X) y TwoFox y mejorar sus funcionalidades, generando versiones de texto estilo-PRG que pueden ser modificadas y permiten recrear el binario original. Puede ser utilizado con herramientas SCM (Administradores de Control de Código Fuente, como VSS, CVS, SVN) y herramientas DVCS (como Git, Mercurial, Plastic, and others), o como programa independiente, para hacer operaciones de Diff (ver diferencias) y Merge (mezclar cambios).

Está liberada la versión v1.19.44 de FoxBin2Prg con los siguientes cambios:

> Mejora: Uso de otro método de finalización por error que parece más eficaz entre distintos PCs (Ralf Wagner)

   Cuando se usa FoxBin2Prg.exe desde un programa externo, se retorna un código de error compatible con el ErrorLevel del Sistema. Un usuario reportó que en algunas PCs no se devolvía este código, por lo que se cambió el método de finalización a un método mejor.

> Mejora: Permitir la exportación de datos de DBFs cuando se usa DBF_Conversion_Support:1 y CFG individual opcional.

   Desde esta versión se permite que en el modo de exportación de solo-estructuras DBF (DBF_Conversion_Support:1) se pueda crear archivos de configuración individuales por cada tabla (tabla.dbf.cfg) para permitir que solamente de esas tablas se exporten también los datos. Normalmente solo se quieren las estructuras y justamente lo útil de esta nueva posibilidad, es poder exportar los datos de algunas tablas, como las de configuración o similares, para poder compararlas si cambian sus valores, ya que previamente la única opción era exportar todo o nada.

> Bug Fix: Un arreglo previo en el manejo de errores en cascada provocó un reseteo del último estado de error de proceso, haciendo que a veces los errores no se reporten.

   En la versión anterior se había reportado que cuando se detectaba un error en un archivo al procesar en modo objeto, todos los demás archivos reportaban errores también. Al solucionar esa incidencia y resetear el indicador de errores por ejecución (l_Error), se provocó que cuando hay errores en un archivo, el siguiente archivo sin errores resetee el estado del anterior, haciendo aparecer como si no hubiera errores. En esta versión se agrega un nuevo indicador de errores de sesión que no se reinicia por cada ejecución, y solo lo hace cuando se destruye el objeto o cuando se llama al método ClearProcessedFiles. Nota: Este bug puede haber enmascarado los errores de archivos con problemas al momento de reportar al final.


Como actualizar el FoxBin2Prg existente:Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.


Link  de descarga de VFPx:
https://vfpx.codeplex.com/releases/view/116407

Link de descarga de GitHub:
https://github.com/fdbozzo/foxbin2prg


Relacionados:

FoxBin2Prg, el sucesor mejorado del Scctext

FoxBin2Prg: Detalle de vistas, Datos de uso, Configuraciones y más

FoxBin2Prg: Guía rápida de uso y confogiración


 Saludos!

jueves, mayo 14, 2015

Técnicas de programación en VFP: Procesos desatendidos y control de progreso visual

Por: Fernando D. Bozzo

Este es un artículo de una serie que se enfocará en técnicas de programación y optimización en distintas áreas.

Un buen desarrollador asimilará estas técnicas como parte de su forma de trabajo habitual, independientemente de que haga un sistema, ún módulo, una rutina o un programa de pruebas personales, ya que le permitirá programar siempre orientado a la eficiencia, la velocidad de ejecución, la encapsulación, la reutilización y la legibilidad y mantenibilidad del código, o sea, las buenas prácticas.

Al tener en cuenta estas técnicas en cada parte del código y en cada rutina, al final lo que se logra es que el sistema completo esté más optimizado porque sus partes lo están.



   Cuando hablamos de procesos desatendidos, hablamos de procesos que no deben bloquearse esperando la entrada del usuario, son procesos que deben realizar una tarea sin ningún tipo de interacción ni interrupción y que deben controlar tanto la finalización exitosa como la aparición de errores, y en ambos casos no debe haber ningún tipo de mensaje "modal" como un messagbox o form que pida datos de ningún tipo mientras se está ejecutando.

Para esto, es necesario entender la separación de capas (visual, negocio, datos, etc) y el por qué de esa separación (pueden leer un artículo sobre el tema).

Lo primero que a cualquiera no adentrado en el uso de capas (o que las conoce pero es muy cómodo:) se le viene a la cabeza, es hacer un bonito formulario con un botón para procesar, uno para salir y luego, obviamente, un método para hacer el proceso... ¡mal! El proceso conviene que esté aparte, en una librería de clases por ejemplo, o en una librería de procedimientos, pero nunca en el formulario. Este proceso debe hacerse pensando en la reusabilidad, que pueda ser invocado tanto desde un formulario como desde el sistema operativo, si se quiere, o desde otro proceso o programa.



Creando el Proceso Desatendido


Crear un proceso desatendido es realmente simple, pero hay que tener en cuenta algunos pilares fundamentales y respetarlos:

1) Debe estar bien encapsulado: por eso lo ideal es hacerlo basado en una clase de tipo custom o session

2) Debe tener un buen control de errores: esto también es fácil, sobre todo con Try/Catch. Este artículo les explica cómo funciona.

3) No debe contener mensajes de ningún tipo: Ni messagebox, ni wait window, nada, solo el proceso

4) Debe generar una salida de información: Lo habitual es un LOG que luego se pueda consultar o incluso procesar por alguna herramienta para obtener información de estado, pero también debe devolver información de errores, de ocurrir alguno.

5) Opcionalmente puede generar información de avance del proceso

6) Es deseable --aunque no imprescindible-- que también tenga una forma de poder testear el control de errores

7) Opcionalmente puede devolver un código de error DOS para usar con programas externos


¿Parece difícil? Ya van a ver que no lo es para nada.


Primero debemos seleccionar el tipo de clase que vamos a usar: ¿custom o session?

La elección depende de a qué nivel queramos encapsular. La ventaja de la clase session es que está preparada especialmente para usar sesión privada de datos y para ser ligera. La "desventaja" es que no se pueden hacer usando el diseñador de clases, y deben hacerse por código, aunque esto tampoco es un problema.

Por otro lado, la clase custom tiene la ventaja de que se puede hacer usando el diseñador de clases y la desventaja de que es algo más pesada que la session y que no tiene sesión privada de datos, con lo que habrá que tener cuidado al abrir tablas y restaurar áreas de trabajo.

Si el proceso es lo suficientemente complejo como para requerir de más de una clase, una tercer alternativa es hacer una clase session principal que es con la que se interactuará luego, y que esta instancie a las demás clases de tipo custom, con lo que logramos tener una envoltura de sesión de datos privada para todas ellas, aunque entre las clases internas puedan compartir datos.



Veamos un ejemplo práctico simple de un proceso desatendido:

Cálculo que requiere recorrer una tabla y totalizar


Como es un ejemplo, la cantidad de registros no es importante aquí, y el proceso de los datos podrá ser tan complejo como ustedes necesiten, pero a efectos del ejemplo lo vamos a simplificar también.

La tabla la podemos crear con este código desde la ventana de comandos de VFP o con un PRG:

CREATE TABLE T_DATOS ( valor N(10,2) )
FOR I = 1 TO 100
   INSERT INTO T_DATOS (valor) VALUES (I)
ENDFOR
USE


Y la clase la podríamos hacer de esta forma, dentro de lib_proceso.prg, donde intercalaré algunos comentarios para explicar lo que hace y cómo funciona, aunque es muy simple, como puede verse:

*-- lib_Proceso.prg
LPARAMETERS toEx as Exception, tlRelanzarError, tnTotal
#DEFINE CR_LF CHR(13)+CHR(10)
LOCAL loProceso, lnResp
loProceso = CreateObject("c_proceso")
lnResp = loProceso.procesar(@toEx, tlRelanzarError, @tnTotal)
loProceso = NULL
RELEASE loProceso

IF _VFP.StartMode <> 4 OR NOT sys(16) == sys(16,0)
  RETURN lnResp
ENDIF

IF EMPTY(lnResp) && Salida sin error
  QUIT
ENDIF

DECLARE INTEGER OpenProcess IN Win32API INTEGER dwDesiredAccess ;

  , INTEGER bInheritHandle, INTEGER dwProcessID
lnHandle = OpenProcess(1, 1, _VFP.PROCESSID)
DECLARE INTEGER TerminateProcess IN Win32API INTEGER hProcess ;

  , INTEGER uExitCode
=TerminateProcess(lnHandle,1) && Salida con error DOS




La parte anterior es la cabecera del proceso, donde se define la variable del objeto del proceso loProceso y la de respuesta lnResp. En la misma se crea al objeto, que está basado en la clase c_proceso definida bajo este texto, se ejecuta el método Procesar y se guarda el resultado del mismo, que será un código de error, en lnResp.

Con StartMode se determina qué tipo de ejecución estamos haciendo, en este caso solo interesa saber si es desde EXE o APP o si este programa es el principal o no, y por eso se comparan los sys(16) y sys(16,0)

En la última parte de la cabecera, si no hay error simplemente se termina y si hay error (y no es modo desarrollo) se termina con ExitProcess(1), que devuelve ese código como retorno de aplicación, que puede ser interpretado desde otros programas externos o scripts.


La siguiente parte, dentro del mismo prg, define la clase c_proceso, basada en  Session, con 3 métodos: procesar, actualizarAvance y writeLog.

Procesar: Es el método principal, ya que lleva a cabo el proceso que nos interesa que sea desatendido. Genera un log al inicio y fin con algunos datos y normalmente también debería generar algún dato intermedio, que no agrego por mantener el ejemplo corto, usa la tabla con los recursos AGAIN, SHARED y ALIAS que nos permite abrirla más de una vez, tiene un método de testeo de errores sencillo, para comprobar cómo se gestionan, y luego el proceso en sí, que es el SCAN donde se va totalizando en una variable y con un retardo ficticio para que parezca un proceso pesado. Finalmente está la parte de captura del error, obtención de datos extra, generación de Log y recolección de basura, donde se cierra la tabla.


DEFINE CLASS c_proceso AS Session
  cLogFile    = 'log_proceso.txt'
  nTestError  = 0

  PROCEDURE procesar(toEx as Exception, tlRelanzarError, tnTotal)
    LOCAL lnCodError, lcMenErr

    TRY
      WITH THIS as c_Proceso OF lib_proceso.prg
        .writeLog( REPLICATE('-',80) )
        .writeLog( 'Inicio Proceso' )
        STORE 0 TO lnCodError, tnTotal
        USE T_DATOS IN 0 AGAIN SHARED ALIAS _T_DATOS
       
        IF THIS.nTestError = 1
            ERROR 'Error provocado (' + STR(.nTestError,2) + ')'
        ENDIF

        SCAN
           IF MOD(RECNO(),10)=0 THEN
              .actualizarAvance( 'Reg.', RECNO(), RECCOUNT() )
           ENDIF
           *-- El proceso real va aquí.
           tnTotal = tnTotal + valor
           INKEY(0.1,'H') && Simulo retardo proceso c/inkey()
        ENDSCAN

        .writeLog( 'Fin Proceso OK!  Total=' + STR(tnTotal) )
      ENDWITH

    CATCH TO toEx
      lnCodError = toEx.ErrorNo
      toEx.UserValue = 'algun dato importante para agregar'

      TEXT TO lcMenErr TEXTMERGE NOSHOW FLAGS 1 PRETEXT 1+2
         Error <<toEx.ErrorNo>>, <<toEx.Message>>
         Proc.<<toEx.Procedure>>, Line <<toEx.LineNo>>
         LineContents: <<toEx.LineContents>>
         Details: <<toEx.Details>>
         UserValue: <<toEx.UserValue>>
      ENDTEXT

      THIS.writeLog( lcMenErr )

      IF tlRelanzarError
        THROW
      ENDIF

    FINALLY
      USE IN (SELECT('_T_DATOS'))
    ENDTRY

    RETURN lnCodError
  ENDPROC


  PROCEDURE actualizarAvance(tcTexto, tnValor, tnTotal)
  ENDPROC


  PROCEDURE writeLog(tcTexto)
    STRTOFILE( tcTexto + CR_LF, THIS.cLogFile, 1 )
  ENDPROC


ENDDEFINE



actualizarAvance: Es un método vacío, sólo con parámetros, que sirve para ofrecer una posibilidad de monitorizar el proceso desde un programa externo, lo cuál veremos que puede ser muy útil. Observen como la llamada a actualizarAvance() está estratégicamente metida en el código para que no se ejecute por cada registro, sino cada cierta cantidad de registros. Esto es importante para evitar que cualquier monitarización externa afecte al rendimiento del proceso.

writeLog: Es la rutina centralizada para escribir el Log. Para el ejemplo opté por un log básico, sin sistema de buffering y escritura directo al disco, aunque esto en la realidad no debería ser así y es más conveniente agregarle un sistema de buffering, para evitar todas las escrituras intermedias al disco, como ya les comenté en este artículo.


Listo! Ya tenemos nuestro proceso terminado! :D



Primero vamos a probar que funciona desde la ventana de comandos de VFP:

oEx = null
nTot = 0
oo = NEWOBJECT("c_proceso", "lib_proceso.prg")
? oo.procesar(@oEx,,@nTot), oEx, nTot

0 .NULL.     5050


> Como vemos, luego de 5 segundos devuelve código de error 0, que es el retorno del método procesar, devuelve .NULL. para al objeto de errores oEx porque no hubo errores, y devuelve nTot=5050 como resultado de la sumatoria del proceso.


Ahora vamos a testear el manejo de errores:

oEx = null
nTot = 0
oo = NEWOBJECT("c_proceso", "lib_proceso.prg")
oo.nTESTERROR = 1
? oo.procesar(@oEx,,@nTot), oEx, nTot
1098 (object)   0


> En este caso devuelve el código de error 1098 como retorno del método procesar, devuelve (object) como valor de oEx y devuelve nTot = 0 porque no pudo hacer el cálculo (aunque si hubiera fallado en medio del cálculo podría haber devuelto un valor intermedio).

Finalmente comprobamos el mensaje del error devuelto:

? oEx.Message
Error provocado ( 1)


Como pudimos comprobar, el proceso funciona perfectamente y sin mostrar ninguna ventana ni mensaje, tal como debe comportarse un proceso desatendido.

Así como está, este proceso se puede utilizar dentro de un programa, o como proceso independiente en un EXE, o como proceso principal en un web-service, y obviamente también se puede llamar desde un form.

Primero vamos a probar nuestro proceso desde una ventana DOS del sistema operativo, para lo cual antes tenemos que generar un proyecto y un EXE de Fox:

BUILD PROJECT lib_proceso.pjx FROM lib_proceso.prg
BUILD EXE lib_proceso.exe FROM lib_proceso.pjx RECOMPILE



Ahora creamos el archivo test_proc.vbs y le ponemos este contenido:

Dim WSHShell, nExitCode, cEXETool
Set WSHShell = CreateObject( "WScript.Shell" )

cEXETool = Replace(WScript.ScriptFullName, WScript.ScriptName, "lib_proceso.exe")
nExitCode = WSHShell.run(cEXETool & " ""0"" ""0"" ""0"" ""0"" ", 0, True)
MsgBox "CodError = " & nExitCode , 0+4096, "Ejecución 1"

cEXETool = Replace(WScript.ScriptFullName, WScript.ScriptName, "lib_proceso.exe")
nExitCode = WSHShell.run(cEXETool & " ""0"" ""0"" ""0"" ""1"" ", 0, True)
MsgBox "CodError = " & nExitCode , 0+4096, "Ejecución 2"



Aquí estamos probando dos casos de prueba:

  • El primero ejecutará el proceso normal, que puede tardar hasta 10 segundos (esperen, no lo corten! :), y muestra un mensaje con el código de error 0 en un messagebox
  •  El segundo ejecutará el proceso simulando un error, por lo que será instantáneo y mostrará un mensaje con el código de error 1 en un messagebox

Con esto comprobamos que desde otro ejecutable o desde el Sistema Operativo, el proceso devuelve un código de error al estilo ERRORLEVEL de DOS.


Finalmente vamos a probar nuestro proceso desde un formulario de lanzamiento y monitorización, como el de esta imagen, donde pueden ver la situación inicial (el form cargado):



En esta captura está el proceso ejecutándose (va por el 60%) :



Y el fin del proceso con el total al terminar:




El código completo lo adjunto al final del artículo, pero vamos a ver la parte importante en detalle.

El botón que lanza el proceso se llama cmd_Procesar, y en su evento click tiene este código:

Local loEx As Exception, lnTot, lnRet, lcText ;
    , loProceso As c_proceso Of lib_proceso.prg

Try
    This.Enabled = .F.
    Thisform.chk_GenerarError.Enabled = .F.
    loEx = Null
    Store 0 To lnTot, lnRet
    loProceso = Newobject("c_proceso", "lib_proceso.prg")
    loProceso.nTestError = Thisform.chk_GenerarError.Value
    Bindevent( loProceso, 'ActualizarAvance', Thisform, 'ActualizarAvance' )
    lnRet = loProceso.procesar(.F.,.T.,@lnTot)
    lcText = Transform(lnTot)

Catch To loEx
    lcText    = "Error " + Transform(loEx.ErrorNo) + ', ' + loEx.Message

Finally
    Unbindevents( loProceso, 'ActualizarAvance', Thisform, 'ActualizarAvance' )
    Messagebox( lcText )
    This.Enabled = .T.
    Thisform.chk_GenerarError.Enabled = .T.
    Store Null To loProceso, loEx
Endtry



Lo más importante es el Bindevents() que se hace del método actualizarAvance() del proceso, y que se bindea a un método llamado igual en el form, donde está la implementación de la visualización, que solamente tiene este código:


LPARAMETERS tcTexto, tnValor, tnTotal

THISFORM.lbl_progreso.Caption = tcTexto + ' (' + ;

   TRANSFORM(tnValor) + '/' + TRANSFORM(tnTotal) + ')'


Con esto pueden comprobar cómo a un proceso desatendido se le puede acoplar una interfaz para controlarlo. Si quisiéramos comprobar el control de errores, solo debemos marcar el check correspondiente (puesto solo para pruebas) y elegir Procesar:




Y esta es una copia del log generado en log_proceso.txt:

--------------------------------------------------------------------------------
Inicio Proceso
Error 1098, Error provocado ( 1)
Proc.procesar, Line 39
LineContents: ERROR 'Error provocado (' + STR(.nTestError,2) + ')'
Details:
UserValue: algun dato importante para agregar
--------------------------------------------------------------------------------
Inicio Proceso
Fin Proceso OK!  Total=      5050



Pero esto deja abierta otra puerta, y es que podemos cambiar la implementación de la visualización del proceso según cómo lo queramos. Por ejemplo, podemos querer que para ver el avance del proceso se muestre una barra de progreso, que es mucho más visual y útil que un porcentaje, o incluso podemos combinar ambos para tener información más precisa:




Bueno, y con esto acabamos el ejemplo. Hemos conseguido complir todos los objetivos de un servicio desatendido comentados al inicio, tenemos la seguridad del control de errores sencillo pero eficiente, tenemos el log de todo lo ocurrido, podemos lanzar el proceso desde otros ejecutables o scripts y podemos acoplar interfaces para controlarlo.


Una cosa que muchos programadores pasan por alto, sin darse cuenta, es que en un sistema, muchos de los subprocesos que se ejecutan (un cálculo, una actualización) son realmente subprocesos desatendidos, sy solo la interfaz que los llama debería mostrar cualquier mensaje de error, de finalización o con resultados.

Vean vuestros programas y podrán darse cuenta de en muchos casos están mezclando mensajes de estado o messagebox en medio de un proceso. Como ven, hay mucho margen para mejorar eso.


Espero que les haya sido útil :)

Estos son los archivos del ejemplo


Hasta la próxima!

domingo, mayo 10, 2015

Nueva versión v2.5.10 de las herramientas Visual FoxPro 9 para PlasticSCM (Incluye FoxBin2Prg.exe v1.19.43)


Estas herramientas son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM (herramienta de control de versiones) para poder invocar a FoxBin2Prg (incluye solo el EXE) desde dentro de la interfaz de Plastic.


Está liberada la versión v2.5.10 de las herramientas Visual FoxPro 9 para PlasticSCM, con los siguientes cambios:






El README.txt explica como se configura en Inglés y Español, y también está explicado en esta nota: Cómo configurar las Herramientas de VFP 9 para Plastic


Nota: Los fuentes del proyecto FoxBin2Prg y el historial de ambios, están en CodePlex, en este link.


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.


Link de descarga de GitHub:
https://github.com/fdbozzo/plasticscm-tools-for-visual-foxpro-9


Saludos!

Nueva versión v1.19.43 de FoxBin2Prg (arreglos y mejoras)

FoxBin2Prg es un programa pensado para sustituir a SccText(X) y TwoFox y mejorar sus funcionalidades, generando versiones de texto estilo-PRG que pueden ser modificadas y permiten recrear el binario original. Puede ser utilizado con herramientas SCM (Administradores de Control de Código Fuente, como VSS, CVS, SVN) y herramientas DVCS (como Git, Mercurial, Plastic, and others), o como programa independiente, para hacer operaciones de Diff (ver diferencias) y Merge (mezclar cambios).

Está liberada la versión v1.19.43 de FoxBin2Prg con los siguientes cambios:

> Mejora: Nueva configuración "RemoveZOrderSetFromProps" para quitar la propiedad ZOrderSet de los objetos que cambian constantemente, provocan diferencias y a veces dan problemas de objeto encima/debajo (Ryan Harris)

   Cuando se trabaja con clases visuales o forms, muchas veces pasa que movemos objetos, algunos los ponemos más arriba o por debajo de otros (cambio del orden Z), guardamos y al volver a abrir algunos controles no aparecen en el nivel de profundidad que los habíamos dejado (por ejemplo, uno que estaba arriba de todo aparece debajo de otro). Fox guarda ese orden de dos formas; para los controles sin herencia usa el orden físico de guardado en la tabla (scx/vcx) y para los controles con herencia usa una propiedad no visible llamada ZOrderSet que solo puede verse si se abre el form o clase como tabla o si se exporta a texto. Este problema de objetos que cambian de orden Z sin sentido aparente en general ocurre porque dos o más objetos tienen el mismo valor asignado a ZOrderSet, con lo que Fox a veces pone primero a uno y otras veces al otro. El nuevo seteo "RemoveZOrderSetFromProps: 1" del archivo foxbin2prg.cfg permite quitar la propiedad ZOrderSet de los objetos, para que se muestren tal como estaban en el diseño original de la clase. Lo único que debe tenerse en cuenta es que si en la clase se usó un orden Z y en la instancia se cambió ese orden, el nuevo orden se perderá y quedará como estaba definido en la clase original. Por eso esta configuración puede ser util tenerla en un directorio particular donde se pongan temporalmente las clases visuales o forms que se quieran arreglar, para luego volver a dejar en su ubicación original.


> Mejora: Hacer que la progressbar no se convierta en la ventana de salida por defecto de los ? (Lutz Scheffler)

   Cuando se usa FoxBin2Prg como objeto para acceder a su API desde otros programas en Fox, por defecto se muestra una ventana de progreso (desactibable) para que se pueda saber en qué parte del proceso se está. El problema que algunos desarrolladores estaban teniendo, era que al intentar mostrar la salida del comando ? en sus forms o ventanas, como FoxBin es una ventana AllwaysOnTop acaparaba esa salida y terminaba impresa sobre la misma. Desde esta versión la ventana de progreso está configurada con AllowOutput=.F. para evitar quitarle el foco a otras ventanas.




> Bug Fix: Arreglo del mensaje de validación de VFP9 SP1

   En la implementación del mensaje de validación del SP1 había un error que impedía mostrar el mensaje. Ya está corregido.


> Bug Fix: FoxBin2Prg no retorna códigos de error cuando se llama como programa externo (Ralf Wagner)

    Había un error en la lógica del código que impedía devolver el código de error DOS para ser tratado por programas externos leyendo ERRORLEVEL u otros mecanismos. Ya está corregido.



> Bug Fix: FoxBin2Prg a veces genera errores OLE cuando se ejecuta más de una vez en modo objeto sobre un archivo con errores (Fidel Charny)

   Había un error en la lógica interna que acumulaba los estados de error de las ejecuciones previas, provocando que a partir del primer error detectado, todos los demás archivos se analizaran como si tuvieran errores. Ya está corregido.


> Bug Fix: Cuando un form tiene AutoCenter=.T., hay veces en que al regenerar el binario y ejecutarlo no se muestra centrado (Esteban Herrero)

    Al generar la vista texto de un form, y luego regenerar el binario, si se usa la propiedad AutoCenter=.T. hay veces que al ejecutar el form no se muestra centrado como se espera. Esto ocurría porque al ensamblar el binario, la propiedad AutoCenter se estaba guardando antes que Top/Left/Width/Height, lo que en algunos casos le impedía a Fox calcular el autocentrado. Ya está corregido.




Como actualizar el FoxBin2Prg existente:Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.


Link  de descarga de VFPx:
https://vfpx.codeplex.com/releases/view/116407

Link de descarga de GitHub:
https://github.com/fdbozzo/foxbin2prg


Relacionados:

FoxBin2Prg, el sucesor mejorado del Scctext

FoxBin2Prg: Detalle de vistas, Datos de uso, Configuraciones y más

FoxBin2Prg: Guía rápida de uso y confogiración


 Saludos!

viernes, mayo 01, 2015

Técnicas de optimización en VFP: Tablas, archivos de texto, accesos al disco y buffering

Por: Fernando D. Bozzo

Este es un artículo de una serie que se enfocará en técnicas de programación y optimización en distintas áreas.

Un buen desarrollador asimilará estas técnicas como parte de su forma de trabajo habitual, independientemente de que haga un sistema, ún módulo, una rutina o un programa de pruebas personales, ya que le permitirá programar siempre orientado a la eficiencia, la velocidad de ejecución, la encapsulación, la reutilización y la legibilidad y mantenibilidad del código, o sea, las buenas prácticas.

Al tener en cuenta estas técnicas en cada parte del código y en cada rutina, al final lo que se logra es que el sistema completo esté más optimizado porque sus partes lo están.



Una de las operaciones más costosas a nivel de recursos en una PC es la de accesos al disco, ya que el disco es probablemente el componente más lento del sistema. Aunque los discos SSD minimizan el problema, la solución real pasa por la programación, ya que tanto los discos rígidos y sobre todo las redes, son los principales cuellos de botella.

Muchas veces un programa o un sistema comienza teniendo un único usuario o unos pocos, pero puede que con el tiempo esa condición cambie, y que comiencen a haber más usuarios, incluso muchos usuarios, y es aquí cuando comienzan a verse realmente los problemas de las decisiones de diseño tomadas y donde ya es tarde para cambiarlo por el esfuerzo que puede requerir, implicando a veces un rediseño, por eso cada línea de código cuenta.



Optimizaciones en el uso de archivos y accesos a disco


Caso 1: Los LOGs


Normalmente, cuando se quiere hacer un log al disco de alguna información importante para nosotros o para su posterior explotación o análisis, se suele usar STRTOFILE() por comodidad o por falta de tiempo. He aquí algunos ejemplos típicos:


1.a) Ejemplo de un LOG dentro de un bucle que puede tener cualquier cantidad de registros:

SCAN FOR <condición>
  ...
  IF <condicion_que_requiere_loguear>
     STRTOFILE( 'alguna información importante', 'LOG.txt', 1 )
  ENDIF
ENDSCAN


1.b) Ejemplo de un LOG en un evento Timer que podría ejecutarse varias veces por segundo:

PROCEDURE TIMER
   ...
  IF <condicion_que_requiere_loguear>
     STRTOFILE( 'alguna información importante', 'LOG.txt', 1 )
  ENDIF
ENDPROC


1.c) Ejemplo de un LOG en un método de cálculo reutilizable, que podría llamarse cientos de veces desde distintas partes de un sistema:

PROCEDURE Sumar_Porcentaje
  ...
  IF <condicion_que_requiere_loguear>
     STRTOFILE( 'alguna información importante', 'LOG.txt', 1 )
  ENDIF
ENDPROC


1.d) Ejemplo de un LOG en un método de proceso largo, donde se realizan diversas operaciones y por cada una se va actualizando el LOG:

PROCEDURE Proceso_Largo
  <operación-1> 
  STRTOFILE( 'alguna información importante', 'LOG.txt', 1 )
  <operación-2> 
  STRTOFILE( 'alguna información importante', 'LOG.txt', 1 )
  ...
  <operación-n>
  ...
ENDPROC

En todos los casos, STRTOFILE() escribirá al disco para ir añadiendo información al LOG. Para unas pocas repeticiones esto puede no ser un problema, pero siempre hay que asumir un caso de uso desfavorable, en este caso intensivo.

Veamos qué ocurre cuando la repetición es de 100 iteraciones:
  • Para 1 usuario implicará 100 accesos a disco
  • Para 5 usuarios implicará 500 accesos a disco
  • Para 10 usuarios implicará 1000 accesos a disco

Puede verse que el impacto se multiplica rápidamente a medida que crece la cantidad de usuarios, lo que puede causar que el sistema de archivos se vaya poniendo cada vez más lento por la metralla de escrituras, pudiendo llegar incluso a no dar abasto en responder las peticiones y provocar el colapso del sistema.

Otro error común al escribir LOGs es que se suele elegir un nombre (ej: LOG.txt) y se olvida el hecho de que esto puede ser usado por varios usuarios concurrentes, lo que puede provocar la de contención de recursos porque dos ó más personas intentan usar el mismo archivo con el mismo nombre; también puede provocar errores, en el caso de que el LOG se use en exclusiva (con FOPEN/FWRITE) o también puede provocar pérdida de información de partes del LOG, ya que mientras un usuario está escribiendo en el LOG, otro no puede hacerlo y STRTOFILE() simplemente no escribe y tampoco generará un error por ello (STRTOFILE solo devuelve la cantidad de bytes escritos, 0 en caso de no poder escribir, pero nadie suele verificar esto)



Soluciones


Para resolver el problema de los usuarios concurrentes, se puede usar un LOG distinto por usuario, que puede tener una numenclatura parecida a "LOG_UsuarioDeRed.txt", o una más completa como "LOG_UsuarioDeRed_NombreMaquina.txt". Si se quiere un historial de LOGs diarios, se podría agregar la fecha al nombre del LOG. Como se ve, variantes hay para todos los gustos.

Para minimizar el impacto de estas escrituras se suele usar la estrategia del buffering (o caché), que implica ir guardando la información que se quiere escribir en una variable de memoria o una propiedad y cada tanto realizar la escritura de todo lo guardado. En este caso hay varias alternativas, que van desde un objeto LOG que autogestione la cantidad de texto que puede guardar para hacer escrituras automáticas al llegar a cierto tamaño acumulado, o a algo más sencillo y práctico como hacer dos métodos, uno de escritura en buffer y uno de flush al disco, que es la técnica que usé en FoxBin2Prg y que muestro a continuación, simplificado y adaptado para el ejemplo:


DEFINE CLASS C_LOG
  #DEFINE CR_LF CHR(13)+CHR(10)
  c_TextoLog = ''
  c_LogFile  = ''

  PROCEDURE Init
      c_LogFile = ADDBS( SYS(2023) ) + 'LOG_' ;
          + CHRTRAN( SYS(0), ' ', '_' ) + '.TXT'
  ENDPROC
 
  PROCEDURE Destroy
      THIS.writeLog_Flush()
  ENDPROC
 
  PROCEDURE writeLog
    LPARAMETERS tcText

    TRY
      WITH THIS
        .c_TextoLog = .c_TextoLog + EVL(tcText,'') + CR_LF
      ENDWITH
    CATCH && En este caso no me interesa reportar errores
    ENDTRY
  ENDPROC

  PROCEDURE writeLog_Flush

    WITH THIS
      IF NOT EMPTY(.c_TextLog)
        STRTOFILE( .c_TextLog + CR_LF, .c_LogFile, 1 )
      ENDIF
      .c_TextLog    = ''
    ENDWITH
  ENDPROC 


ENDDEFINE


En el ejemplo, en el Init() se define en nombre del LOG, luego se usa el método writeLog() para escribir el texto que se quiera en la propiedad c_TextoLog y a la que se agrega un fin de línea, y finalmente el método writeLog_Flush() que se encarga de escribir al disco todo el texto acumulado y de vaciar la propiedad que lo acumula. Cuando se descarga la clase, también se escribe automáticamente al disco lo que quede por escribir.

Este es un método muy simple y efectivo que permite sustituir los STRTOFILE() de los ejemplos anteriores por writeLog() y que solo requiere ubicar las llamadas a writeLog_Flush() fuera de los bucles de repetición o de las rutinas de uso frecuente, pero siempre teniendo en cuenta que en puntos estratégicos debe ser ejecutado, para evitar que el LOG se acumule indefinidamente y cause problemas de memoria.

Usando esta técnica, las escrituras al disco o por la red se pueden minimizar de forma muy notoria, y lograr que el sistema siga siendo responsivo.



Caso 2: Actualización de TABLAS


En Fox, una de las cosas que más se usa son las tablas y los cursores, tanto para guardar datos como para guardar información temporal de proceso, ya que Fox está optimizado para eso a tal punto que manejar un cursor de un millón de registros es más rápido que manejar un array de la misma cantidad de filas.

Pero esta orientación a datos muchas veces no es bien implementada, y es muy común encontrarse con código como este:


DO WHILE <condición>
  ...
  REPLACE campo1 WITH valor1
  REPLACE campo2 WITH valor2
  REPLACE campo3 WITH valor3
  REPLACE campo4 WITH valor4
  REPLACE...
  ...
ENDDO


Al igual que en el Caso 1, si este bucle se repite en 100 iteraciones y usando solo 5 REPLACE:
  • Para 1 usuario implicará 500 accesos a disco
  • Para 5 usuarios implicará 2500 accesos a disco
  • Para 10 usuarios implicará 5000 accesos a disco

Puede notarse que en este caso todavía es peor que en el anterior, ya que muchos están acostumbrados a poner varios REPLACE en sucesión, por motivos como que "es más fácil para copiar" o simplemente porque les gusta verlos separados...



Esta es otra variante bastante común que se puede encontrar, donde es necesario hacer REPLACE solo bajo ciertas condiciones y muchos lo implementan así:

SCAN FOR <condición>
  ...
  IF <cond.1>
    REPLACE campo1 WITH valor1
  ENDIF
  IF <cond.2>
    REPLACE campo2 WITH valor2
  ENDIF
  IF <cond.3>
    REPLACE campo3 WITH valor3
  ENDIF
  IF <cond.4>
    REPLACE campo4 WITH valor4
  ENDIF
  IF ...
    REPLACE...
  ...
ENDSCAN


La única diferencia con el caso anterior, es que en cada iteración la cantidad de reemplazos no es fija porque depende de condiciones, pero como siempre, hay que ubicarse en el peor caso donde todas las condiciones puedan ser verdaderas, por lo que en este caso se puede llegar a los mismos 5 REPLACE del ejemplo anterior y a las mismas estadísticas de accesos a disco por usuario.


La realidad es que tanto esta como la otra son malas prácticas y deben evitarse a toda costa, porque penalizan mucho el rendimiento, y más en una red.



Soluciones


Básicamente las soluciones pasan por realizar estos reemplazos en una sola operación, y estas técnicas sirven para cubrir ambos casos con mucha facilidad, como puede verse en los siguientes ejemplos.


Ejemplo 1: Usando un objeto de registro


DO WHILE <condición>
  ...
  SCATTER NAME loReg && o SCATTER FIELDS para más precisión
  loReg.campo1 = valor1
  loReg.campo2 = valor2
  loReg.campo3 = valor3
  loReg.campo4 = valor4
  loReg.campoN...
  GATHER NAME loReg && Un único reemplazo
  ...
ENDDO


Ejemplo 2: Usando variables

SCAN FOR <condición>
  ...
  lc_campo1 = valor1
  lc_campo2 = valor2
  lc_campo3 = valor3
  lc_campo4 = valor4
  lc_campoN...
  REPLACE campo1 WITH lc_campo1 ;
    , campo2 WITH lc_campo2 ;
    , campo3 WITH lc_campo3 ;
    , campo4 WITH lc_campo4 ;
    , campoN WITH lc_campoN && Un único REPLACE para todos
  ...
ENDSCAN


Ejemplo 3: Usando un array

SCAN FOR <condición>
  ...
  SCATTER TO ARRAY laReg && o SCATTER FIELDS para más precisión
  laReg(1) = valor1
  laReg(2) = valor2
  laReg(3) = valor3
  laReg(4) = valor4
  laReg(N)...
  GATHER FROM laReg && Un único reemplazo
  ...
ENDSCAN


Y para reemplazos condicionados se puede usar el mismo código de los ejemplos, solo que condicionando las asignaciones de las variables o propiedades.



El caso anterior fue para reemplazos de datos existentes, pero para registros nuevos es lo mismo, con la salvedad de que como primera opción más recomendable se agrega el Insert-SQL:

Ejemplo 1: Usando Insert-SQL para un registro

SCAN FOR <condición>
  ...
  SCATTER BLANK NAME loReg
  loReg.campo1 = valor1
  loReg.campo2 = valor2
  loReg.campo3 = valor3
  loReg.campo4 = valor4
  loReg.campoN...
  INSERT INTO <tabla> FROM NAME loReg
  ...
ENDSCAN


Ejemplo 2: Usando Insert-SQL para varios registros

SCAN FOR <condición>
  ...
  DIMENSION laReg(3,5)
  laReg(1,1) = valor1
  laReg(1,2) = valor2
  laReg(1,3) = valor3
  laReg(1,4) = valor4
  laReg(1,5...
  ...
  INSERT INTO <tabla> FROM laReg
  ...
ENDSCAN


Otra variante es usar un cursor con la misma estructura que la tabla, realizar los Inserts en el cursor y luego volcarlo en la tabla.

Como se puede ver opciones hay muchas, y cada una puede ser más óptima que la otra dependiendo de nuestras necesidades o de cómo queramos implementarlo, pero para el buffering en memoria podemos usar tanto variables como cursores, pudendo incluso usar buffering de tablas o registros mediante CursorSetprop().

Nota: En el caso de usar cursores o buffering de tablas o registros, recordar cada tanto usar la función SYS(1104) para liberar los buffers de memoria. Como ejemplo, cada 100 registros reemplazados se podría forzar la limpieza de los buffers para liberar memoria.




Caso 3: Escritura de grandes cantidades de texto (>1 MB y <= 2 GB)


Hay situaciones en las que tenemos un proceso que requiere ir generando información al disco en formato texto. La diferencia con un LOG, como vimos al inicio, es que un LOG puede desactivarse o condicionarse, mientras que en este caso hablamos de un proceso que sí o sí debe escribir al disco para generar un archivo de texto en múltiples pasos o subprocesos. Un ejemplo de este tipo de proceso puede ser un parser o un conversor, donde se va interpretando el origen de datos (o un archivo origen) y a la vez se va generando la información de salida a un archivo de texto.

Si bien la primera reacción puede ser la de usar la técnica del buffering del Caso 1 mediante el uso de una variable o una propiedad acumuladora, es necesario saber que para textos superiores a 1 MB FoxPro se empieza a poner lento, principalmente por la acumulación en una variable que va creciendo y que además va consumiendo cada vez más memoria.

En estos casos, lo más óptimo es usar funciones de bajo nivel como FOPEN/FWRITE con buffering, lo que da una velocidad que como mínimo duplica al buffering por variable o propiedad acumulada.

Aunque pueda parecer contradictorio decir que una función de manejo de archivos de bajo nivel sea más rápida en estos casos que una variable, la explicación está en que no solo Fox no se maneja bien con variables con mucho contenido que se actualiza constantemente, sino que además las funciones de manejo de archivos a bajo nivel con uso de buffering están optimizadas para justamente para no escribir todo el tiempo al disco, sino que implementan su propio esquema de buffering.




Caso 4: Generación de texto con TEXT/ENDTEXT


No se puede hablar de generación de texto sin hablar de TEXT/ENDTEXT, que es uno de los comandos más potentes y versátiles de FoxPro desde los inicios, pero primero veamos un ejemplo de la salida que queremos conseguir:




Fecha: 01/05/2015                    Fernando Caimo

Número de viajes: 30
Kilómetros hechos: 275

------------------------------------------------------------



Así es cómo se puede generar este texto usando variables y contenido dinámico:


cTexto = ''
cTexto = cTexto + chr(13)+chr(10)
cTexto = cTexto + 'Fecha: ' + dtoc(date()) + space(20) ;
  + cApellidoYNombre + chr(13)+chr(10)
cTexto = cTexto + chr(13)+chr(10)
cTexto = cTexto + 'Número de viajes:  ' + TRANSFORM(nViajes) ;
  + chr(13)+chr(10)
cTexto = cTexto + 'Kilómetros hechos: ' + TRANSFORM(nKM) ;
  + chr(13)+chr(10)
cTexto = cTexto + chr(13)+chr(10)
cTexto = cTexto + replicate('-', 60)



Y esta es la forma en que se hace con TEXT/ENDTEXT:

TEXT TO cTexto ADDITIVE TEXTMERGE NOSHOW FLAGS 1+2 PRETEXT 1+2

Fecha: <<date()>>                    <<cApellidoYNombre>>

Número de viajes: <<nViajes>>
Kilómetros hechos: <<nKM>>

------------------------------------------------------------

ENDTEXT


....no hay contraste! TEXT/ENDTEXT es tan increíblemente versátil y claro para generar este tipo de documentos que deja en vergüenza al resto de métodos disponibles.

Pero tanta potencia tiene sus casos de uso y su coste, por lo que tampoco es cuestión de usarlo para cada línea de texto que se quiera generar, y es que para líneas individuales, y más si se usa de la siguiente forma, no es recomendable:

FOR X=1 TO loProcedure._ProcLine_Count
  TEXT TO lcMemo ADDITIVE TEXTMERGE NOSHOW FLAGS 1+2 PRETEXT 1+2
    <<loProcedure._ProcLines(X)>>
  ENDTEXT
ENDFOR

STRTOFILE(lcMemo, 'salida.txt', 1)



Como puede verse, aunque la parte de escritura con STRTOFILE está optimizada porque está fuera del bucle, lo que de por sí impactará poco en accesos a disco, el TEXT/ENDTEXT está en un bucle de repetición cuya iteración puede ser alta, y el problema con este caso en particular es que por cada línea que se genera y agrega a lcMemo, se requiere inicializar una estructura de parseo, parsear cada uno de los parámetros indicados y luego procesar el texto entre TEXT y ENDTEXT usando los parámetros indicados. Y todo esto para una línea en un bucle es demasiado coste para la CPU.

En su lugar, y para este caso particular, lo más conveniente y óptimo es usar una variable:

FOR X=1 TO loProcedure._ProcLine_Count
  lcMemo = lcMemo + chr(13)+chr(10) + loProcedure._ProcLines(X)
ENDFOR

STRTOFILE(lcMemo, 'salida.txt', 1)




Resumen


El objetivo siempre debe ser hacer el código más óptimo para cada situación, y en el caso de los archivos siempre se trata de minimizar los accesos a disco usando técnicas de buffering.

Lo que se logra con estas técnicas, específicamente, es:
  • Menor cantidad de accesos a disco (o red)
  • Menor uso de recursos del sistema
  • Disminución de posibilidades de contención
  • Mayor velocidad de proceso
  • Alargamiento de la vida útil de los discos, tanto rígidos como SSD

Puede que hayan quedado fuera algunas cosas, pero quería hacer un resumen con algunas de las más importantes a tener en cuenta.


Hasta la próxima! :D