PDA

Ver la Versión Completa : Una Pregunta Teórica sobre Archivos UDT


nlsgarcia
21-02-2007, 22:37:04
:) Hola:

El siguiente programa funciona correctamente:



program TestFile;
{$APPTYPE CONSOLE}
uses
SysUtils, StrUtils;
Type
TPrnCmd = Record
ProcPrnCmd : array [0..40] of char;
DatePrnCmd : array [0..12] of char;
TimePrnCmd : array [0..12] of char;
Status : array [0..30] of char;
CRLF : array [0..1] of char;
end;
var
i : integer;
RegPrnCmd : TPrnCmd;
function LogPrnCmd(LogFile, ProcPrnCmd, StatusProc : String) : Boolean;
var
i : integer;
f: File of TPrnCmd;
// RegPrnCmd : TPrnCmd;
DateProc : String;
begin
LogFile := GetCurrentDir + '\' + LogFile;
AssignFile(f, LogFile);
FileMode := fmOpenWrite ;
if FileExists(LogFile) then
Reset(f)
else
Rewrite(f);
DateProc := FormatDateTime('dd/mm/yyyy',Date);
StrPCopy(RegPrnCmd.ProcPrnCmd,ProcPrnCmd);
StrPCopy(RegPrnCmd.DatePrnCmd,MidStr(DateProc,7,4) + '-' + MidStr(DateProc,4,2) + '-' + MidStr(DateProc,1,2));
StrPCopy(RegPrnCmd.TimePrnCmd,FormatDateTime('hh:mm:ss',Time));
StrPCopy(RegPrnCmd.Status,StatusProc);
StrPCopy(RegPrnCmd.CRLF,chr(13)+chr(10));

Seek(f, FileSize(f));
Write(f,RegPrnCmd);
CloseFile(f);
end;
begin
for i := 1 to 10 do
LogPrnCmd('TestFile.txt','Proc'+IntToStr(i),'Status'+IntToStr(i));
end.



La estructura UDT RegPrnCmd : TPrnCmd, esta definida a nivel global, y esta en la function LogPrnCmd, pero como comentario. Si se comenta la que esta a nivel global (RegPrnCmd) y se usa a nivel local, eliminando los carácteres de comentario, el programa da el error: "Project TestFile.exe raised exception class EInOutError with message I/O error 6. Process Stopped. Use Step or Run to continue.

La pregunta es: ¿Por que funciona si la declaración de RegPrnCmd, es a nivel global y falla si es declarada a nivel local?

Como dije anteriormente es una pregunta teórica, el programa ya funciona pero me gustaría aclarar la duda.

Gracias de antemano por toda la colaboración prestada. :)

Lepe
23-02-2007, 14:23:06
Usar GetCurrentDir no es normal en windows, ya que la carpeta activa puede ser diferente a donde está tu programa, usando ExtractFilePath(paramstr(0)) obtienes la ruta de tu ejecutable.

I/O error 6 es invalid handle (descriptor de archivos erróneno) muy probablemente porque la ruta currentdir+ LogFile no existe.


La pregunta no tiene cabida, ya que no tiene nada que ver con el error que está dando.

Saludos

nlsgarcia
23-02-2007, 20:36:50
:) Hola:

Gracias por tu comentario.

Probe lo que me indicastes y el resultado es el mismo. ¿Sera posible que sea problema del compilador de Delphi o sera un error de concepto?

Dado que me comentas que es poco frecuente el uso de GetCurrentDir y me aconsejas usar ExtractFilePath(paramstr(0)) como una instrucción más estandar, pregunto: ¿Hay algún site ó libro que me recomiendes sobre buenas prácticas de programación en Delphi?

Nuevamente gracias por tus comentarios :)

Lepe
24-02-2007, 15:34:17
Estas funciones están en Delphi desde hace muchos años, dudo que sea un error del compilador, ya que se han usado intensivamente.

En principio no veo ningún fallo en el código, pero son tantas variables a tener en cuenta, que es difícil depurar.

Lo primero, haz un ShowMessage de LogFile para ver si hay 2 contrabarras en lugar de una y mirar el nombre y ruta del archivo. Intenta borrar el archivo primero para empezar desde cero, es muy común que en la primera ejecución el programa da un fallo porque no existe el archivo, después de parar el programa y volverlo a ejecutar, el fichero ya existe, por tanto funciona bien.

