domingo, febrero 25, 2018

VFP: Cómo mover una tabla de una Base de Datos a otra

En ocasiones puede pasar que tengamos una tabla que inicialmente pertenece a una base de datos, pero que luego por algún motivo necesitemos quitarla de esa base de datos y moverla a otra distinta con todos sus datos.

A priori esto puede parecer algo sencillo, pero dependiendo de algunas circunstancias puede ser bastante más complicado.

Veamos algunos ejemplos, para los que VFP debe estar ubicado en el directorio de los datos.
Recordar siempre hacer un backup antes!




Caso 1: Tabla con nombres de campo cortos (hasta 10 caracteres) y sin triggers


Supongamos por ejemplo que tenemos una "tabla1" con un campo "NOMBRE" y un índice por el mismo, perteneciente a la base de datos BDD1

En este caso la operación es sencilla, basta con quitar la tabla de una base de datos y agregarla a la otra base de datos:


1) Para definir una situación inicial de ejemplo, creamos una BDD1 con una Tabla1 con datos e índice (en un caso real ir directo al paso 2):

CREATE DATABASE bdd1
CREATE TABLE tabla1 (nombre c(30))
INSERT INTO tabla1 (nombre) VALUES ("Francisco")
INSERT INTO tabla1 (nombre) VALUES ("Antonio")
INSERT INTO tabla1 (nombre) VALUES ("Juan")
INDEX on nombre TAG nombre
CLOSE ALL


2) Ahora movemos la tabla a la otra BDD:

OPEN DATABASE bdd1 EXCLUSIVE && Abrimos la BDD origen 
REMOVE TABLE tabla1          && Quitamos la tabla de la misma 
OPEN DATABASE bdd2 EXCLUSIVE && Abrimos la BDD destino
ADD TABLE tabla1             && Agregamos la tabla
CLOSE ALL


Listo! Esta opción es la más simple y rápida, y no implica trasvasar datos.




Caso 2: Tabla con nombres de campo largos (más de 10 caracteres) y sin triggers


Ahora supongamos por ejemplo que tenemos una "tabla1" con un campo "NOMBRE_LARGO" y un índice por el mismo, perteneciente a la base de datos BDD1

En este caso la operación sigue siendo sencilla, aunque no podremos seguir los pasos del Caso 1 porque perderemos los nombres largos y los índices que dependan de ellos dejarán de funcionar.


1) Para definir una situación inicial de ejemplo, creamos una BDD1 con una Tabla1 con datos e índice (en un caso real ir directo al paso 2):

CREATE DATABASE bdd1CREATE TABLE tabla1 (nombre_largo c(30))
INSERT INTO tabla1 (nombre_largo) VALUES ("Francisco")
INSERT INTO tabla1 (nombre_largo) VALUES ("Antonio")
INSERT INTO tabla1 (nombre_largo) VALUES ("Juan")
INDEX on nombre_largo TAG nombre
CLOSE ALL


2) Ahora movemos la tabla a la otra BDD en un directorio de trabajo y traemos todo lo hecho:

MKDIR copia                     && Creamos un dir de trabajo
COPY FILE BDD2.* TO copia\*.*   && Copiamos la BDD destino
USE tabla1 EXCLUSIVE

COPY STRUCTURE TO copia\tabla1 ;&& Copiamos la estructura de la
   WITH CDX DATABASE copia\BDD2 && tabla y CDX a la BDD destino
CLOSE ALL
OPEN DATABASE copia\bdd2 EXCLU  && Abrimos la BDD destino
USE copia\tabla1 EXCLUSIVE

APPEND FROM tabla1              && Recuperamos los datos originales
CLOSE ALL
OPEN DATABASE bdd1 EXCLUSIVE    && Abrimos la BDD origen
REMOVE TABLE tabla1             && Quitamos la tabla de la misma
CLOSE ALL 
COPY FILE copia\*.* TO *.*      && Traemos los archivos modificados


Terminado! Esta opción es la más simple y rápida, aunque implica trasvasar datos.




Caso 3: Tabla con propiedades de BDD


Otro caso que se puede presentar es tener una tabla asociada a una BDD que use propiedades específicas de esta, como reglas de validación de campos, mensajes de validación, comentarios de tabla o campos, triggers, etc.

Este caso es uno de los más complejos, porque si seguimos los pasos del Caso 1, perderemos los nombres largos y los índices que dependan de ellos dejarán de funcionar., y si seguimos los pasos del  Caso 2 tendremos los datos y los índices, pero no las propiedades de BDD comentadas.

