Entwickler-Ecke

Windows API - CopyFile / ForceDirectories - Pfadlänge


s!lenCe - Di 03.11.09 10:01
Titel: CopyFile / ForceDirectories - Pfadlänge
Hallo allerseits,

folgendes Problem:

ich will eine Datei (bzw. mehrere) kopieren, dessen Zielpfad (mit Dateinamen) die Begrenzung von 255 Zeichen
überschreitet. Ich habe CopyFile (CopyFileW, CopyFileA) versucht, jedoch kopiert er mir die Datei damit nicht.
Gibt es eine Alternative zu CopyFile um soetwas realisieren zu können? Oder ist CopyFile mit irgendwelchen Tricks
vielleicht doch dazu in der Lage? Mal jetzt davon abgesehen, dass der Explorer selbst nicht mit einem Pfad klar kommt,
der über 255 Zeichen lang ist.



MfG silenCe


Delete - Di 03.11.09 10:38

Versuch es mal mit UNC Pfadangaben. Ich glaube mal gehört zu haben, dass man damit die Längenbegrenzung der Pfadangabe (260 Zeichen) umgehen kann. Wahrscheinlich musst du dann aber auch CopyFileW benutzen.


s!lenCe - Di 03.11.09 11:14

user profile iconLuckie hat folgendes geschrieben Zum zitierten Posting springen:
Versuch es mal mit UNC Pfadangaben. Ich glaube mal gehört zu haben, dass man damit die Längenbegrenzung der Pfadangabe (260 Zeichen) umgehen kann. Wahrscheinlich musst du dann aber auch CopyFileW benutzen.


Das habe ich auch schon irgendwo gelesen und habe es auch schon ausprobiert, bin aber nicht ganz sicher, ob das so richtig war.


