Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Windows (https://www.clubdelphi.com/foros/forumdisplay.php?f=26)
-   -   Tratamiento de errores entorno al API de Windows (https://www.clubdelphi.com/foros/showthread.php?t=44364)

dec 04-06-2007 23:17:40

Tratamiento de errores entorno al API de Windows
 
Hola,

¿Qué tal va eso? Espero que bien. :)

Veréis. Tengo un problema. Resulta que cuando uno trabaja con componentes y ejecuta sus métodos y tal, bueno, el tratamiento de errores suele consistir en estar al tanto de las excepciones que puedan producirse.

Ahora bien, cuando uno trata con el API de Windows directamente, me parece que no pueden esperarse excepciones, sino "errores del sistema", que no pueden capturarse, por tanto, como si fueran excepciones.

Pondré un ejemplo. Si uno ejecuta la función "ShFileOperation" para borrar un archivo, por ejemplo, obtendrá un error del sistema si el archivo a borrar no existe. Pero no vale de nada (parece ser) hacer algo como:

Código Delphi [-]
try
  ShFileOperation();
except
  // Capturamos la excepción
end;

Porque, sencillamente, no es posible capturar ninguna excepción... pero el sistema sí advierte del error, y no sólo lo hace retornando un valor distinto de cero con la función susomentada, pero muestra un mensaje de error... que no queda muy curioso, porque además es de los típicos crípticos que muestra Windows...

Hombre. Se entiende que algo ha ido mal. Incluso llega a comprenderse por el mensaje de error (después de darle la vuelta a las palabras) que lo que ocurre es que no puede borrarse un archivo que no existe, pero, ¿hay alguna forma de evitar este mensaje error?

Obviamente no de evitarlo "como si no hubiera pasado nada", sino evitarlo en el sentido de hacérselo llegar al usuario por otros medios, en alguna variable, por ejemplo, no mediante un mensaje que uno no controla...

Así que me pregunto cómo pueden tratarse este tipo de errores, para los cuales no existen excepciones. Conozco funciones como "GetLastError", pero, no me queda muy claro su uso. De hecho siguiendo con el ejemplo, trato de "capturar" el error mediante esta función, pero, no lo consigo...

A ver si me podéis echar una mano monstruos, y bueno, a todo aquél que como yo está perdido en este asunto.

Gracias de antemano pataliebres. Buenos días, buenas tardes o buenas noches, dependiendo de donde estén vuecencias. :D :D

egostar 04-06-2007 23:36:18

Hola David

No se si esto te pudiera servir.

Salud OS.

dec 04-06-2007 23:49:32

Hola,

Te agradezco el enlace egostar. Curiosamente, el autor del código visto estaría en las mismas que yo, puesto que escribe algo muy similar a lo que yo mismo escribo:

Código:

  iSHerr = SHFileOperation (&shfos);
  if (iSHerr == 0)
      AfxMessageBox ("Worked fine");
  else
  {
      wsprintf (szDebug, "SHFO gave error %d", GetLastError());
      AfxMessageBox (szDebug);
  }

Bien. El caso es que, efectivamente, la función "SHFileOperation" no retornará cero en caso de error, así que el código:

Código:

      wsprintf (szDebug, "SHFO gave error %d", GetLastError());
      AfxMessageBox (szDebug);

... se ejecutaría en caso de que la función "SHFileOperation" fallase. Pero el tema está en que también aparecería un mensaje de error del sistema... sin que uno, aparentemente, pueda hacer nada por evitarlo.

Y añadiré algo más aún... haciendo algo más o menos así:

Código Delphi [-]
begin

  if SHFileOperation() = 0 then
    TNbUtilities.FijarVariable(rsVarResultadoAccion,rsValorTrue)
  else
  begin
    TNbUtilities.FijarVariable(rsVarResultadoAccion,rsValorFalse);
    TNbUtilities.FijarVariable(rsVarUltimoError,SysErrorMessage(GetLastError()));
  end;

Si no existe la carpeta a borrar (suponiendo que esto es lo que vamos a hacer), efectivamente, se ejecuta el "else" del código de más arriba, pero, ¡el mensaje que obtengo de "SysErrorMessage" es "La operación se completó correctamente"...

O sea... definitivamente algo se me escapa en todo esto. Pero gracias egostar, verás como al final sacamos algo en claro entre todos. :)