Aquí se podría usar FoxBin2Prg, pero en este caso es necesario conocer las estructuras DB2 (tabla) y DC2 (base de datos) generadas, ya que habrá que cortar y pegar algunas secciones del DBC1 para pasarlos al DBC2, además de modificar el DB2.

Vamos a ver un ejemplo con la misma tabla que el Caso 2 y algunas propiedades de BDD.


1) Para definir una situación inicial de ejemplo, creamos una BDD1 con una Tabla1 con datos, índice y algunas propiedades de BDD (en un caso real ir directo al paso 2):

CREATE DATABASE bdd1
CREATE TABLE tabla1 ;
    ( nombre_largo C(30) ;
    CHECK NOT EMPTY(nombre_largo) ;
    ERROR "El nombre no puede estar vacío" ;
    , codigo I AUTOINC NEXTVALUE 1 STEP 1 ;
    PRIMARY KEY )

INSERT INTO tabla1 (nombre_largo) VALUES ("Francisco")
INSERT INTO tabla1 (nombre_largo) VALUES ("Antonio")
INSERT INTO tabla1 (nombre_largo) VALUES ("Juan")
INDEX on nombre_largo TAG nombre
DBSETPROP("tabla1.nombre_largo","FIELD","Comment";
    ,"Comentario del campo nombre_largo")
DBSETPROP("tabla1","TABLE","Comment","Comentario de la tabla")
CLOSE ALL



2) Creamos un archivo de configuración específico para nuestra Tabla1 (tabla1.dbf.cfg) con esta configuración:

DBF_Conversion_Support: 8 && Permite exportar e importar estructura, indices y datos


3) Creamos la vista de texto (DB2) de esta Tabla, la vista de texto (DC2) de su base de datos BDD1 y la vista de texto (DC2) de la base de datos destino BDD2 con FoxBin2Prg


4) Si abrimos la vista de texto de la tabla del ejemplo (tabla1.db2), veremos que tiene dentro toda la información de la estructura al principio y luego los registros de datos. En esta cabecera debemos cambiar el nombre de la base de datos actual (BDD1) por la que queremos que sea la nueva (BDD2) y lo volvemos a guardar:

<TABLE>
    <MemoFile></MemoFile>
    <CodePage>1252</CodePage>
    <LastUpdate></LastUpdate>
    <Database>bdd2.dbc</Database>



5) Si abrimos la vista de texto de la base de datos del ejemplo (bdd1.dc2), veremos que figuran todas las tablas con una estructura como esta:

<DATABASE>
    <Name>BDD1</Name>
    <Comment></Comment>
    <Version>10</Version>
    <DBCEvents>.F.</DBCEvents>
    <DBCEventFilename></DBCEventFilename>

    <TABLES>

        <TABLE>
            <Name>TABLA1</Name>
            <Comment>Comentario de la tabla</Comment>
            <Path>tabla1.dbf</Path>

            ... 
        </TABLE>


Los tags <TABLE></TABLE> contienen toda la información de cada tabla, sus campos, índices y comentarios, parte de los cuales están repetidos en la información de la tabla.


6) La sección completa <TABLE></TABLE> de la tabla que nos interesa pasar, la cortamos de esta estructura y la pegamos en la estructura de destino (bdd2.dc2), cuidando de insertarla en orden alfabético por el nombre de la tabla (el tag <NAME>), y guardando el cambio en ambos archivos (bdd1.dc2 y bdd2.dc2)

Nota: Si la base de datos bdd2 es nueva, no tendrá tablas dentro por lo que la estructura externa <TABLES></TABLES> que contiene la tablas tampoco existirá, por lo que podría haber que crearla manualmente, ya que estamos pasando solo una tabla.


7) Abrimos el archivo de la base de datos origen (bdd1.dc2) con FoxBin2Prg para que regenere el DBC sin la Tabla1


8) Abrimos el archivo de la tabla (tabla1.db2) con FoxBin2Prg para que la regenere con su nueva base de datos referenciada


9) Abrimos el archivo de la base de datos destino (bdd2.dc2) con FoxBin2Prg para que regenere el DBC con la nueva información de la Tabla1


¡Al fin! Ya tenemos pasada la tabla1 de la bdd1 a la bdd2 con toda su información relacionada.




Resumen:


Hay otras formas de pasar una tabla a otra base de datos, pero normalmente requieren usar programación específica para dichos casos y tener un conocimiento más técnico y profundo de cada componente.

