2012-04-02 8 views
-1

Ich habe ein Problem mit dem SetLength Befehl.Alternative zu SetLength

Es ist im Grunde dieses Problem:

ich mit WindowsAPI arbeiten nur so gut, wie ich kann. Mein Ziel ist es, eine bestimmte Größe zu einem dynamischen Array von Zeichen zu setzen:

Hier einige Code zu verstehen:

var 
thefile : array of char; 
// or thefile : array [0..9999] of char; // <---- not really a good way, works tho 

FileHandle := CreateFileA(paramstr0, GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0); 
dwSize := GetFileSize(FileHandle,NIL); 
SetFilePointer(FileHandle, 0, nil, FILE_BEGIN); 
SetLength(TheFile, dwsize); // <--- I can't use this 
ReadFile(FileHandle, thefile[1], dwSize , dwRead, nil); 
CloseHandle (FileHandle); 
CloseHandle(CreateFileA (sfile, 0, 0,NIL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL,0)); 
FileHandle := CreateFileA(sFile, GENERIC_WRITE, FILE_SHARE_WRITE, nil, CREATE_ALWAYS, 0, 0); 
WriteFile (FileHandle, thefile, dwSize, testCardinal, NIL); 
CloseHandle (FileHandle); 

Wie kann ich ersetzen SetLength? Ich möchte auch verstehen, wie Windows/Delphi das Array im Speicher reserviert usw. Vielen Dank für Ihre Hilfe

Antwort

8

Sie haben mehrere Fehler im Code

  1. Wenn Sie die CreateFile Funktion verwendet man das überprüfen müssen gab Handle gegen den INVALID_HANDLE_VALUE zurück.

  2. Immer müssen Sie versuchen .. endlich um die Ressourcen zu befreien.

  3. Sie versuchen die ReadFile Funktion Lesen der Inhalte innerhalb des thefile Puffer verwenden, um einen Index 1 verwenden, das ist falsch Sie einen Index 0, da die dynamischen Arrays Null sind Index basiert verwenden müssen.

  4. Sie verwenden diesen Code

Closehandle (CreateFileA (sfile, 0, 0, NIL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0));

FileHandle := CreateFileA(sFile, GENERIC_WRITE, FILE_SHARE_WRITE, nil, CREATE_NEW, 0, 0); 

Versuchen Sie, diese verbesserte Version des Codes

Um eine leere Datei zu erstellen und dann erneut, um die Funktion Create mit der Datei zu öffnen und zu schreiben, können Sie die gleichen

in einem Schritt zu tun.

Btw, es ist nichts falsch mit der Verwendung SetLengh, wird das Problem in dem Punkt erklärt 3.

var 
    thefile : array of AnsiChar; 
begin 

    FileHandle := CreateFileA(paramstr0, GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0); 
    if FileHandle <> INVALID_HANDLE_VALUE then 
    try 
    dwSize := GetFileSize(FileHandle, nil); 
    SetFilePointer(FileHandle, 0, nil, FILE_BEGIN); 
    SetLength(TheFile, dwsize); 
    ReadFile(FileHandle, thefile[0], dwSize , dwRead, nil); 
    finally 
    CloseHandle (FileHandle); 
    end; 

    FileHandle := CreateFileA(sFile, GENERIC_WRITE, FILE_SHARE_WRITE, nil, CREATE_NEW, 0, 0); 
    if FileHandle <> INVALID_HANDLE_VALUE then 
    try 
    WriteFile (FileHandle, thefile[0], dwSize, testCardinal, nil); 
    finally 
    CloseHandle (FileHandle); 
    end; 

    SetLength(TheFile, 0);  
end; 
+0

Vielen Dank für Ihre Hilfe. Versuchen Sie es und schließlich ist ein guter Tipp, um Fehler zu vermeiden. –

+1

try..finally macht Sinn, wenn Ausnahmen ausgelöst werden, aber hier, ich vermute, dass nur ein wenig Speicher eine Ausnahme auslösen kann, da Windows-APIs keine Ausnahmen auslösen, sondern Fehlercodes zurückgeben. Zum Beispiel kann die ReadFile() - Funktion bei einem Fehler FALSE zurückgeben. In jedem Fall ist die Verwendung von APIs in diesem Fall nicht sinnvoll. Und das Hinzufügen von 'SetLength (TheFile, 0)' am Ende wird überhaupt nicht benötigt - dies wird bereits vom Compiler erledigt. –