Mira lo que comenté en este otro hilo (http://www.clubdelphi.com/foros/showpost.php?p=184351&postcount=4) sobre rutas largas.

Un buen libro es la cara oculta de delphi 4 (http://www.latiumsoftware.com/descarga/lcod4.php) de Ians Marteens (antiguo pero muy útil y válido).

Saludos

nlsgarcia
24-02-2007, 16:40:24
:) Hola:

Te comento:

1.- El programa funciona bien si la variable UDT es declarada a nivel global, si es declarada a nivel local da el error antes mencionado.

2.- Con la variable UDT declarada a nivel global, no importa si el archivo existe o si se crea en el proceso, es decir: No hay problemas con la composición del string de ruta del archivo.

3.- Si no hay errores de concepto o del string de ruta del archivo, entonces pregunto nuevamente: ¿Sera problema del compilador?

Te pongo un ejemplo de problemas del compilador: La función FormatMaskText no funciona como se indica en la ayuda de Delphi, (ver http://qc.borland.com/wc/qcmain.aspx?d=25360).

Este es uno de algunos funcionamientos atípicos en el compilador de Delphi que he encontrado durante mi trabajo, es por eso que creo en mi humilde opinión que el problema puede ser del compilador de Delphi, sin embargo admito que es difícil ímaginar que un compilador tan robusto y de tanta trayectoria como Delphi presente algún tipo de detalle.

Nota: Cuando digo compilador, me refiero a todo el lenguaje como tal, obviamente el problema puede ser de una unidad en particular, de un elemento del lenguaje en particular y no del compilador en si mismo, pero es una manera de generalizar el problema a efectos de explicación, dado que a ciencia cierta no puedo decir donde esta el problema con seguridad.

Nuevamente gracias por tus comentarios. :)

roman
24-02-2007, 19:08:18
El siguiente programa funciona correctamente

No. Realmente no funciona bien.

¿Por que funciona si la declaración de RegPrnCmd, es a nivel global y falla si es declarada a nivel local?

No funciona de ninguna manera, es sólo que has tenido suerte, pero, la programación no es cosa de suerte (http://www.clubdelphi.com/foros/showthread.php?t=161&highlight=suerte).

Como dije anteriormente es una pregunta teórica, el programa ya funciona pero me gustaría aclarar la duda

No, el programa aún no funciona y espero convencerte de ello para que no cargues con una bomba de tiempo que en algún momento te pueda estallar.

Es un problema interesante pues, a primera (y segunda) vista, no tiene sentido que sólo por cambiar el ámbito de la variable, deje de funcionar.

Ahora bien, puedes hacer que "funcione" usando la variable local simplemente declarándola antes del File of TPrnCmd, y no después:


function LogPrnCmd(LogFile, ProcPrnCmd, StatusProc : String) : Boolean;
var
i : integer;
RegPrnCmd : TPrnCmd;
f: File of TPrnCmd;
// RegPrnCmd : TPrnCmd;
DateProc : String;


Bueno, pero eso no puede ser una solución. Seguiría siendo una cuestión de suerte.

El error que obtienes es I/O error 6, que corresponde, como te comentó Lepe, a Invalid File Handle. No es una cuestión de que el archivo no se encuentre, porque entonces obtendrías un error I/O error 2 (file not found) o 3 (path not found), pero no, tú obtienes un error que te dice que estás usando un descriptor de archivo incorrecto. Si hiciste una traza del programa te habrás dado cuenta que el error aparece en la línea:


Seek(f, FileSize(f));


Y, de hecho, el error lo genera FileSize. Pero si pones Seek(F, 28), el error lo genera Seek. Es decir, el error se genera cada vez que tratas de usar la variable F con alguna de las funciones de manejo de archivos. F es quien tiene el descriptor.

Ahora, ¿por qué está mal ese descriptor siendo que lo acabamos de crear con AssignFile? Algo, lo está dañando. Está claro que ni Reset ni Rewrite pueden dañarlo; como dice Lepe, esos procedimientos están ahí desde los tiempos de Matusalén (perdón, de Niklas Wirth).

Lo único que hay entre la creación del descriptor y su uso son las asignaciones con StrPCopy. Y, como dijera Sherlock Holmes, cuando todo lo demás se ha descartado, lo que quede, por improbable que esto sea, es la verdad.

Vayamos a la documentación de StrPCopy:


StrPCopy copies Source into a null-terminated string Dest. It returns a pointer to Dest.

StrPCopy does not perform any length checking.

The destination buffer must have room for at least Length(Source)+1 characters.



¡Ajá! ¿Notas el +1? Ese +1 está porque necesitas espacio para el caracter nulo de terminación de la cadena.

Tu código no hace ninguna verificación de la longitud de las cadenas que copias, aunque me parece que con las primeras no hay problema porque los datos que pasas son cortos. Aún así tienes que agregar código para verficar.

Pero la última copia:


StrPCopy(RegPrnCmd.CRLF,chr(13)+chr(10));


es la que revienta todo. RegPrnCmd.CRLF está declarado como array [0..1] of char, esto es, dos caracteres, y tú necesitas espacio para 2 (#13#10) + 1 (#0) caracteres. Entonces, StrPCopy escribe el caracter de terminación #0 en la parte de memoria que hay después del espacio asignado a RegPrnCmd.

Según creo recordar, las variables locales se guardan en el stack o pila, así que, dependiendo del orden en que declares las variables, ese #0 sobreescribirá o no otra de las variables locales. Y una de esas variables, resulta ser la que contiene ¡el descriptor de archivo!

Si declaras la variable globalmente, no afecta al stack pues se localizan en regiones distintas de la memoria. Pero es sólo por suerte, suerte de que el caracter de más no esté sobrescribiendo algo que afecte el programa.

// Saludos

Lepe
24-02-2007, 20:10:33
¡¡¡ Qué lección de maestría !!!

Saludos.

nlsgarcia
24-02-2007, 23:01:37
:) Hola:

Gracias por tu análisis y por tus comentarios, el programa ya fue corregido.:D

Por favor analiza estos posibles errores de compilador y me comentas:

Problema con Rewrite:http://www.clubdelphi.com/foros/showthread.php?t=40597&highlight=Problema+con+Rewrite :confused:

La función FormatMaskText no funciona como se indica en la ayuda de Delphi, : http://qc.borland.com/wc/qcmain.aspx?d=25360 (http://qc.borland.com/wc/qcmain.aspx?d=25360)) :confused:

Lepe me recomendo un excelente libro digital: El lado oscuro de Delphi 4, pregunto: ¿hay algún libro digital o site que me recomiendes sobre buenas practicas de programación en Delphi adicional a este?

Nuevamente Gracias. :)

roman
25-02-2007, 00:19:44
Bueno, no hemos dicho que delphi o el compilador esté libre de errores. Pero el de FormatMaskText no es un problema del compilador, simplemente la función al parecer tiene fallos. Y el otro caso pues ¿qué decir? La verdad dudo mucho que el problema sea tal como lo describe; yo probé ese mismo código con y sin línea en blanco y con la misma versión de Delphi, y en ambos casos funciona bien. Tu caso era posible analizarlo porque estaba todo el código; en el otro caso habría que ver qué más hay que pueda estar afectando.

Pero, aunque seguramente delphi no está libre de fallos, lo importante aquí, en mi opinión, es como muchas situaciones que podríamos jurar que se deben a bugs, resultan tener explicación y se deben a fallos nuestros.

// Saludos

roman
25-02-2007, 00:27:37
Por cierto- no lo he probado y por eso pregunto -¿no sería más sencillo si en lugar de declarar:


TPrnCmd = Record
ProcPrnCmd : array [0..40] of char;
DatePrnCmd : array [0..12] of char;
TimePrnCmd : array [0..12] of char;
Status : array [0..30] of char;
CRLF : array [0..1] of char;
end;


lo hicieras así:


TPrnCmd = Record
ProcPrnCmd : String[41];
DatePrnCmd : String[13];
TimePrnCmd : String[13];
Status : String[31];
CRLF : String[2];
end;


Según recuerdo, con strings de tamaño fijo no tienes problemas con los records y te permite hacer asignaciones directas en lugar de usar funciones como StrPCopy más propias de C que de Pascal.

// Saludos

nlsgarcia
25-02-2007, 01:50:49
:) Hola:

Si modifico el programa como tu me sugieres, tendria algo como esto:


program TestFile;
{$APPTYPE CONSOLE}
uses
SysUtils, StrUtils;
Type
TPrnCmd = Record
ProcPrnCmd : String[41];
DatePrnCmd : String[13];
TimePrnCmd : String[13];
Status : String[31];
CRLF : String[2];
end;
var
i : integer;
RegPrnCmd : TPrnCmd;
function LogPrnCmd(LogFile, ProcPrnCmd, StatusProc : String) : Boolean;
var
i : integer;
f: File of TPrnCmd;
DateProc : String;
AuxStr : String;
CRLF : String;
begin
LogFile := GetCurrentDir + '\' + LogFile;
AssignFile(f, LogFile);
FileMode := fmOpenWrite ;
if FileExists(LogFile) then
Reset(f)
else
Rewrite(f);
DateProc := FormatDateTime('dd/mm/yyyy',Date);
CRLF := Chr(13) + Chr(10);
RegPrnCmd.ProcPrnCmd := ProcPrnCmd;
RegPrnCmd.DatePrnCmd := MidStr(DateProc,7,4) + '-' + MidStr(DateProc,4,2) + '-' + MidStr(DateProc,1,2);
RegPrnCmd.TimePrnCmd := FormatDateTime('hh:mm:ss',Time);
RegPrnCmd.Status := StatusProc;
RegPrnCmd.CRLF :=CRLF;
Seek(f, FileSize(f));
Write(f,RegPrnCmd);
CloseFile(f);
end;
begin
for i := 1 to 10 do
LogPrnCmd('TestFile.txt','Proc'+IntToStr(i),'Status'+IntToStr(i));
end.



Y la salida obtenida seria esta:

Proc1 2007-02-24 19:50:11 Status1 
Proc2 2007-02-24 19:50:11 Status2 
Proc3 2007-02-24 19:50:11 Status3 
Proc4 2007-02-24 19:50:11 Status4 
Proc5 2007-02-24 19:50:11 Status5 
Proc6 2007-02-24 19:50:11 Status6 
Proc7 2007-02-24 19:50:11 Status7 
Proc8 2007-02-24 19:50:11 Status8 
Proc9 2007-02-24 19:50:11 Status9 
Proc10 2007-02-24 19:50:11 Status10 

Es por el caracter , que no use el tipo String.

Gracias nuevamente por tus comentarios. :)