Lo que he querido mostrar en este caso es una forma genérica que sirve con cualquier tabla y cualquier base de datos, donde todo se reduce a cortar/pegar algunas secciones de un archivo de texto a otro y dejar que FoxBin2Prg se encargue de la parte técnica.

Nota importante sobre triggers: Se debe tener en cuenta que cuando hay triggers de por medio, si bien FoxBin2Prg puede pasar la información de una BDD a otra, este caso sí requiere tener conocimientos de los mismos y de procedimientos almacenados, ya que aunque se pase automáticamente la información de la tabla y sus metadatos, no será igual con los procedimientos almacenados, los cuáles habrá que seleccionar cuidadosamente y pasar también a la base de datos de destino, pero además de esto se debe considerar si es posible hacer esta mudanza de base de datos, porque normalmente los triggers vinculan tablas (ej; si se borra o actualiza en una que se borre o actualice en cascada en otras) y es muy probable que eso implique o bien quitar triggers o bien mover varias tablas en conjunto.


Hasta la próxima! :D


viernes, enero 26, 2018

PBF: Metodología de Desarrollo bastante usada y no reconocida

Como sabemos, hay varias metodologías de desarrollo que han evolucionado desde sus comienzos hasta la actualidad, hagamos un breve repaso de ellas (definiciones tomadas de Wikipedia):


1970



1980



1990



2000



2010




Persistentes en el tiempo

Varias metodologías antiguas han desaparecido o ya casi no se usan, y hay metodologías que han comenzado alrededor del año 2000 y que han ido madurando en el tiempo, siendo (a día de hoy) las siguientes:


Pero hay una que no sale en nigún sitio, que no está documentada, con una (lamentablemente) larga fila de seguidores, y que creo que más de uno habrá visto, aunque no supiera su nombre:


Programación Basada en la Fé (PBF)
No hay link justamente porque formalmente no existe, pero está ahí...

¿Cómo funciona?
Es fácil: se hacen desarrollos en modo "para ayer", donde no hay tiempo para detenerse a comprobar la calidad o hay un mínimo de tiempo para algunas pruebas poco fiables porque ese tiempo de pruebas se ha ido reduciendo para poder meter alguna característica más.

¿Y las pruebas automatizadas?
Bien, gracias. Si hay suerte, puede que haya algunos tests semi-automatizados de hace varios años, cuando en algún momento había tiempo para los mismos, con lo que al ejecutarlos ahora podemos comprobar que algunas especificaciones de hace muchos años aún funcionan, pero de las nuevas no sabemos nada.

Obviamente, esta metodología genera muchas incidencias y los usuarios las reportan, y hete aquí dos puntos fuertes de esta metodología:

  1. Cada cierto tiempo las incidencias abiertas "se cierran", aún sin estar seguros de que se corrigieron, sobre todo las más antiguas o sangrantes, todo sea por que salgan bien las estadísticas. Si la incidencia no se reabre quiere decir que se ha acertado con la solución y que se puede seguir así, pero si se reabre, entonces significa que no hubo suerte y que hay que intentar otra cosa, incluso con el método de prueba y error, más una dosis de Fé (o sea, "ojalá que funcione").
  2. Con el tiempo los usuarios se cansan de reportar errores debido al desgaste, y entonces disminuyen las incidencias (las reportadas, no las reales), dando la señal aparente de que las cosas mejoran, cuando es todo lo contrario (nuevamente, favoreciendo las estadísticas)
De esta metodología solo puede esperarse una cosa: El Big Bang . Porque tarde o temprano ocurrirá y se irá todo al traste.

El sistema realmente es bastante perverso y las responsabilidades están repartidas entre todos los involucrados, desde los que piden cosas imposibles o innecesarias en tiempos ilusorios hasta los que debieran velar por la calidad de la solución y de la arquitectura de las aplicaciones, pasando por quienes deben gestionar todo el proceso.

Lamentablemente muchos son presa de esta metodología y aunque tengan buena voluntad, no tienen forma de luchas contra el Sistema, ya que ponerle solución implicaría hacer cambios que no todos están dispuestos a hacer. Al final la excusa de que "siempre se hizo así" muchas veces gana.

Cualquier parecido con la realidad es pura casualidad...


Seguramente alguno tendrá alguna anécdota para comentar al respecto.

Hasta la próxima!


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

sábado, enero 13, 2018

PlasticSCM: Cambiar la ubicación del Repositorio