+1

@ArnaudBouchez, Sie können nie 100% sicher sein, wo eine Ausnahme ausgelöst wird oder ob eine "inoffensive" Codezeile eine Ausnahme auslösen kann, in diesem Fall stellt der try ... schließlich sicher, dass die offene Datei-Handle geschlossen wird. über den error handle vielleicht habe ich die Erwähnung über die 'RaiseLastOSError' Funktion vergessen, endlich über den' SetLength (TheFile, 0) 'erinnere mich, dass dieser Code unvollständig ist und wir den Kontext nicht kennen, also vielleicht die' TheFile' Variable an einem anderen Ort wiederverwendet werden. Jedenfalls gibt es immer einen Raum, um eine Antwort zu verbessern, aber ich denke, dass dieser hier vollständig genug ist. – RRUZ

1

Sie können GetMem/FreeMem mit einem generischen Puffer stattdessen ein dynamisches Array zu verwenden. Es ist viel ähnlicher, wie Sie es in C machen würden.

Wie auch immer, einen Puffer so groß wie die Datei zu setzen, ist normalerweise nicht die beste Lösung. Normalerweise verwenden Sie einen Puffer fester Größe und lesen/schreiben eine Datei in Blöcken. Sie sind möglicherweise nicht in Lage, ein ganzes Array zuzuweisen, selbst wenn genügend freier Speicher vorhanden ist. Der Array-Speicher muss zusammenhängend sein, daher benötigen Sie einen freien Speicherblock, der genauso groß oder größer ist wie das Array, das Sie zuordnen müssen. Speicher kann im Laufe der Zeit fragmentiert werden, so dass ein Block, der groß genug ist, nicht verfügbar sein könnte.

Und selbst wenn Sie in der Lage sind, es zuzuordnen, können Sie das Betriebssystem zwingen, etwas Arbeitsspeicher aus dem RAM zu verschieben, um es zuzuordnen - andere Anwendungen verlangsamen. Die Größe des Puffers sollte dabei unter Berücksichtigung des verfügbaren RAM, der Leistung und der Plattengeschwindigkeit festgelegt werden.

Wie Delphi Speicher zuweist, hängt davon ab, welchen Speichermanager Sie verwenden.Die meisten werden größere Blöcke mit Windows-Speicherzuordnungsfunktionen anfordern und sie nach Bedarf unterzuordnen (um die Speicherverwaltung zu beschleunigen, war Windows nicht dafür ausgelegt, effizient kleine Speicherblöcke wie die meisten OO-Anwendungen zuzuweisen). Blöcke können nur dann an das Betriebssystem zurückgegeben werden, wenn sie nicht mehr verwendete Daten enthalten.

Warum ein Array von Char? In Delphi ist ein Char kein 8-Bit-Typ. Zum Beispiel ist es 8 Bit bis Delphi 2007, aber ein 16-Bit-Typ ab 2009, weil es im Gegensatz zu C ein Unicode-Zeichen wurde. Verwenden Sie den Byte-Typ (oder UInt8 in neueren Versionen), es bleibt ein 8-Bit-Typ, egal welche Version von Delphi und Zielplattform Sie verwenden.

Wenn Sie eine Datei kopieren müssen, wird TStream.CopyFrom() in einem einzigen Aufruf das tun, was Sie benötigen (nachdem Sie beide Streams erstellt haben).

+0

danke für Ihre Hilfe. Ich verstehe jetzt mehr über den Speichermanager. –

3

SetLength() ist nicht das Problem hier.

Wenn Sie wirklich, wie Windows-APIs, verwenden Sie einfach die CopyFile() Funktion, die für Sie die ganze Arbeit tun:

CopyFile(pointer(paramstr0),pointer(sFile)); 

einen vorübergehenden festen Puffer von mehreren MB verwenden werden schneller sein als alle Dateien auf einmal zu lesen.

Und Ihr Code wird nicht in der Lage, Dateien> 2 GB zu behandeln. Sie müssen Int64 Größe und Position in Ihrem Code behandeln.

IMHO sollten Sie besser nicht Windows-APIs hier verwenden, um Dateien zu handhaben, aber TFileStream, die plattformübergreifend ist.

SetLength() ist vielleicht einer der wenigen guten Anrufe hier - aber Sie sollten theFile[0] oder pointer(theFile)^ statt theFile[1] codiert haben.