Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Varios (https://www.clubdelphi.com/foros/forumdisplay.php?f=11)
-   -   ¿GetFileVersionInfo modifica su primer parámetro? (FileName, la ruta de archivo dada) (https://www.clubdelphi.com/foros/showthread.php?t=81232)

Al González 24-10-2012 19:00:52

¿GetFileVersionInfo modifica su primer parámetro? (FileName, la ruta de archivo dada)
 
Hola, voy a trabajar con la función GetFileVersionInfo de la API de Windows, la cual sirve para obtener datos de un archivo ejecutable, como su versión, fabricante, etcétera.

En la unidad SysUtils de Delphi viene una rutina que hace uso de ella (también he visto varias clases de terceros que la implementan):

Código Delphi [-]
function GetFileVersion(const AFileName: string): Cardinal;
var
  FileName: string;
  InfoSize, Wnd: DWORD;
  VerBuf: Pointer;
  FI: PVSFixedFileInfo;
  VerSize: DWORD;
begin
  Result := Cardinal(-1);
  // GetFileVersionInfo modifies the filename parameter data while parsing.
  // Copy the string const into a local variable to create a writeable copy.
  FileName := AFileName;
  UniqueString(FileName);
  InfoSize := GetFileVersionInfoSize(PChar(FileName), Wnd);
  if InfoSize <> 0 then
  begin
    GetMem(VerBuf, InfoSize);
    try
      if GetFileVersionInfo(PChar(FileName), Wnd, InfoSize, VerBuf) then
        if VerQueryValue(VerBuf, '\', Pointer(FI), VerSize) then
          Result:= FI.dwFileVersionMS;
    finally
      FreeMem(VerBuf);
    end;
  end;
end;

Lo que me llama la atención es esa afirmación de "GetFileVersionInfo modifies the filename parameter". Entiendo y encuentro lógico que debido a ello Borland usara una cadena alterna "UniqueString(FileName)".

Pero al hacer pruebas no encontré que GetFileVersionInfo realmente modificara el valor del primer argumento. Así que mi duda es por qué se tiene esa precaución y, asumiendo que la afirmación del comentario es cierta, ¿en qué circunstancias ocurre tal modificación?

Esta vez no tuve suerte en Google, ¿alguna pista?

Gracias de antemano. :)

dec 24-10-2012 19:53:39

Hola,

A ver. Voy a tirar al aire con lo primero (y más simple, porque, de mi cerebro no pueden salir sino cosas simples) que se me ocurre. Digo que acaso el prototipo de dicha función se definió en su día con un argumento "constante". Tiempo después, por el motivo que fuese, se vió que era menester modificar dicho argumento, de manera que no valía que se dejase como constante. Si estoy en lo cierto, tal vez se tomó la solución "chapuza", esto es, utilizar una variable local para hacer las modificaciones correspondientes, manteniendo el prototipo de la función, ¡digo yo!, por razones de compatibilidad...

P.D. En efecto, esta explicación mía no puede tener sentido,... ¿verdad que no tiene sentido? Y por diversas razones además, casi estoy por borrar este mensaje... :) :D

roman 24-10-2012 20:09:01

Quizá algún "bug" en alguna versión antigua de Windows y el "parche se quedó hasta nuestros dias. En todo caso la ocumentación de la API no dice nada al respecto.

// Saludos

Casimiro Noteví 24-10-2012 20:13:32

Yo uso esa función desde hace muchos años en todos los proyectos y nunca he tenido problemas de ese tipo, (sólo la he usado en winXP con D7 y D2007)

roman 24-10-2012 20:24:29

Pero, ¿usas directamente la API de Windows o la función de la unidad SysUtils?

// Saludos

dec 24-10-2012 20:58:33

O sea, que, mi respuesta mi mirarla siquiera. :D :D :D

roman 24-10-2012 21:01:45

Cita:

Empezado por dec (Mensaje 447863)
O sea, que, mi respuesta mi mirarla siquiera. :D :D :D

Cita:

Empezado por dec (Mensaje 447857)
Hola,

A ver. Voy a tirar al aire con lo primero (y más simple, porque, de mi cerebro no pueden salir sino cosas simples) que se me ocurre. Digo que acaso el prototipo de dicha función se definió en su día con un argumento "constante". Tiempo después, por el motivo que fuese, se vió que era menester modificar dicho argumento, de manera que no valía que se dejase como constante. Si estoy en lo cierto, tal vez se tomó la solución "chapuza", esto es, utilizar una variable local para hacer las modificaciones correspondientes, manteniendo el prototipo de la función, ¡digo yo!, por razones de compatibilidad...

P.D. En efecto, esta explicación mía no puede tener sentido,... ¿verdad que no tiene sentido? Y por diversas razones además, casi estoy por borrar este mensaje... :) :D

