2010-12-13 13 views
18

Cuando cualquier descendiente de TGraphic registra su propio formato de archivo gráfico con un procedimiento de clase TPicture.RegisterFileFormat(), todos se almacenan en la variable global Graphics.FileFormats.¿Cómo obtener todos los formatos de archivo admitidos desde la unidad de Gráficos?

Lástima que la variable FileFormats no se encuentre en la sección "interfaz" de "Graphics.pas", por lo que no puedo acceder a ella. Necesito leer esta variable para implementar un filtro especial para mi control de lista de archivos.

¿Puedo obtener esa lista sin un manual que corrige el código fuente de Graphics.pas?

+2

También se relaciona [QC informe # 11837] (http: // qc.embarcadero.com/wc/qcmain.aspx?d=11837) vale la pena votar –

Respuesta

20

Se está trabajando con un control de lista de archivos, y, presumiblemente, por tanto, una lista de nombres de archivo. Si no necesita conocer los tipos de clase reales TGraphic que están registrados, solo si una extensión de archivo determinada está registrada o no (por ejemplo, para verificar si una llamada posterior al TPicture.LoadFromFile() es probable que tenga éxito), puede utilizar el público GraphicFileMask() función para obtener una lista de extensiones de archivo registradas y luego comparar sus nombres de archivo a esa lista.Por ejemplo:

uses 
    SysUtils, Classes, Graphics, Masks; 

function IsGraphicClassRegistered(const FileName: String): Boolean; 
var 
    Ext: String; 
    List: TStringList; 
    I: Integer; 
begin 
    Result := False; 
    Ext := ExtractFileExt(FileName); 
    List := TStringList.Create; 
    try 
    List.Delimiter := ';'; 
    List.StrictDelimiter := True; 
    List.DelimitedText := GraphicFileMask(TGraphic); 
    for I := 0 to List.Count-1 do 
    begin 
     if MatchesMask(FileName, List[I]) then 
     begin 
     Result := True; 
     Exit; 
     end; 
    end; 
    finally 
    List.Free; 
    end; 
end; 

O, simplemente puede cargar el archivo y ver lo que sucede:

uses 
    Graphics; 

function GetRegisteredGraphicClass(const FileName: String): TGraphicClass; 
var 
    Picture: TPicture; 
begin 
    Result := nil; 
    try 
    Picture := TPicture.Create; 
    try 
     Picture.LoadFromFile(FileName); 
     Result := TGraphicClass(Picture.Graphic.ClassType); 
    finally 
     Picture.Free; 
    end; 
    except 
    end; 
end; 

Actualización: si desea extraer las extensiones y las descripciones, se puede utilizar TStringList.DelimitedText para analizar el resultado de la función GraphicFilter():

uses 
    SysUtils, Classes, Graphics; 

function RPos(const ASub, AIn: String; AStart: Integer = -1): Integer; 
var 
    i: Integer; 
    LStartPos: Integer; 
    LTokenLen: Integer; 
begin 
    Result := 0; 
    LTokenLen := Length(ASub); 
    // Get starting position 
    if AStart < 0 then begin 
    AStart := Length(AIn); 
    end; 
    if AStart < (Length(AIn) - LTokenLen + 1) then begin 
    LStartPos := AStart; 
    end else begin 
    LStartPos := (Length(AIn) - LTokenLen + 1); 
    end; 
    // Search for the string 
    for i := LStartPos downto 1 do begin 
    if Copy(AIn, i, LTokenLen) = ASub then begin 
     Result := i; 
     Break; 
    end; 
    end; 
end; 

procedure GetRegisteredGraphicFormats(AFormats: TStrings); 
var 
    List: TStringList; 
    i, j: Integer; 
    desc, ext: string; 
begin 
    List := TStringList.Create; 
    try 
    List.Delimiter := '|'; 
    List.StrictDelimiter := True; 
    List.DelimitedText := GraphicFilter(TGraphic); 
    i := 0; 
    if List.Count > 2 then 
     Inc(i, 2); // skip the "All" filter ... 
    while i <= List.Count-1 do 
    begin 
     desc := List[i]; 
     ext := List[i+1]; 
     j := RPos('(', desc); 
     if j > 0 then 
     desc := TrimRight(Copy(desc, 1, j-1)); // remove extension mask from description 
     AFormats.Add(ext + '=' + desc); 
     Inc(i, 2); 
    end; 
    finally 
    List.Free; 
    end; 
end; 

actualización 2: si están interesados ​​sólo en una lista de extensiones de archivos gráficos registrados, entonces, asumiendo List es una ya creada TStrings descendiente, utilice esto:

ExtractStrings([';'], ['*', '.'], PChar(GraphicFileMask(TGraphic)), List); 
+0

Probablemente deba decir aquí, así como su comentario a @Cosmin, que 'GraphicFilter' se puede analizar para obtener descripciones y máscaras. –

+1

@DavidHeffernan: hecho. –

+2

+1 porque no es "hacky" –

11

El proyecto GlScene tiene una unidad PictureRegisteredFormats.pas que implementa un truco para eso.

+0

+1, esta unidad funciona bastante bien. – RRUZ

+0

¡Genial! Muchas gracias, Uwe. ¿Cómo crees que será correcto si publico la solución de GIScene aquí para la comunidad? Es de código abierto de todos modos – Andrew

+0

La razón por la que no lo publiqué aquí por mi cuenta fue que no quería pensar exactamente en esa pregunta ... –

9

Aquí hay un truco alternativo que podría ser más seguro luego la solución GLScene. Sigue siendo un truco, porque la estructura deseada es global pero en la sección de implementación de la unidad Graphics.pas, pero mi método usa muchas menos "constantes maigc" (desplazamientos codificados en el código) y utiliza dos métodos distintos para detectar la función GetFileFormats en Graphics.pas.

Mi código explota el hecho de que tanto TPicture.RegisterFileFormat como TPicture.RegisterFileFormatRes necesitan llamar inmediatamente a la función Graphics.GetFileFormats. El código detecta el código de operación offset relativo CALL y registra la dirección de destino ambos. Solo avanza si ambos resultados son iguales y esto agrega un factor de seguridad. El otro factor de seguridad es el método de detección en sí mismo: incluso si el prólogo generado por el compilador cambiara, siempre que la primera función llamada sea GetFileFormats, este código lo encontrará.

No voy a poner el "Warning: This will crash when Graphics.pas is compiled with the 'Use Debug DCUs' option." en la parte superior de la unidad (como se encuentra en el código GLScene), porque lo he probado con debug dcu y sin errores de depuración y funcionó. También probado con paquetes y todavía funcionó.

Este código solo funciona para objetivos de 32 bits, de ahí el amplio uso de Integer para operaciones de puntero. Intentaré hacer que esto funcione para objetivos de 64 bits tan pronto como tenga mi compilador Delphi XE2 instalado.

Actualización: Una versión de prueba de 64 bits se puede encontrar aquí: https://stackoverflow.com/a/35817804/505088

unit FindReigsteredPictureFileFormats; 

interface 

uses Classes, Contnrs; 

// Extracts the file extension + the description; Returns True if the hack was successful, 
// False if unsuccesful. 
function GetListOfRegisteredPictureFileFormats(const List: TStrings): Boolean; 

// This returns the list of TGraphicClass registered; True for successful hack, false 
// for unsuccesful hach 
function GetListOfRegisteredPictureTypes(const List:TClassList): Boolean; 

implementation 

uses Graphics; 

type 
    TRelativeCallOpcode = packed record 
    OpCode: Byte; 
    Offset: Integer; 
    end; 
    PRelativeCallOpcode = ^TRelativeCallOpcode; 

    TLongAbsoluteJumpOpcode = packed record 
    OpCode: array[0..1] of Byte; 
    Destination: PInteger; 
    end; 
    PLongAbsoluteJumpOpcode = ^TLongAbsoluteJumpOpcode; 

    TMaxByteArray = array[0..System.MaxInt-1] of Byte; 
    PMaxByteArray = ^TMaxByteArray; 

    TReturnTList = function: TList; 

    // Structure copied from Graphics unit. 
    PFileFormat = ^TFileFormat; 
    TFileFormat = record 
    GraphicClass: TGraphicClass; 
    Extension: string; 
    Description: string; 
    DescResID: Integer; 
    end; 

function FindFirstRelativeCallOpcode(const StartOffset:Integer): Integer; 
var Ram: PMaxByteArray; 
    i: Integer; 
    PLongJump: PLongAbsoluteJumpOpcode; 
begin 
    Ram := nil; 

    PLongJump := PLongAbsoluteJumpOpcode(@Ram[StartOffset]); 
    if (PLongJump^.OpCode[0] = $FF) and (PLongJump^.OpCode[1] = $25) then 
    Result := FindFirstRelativeCallOpcode(PLongJump^.Destination^) 
    else 
    begin 
     for i:=0 to 64 do 
     if PRelativeCallOpcode(@Ram[StartOffset+i])^.OpCode = $E8 then 
      Exit(StartOffset + i + PRelativeCallOpcode(@Ram[StartOffset+i])^.Offset + 5); 
     Result := 0; 
    end; 
end; 

procedure FindGetFileFormatsFunc(out ProcAddr: TReturnTList); 
var Offset_from_RegisterFileFormat: Integer; 
    Offset_from_RegisterFileFormatRes: Integer; 
begin 
    Offset_from_RegisterFileFormat := FindFirstRelativeCallOpcode(Integer(@TPicture.RegisterFileFormat)); 
    Offset_from_RegisterFileFormatRes := FindFirstRelativeCallOpcode(Integer(@TPicture.RegisterFileFormatRes)); 

    if (Offset_from_RegisterFileFormat = Offset_from_RegisterFileFormatRes) then 
    ProcAddr := TReturnTList(Pointer(Offset_from_RegisterFileFormat)) 
    else 
    ProcAddr := nil; 
end; 

function GetListOfRegisteredPictureFileFormats(const List: TStrings): Boolean; 
var GetListProc:TReturnTList; 
    L: TList; 
    i: Integer; 
begin 
    FindGetFileFormatsFunc(GetListProc); 
    if Assigned(GetListProc) then 
    begin 
     Result := True; 
     L := GetListProc; 
     for i:=0 to L.Count-1 do 
     List.Add(PFileFormat(L[i])^.Extension + '=' + PFileFormat(L[i])^.Description); 
    end 
    else 
    Result := False; 
end; 

function GetListOfRegisteredPictureTypes(const List:TClassList): Boolean; 
var GetListProc:TReturnTList; 
    L: TList; 
    i: Integer; 
begin 
    FindGetFileFormatsFunc(GetListProc); 
    if Assigned(GetListProc) then 
    begin 
     Result := True; 
     L := GetListProc; 
     for i:=0 to L.Count-1 do 
     List.Add(PFileFormat(L[i])^.GraphicClass); 
    end 
    else 
    Result := False; 
end; 

end. 
+0

Tengo una versión que funciona para 64 bits. ¿Te gustaría que lo pegue para ti? –

+7

La función 'GetListOfRegisteredPictureForFormats()' se puede implementar de manera diferente utilizando 'TStringList.DelimitedText' para analizar el resultado del público [' Graphics.GraphicFilter() '] (http://docwiki.embarcadero.com/Libraries/XE2/ función en/Vcl.Graphics.GraphicFilter). Esta es la misma función que 'TOpenPictureDialog' utiliza para crear su' Filtro'. No es necesario un hack de bajo nivel. Solo se necesitaría un hack de bajo nivel cuando se accede al campo 'TFileFormat.GraphicClass', las descripciones y extensiones registradas son públicamente accesibles, pero no estrictamente avanzadas. –

+1

Bueno, ambas soluciones nuevas son aceptables. Yo voté por ambos) Desmarqué la respuesta de Uwe hasta que termine el tiempo de espera de la recompensa. – Andrew

Cuestiones relacionadas