2008-11-18 6 views
16

Normalmente, en Delphi se declararía una función con un número variable de argumentos utilizando el método 'array of const'. Sin embargo, para la compatibilidad con el código escrito en C, hay una directiva 'varargs' mucho desconocida que se puede agregar a una declaración de función (lo aprendí al leer el excelente documento 'Pitfalls of convering' de Rudy).¿Cómo puede una función con 'varargs' recuperar el contenido de la pila?

A modo de ejemplo, se podría tener una función en C, declarada así:

void printf(const char *fmt, ...) 

En Delphi, esto se convertiría en:

procedure printf(const fmt: PChar); varargs; 

Mi pregunta es: ¿Cómo puedo llegar a el contenido de la pila al implementar un método que se define con la directiva 'varargs'?

Espero que existan algunas herramientas para esto, como las traducciones de Dephi de las funciones va_start(), va_arg() y va_end(), pero no puedo encontrarlo en ninguna parte.

Por favor ayuda!

PD: Por favor, no se distraiga en las discusiones sobre la alternativa 'por qué' o 'array of const' - Necesito esto para escribir parches tipo C para las funciones dentro de los juegos de Xbox (vea el proyecto Delphi Xbox emulator ' Dxbx 'en sourceforge para más detalles).

Respuesta

18

OK, veo la aclaración en su pregunta que significa que debe implementar una importación de C en Delphi. En ese caso, debe implementar varargs usted mismo.

El conocimiento básico necesario es la convención de llamadas de C en el x86: la pila crece hacia abajo, y C empuja los argumentos de derecha a izquierda. Por lo tanto, un puntero al último argumento declarado, después de que se incrementa por el tamaño del último argumento declarado, apuntará a la lista de argumentos de la cola. A partir de entonces, es simplemente una cuestión de leer el argumento e incrementar el puntero en un tamaño apropiado para avanzar más en la pila. La pila x86 en el modo de 32 bits generalmente está alineada con 4 bytes, y esto también significa que los bytes y las palabras se pasan como enteros de 32 bits.

De todos modos, aquí hay un registro de ayuda en un programa de demostración que muestra cómo leer datos. Tenga en cuenta que Delphi parece estar pasando tipos Extendidos de una manera muy extraña; sin embargo, es probable que no tenga que preocuparse por eso, ya que los flotadores de 10 bytes generalmente no se usan ampliamente en C, y ni siquiera se implementan en el último MS C, IIRC.

{$apptype console} 

type 
    TArgPtr = record 
    private 
    FArgPtr: PByte; 
    class function Align(Ptr: Pointer; Align: Integer): Pointer; static; 
    public 
    constructor Create(LastArg: Pointer; Size: Integer); 
    // Read bytes, signed words etc. using Int32 
    // Make an unsigned version if necessary. 
    function ReadInt32: Integer; 
    // Exact floating-point semantics depend on C compiler. 
    // Delphi compiler passes Extended as 10-byte float; most C 
    // compilers pass all floating-point values as 8-byte floats. 
    function ReadDouble: Double; 
    function ReadExtended: Extended; 
    function ReadPChar: PChar; 
    procedure ReadArg(var Arg; Size: Integer); 
    end; 

constructor TArgPtr.Create(LastArg: Pointer; Size: Integer); 
begin 
    FArgPtr := LastArg; 
    // 32-bit x86 stack is generally 4-byte aligned 
    FArgPtr := Align(FArgPtr + Size, 4); 
end; 

class function TArgPtr.Align(Ptr: Pointer; Align: Integer): Pointer; 
begin 
    Integer(Result) := (Integer(Ptr) + Align - 1) and not (Align - 1); 
end; 

function TArgPtr.ReadInt32: Integer; 
begin 
    ReadArg(Result, SizeOf(Integer)); 
end; 

function TArgPtr.ReadDouble: Double; 
begin 
    ReadArg(Result, SizeOf(Double)); 
end; 

function TArgPtr.ReadExtended: Extended; 
begin 
    ReadArg(Result, SizeOf(Extended)); 
end; 

function TArgPtr.ReadPChar: PChar; 
begin 
    ReadArg(Result, SizeOf(PChar)); 
end; 

procedure TArgPtr.ReadArg(var Arg; Size: Integer); 
begin 
    Move(FArgPtr^, Arg, Size); 
    FArgPtr := Align(FArgPtr + Size, 4); 
end; 

procedure Dump(const types: string); cdecl; 
var 
    ap: TArgPtr; 
    cp: PChar; 
begin 
    cp := PChar(types); 
    ap := TArgPtr.Create(@types, SizeOf(string)); 
    while True do 
    begin 
    case cp^ of 
     #0: 
     begin 
     Writeln; 
     Exit; 
     end; 

     'i': Write(ap.ReadInt32, ' '); 
     'd': Write(ap.ReadDouble, ' '); 
     'e': Write(ap.ReadExtended, ' '); 
     's': Write(ap.ReadPChar, ' '); 
    else 
     Writeln('Unknown format'); 
     Exit; 
    end; 
    Inc(cp); 
    end; 
end; 

type 
    PDump = procedure(const types: string) cdecl varargs; 
var 
    MyDump: PDump; 

function AsDouble(e: Extended): Double; 
begin 
    Result := e; 
end; 

function AsSingle(e: Extended): Single; 
begin 
    Result := e; 
end; 

procedure Go; 
begin 
    MyDump := @Dump; 

    MyDump('iii', 10, 20, 30); 
    MyDump('sss', 'foo', 'bar', 'baz'); 

    // Looks like Delphi passes Extended in byte-aligned 
    // stack offset, very strange; thus this doesn't work. 
    MyDump('e', 2.0); 
    // These two are more reliable. 
    MyDump('d', AsDouble(2)); 
    // Singles passed as 8-byte floats. 
    MyDump('d', AsSingle(2)); 
end; 

begin 
    Go; 
end. 
+1

¡Esto se ve genial! Me sorprendió ver que de hecho no hay necesidad de utilizar ensamblaje para acceder al contenido del registro ESP. Gracias por esto, ¡excelente ejemplo también! – PatrickvL

+1

Tenga en cuenta que el código necesita adaptación si va a funcionar en x64: la función Align en particular trunca los punteros a valores de 32 bits. –

2

He encontrado this (de una guy sabemos :))

Para escribir estas cosas correctamente, tendrá que utilizar BASM, construida en ensamblador, y el código de la secuencia de llamada de Delphi en asm. Espero que tengas una buena idea de de lo que tienes que hacer. Tal vez una publicación en el grupo .basm te ayude si te quedas atascado.

1

Delphi no permite implementar una rutina varargs. Solo funciona para importar funciones cdecl externas que usan esto.

Dado que varargs se basa en la convención de llamadas cdecl, básicamente necesita volver a implementarlo en Delphi, utilizando el ensamblaje y/o varios tipos de manipulación del puntero.

+0

No, la lista de argumentos solo termina en cero si el llamante pasa cero como último argumento. La página que citas dice eso. La función printf no necesita tener un cero para terminar la lista porque puede averiguar cuántos argumentos hay basados ​​en la cadena de formato. –

+0

En ese momento, mi error. Voy a editar en consecuencia. –

Cuestiones relacionadas