seoane 04-06-2007 23:58:38

Cita:

Empezado por dec
... se ejecutaría en caso de que la función "SHFileOperation" fallase. Pero el tema está en que también aparecería un mensaje de error del sistema... sin que uno, aparentemente, pueda hacer nada por evitarlo.

Te fijaste que usa el flag FOF_NOERRORUI :confused:

dec 05-06-2007 00:01:07

Hola,

Cita:

Empezado por Seoane
Te fijaste que usa el flag FOF_NOERRORUI

Eh... pues no, la verdad. Y tiene muy buena pinta. Gracias Seoane. La probaré enseguida. Pero, sin embargo, me queda la duda de que cuando la función falla (porque no existe la carpeta a borrar) no recibo el mensaje adecuado con "GetLastError", puesto que este es "La operación se completó correctamente", cuando no es así...

Voy a probar esa "bandera". Gracias otra vez Seoane. :)

roman 05-06-2007 00:02:07

Yo lo veo difícil. Imagina que tú haces una dll con una función HazEsto que hace esto:

Código Delphi [-]
function HazEsto(): Integer;
begin
  ShowMessage('Esto es un error, a ver cómo lo ocultas, je, je, je');
  Result := 1;
end;

A lo que voy es: como no provea la misma api de esa función, un mecanismo para omitir los mensajes de error, no veo por donde pueda evitarse. Bueno, yo supongo que seoane puede inyectar un código a shellapi, pero habrá que esperar a que lea esto.

// Saludos

dec 05-06-2007 00:03:09

Hola,

Gracias Seoane. Funciona estupendamente la bandera "FOF_NOERRORUI". :)

Ya no muestra el mensaje de error del sistema. Peeeeeeeeeero... sigo obteniendo como "último error" un "Operación completada correctamente"... aunque ahora mismo ya no sé muy bien qué pensar de esto, puesto que estoy un poco eufórico tras haber solucionado en buena medida el asunto con la banderita de marras. :)

roman 05-06-2007 00:03:26

Ja, ja, juro que no había las dos respuestas anteriores cuando escribí la mía. Pero ya veo que sí se provee el mecanismo que mencioné.

// Saludos

dec 05-06-2007 00:04:21

Hola,

Sí; los tiros van por donde apuntas Román. Se ve que la función de marras del API de Windows muestra el mensaje de error... a no ser que se indique (véase más arriba) específicamente que no muestre mensajes de error. :)

roman 05-06-2007 00:18:08

Al parecer GetLastError siempre regresará cero. No obstante ShFileOperation devuelve un valor distinto de cero en caso de error. De ahí puedes partir.

// Saludos

dec 05-06-2007 00:33:07

Hola,

De ahí parto Román... o es la intención: como "ShFileOperation" retorna "no cero"... busco el error con "GetLastError", pero, no parece estar ahí.

seoane 05-06-2007 00:33:52

Pues a mi lo que me preocupa es que hace esta instruccion:
Código Delphi [-]
TNbUtilities.FijarVariable(rsVarResultadoAccion,rsValorFalse);
Fíjate que la instrucción GetLastError no solo muestra el ultimo error, sino el ultimo resultado de una operación de la api. Así que si dentro de esa rutina se llama, aunque solo sea indirectamente, a una API, GetLastError nos estaría devolviendo ese resultado y no el error que nos interesa.

dec 05-06-2007 00:49:38

Hola,

Sí; comprendo lo que dices Seoane. Había pensado en ello... y bueno, tal vez sea por eso que "SysErrorMessage(GetLastError())" retorna "La operación se completó correctamente".

Voy a probar el asunto de modo que nada más se ejecute luego de "ShFileOperation" y cuento el resultado. :)

dec 05-06-2007 00:53:52

Hola,

Nope... algo como esto:

Código Delphi [-]
  if (ShFileOperation(FileOp) = 0) then
    TNbUtilities.FijarVariable(rsVarResultadoAccion,rsValorTrue)
  else
  begin
    TNbUtilities.FijarVariable(rsVarResultadoAccion,rsValorFalse);
    TNbUtilities.FijarVariable(rsVarUltimoError,SysErrorMessage(GetLastError()));
  end;

Sigue retornando en "SysErrorMessage(GetLastError())" "La operación se ha completado correctamente", incluso cuando no es así, es decir, "ShFileOperation" retornó algo distinto de cero, puesto que "la carpeta a borrar" no existe...

PD. Estoy ahora investigando sobre la función "Win32Check", porque lo que dice la ayuda parece muy interesante...

seoane 05-06-2007 00:57:50

Fijate lo que cuenta microsoft :D
Cita:

Empezado por Microsoft
Returns zero if successful; otherwise nonzero. Applications normally should simply check for zero or nonzero.

It is good practice to examine the value of the fAnyOperationsAborted member of the SHFILEOPSTRUCT. SHFileOperation can return 0 for success if the user cancels the operation. If you do not check fAnyOperationsAborted as well as the return value, you cannot know that the function accomplished the full task you asked of it and you might proceed under incorrect assumptions.

Do not use GetLastError with the return values of this function.

To examine the nonzero values for troubleshooting purposes, they largely map to those defined in Winerror.h. However, several of its possible return values are based on pre-Win32 error codes, which in some cases overlap the later Winerror.h values without matching their meaning. Those particular values are detailed here, and for these specific values only these meanings should be accepted over the Winerror.h codes. However, these values are provided with these warnings:

Parece ser que el valor que devuelve la función SHFileOperation lo tienes que comparar con los que se muestran en la tabla para obtener una descripción del mismo.

PD: Si es que no leemos la ayuda :p :D

El enlace: http://msdn2.microsoft.com/en-us/library/ms647743.aspx

dec 05-06-2007 01:02:22

Hola,

Je, je, je... Sí. No sé si he dicho antes que la función "ShFileOperation" es mucha función... que necesito leer la ayuda, vamos. :)

Sin embargo... parece que en este caso "Win32Check" puede ayudarnos. Resulta que esta función "comprueba" que la instrucción que encierra se ejecuta correctamente, y, cuando no es así, se levanta una excepción.

En el caso que nos ocupa es una excepción del tipo "EOSError", con el mensaje "manejador inválido" (no existe la carpeta, será). Bueno. Estupendo... creo que hoy he aprendido gracias a todos algo que no sabía.

Código Delphi [-]
  try
    if Win32Check((ShFileOperation(FileOp) = 0)) then
      TNbUtilities.FijarVariable(rsVarResultadoAccion,rsValorTrue)
  except
    on E: Exception do begin
      TNbUtilities.FijarVariable(rsVarResultadoAccion,rsValorFalse);
      TNbUtilities.FijarVariable(rsVarUltimoError,Format(rsErrorExcepciones,[E.ClassName,E.Message]));
    end;
  end;

Gracias a todos pataliebres. :D :D :D

egostar 05-06-2007 01:03:11

Cita:

Empezado por dec
Hola,

De ahí parto Román... o es la intención: como "ShFileOperation" retorna "no cero"... busco el error con "GetLastError", pero, no parece estar ahí.

Según Microsoft no debes de usar "GetLastError" para esta función

Cita:

Empezado por msdn2.microsoft.com
Return Value


Returns zero if successful; otherwise nonzero. Applications normally should simply check for zero or nonzero.

It is good practice to examine the value of the fAnyOperationsAborted member of the SHFILEOPSTRUCT. SHFileOperation can return 0 for success if the user cancels the operation. If you do not check fAnyOperationsAborted as well as the return value, you cannot know that the function accomplished the full task you asked of it and you might proceed under incorrect assumptions.


Do not use GetLastError with the return values of this function.

Aquí el artículo completo.

Salud OS.

dec 05-06-2007 01:12:04

Hola,

Gracias egostar. Me quedo con todo, pero, particularmente con esto:

Cita:

It is good practice to examine the value of the fAnyOperationsAborted member of the SHFILEOPSTRUCT. SHFileOperation can return 0 for success if the user cancels the operation.
Puesto que, efectivamente, he comprobado que si se cancela la tarea la función sigue retornando cero, o sea "True". :)

Gracias otra vez a todos.

dec 05-06-2007 01:14:02

Hola,

O sea:

Código Delphi [-]
function MoverCarpeta(const carpetaOrigen,
  carpetaDestion: string) : boolean;
var
  FileOp: TSHFileOpStruct;
begin
  FillChar(FileOp, SizeOf(FileOp),#0);
  with FileOp do begin
    wFunc := FO_MOVE;
    Wnd := GetActiveWindow();
    pTo := PChar(carpetaOrigen);
    pFrom := PChar(carpetaDestion+#0#0);
    fFlags := FOF_NOCONFIRMATION or FOF_SILENT
     or FOF_ALLOWUNDO or FOF_NOERRORUI;
  end;
  if Win32Check((ShFileOperation(FileOp) = 0)) then
    result := not FileOp.fAnyOperationsAborted
  else
    result := false;
end;

Má o meno.... :D

seoane 05-06-2007 01:17:30

No entiendo porque usar Win32Check. Esa función solo crea una excepción cuando el valor que le pasas no es TRUE, siempre el mismo tipo de excepción. Si es eso lo que quieres, perfecto, pero no creo que te haga falta.

dec 05-06-2007 01:24:31

Hola,

Bueno. El que se levante una excepción puede ayudar. Ahora, la idea es que dicha excepción (en mi caso) aporte "algo". Es decir, no es lo mismo (en mi caso al menos, y según lo veo ahora...) que una función retorne "false" y nada más... a que retorne "false" y exista una variable global, digamos, "ultimoError", donde pueda consultarse el motivo del fallo de la función.

Esa es la idea. Ahora bien, se supone que no siempre tiene que retornar el mismo mensaje. Y ahí puede que esté la madre del cordero. Efectivamente, la excepción puede que sea siempre, como dices, del tipo "EOSError", empero, el mensaje de error que la acompañe, ¿será siempre el mismo?

En el caso de que el archivo o carpeta a borrar no exista, el mensaje de la excepción es "System Error. Code: 6. Controlador no válido". Supongo que en otro errores se darán otro tipo de mensajes, pero, ahora mismo no se me ocurre cómo puedo probar el asunto. :)

dec 05-06-2007 01:39:56

Hola,

Es curioso, ¿no?

Código Delphi [-]
  // SysUtils.pas


function Win32Check(RetVal: BOOL): BOOL;
begin
  if not RetVal then RaiseLastOSError;
  Result := RetVal;
end;

procedure RaiseLastOSError;
begin
  RaiseLastOSError(GetLastError);
end;

procedure RaiseLastOSError(LastError: Integer);
var
  Error: EOSError;
begin
  if LastError <> 0 then
    Error := EOSError.CreateResFmt(@SOSError, [LastError,
      SysErrorMessage(LastError)])
  else
    Error := EOSError.CreateRes(@SUnkOSError);
  Error.ErrorCode := LastError;
  raise Error;
end;

O sea. Si la función del API retorna "false" (esto usando "Win32Check"), levantamos una excepción con el último error del sistema. Cuando hacemos esto miramos, precisamente, el valor de "GetLastError", y creamos una excepción "EOSError" con el mensaje que consigamos de "SysErrorMessage"...

Y si "GetLastError" retorna un bonito cero... creamos la excepción con el mensaje "Error desconocido"...

¿Sólo a mí me parece curioso, luego de haber seguido este Hilo desde un principio? :)

Ahora, hasta ahí llego... me parece curioso, me llama la atención, pero, tampoco sé decir exactamente porqué... :)

dec 05-06-2007 01:55:45

Hola,

Qué grandes sois... estoy probando por aquí el asunto y se ve estupendo. Os agradezco de veras vuestras aportaciones. :)