(Publicado originalmente por Carton Jeston en el foro Google de la Comunidad Visual FoxPro)
https://groups.google.com/d/msg/publicesvfoxpro/faUPGOBsdBs/kfg7jBtmBAAJ

---

Después de instalar plasticscm en su carpeta por defecto (c:\Program Files\PlasticSCM5\) los repositorios se guardan dentro de la subcarpeta SERVER.  No me parece una gran idea tener datos importantes en la misma partición de windows y necesito, por motivos de seguridad y para facilitar el backup, que estén separados y en otra ubicación.

Lo primero es salir del plasticscm y asegurar que no hay ningún servicio dependiente o archivo abierto y hacer una copia de seguridad de toda la carpeta PlasticSCM5 por si fallara algo.

Para este ejemplo, teniendo una unidad/particion D: para los datos importantes, creare una carpeta llamada PLASTIC en el disco D: (D:\PLASTIC) que es la nueva ubicación donde se guardaran los repositorios. Recomiendo usar siempre minúsculas aunque Windows no se vea afectado por ello.

Dentro de la subcarpeta SERVER hay un archivo de texto llamado DB.CONF, que si se instala por defecto con la base de datos SQLITE deberá tener esta información:


<DbConfig>
 
<ProviderName>sqlite</ProviderName>
 
<ConnectionString>Data Source={0};Synchronous=FULL;Journal Mode=WAL;Pooling=true</ConnectionString>
 
<DatabasePath></DatabasePath>
</DbConfig>

Y entre <DatabasePath>d:\plastic</DatabasePath> insertamos la ubicación de la carpeta que hemos creado antes, recordando que mejor en minúsculas, quedando así:



<DbConfig>
 
<ProviderName>sqlite</ProviderName>
 
<ConnectionString>Data Source={0};Synchronous=FULL;Journal Mode=WAL;Pooling=true</ConnectionString>
 
<DatabasePath>d:\plastic</DatabasePath>
</DbConfig>

Ahora guardamos los cambios el archivo que hemos editado DB.CONF.

Como hemos observado anteriormente, las bases de datos que empleaba plasticscm era SQLITE, así que buscamos en la misma carpeta SERVER todos los archivos con extensión .SQLITE y tendrás algo así:

repositories.plastic.sqlite
rep_1.sqlite
rep_2.sqlite
etc.


Ahora hay que moverlos todos a la nueva carpeta que creamos (D:\PLASTIC) y si todo ha ido bien, al abrir plasticscm apuntara a los repositorios guardados en esta ubicación.

Ya está.

En mi caso, cuando hago una versión nueva final de mi aplicación, hago la copia del workspace (donde esta nuestro proyecto, formularios, prg,etc) que tengo en esa unidad de datos y ahora ademas hago la copia de los repositorios para salvaguardar también la base de datos de los cambios de la versión de la aplicación. No hay que olvidar que un mal paso, un virus o una rotura de disco duro y siempre hay que tener copia de seguridad.



¿Como parar/reanudar el servicio de plastic para hacer un backup/restore de los repositorios?

Como he comentado arriba, conviene para el servicio de plasticscm antes de hacer un backup y sobre todo, al restaurar copiando nuestros repositorios puede dar errores.

Te darás cuenta que en la carpeta de los repositorios en algún momento habrán dos archivos con el nombre de un repositorio y con la extensión .sqlite-shm y .sqlite-wal y es porque plasticscm esta haciendo algún trabajo en segundo plano.

Para evitar conflictos, lo mejor es parar el servicio y que termine lo que esta haciendo y después hacer nuestras copias de los repositorios sin ningún problema.

Pulsar Ctrl+Alt+Supr y elegir la opción "Iniciar administrador de tareas". Pulsar en la pestaña Servicios, buscar el nombre PLASTIC SERVER 5 y pulsas con el botón derecho del ratón. Ahora elegir Detener servicio, hacer nuestras copias o restaurarlas en cuanto desaparezcan los dos archivos mencionados arriba y al terminar, botón derecho de nuevo sobre el servicio y elegir Iniciar servicio.

También puedes acceder a los Servicios a través del Panel de control de Windows, Herramientas administrativas y elegir Servicios.

Otro metodo alternativo mediante la consola de comandos del sistema (ejecutar-cmd) o automatizar mediante un archivo BAT o similar o bien acceso directo:

net start "plastic server 5"    >>> inicia el servicio     
                                                            
net stop  "plastic server 5"    >>> detiene el servicio    

[N.del E.] o también:

sc \\localhost start "plastic server 5"                      
                                                             
sc \\localhost stop  "plastic server 5"                      




Particularmente me gusta mucho este método por su automatización y porque te indica cuando ha terminado la operación correctamente y seguro para un proceso desatendido. Tener en cuenta de probarlo por primera vez manualmente y ver que el nombre del servicio no ha cambiado, actualmente es la versión plasticSCM 5.



Número del archivo de base de datos de Plastic

Para poder saber qué workspace tenemos en qué repositorio (BDD), debemos usar el comando cm desde la consola de Windows, con este parámetro:


cm lrep --format="TABLE"                                      

Que en mi caso deja una salida como esta:

 1 default localhost:8087                                     
 2 dvcs_vfp9 localhost:8087                                   
 5 foxpro_diff_merge localhost:8087                           
 6 filename_caps localhost:8087                               


Donde la primera columna contiene el número de repositorio, la segunda el nombre del workspace y la tercera la máquina:puerto donde apunta.

Si se está usando la nomenclatura de repositorio por defecto ( osea que no se ha cambiado) y se usa SQLite, entonces  el nombre del repositorio es de la forma:

rep_NN.plastic.sqlite

Y aplicando el número obtenido a la nomenclatura, se puede ver que el nombre de BDD del repositorio que contiene el workspace  dvcs_vfp9, será:

rep_2.plastic.sqlite


Nota: Para que el comando cm funcione sin tener que indicar la ruta cada vez, es buena idea agregar la ruta "c:\Program Files\PlasticSCM5\client" al PATH de Windows.



Hasta la próxima! :D

Artículos relacionados:
PlasticSCM: Opciones de conectividad, transporte del código fuente y backup
PlasticSCM: Cómo configurar la replicación de repositorios Plastic locales y remotos


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, noviembre 12, 2017

Plastic SCM: Índice de videos de YouTube con algunas operaciones comunes

El presente post es para tener un índice con los videos que fui haciendo sobre el uso de Plastic SCM con Visual FoxPro 9.


PlasticSCM: Como cambiar el tipo de archivo a texto



PlasticSCM: Cómo cancelar un merge en proceso


PlasticSCM: Resolución manual de conflictos en FoxPro 9



PlasticSCM: Deshacer un changeset con merge sustractivo en FoxPro 9

FoxPro 9: Deshacer un merge de dos ramas en PlasticSCM



FoxPro 9: Merge en PlasticSCM de dos ramas con un directorio duplicado

Modificación en FoxPro 9 y checkin en PlasticSCM


Merge de 2 ramas y resolución de conflictos (directorio repetido)



PlasticSCM: Merge con Visual FoxPro 9

PlasticSCM: Merge con Visual FoxPro 9 - video 2





Hasta la próxima! :D


lunes, marzo 27, 2017

VFP: Algunas formas de usar los arrays

Los arrays son una colección de elementos muy útil y versátil en cualquier lenguaje, que a diferencia de los cursores o tablas permiten también almacenar objetos o referencias a los mismos.

En Visual FoxPro el uso de los arrays conviene mantererlo controlado en cuanto a tamaño, ya que por encima de algunos cientos de elementos, las búsquedas sobre un array comienzan a ponerse lentas. Por eso, para grandes tamaños siempre conviene usar cursores o tablas.



Definición y dimensionamiento


Hay dos tipos de arrays: en forma de variable y en forma de propiedad, ambos usan la misma sintaxis, pero solo se diferencian en la forma de declararse o en dónde se declaran.

Por ejemplo, como variable un array se puede definir de estas formas:

LOCAL laLista(1), laTabla(1,1)  && Se definen y dimensionan en un paso

Y luego se pueden redimensionar así:

DIMENSION laLista(10), laTabla(4,2)

Para una clase, usando el cuadro de diálogo de agregar propiedad (ej.en un form: menú form/New property), lo mismo de antes se define como laLista[1] y luego otra propiedad nueva como laTabla[4,2]

Luego, para redimensionar las propiedades se hace lo mismo, pero indicando el nombre del objeto:

DIMENSION thisform.laLista(10), thisform.laTabla(4,2)

Hay una sintaxis adicional cuando se requiere agregar un array que no sea posible poner en diseño, que es usando AddProperty:

AddProperty( thisform, "laLista[1]" )



Asignación de valores


Para asignar valores, se hace como cualquier variable o propiedad:

laLista(1) = 100
laLista(2) = CREATEOBJECT("custom")
laLista(3) = "Texto"