Delphi-Quelltext
1:
CopyFileW(PWideChar(Quelle), PWideChar('\\?\' + Ziel),false)                    


Wie gesagt das mit dem '\\?\' habe ich irgendwo gelesen, da hat es sich aber auf CreateFile bezogen. Ich habe auch versucht das beiden voranzustellen,
also Quelle und Ziel, obwohl nur das Ziel die Länge überschreitet. Hat aber alles nicht funktioniert.


Mit fällt gerade auf ich brauch das selbe auch für ForceDirectories, da ich mit dieser Funktion erst die Verzeichnisse anlege und dann die Datei kopiere.
Und ForceDirectories legt mir auch nicht alle Verzeichnisse an nur bis zu dem Punkt an dem die 260 Zeichen voll sind.



MfG silenCe


Gausi - Di 03.11.09 11:24

wikipedia hat folgendes geschrieben:
Das Format für lokale Pfade unter Windows, also Daten, die auf dem verwendeten Rechner selbst vorhanden sind, ist:

Quelltext
1:
\\.\C:\Daten\null                    
Hast du das mal so ausprobiert?

Edit: Bei deinem Konstrukt ist auch wichtig: Ziel und Quelle müssen WideStrings sein. Der Typecast von Delphi-2007-String nach PWideChar ergibt Unsinn.
Unter D2009 kannst du mit der CopyFile und PChar-Version arbeiten. Da sind das ja die Unicode-Varianten.


s!lenCe - Di 03.11.09 11:37

user profile iconGausi hat folgendes geschrieben Zum zitierten Posting springen:
wikipedia hat folgendes geschrieben:
Das Format für lokale Pfade unter Windows, also Daten, die auf dem verwendeten Rechner selbst vorhanden sind, ist:

Quelltext
1:
\\.\C:\Daten\null                    
Hast du das mal so ausprobiert?

Edit: Bei deinem Konstrukt ist auch wichtig: Ziel und Quelle müssen WideStrings sein. Der Typecast von Delphi-2007-String nach PWideChar ergibt Unsinn.
Unter D2009 kannst du mit der CopyFile und PChar-Version arbeiten. Da sind das ja die Unicode-Varianten.


Nutze Delphi7 und wenn ich keinen Typecase mache bzw. PChar nehme meckert der Compiler. Also ich habe jetzt auf jeden Fall rausgefunden, dass er die Datei
sowieso gar nicht kopieren kann, da das ForceDirectories, das vor dem Kopieren ausgeführt wird, um die Verzeichnisstruktur für die Datei anzulegen, auch fehlschlägt
mit der Fehlermeldung der angegebene Pfad wäre zu lang - ist er ja auch eigentlich :).

So habe während ich den Beitrag geschrieben habe nun das mit dem ForceDirectories hinbekommen.


Delphi-Quelltext
1:
ForceDirectories('\\?\' + Pfad)                    


So legt er mir einen Pfad an, der auch länger als 260 Zeichen ist. Jetzt muss ich nur noch das CopyFile ans Laufen kriegen.



MfG silenCe


Gausi - Di 03.11.09 11:39

Wie gesagt, der direkte Typecast von String nach PWideChar ergibt Unsinn. Wenn du normale Strings unter D7 benutzt, dann muss das so aussehen:


Delphi-Quelltext
1:
PWideChar(WideString(myString))                    


s!lenCe - Di 03.11.09 11:55

user profile iconGausi hat folgendes geschrieben Zum zitierten Posting springen:
Wie gesagt, der direkte Typecast von String nach PWideChar ergibt Unsinn. Wenn du normale Strings unter D7 benutzt, dann muss das so aussehen:


Delphi-Quelltext
1:
PWideChar(WideString(myString))                    


Es funktioniert! Endlich ~.~
Puhh ich danke dir ^^

Also der Code sieht nun wie folgt aus.
Einmal die Verzeichnisstruktur mit ForceDirectories anlegen und dann die Datei kopieren. Beide kommen nun mit Pfadangaben, die länger als 260 Zeichen sind, klar.


Delphi-Quelltext
1:
2:
3:
ForceDirectories('\\?\' + ExtractFilePath(Pfad))

CopyFileW(PWideChar(WideString(Quelle)),PWideChar(WideString('\\?\' + Ziel)),false)



EDIT:
Verdammt habe mich wohl zu früh gefreut. Habe nochmal einen längeren Pfad angelegt (genau 265 Zeichen) und daran scheitert ForceDirectories doch mit der Fehlermeldung
der Pfad sei zu lang. -.-


MfG silenCe


Delete - Di 03.11.09 13:23

Wahrscheinlich weil ForceDirectories keine Widestrings benutzt. Da wirst du dir wohl deine eigene Unicode Variante implementieren müssen.


s!lenCe - Di 03.11.09 13:48

Ich habe gerade noch diese Funktion gefunden:

SHCreateDirectoryEx [http://msdn.microsoft.com/en-us/library/bb762131%28VS.85%29.aspx]

Genau wie ForceDirectories legt sie komplette Verzeichnisstrukturen an. Nur ich bekomm sie nicht ans Laufen, da ich nicht weiß welche Unit ich einbinden soll.
Normal doch nur die Windows?



MfG silenCe


Delete - Di 03.11.09 14:22

Musst du wahrscheinlich selbst deklarieren oder probier mal Shlobj.


s!lenCe - Di 03.11.09 16:46

So habe mir jetzt einfach meine eigene Variante geschrieben.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
FUNCTION ForceDirectoriesW(Dir : WideString) : Boolean;
VAR
  tmp : WideString;
  Position : Integer;
BEGIN
  Result := True;
  // Dateinamen entfernen und letzten Backslash anfügen
  Dir := ExtractFilePath(Dir);
  Dir := IncludeTrailingPathDelimiter(Dir);
  // Laufwerksbuchstaben ausschneiden
  tmp := Copy(Dir, 13);
  Delete(Dir, 13);

  // Bis zum letzten Verzeichnis durchgehen
  WHILE Pos('\', Dir) <> 0 DO
    BEGIN
      // Position des nächsten Backslash ermitteln
      Position := Pos('\', Dir);
      // Nächstes Verzeichnis an den Pfad hängen
      tmp := tmp + Copy(Dir, 1, Position);
      Delete(Dir, 1, Position);
      // Verzeichnis erstellen
      IF NOT DirectoryExists(tmp) THEN
        IF NOT CreateDirectoryW(PWideChar(WideString('\\?\' + tmp)), nilTHEN
          BEGIN
            Result := False;
            Exit;
          END;
    END;
END;




MfG silenCe


beastofchaos - So 22.05.11 11:25

Ich hab leider auch ein kleines Problem mit CopyFile() - Und zwar soll man in meinem Programm ein Lied (mp3 oder wav) kopieren können. Habe einmal einen Button "Browse", einen anderen "Add".


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
path := ExtractFilePath(Application.ExeName);
.
.
.

procedure TSoundForm.BrowseClick(Sender: TObject);
var
  Endung: String;
begin
  if dlgOpen.Execute then
    begin
    NewSong := dlgOpen.FileName;
    Endung := Copy(NewSong, length(NewSong) - 23);        
    if (Endung <> 'mp3'and (Endung <> 'wav'then
      begin
      ShowMessage('Das Lied muss eine mp3- oder wav-Datei sein!');  // Fehlermeldung
      BrowseClick(Button4);                                         // Man kann erneut ein Lied aussuchen
      Exit;
      end
    else
      begin
      Edit1.Text := NewSong;                                        // NewSong enthält Pfad (+Name und Endung)
      Selected := True;                                             // Button Add weiß nun, dass ein Lied ausgewählt ist
      end;
    end;
end;

procedure TSoundForm.AddClick(Sender: TObject);
var
  NewSongName: String;
  i, i2: Integer;
  Finished: Boolean;
begin
  Finished := False;
  if Selected then
    begin
    Dec(i, i);
    repeat                                                               // mit repeat...until filter ich den Pfad aus "NewSong", so dass ich am Ende nur noch den Namen (+ Endung) habe in "NewSongName"
      begin
      NewSongName := Copy(NewSong, length(NewSong) - i, 1 + i);
      if NewSongName[1] = '\' then
        begin
        NewSongName := copy(NewSongName, 2, length(NewSongName) - 1);
        Finished := True;
        end;
      Inc(i)
      end;
    until Finished;
    CopyFile(PChar(NewSong),PChar(path + 'Sounds\Background\' + NewSong), False)
    end
  else
    ShowMessage('Bitte wähle erst eine Datei aus!');
end;





Es kommt KEINE Fehlermeldung, aber andererseits lässt sich unter Sound\Background kein neues lied finden... Geht das möglicherweise nicht mit mp3, etc. - ich hab das bisher nur mit *txt gesehen.

Gruß, Thomas


jaenicke - So 22.05.11 12:16

user profile iconbeastofchaos hat folgendes geschrieben Zum zitierten Posting springen:
Es kommt KEINE Fehlermeldung
Woher auch, die hast die Fehlerbehandlung ja auch komplett ignoriert... :roll: :autsch:

Den Rückgabewert wirfst du genauso weg wie du danach bei einem Fehlschlag nicht mit SysErrorMessage + GetLastError schaust warum es nicht ging... :roll:

Siehe Doku...
http://msdn.microsoft.com/en-us/library/aa363851(v=vs.85).aspx


beastofchaos - So 22.05.11 12:51

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconbeastofchaos hat folgendes geschrieben Zum zitierten Posting springen:
Es kommt KEINE Fehlermeldung
Woher auch, die hast die Fehlerbehandlung ja auch komplett ignoriert... :roll: :autsch:

Den Rückgabewert wirfst du genauso weg wie du danach bei einem Fehlschlag nicht mit SysErrorMessage + GetLastError schaust warum es nicht ging... :roll:

Siehe Doku...
http://msdn.microsoft.com/en-us/library/aa363851(v=vs.85).aspx


Mit Fehlermeldung mein ich cnith die von Delphi, sondern meine Eigene (siehe Quelltext Zeile 16 :P)

Nachdem ich mir die Namen der SoundNames ausgegeben hab lassen, ist mir aufgefallen, dass ich am Ende die Datei abspeicher mit dem falschen Namen (In Zeile 49 statt "+ NewSong" -> "+ NewSongName").

Das Problem kontne ich also lösen. Ich hab jetzt leider noch ein Problem ,dass nichts mit der CopyFile()-Prozedur zu tun hat, aber in diesem Programmteil vorkommt. Und zwar speicher ich mit der Prozedur "FindFiles()" aus unser Liberary in der TStringList "SoundNames". Jedesmal, wenn ich nun ein Liad hinzufpge mit "Add" speicher ich alles in einer temporören TStringList namens SoundNamesBefore, um abzugleichen, ob ich das Lied shcon einmal hochgeladen habe - Damit will ich nur die neuen Lieder in meine TStringList laden. Quelltext sieht so aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
.
.
.
  else
    begin
    SoundNamesBefore := SoundNames;
    SoundNames.Clear;
    There := False;
    FindFiles(path + 'Sounds\Background''*.mp3', False, SoundNames);
    for i := 0 to SoundNames.Count - 1 do
      begin
      for i2 := 0 to SoundNamesBefore.Count - 1 do if (SoundNames[i] = SoundNamesBefore[i2]) then
        There := True;
      if not There then
        begin
        Memo1.Lines.Add('1');
        SetLength(FBackgroundStream, length(FBackgroundStream) + 1);
        FBackgroundStream[i] := BASS_StreamCreateFile(false, PAnsiChar(AnsiString(SoundNames[i])), 000);
        end
      else
        Memo1.Lines.Add('0');
      There := False;
      end;

    Memo1.Lines := SoundNamesBefore;
    Memo1.Lines.Add('------------');
    Memo1.Lines.AddStrings(SoundNames);
    end;


Für wen das jetzt zu unverständliches Gelafer war: Ich hab im Moment 4 Pfade(Strings) in SoundNames - die speicher ich dann in SoundNamesBefore und clear SoundNames. Dann ermittel ich mit "FindFiles" alle Namen in dem gesuchten Ordner. Dadurch dass ich eins hinzugefügt habe, sind nun 5 Pfade in SoundNames.
Wenn ich jetzt am Ende SoundNamesBefore mal ausgebe, hat es FÜNF Einträge.

Ist die StringList vll. durch Zeile 6 in meinem Quelltext eine "Referenzvariable geworden???". Und wie löse ich das Problem

Gruß, Thomas


jaenicke - So 22.05.11 13:06

Eine TStringList ist ein Objekt und damit immer nur eine Referenz auf das Objekt.

Da musst du eine neue Instanz erstellen und den Inhalt kopieren, wenn du eine Kopie möchtest.


beastofchaos - So 22.05.11 13:10

Was meisnt du mit Instanz?

Auch wenn das in meinen Augen unsauber ist, werd ich einfach die Memo unsichtbar machen lassen, "SoundNamesBefore" da rein schreiben und am Ende das wieder in SoundNamesBefore speichern :P Da könnte ich eigentlich sogar SoundNamesBefore ganz weglassen :)


Dude566 - So 22.05.11 13:19

Eine Instanz ist ein erzeugtes Exemplar einer Klasse(Objekt).


beastofchaos - So 22.05.11 13:51

user profile iconDude566 hat folgendes geschrieben Zum zitierten Posting springen:
Eine Instanz ist ein erzeugtes Exemplar einer Klasse(Objekt).


Wie sieht das dann im Quelltext aus? xD

Ich verstehe da jetzt nicht den Unterschied zu meinem erzeugten Objekt(TStringList)


jaenicke - So 22.05.11 14:01

Dein Objekt ist eine Instanz der Klasse. ;-)

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
var
  List1, list2: TStringList;
begin
  List1 := TStringList.Create;
  try
    List1.Add('a');
    List2.Add('b');
    List2 := TStringList.Create;
    try
      List2.Assign(List1);
      ShowMessage(List2[0]);
    finally
      List2.Free;
    end;
  finally
    List.Free;
  end;
Du hast eine Instanz von TStringList in List1 und eine in List2.


beastofchaos - So 22.05.11 14:23

Deine Prozedur da gibt mir och "a" aus, oder? Weil du es mit Assign() zuweist - ich vermute mal, dass Assign auch etwas zu einer Referenzvariable macht, obwohl ich das bisher nur vom Textdateien schreiben und auslesen kenne.
Wie mache ich es denn dann richtig? Weil ich iwie nicht den Sinn in deiner Prozedur sehe. Ich würds so machen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
  List1 := TStringList.Create;
  List2 := TStringList.Create;

  List2 := List1    // List2 wird eine Referenz von List1

  List1.Add('a');

  ShowMessage(List2[0]);    // Gibt 'a' aus, da es auf List1 verweist! Das will ich aber umgehen

  List1.Free;
  List2.Free;


jaenicke - So 22.05.11 14:31

Nein, das macht keine Referenz, es kopiert nur den bisherigen Inhalt.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
var
  List1, list2: TStringList;
begin
  List1 := TStringList.Create;
  try
    List1.Add('a');
    List2.Add('b');
    List2 := TStringList.Create;
    try
      List2.Assign(List1);
      List1[0] := 'weder a noch b';
      ShowMessage(List2[0]);
    finally
      List2.Free;
    end;
  finally
    List.Free;
  end;
Es kommt immer noch a heraus, da die beiden Instanzen nicht voneinander abhängen.


beastofchaos - So 22.05.11 14:59

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Nein, das macht keine Referenz, es kopiert nur den bisherigen Inhalt.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
var
  List1, list2: TStringList;
begin
  List1 := TStringList.Create;
  try
    List1.Add('a');
    List2.Add('b');
    List2 := TStringList.Create;
    try
      List2.Assign(List1);
      List1[0] := 'weder a noch b';
      ShowMessage(List2[0]);
    finally
      List2.Free;
    end;
  finally
    List.Free;
  end;
Es kommt immer noch a heraus, da die beiden Instanzen nicht voneinander abhängen.


GENIAL!!

Danke :) Also statt " := " die Prozedur Assign() nehmen - dankeschöööön

Gruß, Thomas