seoane 05-06-2007 02:25:15

Bueno, yo crearia una funcion como esta:
Código Delphi [-]
const
  DE_SAMEFILE  = $71;
  DE_MANYSRC1DEST  = $72;
  DE_DIFFDIR  = $73;
  DE_ROOTDIR  = $74;
  DE_OPCANCELLED  = $75;
  DE_DESTSUBTREE  = $76;
  DE_ACCESSDENIEDSRC  = $78;
  DE_PATHTOODEEP  = $79;
  DE_MANYDEST  = $7A;
  DE_INVALIDFILES  = $7C;
  DE_DESTSAMETREE  = $7D;
  DE_FLDDESTISFILE  = $7E;
  DE_FILEDESTISFLD  = $80;
  DE_FILENAMETOOLONG  = $81;
  DE_DEST_IS_CDROM  = $82;
  DE_DEST_IS_DVD  = $83;
  DE_DEST_IS_CDRECORD  = $84;
  DE_FILE_TOO_LARGE  = $85;
  DE_SRC_IS_CDROM  = $86;
  DE_SRC_IS_DVD  = $87;
  DE_SRC_IS_CDRECORD  = $88;
  DE_ERROR_MAX  = $B7;
  DE_UNKHOWN = $402;
  ERRORONDEST  = $10000;

function SHCheck(Value: Integer): Boolean;
var
  Error: EOSError;
begin
  if Value <> 0 then
  begin
    Result:= FALSE;
    case Value of
      DE_SAMEFILE: Error:= EOSError.Create('The source and destination files are the same file.');
      DE_MANYSRC1DEST: Error:= EOSError.Create('Multiple file paths were specified in the source buffer, but only one destination file path.');
      DE_DIFFDIR: Error:= EOSError.Create('Rename operation was specified but the destination path is a different directory. Use the move operation instead.');
      DE_ROOTDIR: Error:= EOSError.Create('The source is a root directory, which cannot be moved or renamed.');
      DE_OPCANCELLED: Error:= EOSError.Create('The operation was cancelled by the user, or silently cancelled if the appropriate flags were supplied to SHFileOperation.');
      DE_DESTSUBTREE: Error:= EOSError.Create('The destination is a subtree of the source.');
      DE_ACCESSDENIEDSRC: Error:= EOSError.Create('Security settings denied access to the source.');
      DE_PATHTOODEEP: Error:= EOSError.Create('The source or destination path exceeded or would exceed MAX_PATH.');
      DE_MANYDEST: Error:= EOSError.Create('The operation involved multiple destination paths, which can fail in the case of a move operation.');
      DE_INVALIDFILES: Error:= EOSError.Create('The path in the source or destination or both was invalid.');
      DE_DESTSAMETREE: Error:= EOSError.Create('The source and destination have the same parent folder.');
      DE_FLDDESTISFILE: Error:= EOSError.Create('The destination path is an existing file.');
      DE_FILEDESTISFLD: Error:= EOSError.Create('The destination path is an existing folder.');
      DE_FILENAMETOOLONG: Error:= EOSError.Create('The name of the file exceeds MAX_PATH.');
      DE_DEST_IS_CDROM: Error:= EOSError.Create('The destination is a read-only CD-ROM, possibly unformatted.');
      DE_DEST_IS_DVD: Error:= EOSError.Create('The destination is a read-only DVD, possibly unformatted.');
      DE_DEST_IS_CDRECORD: Error:= EOSError.Create('The destination is a writable CD-ROM, possibly unformatted.');
      DE_FILE_TOO_LARGE: Error:= EOSError.Create('The file involved in the operation is too large for the destination media or file system.');
      DE_SRC_IS_CDROM: Error:= EOSError.Create('The source is a read-only CD-ROM, possibly unformatted.');
      DE_SRC_IS_DVD: Error:= EOSError.Create('The source is a read-only DVD, possibly unformatted.');
      DE_SRC_IS_CDRECORD: Error:= EOSError.Create('The source is a writable CD-ROM, possibly unformatted.');
      DE_ERROR_MAX: Error:= EOSError.Create('MAX_PATH was exceeded during the operation.');
      DE_UNKHOWN : Error:= EOSError.Create('An unknown error occurred. This is typically due to an invalid path in the source or destination. This error does not occur on Microsoft Windows Vista and later.');
      ERRORONDEST: Error:= EOSError.Create('An unspecified error occurred on the destination.');
      DE_ROOTDIR or ERRORONDEST: Error:= EOSError.Create('Destination is a root directory and cannot be renamed.');
    else
      Error:= EOSError.Create('Error desconocido');
    end;
    Error.ErrorCode:= Value;
    raise Error;
  end else
    Result:= TRUE;