thisform.laLista(1) = 100
...



Redimensionamiento


Para redimensionar un array se usa el mismo comando que para definirlo, DIMENSION. Solo hay que tener en cuenta algunos casos:

- Para agrandar un array bidimensional con datos, solo se pueden agregar filas, no columnas, ya que si se agregan columnas (técnicamente posible) se distribuirán los datos existentes para usar las nuevas columnas, lo que hará que ya no estén en sus ubicaciones originales. En este caso conviene copiar de un array a otro nuevo con las columnas que se requieran.

- Para achicar un array, conviene hacerlo desde abajo hacia arriba, o sea, desde el índice mayor al menor

Para ejemplificar la eliminación de elementos, algunos habrán intentado hacerlo así:

lnFilas =  ALEN(laLista)
FOR I = 1 TO lnFilas
   ADEL(laLista, I) 
   lnFilas = lnFilas - 1
   DIMENSION laLista(lnFilas)
ENDFOR

El código anterior tiene el problema de que al llegar a la mitad del array, ocurrirá un error porque se estará intentando borrar (ADEL) un elemento cuyo índice supera la cantidad de elementos, y es por eso que esto debe hacerse en orden inverso:

lnFilas =  ALEN(laLista)
FOR I = lnFilas TO 1 STEP -1
   ADEL(laLista, I) 
   lnFilas = lnFilas - 1
   DIMENSION laLista(lnFilas)
ENDFOR

Si bien el algoritmo anterior no tiene sentido para cuando se quieren borrar todos los elementos, porque es mucho más rápido hacer un DIMENSION laLista(1) y luego laLista = "", es la forma correcta de borrar elementos intermedios.



Caso especial de uso


Normalmente los arrays se usan para listas de valores conocidos, como nombres de ciudades, paises y selecciones en general, pero hay un caso especial que requiere una forma distinta de manipular el array, que es cuando no se sabe con antelación cuántos elementos tendrá el array, pero es necesario tenerlo predefinido con un elemento.

El problema se presenta cuando se quiere agregar un elemento nuevo, ya que normalmente se usa ALEN() para saber cuantos tiene y agregar uno nuevo, pero ALEN() devolverá siempre como mínimo 1 y es posible que ese elemento tenga valor, o no, y no siempre es posible usar EMPTY() para controlar si hay un dato, porque un 0 o una cadena vacía podría ser el dato...

¿Cómo controlar esa situación? Para eso se puede usar un contador de elementos:

lnFilasLista = 0
DIMENSION laLista(1)

Y luego, cuando se va a agregar un nuevo elemento, simplemente se incrementa y redimensiona a la par:

lnFilasLista  = lnFilasLista + 1
DIMENSION laLista(lnFilasLista)
laLista(lnFilasLista) = "nuevo valor"


La siguiente es una técnica que define una API para interactuar con el array anterior, donde se encapsulan las operaciones de dimensionar y agregar valores. En este caso lo defino como una clase instanciable, pero los métodos bien podrían ponerse directamente en un form u otra clase.

Este es un ejemplo de uso:

loArrayMgr = createobject("cl_ArrayMgr") 
loArrayMgr.Add("un valor")
loArrayMgr.Add("otro valor")
loArrayMgr.Add("un valor más")
? loArrayMgr.Length
3
loArrayMgr.Delete(2)
? loArrayMgr.Datos(2)
"Un valor más"
? loArrayMgr.Length
2

Y la clase podría definirse así en un PRG o visualmente en la clase que se requiera:

DEFINE CLASS cl_ArrayMgr AS Custom
   DIMENSIO aDatos[1]
   nDatos = 0 

   FUNCTION Length
      RETURN THIS.nDatos
   ENDFUNC

   PROCEDURE Add(tuValor)
      THIS.nDatos = THIS.nDatos + 1
      DIMENSION THIS.aDatos(THIS.nDatos)
      THIS.aDatos(THIS.nDatos) = tuValor
   ENDPROC

   PROCEDURE Delete(tnElemento)
      IF THIS.nDatos > 1 AND tnElemento <= THIS.nDatos
         ADEL(THIS.aDatos, tnElemento)
         THIS.nDatos = THIS.nDatos - 1
         DIMENSION THIS.aDatos(THIS.nDatos)
      ENDIF
   ENDPROC
ENDDEFINE


Sobre este ejemplo se pueden hacer variaciones y mejoras, pero creo que se entiende la idea de cómo manipular un array en las condiciones comentadas.


Hasta la próxima! :D