Interesante punto de vista... :rolleyes:

:D

// Saludos

Casimiro Noteví 24-10-2012 21:06:21

Cita:

Empezado por roman (Mensaje 447861)
Pero, ¿usas directamente la API de Windows o la función de la unidad SysUtils?
// Saludos

Ahí me has pillado... voy a ver...
Es la que está implementada en 'windows'; y es este código horroroso:

Código Delphi [-]
function getmVersion(cExe:string='') : string;
var
  InfoSize, H, RsltLen: cardinal;
  VersionBlock: pointer;
  Rslt: PVSFixedFileInfo;
begin
  if (cExe='') or (not FileExists(cExe)) then
    cExe := Application.ExeName;
  //
  //InfoSize := GetFileVersionInfoSize(PChar(Application.ExeName), H);
  InfoSize := GetFileVersionInfoSize(pChar(cExe), H);
  VersionBlock := AllocMem(InfoSize);
  try
    //GetFileVersionInfo(pChar(Application.ExeName), H, InfoSize, VersionBlock);
    GetFileVersionInfo(pChar(cExe), H, InfoSize, VersionBlock);
    VerQueryValue(VersionBlock, '\', Pointer(Rslt), RsltLen);
    result := Format('%d.%d.%d.%d', [
    Rslt.dwProductVersionMS div 65536,
    Rslt.dwProductVersionMS mod 65536,
    Rslt.dwProductVersionLS div 65536,
    Rslt.dwProductVersionLS mod 65536]);
  finally
    FreeMem(VersionBlock);
  end;
end;

dec 24-10-2012 21:07:26



:D :D :D

P.D. Perdona Al,... algún moderador serio puede borrar los mensajes que considere oportuno. No presentaré queja.

roman 24-10-2012 21:09:38

Pues ahí está. Usas la API directamente. Si no te ha dado problemas nunca es de suponerse que el programador original en SysUtils estaba pensando en alguna otra cosa :)

// Saludos

roman 24-10-2012 21:10:38

Cita:

Empezado por dec (Mensaje 447866)
algún moderador serio puede borrar los mensajes que considere oportuno.

En estos momentos no contamos con ese tipo de moderadores :rolleyes: :D

// Saludos

roman 24-10-2012 21:16:41

Por cierto, no sé a quién iban dirigidas las fanfarrias, pero en un sano ejercicio de autoestima ya las he escuchado varias veces :D

// Saludos

dec 24-10-2012 22:04:40

Hola,

Las fanfarrías de la Diana iban, sin lugar a duda alguna, dirigidas a un humilde servidor de ustedes. :D :D :D

Al González 24-10-2012 22:11:55

Todo esto es muy extraño, incluyendo exquisita música del video. :D

Tu teoría es cuando menos interesante, David, puede que por ahí vayan los tiros.

A ver, paso siguiente: ¿podrían ayudarme a averiguar en qué versión de Delphi apareció la función GetFileVersion y desde cuándo tiene ese comentario de "GetFileVersionInfo modifies the filename..."?

Vamos, aquellos que tengan Delphis anteriores al 7, ¿podrían por favor abrir la unidad SysUtils.pas para verificar esto? Creo que esa unidad no existía en las primeras versiones, pero en esos casos y hasta para más seguridad está la alternativa de buscar GetFileVersion con Find in Files sobre los fuentes de la RTL / VCL (C:\Archivos de programa\Borland\DelphiX\Source, marcando la opción "Include subdirectories").

Muchas gracias. :)

dec 24-10-2012 22:35:43

Hola,

Mi teoría parecía buena, pero, según la escribía me sonaba raro, porque, en efecto, existen en Delphi diferentes mecanismos para mantener la compatilidad hacia atrás sin necesidad de hacer algo como lo dicho. De todas formas... quién sabe... igual he acertado a la primera, ¡que ya sería una novedad!

Por otro lado, supongo que no te servirá de mucho, pero, acabo de comprobar que tanto en Delphi 2007 como en Delphi XE2 aparece dicha función implementa y comentada tal como has señalado. Lamento no disponer de versiones de Delphi anteriores para mirar a ver. ;)

Casimiro Noteví 24-10-2012 22:59:41

Delphi 5, en comctrls.pas

Código Delphi [-]
function GetComCtlVersion: Integer;
var
  FileName: string;
  InfoSize, Wnd: DWORD;
  VerBuf: Pointer;
  FI: PVSFixedFileInfo;
  VerSize: DWORD;
begin
  if ComCtlVersion = 0 then
  begin
    // GetFileVersionInfo modifies the filename parameter data while parsing.
    // Copy the string const into a local variable to create a writeable copy.
    FileName := ComCtlDllName;
    InfoSize := GetFileVersionInfoSize(PChar(FileName), Wnd);
    if InfoSize <> 0 then
    begin
      GetMem(VerBuf, InfoSize);
      try
        if GetFileVersionInfo(PChar(FileName), Wnd, InfoSize, VerBuf) then
          if VerQueryValue(VerBuf, '\', Pointer(FI), VerSize) then
            ComCtlVersion := FI.dwFileVersionMS;
      finally
        FreeMem(VerBuf);
      end;
    end;
  end;
  Result := ComCtlVersion;
end;

Al González 25-10-2012 01:12:41

Gracias, Casi.

Y en XE2 tampoco varían las cosas, tiene el mismo comentario en el código.

Pienso que el origen de esto pudo ser un defecto de alguna de las versiones de Windows contemporáneas de la primera versión de Delphi que tuvo esa particularidad. ¿Alguien que pueda probar sobre Windows 95, 98, Me, 2000?

Hay algo que puede significar una pista: La modificación a la que alude el comentario podría tener que ver con la aparición de los "nombres largos" en las rutas de archivos. Ya saben, antes un nombre de archivo o directorio no podía tener más de 8 caracteres ni llevar espacios, ahora sí. No obstante, luego, en algunas ventanas de comandos, algunos de esos nombres largos aparecían con un nombre corto como "ARCHIVO~" porque Windows en realidad hacía una especie de chapuza guardando los nombres largos como nombres cortos (no sé si por el formato del disco, o algo así).

Creo que quizá la función modificaba la ruta dada para convertir el nombre largo a su nombre corto "real", y así extraer del archivo el recurso que guarda los datos de versión. O bien, y relacionado con esto, para convertir el nombre por completo a mayúsculas. El hecho es que la modificación no podría (no debería) ser para crear una cadena más larga, sino una cadena o más corta o de la misma longitud, pues esa memoria le pertenece a la aplicación que llama a la función.

De todas formas espero el mismo favor que ha hecho Casimiro (gracias por la molestia, Casi), de quienes tengan versiones de Delphi anteriores a la 5. :)

Delphius 25-10-2012 01:33:35

Hola,
Al ¿No tendrá que ver algo que acabo de leer en el MSDN?:

Cita:

lptstrFilename [in]
Type: LPCTSTR
The name of the file. If a full path is not specified, the function uses the search sequence specified by the LoadLibrary function.
Allí dice que si no se especifica una ruta completa se hará una búsqueda de secuencia. Posiblemente a eso se refiera... en una de esas esta función se invoca de forma recursiva para poder invocar reiteradamente a la búsqueda y poder encontrar así la información y para ahorrarse parámetros y variable se sobreescribe lo pasado en el parámetro.

Saludos,

roman 25-10-2012 02:04:30

Cita:

Empezado por roman (Mensaje 447858)
Quizá algún "bug" en alguna versión antigua de Windows y el "parche se quedó hasta nuestros dias. En todo caso la ocumentación de la API no dice nada al respecto.

Cita:

Empezado por Al González (Mensaje 447895)
Pienso que el origen de esto pudo ser un defecto de alguna de las versiones de Windows contemporáneas de la primera versión de Delphi que tuvo esa particularidad.

Bueno, al menos no soy el único...

:rolleyes:

// Saludos

Al González 25-10-2012 03:14:16

Cita:

Empezado por Delphius (Mensaje 447896)
Hola,
Al ¿No tendrá que ver algo que acabo de leer en el MSDN?

Allí dice que si no se especifica una ruta completa se hará una búsqueda de secuencia. Posiblemente a eso se refiera...

Podría estar relacionado, Marcelo, aunque no me suena que sea por ahí.

Cita:

Empezado por Delphius (Mensaje 447896)
en una de esas esta función se invoca de forma recursiva para poder invocar reiteradamente a la búsqueda y poder encontrar así la información y para ahorrarse parámetros y variable se sobreescribe lo pasado en el parámetro.

No sé, no sé, creo que sería un algoritmo un tanto extraño para el propósito de la función.

Román: Disculpa, había leído esa parte de tu cita, pero con las distracciones del día lo olvidé casi por completo. :)

roman 25-10-2012 03:26:26

Oye Al, y las clases de terceros que has visto, ¿hacen lo mismo o algo similar? Porque buscando código en internet he visto algunos ejemplos que parece que se limitaron a copiar y pegar el código de Delphi, así que no sé hasta que punto realmente refuerzan la idea de que hay algo de cierto o sólo copian lo que vieron.

// Saludos

Al González 25-10-2012 19:26:08

En efecto, Román, varias clases de terceros reproducen la misma leyenda, haciendo lo mismo con la cadena de la ruta de archivo. Dan toda la impresión de que simplemente se basaron en el código de la función de Delphi. Y claro, eso contribuye a "meter miedo". Cabe decir que no he encontrado algo que vaya en la misma dirección bajo código escrito en otros lenguajes.

Algo que certidumbre se obtiene al ver otros ejemplos en Delphi que se olvidan de la susodicha precaución, como esta clase de David Simpson con más de 1400 descargas desde la página de Embarcadero, creada en 1999 y actualizada hasta el año 2003:

http://cc.embarcadero.com/Item/13836

Esta otra del proyecto JEDI, actualizada hasta el año 2009:

http://upload.infosae.com.br/APLICAT...lFileUtils.pas

Y hasta de los propios compañeros del club:

http://www.clubdelphi.com/foros/show...232#post394232

En principio crearé mi clase sin esa desechada previsión (dejando sólo una breve nota alusiva al tema). Mientras tanto, veremos si hay oportunidad y tiempo de probar con versiones antiguas de Delphi y Windows, o de contactar a los creadores de las funciones GetFileVersionInfo (Microsoft) y GetComCtlVersion / GetFileVersion (Borland / Embarcadero).

Cualquier dato para sumar al tema es bienvenido.

Saludos.

roman 25-10-2012 19:36:18

Algo que no me queda claro es en qué afectaría que la rutina de la API escribiera sobre el argumento. No recuerdo cómo funciona el paso de parámetros de tipo string, o cómo afecta el modificador const. Porque si el parámetro pasa a la pila, como otros parámetros, no tendría porque haber problemas, aunque quizá fuera necesario no usar const, tal como, por cierto, hace el código de rastafarey.

// Saludos

Al González 25-10-2012 21:05:40

Cita:

Empezado por roman (Mensaje 447956)
Algo que no me queda claro es en qué afectaría que la rutina de la API escribiera sobre el argumento. No recuerdo cómo funciona el paso de parámetros de tipo string, o cómo afecta el modificador const. Porque si el parámetro pasa a la pila, como otros parámetros, no tendría porque haber problemas, aunque quizá fuera necesario no usar const [...]

Con valores que tienen contadores de referencias, usar Const es más eficiente, se reduce en varias instrucciones el código máquina de la función que declara el parámetro. De lo contrario se añade una referencia más a la cuenta. Prescindir del Const sólo tiene sentido si el parámetro ha de usarse también como variable de trabajo (modificable) dentro de la función que declara el parámetro. Esta es una buena convención que vino a inculcar Borland, y en lo personal (y saliéndome un poco del tema) he de decir que la extiendo más allá de los parámetros que usan contadores de referencias, es decir, hasta con parámetros simples como los de tipo Integer suelo usar Const si la rutina no ha de modificar su valor (ahí no hay impacto en el código máquina generado, pero lo hago porque puede ser informativo para el programador que lea el código).

Respecto a la primera parte de tu duda, hice este ejemplo para ayudar a esclarecerla:

Código Delphi [-]
Procedure Modificar (C :PChar);
Begin
  StrCopy (C, '¡X!');
End;

Procedure Proc1 (S :String);
Begin
  Modificar (PChar (S));
End;

Procedure Proc2 (Const S :String);
Begin
  Modificar (PChar (S));
End;

procedure TForm1.Button1Click(Sender: TObject);
Var
  Nombre :String;
begin
  Nombre := Self.Name;

  // Antes de ser modificado el nombre
  ShowMessage ('El nombre del formulario es: ' + Nombre);
  ShowMessage ('El nombre del formulario es: ' + Self.Name);

  Proc1 (Nombre);  // O bien "Proc2 (Nombre);"

  // Después de ser modificado el nombre
  ShowMessage ('El nombre del formulario es: ' + Nombre);
  ShowMessage ('El nombre del formulario es: ' + Self.Name);
  
  Close;
end;

En pantalla aparecerán los resultados:

Cita:

El nombre del formulario es: Form1

El nombre del formulario es: Form1

El nombre del formulario es: ¡X!

El nombre del formulario es: ¡X!
Todo reside en el hecho, como habrás de recordar, de que las variables y parámetros de tipo String no guardan el valor de la cadena, sino un puntero hacia dicho valor.

Para quienes inicien en el tema de los punteros-cadena, lo siguiente puede ayudar a entenderlo:

-- Memoria RAM --
...
Byte 3400: 'F'
Byte 3401: 'o'
Byte 3402: 'r'
Byte 3403: 'm'
Byte 3404: '1'
Byte 3405: #0 (fin de la cadena)
...

Al comienzo Self.Name es igual a 'Form1', porque la propiedad Name del formulario es un puntero de valor 3400. Al procedimiento Modificar le termina llegando ese valor (3400) como parámetro puntero (PChar) y, en esa ubicación de memoria, cambia la 'F' por '¡', la 'o' por 'X', la 'r' por '!' y la 'm' por #0 (nuevo fin de la cadena).

Espero haberme explicado. :)

Casimiro Noteví 25-10-2012 21:17:31

Estas cosas se entienden muy bien si conoces el lenguaje ese que no te gusta, el C :D

roman 25-10-2012 21:40:58

Cita:

Empezado por Al González (Mensaje 447960)
Espero haberme explicado. :)

Sí, claro. Gracias.

Por un momento pensé que si no se usaba const entonces el parámetro no pasaba como apuntador, pero ya veo que no es así.

Por lo que se ve, esto va a ser más misterioso que el CopyObject :rolleyes:

// Saludos

escafandra 25-10-2012 22:30:15

Hablando de C, la API GetFileVersionInfo se muestra así en MSDN:
Código:

BOOL WINAPI GetFileVersionInfo(LPCTSTR lptstrFilename, DWORD dwHandle, DWORD dwLen, LPVOID lpData);
LPCTSTR lptstrFilename es lo mismo que const char* lptstrFilename, es decir que lptstrFilename apunta a una cadena de caracteres, estilo C, constante o lo que es lo mismo, la API GetFileVersionInfo no va a modificar el valor apuntado por lptstrFilename.

De forma que tal y como se declara en este momento GetFileVersionInfo se puede usar sin necesidad de una variable de paso sin temor a que sea modificada.

En mi opinión cualquier comentario encontrado en el código que ponga en duda la integridad del valor de la cadena de caracteres pasada como parámetro no hace otra cosa que intimidar y confundir al que posteriormente lo lea y muy posiblemente se encuentre versión tras versión como temor ancestral que nadie se atreve a combatir. :D


Saludos.

ecfisa 25-10-2012 22:50:36

Hola.

Yo tenía la misma idea que lo que menciona escafandra. Pero para quitarle cualquier resquicio a la duda, estuve haciendo unas pruebas e incluso con estas alteraciones del código no se registra cambio alguno:
Código Delphi [-]
function GetFileVer(var aFileName: string): Cardinal;
var
  InfoSize, Wnd, VerSize: DWORD;
  VerBuf: Pointer;
  FI: PVSFixedFileInfo;
begin
  Result := Cardinal(-1);
  InfoSize := GetFileVersionInfoSize(PChar(aFileName), Wnd);
  if InfoSize <> 0 then
  begin
    GetMem(VerBuf, InfoSize);
    try
      if GetFileVersionInfo(PChar(aFileName), Wnd, InfoSize, VerBuf) then
        if VerQueryValue(VerBuf, '\', Pointer(FI), VerSize) then
          Result:= FI.dwFileVersionMS;
    finally
      FreeMem(VerBuf);
    end;
  end;
end;

Saludos.:)

Al González 26-10-2012 00:13:03

Gracias, escafandra, ecfisa. Creo que desafiaré a la leyenda... :)

Casi, me gusta programar en C, pero como no soy conformista, busco algo que además de potencia posea elegancia y un toque de "humano", de ahí mi predilección por Pascal. ^\||/

Román, ¿creerás que esta gente de Embarcadero ha estado haciéndole cambios a la función _CopyObject? Aunque, según se mira en System.pas, sólo para adaptarla junto con muchas otras a 64 bits. Algún día sabremos cuál es (¿era?) su misterioso propósito. ;)


La franja horaria es GMT +2. Ahora son las 00:39:15.

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