end;

Y modificaría tu función de la siguiente manera:
Código Delphi [-]
function MoverCarpeta(const carpetaOrigen,
  carpetaDestion: string) : boolean;
var
  FileOp: TSHFileOpStruct;
begin
  FillChar(FileOp, SizeOf(FileOp),#0);
  with FileOp do begin
    wFunc := FO_MOVE;
    Wnd := GetActiveWindow();
    pTo := PChar(carpetaOrigen);
    pFrom := PChar(carpetaDestion+#0#0);
    fFlags := FOF_NOCONFIRMATION or FOF_SILENT
     or FOF_ALLOWUNDO or FOF_NOERRORUI;
  end;
  Result:= FALSE;
  if SHCheck(ShFileOperation(FileOp)) then
    Result := not FileOp.fAnyOperationsAborted;
end;

Ahora para dejarlo bonito deberías traducir los mensajes de error :p

dec 05-06-2007 02:48:11

Hola,

Joroba Seoane... no sé yo si llegaré a tanto (aunque tengo el trabajo hecho por lo visto), pero se agradece de veras. Sin embargoooooooooooo.... :) Puestos a ser puñeteros, pero puñeteros... ¿están contemplados todos los mensajes? Sí, sé que son muchos, pero, ¿están contemplados todos? ¿Los del futuro también? :D No sé si me explico. :D :D

En todo caso un monstruo, Seoane, ya te digo. :)

dec 05-06-2007 03:22:54

Hola,

Vale... vale... soy un puñetero. :D :D :D

egostar 05-06-2007 03:30:38

Cita:

Empezado por dec
Hola,

Joroba Seoane... no sé yo si llegaré a tanto (aunque tengo el trabajo hecho por lo visto), pero se agradece de veras. Sin embargoooooooooooo.... :) Puestos a ser puñeteros, pero puñeteros... ¿están contemplados todos los mensajes? Sí, sé que son muchos, pero, ¿están contemplados todos? ¿Los del futuro también? :D No sé si me explico. :D :D

En todo caso un monstruo, Seoane, ya te digo. :)

Cita:

Empezado por seoane

Código Delphi [-]
    
else
      Error:= EOSError.Create('Error desconocido');

Tu duda ya la contemplo Seoane, si si yo también soy puñetero:D:D:D

Salud OS.

dec 05-06-2007 03:43:58

Hola,

Ah... ahora que no nos oye Seoane (lo que le he respondido ha sido la gota que ha colmado el vaso y se marchó a dormir, e hizo bien, seguramente...), digo, ahora que no nos oye diré que muy probablemente haré uso de su código, entre otras cosas, porque, efectivamente, siempre estará el error "desconocido"...

Pero me estoy liando yo también... creo que me voy a dormir también... :D :D

egostar 05-06-2007 04:31:32

Cita:

Empezado por dec
Hola,

Ah... ahora que no nos oye Seoane (lo que le he respondido ha sido la gota que ha colmado el vaso y se marchó a dormir, e hizo bien, seguramente...), digo, ahora que no nos oye diré que muy probablemente haré uso de su código, entre otras cosas, porque, efectivamente, siempre estará el error "desconocido"...

Pero me estoy liando yo también... creo que me voy a dormir también... :D :D

A mira, o sea que estas jalando hilo para sacar madeja.

Espero que se levante temprano para que siga con el código.:D:D

Salud OS.


La franja horaria es GMT +2. Ahora son las 17:33:29.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi