2008-11-18 5 views
16

Normalerweise würde man in Delphi mit der Methode 'array of const' eine Funktion mit einer variablen Anzahl von Argumenten deklarieren. Aus Gründen der Kompatibilität mit in C geschriebenem Code gibt es jedoch eine viel unbekannte 'varargs'-Direktive, die zu einer Funktionsdeklaration hinzugefügt werden kann (dies habe ich beim Lesen von Rudys hervorragendem' Pitfalls of convering'-Dokument erfahren).Wie kann eine Funktion mit 'varargs' den Inhalt des Stapels abrufen?

Als Beispiel eine Funktion in C haben könnte, wie dies erklärt:

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

In Delphi, dies würde:

procedure printf(const fmt: PChar); varargs; 

Meine Frage ist: Wie kann ich auf der Inhalt des Stacks bei der Implementierung einer Methode, die mit der 'varargs' Direktive definiert ist?

Ich würde erwarten, dass einige Werkzeuge dafür existieren, wie Depi Übersetzungen der Funktionen va_start(), va_arg() und va_end(), aber ich kann das nirgendwo finden.

Bitte helfen!

PS: Bitte nicht in Diskussionen über die 'Warum' oder die 'Array of Const' Alternative driften - ich brauche das, um C-ähnliche Patches für Funktionen in Xbox-Spielen zu schreiben (siehe das Delphi-Xbox-Emulator-Projekt ' Dxbx 'auf Sourceforge für Details).

Antwort

18

OK, ich sehe die Klarstellung in Ihrer Frage, dass Sie einen C-Import in Delphi implementieren müssen. In diesem Fall müssen Sie Varargs selbst implementieren.

Das grundlegende Wissen, das benötigt wird, ist die C-Aufrufkonvention auf dem x86: der Stapel wächst nach unten, und C schiebt Argumente von rechts nach links. Daher zeigt ein Zeiger auf das letzte deklarierte Argument, nachdem es um die Größe des letzten deklarierten Arguments inkrementiert wurde, auf die Tail-Argumentliste. Von nun an ist es einfach, das Argument zu lesen und den Zeiger um eine geeignete Größe zu erhöhen, um sich tiefer in den Stapel zu bewegen. Der x86-Stack im 32-Bit-Modus ist im Allgemeinen 4-Byte-ausgerichtet und dies bedeutet auch, dass Bytes und Wörter als 32-Bit-Ganzzahlen übergeben werden.

Wie auch immer, hier ist ein Helfer Datensatz in einem Demo-Programm, das zeigt, wie man Daten auslesen kann. Beachten Sie, dass Delphi Extended-Typen auf sehr seltsame Weise zu übergeben scheint; Sie werden sich jedoch wahrscheinlich nicht darum kümmern müssen, da 10-Byte-Gleitkommazahlen in C im Allgemeinen nicht weit verbreitet sind und in der neuesten MS C, IIRC nicht einmal implementiert sind.

{$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

Das sieht gut aus! Ich war überrascht zu sehen, dass es in der Tat keine Notwendigkeit gibt, Assembly zu verwenden, um zu dem Inhalt des ESP-Registers zu gelangen. Danke dafür - tolles Beispiel auch! – PatrickvL

+1

Beachten Sie, dass der Code angepasst werden muss, wenn er mit x64 arbeiten soll - die Align-Funktion schneidet insbesondere Zeiger auf 32-Bit-Werte ab. –

2

Ich fand this (von guy wir wissen :))

dieses Zeug zu schreiben, richtig benötigen Sie BASM, Delphi gebaut in Assembler und Code, um die Aufrufsequenz in asm verwenden. Hoffentlich haben Sie eine gute Idee von dem, was Sie tun müssen. Vielleicht hilft ein Post in der .BASM-Gruppe, wenn Sie stecken bleiben.

1

Delphi lässt Sie keine Varargs-Routine implementieren. Es funktioniert nur für den Import externer Cdecl-Funktionen, die dies verwenden.

Da varargs auf der cdecl-Aufrufkonvention basiert, müssen Sie es im Grunde in Delphi neu implementieren, indem Sie Assembly und/oder verschiedene Arten der Zeigermanipulation verwenden.

+0

Nein, die Liste der Argumente ist nur nullterminiert, wenn der Aufrufer Null als letztes Argument übergibt. Die von Ihnen zitierte Seite sagt das. Die printf-Funktion muss keine Null haben, um die Liste zu beenden, da sie herausfinden kann, wie viele Argumente auf der Formatzeichenfolge basieren. –

+0

Richtig, mein Fehler. Ich werde entsprechend bearbeiten. –

Verwandte Themen