Benutzer mit den meisten Antworten
FileGet (von VB6 nach VB.NET)

Frage
-
Hallo!
Ich habe eine Funktion in VB6, die mir gelockte Dateien kopieren kann.
Jetzt wurde aber "Get" geändert, und ich schaffe es nicht, das ursprüngliche Verhalten wiederherzustellen.
Ich bekomme immer die EndOfStream-Exception.
Kann jemand vielleicht gucken, ob er sieht, was ich falsch mache?
Public Function FileForceCopy(ByVal uSource As String, ByVal uDestination As String, Optional ByRef uErr As String = "") As Boolean
modIO.CreateDirectory(modIO.GetDirectoryName(uDestination))'Zielordner erstellen
Dim lSourceLen As Integer
Dim iSF As Short
Dim iDF As Short
Dim bChunk() As Byte
Dim lBytesToGet As Integer
Dim lBytesCopied As Integer
System.Diagnostics.Debug.Assert(modIO.FileExists(uSource), "")'Sichergehen, dass die Quelldatei existiert
lSourceLen = FileLen(uSource)
iSF = FreeFile
FileOpen(iSF, uSource, OpenMode.Binary, , OpenShare.Shared)
iDF = FreeFile
modIO.DeleteFile(uDestination)'Versuchen, die Zieldatei zu löschen, falls sie schon existiert
Debug.Print(uDestination)
FileOpen(iDF, uDestination, OpenMode.Binary)
'/* How many bytes to get each time */
lBytesToGet = 4000000 '/* Changed as Karl said ("I'd use 4Mb rather than 4Kb, for example.") 1.000.000 bytes = 1 MB
lBytesCopied = 0
'/* Keep copying until finishing all bytes */
Do While lBytesCopied < lSourceLen
'/* Check how many bytes left */
If lBytesToGet < (lSourceLen - lBytesCopied) Then
'/* Copy 4.000.000 bytes */
'UPGRADE_WARNING: Lower bound of array bChunk was changed from 1 to 0. Click for more: 'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="0F1C9BE1-AF9D-476E-83B1-17D43BECFF20"'
ReDim bChunk(lBytesToGet)
'UPGRADE_WARNING: Get was upgraded to FileGet and has a new behavior. Click for more: 'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="9B7D5ADD-D8FE-4819-A36C-6DEDAF088CC7"'
FileGet(iSF, bChunk, , True, True)
Else
'/* Copy the rest */
'UPGRADE_WARNING: Lower bound of array bChunk was changed from 1 to 0. Click for more: 'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="0F1C9BE1-AF9D-476E-83B1-17D43BECFF20"'
ReDim bChunk((lSourceLen - lBytesCopied))
'UPGRADE_WARNING: Get was upgraded to FileGet and has a new behavior. Click for more: 'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="9B7D5ADD-D8FE-4819-A36C-6DEDAF088CC7"'
FileGet(iSF, bChunk, , True, True)
End If
lBytesCopied = lBytesCopied + UBound(bChunk)
'/* Put data in destination file */
'UPGRADE_WARNING: Put was upgraded to FilePut and has a new behavior. Click for more: 'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="9B7D5ADD-D8FE-4819-A36C-6DEDAF088CC7"'
FilePut(iDF, bChunk)
Loop
FileClose(iSF)
FileClose(iDF)
Antworten
-
Hallo Hermie,
ich würde das Migrieren solcher Methoden gleich ganz sein lassen.
Das veraltete FileGet/Put API sollte man nur für strukturierte Dateien aus VB Classic verwenden,
und empfehlenswert wäre, diese baldmöglichst zu Konvertieren auf einen BinaryReader/Writer.
Für neueren Code sollte man sie tunlichst meiden.Um eine Datei zu kopieren reicht im einfachsten Falle: File.Copy,
was direkt über das Windows-API CopyFile arbeitet.Willst Du mit Chunks arbeiten willst so kannst über zwei FileStreams arbeiten
und dort die mit .NET 4.0 verfügbare Methode Stream.CopyTo verwenden.
(Karl E. Petersons Ratschlag ist aber auch etwas in die Jahre gekommen ;-)Ich verzichte zunächst mal auf ein Beispiel, da es eine gute Gelegenheit
zum Einarbeiten ist. Für mehr siehe: Grundlegende Datei-E/AVerursacher für Deine Ausnahme dürfte hier die verwendeten Arrays sein -
- auch wenn ich es jetzt nicht geprüft habe:Arrays in .NET sind im Standard 0-basiert und Visual Basic .NET übernimmt als Parameter
Da eine Array Variable anfangs nicht initialisiert ist (Nothing),
die obere Grenze - Du würdest bei ReDim bChunk(4000000) ein Array mit 4000001 Bytes erhalten.
ist das ReDim hier überflüssig und in VB.NET sinnvoller formuliert als:
siehe dazu: Gewusst wie: Ändern der Größe eines Arrays ff.Dim bChunk = new Byte(lBytesToGet - 1) {}
Gruß Elmar
- Als Antwort vorgeschlagen Thorsten DörflerModerator Montag, 30. Mai 2011 05:39
- Als Antwort markiert Thorsten DörflerModerator Sonntag, 12. Juni 2011 09:25
-
Hallo Hermie,
eine gesperrte Datei kann man nicht kopieren, das geht auch mit dem obigen Code nicht,
denn dahinter steckt ebenso ein FileStream - in Verbindung mit einem BinaryReader/Writer (für Randomdateien)
bzw. StringReader/Writer (für Textdateien).
Das Gesamte API in dem Bereich existiert einzig und allein zur Kompatbilität mit VB 6 Dateien.Ob - hier lesender - Zugriff auf eine Datei gewährt wird, hängt vielmehr davon ab andere Programme
die Datei eröffnet haben und wird durch die FileShare-Enumeration festgelegt.
Und die wiederum findet man auch im Windows API bei OpenFile/CreateFile (was am Ende aufgerufen wird).
Wenn ein Programm kein kooperatives Lesen ermöglicht gibt es auch kein darum herummogeln.Zum selber Probieren einmal ein CopyFile (IMHO wie gesagt relativ sinnfrei, File.Copy tuts auch)
mit einer kleinen Testroutine die Dateien von einem Verzeichnis in ein anderes kopiert:Public Shared Sub TestCopyFile() Dim watch = Stopwatch.StartNew() For Each sourcefileName In Directory.EnumerateFiles("C:\TEMP") Dim destinationFileName = Path.Combine("E:\TEMP", Path.GetFileName(sourcefileName)) Try ' Die einfachere Variante 'File.Copy(sourcefileName, destinationFileName, True) ' Wenn man das ganze kompliziert mag (viel bringen tut es kaum) CopyFile(sourcefileName, destinationFileName) Console.WriteLine("CopyFile {0} => {1}", sourcefileName, destinationFileName) Catch ex As Exception Console.WriteLine("CopyFile Fehler: {0}: {1}", sourcefileName, ex.Message) End Try Next watch.Stop() Console.WriteLine("TestCopyFile: {0} ms", watch.ElapsedMilliseconds) End Sub Public Shared Sub CopyFile(ByVal sourceFileName As String, ByVal destinationFileName As String) Const BufferSize = 64 * 1024 ' 64 KB If String.IsNullOrEmpty(sourceFileName) Then Throw New ArgumentNullException("sourceFileName") End If If String.IsNullOrEmpty(destinationFileName) Then Throw New ArgumentNullException("destinationFileName") End If Using sourceStream As New FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, FileOptions.SequentialScan) Dim destinationDirectory = Path.GetDirectoryName(destinationFileName) Directory.CreateDirectory(destinationDirectory) Using destinationStream As New FileStream(destinationFileName, FileMode.Create, FileAccess.Write, FileShare.None, BufferSize) sourceStream.CopyTo(destinationStream, BufferSize) End Using End Using End Sub
Will man sicherstellen, dass die Datei geschrieben wurde, wäre noch ein FileOptions.WriteThrough
für den Ausgabestream zu empfehlen, was dann aber nicht mehr vergleichbar (und langsamer) wäre.Für Dich evtl. noch interessant, weil neu zu VB6 ist der Einsatz der Using-Anweisung,
die sicherstellt, dass die Streams bei Fehlern (Ausnahmen) geschlossen werden.
Die eigentliche Ausnahem wird dann, weil nicht behandelt, an den Aufrufer weitergereicht,
wofür in der Testroutine die Try Catch-Finally-Anweisung verwendet wird.Gruß Elmar
- Bearbeitet Elmar BoyeEditor Dienstag, 24. Mai 2011 16:33 Link
- Als Antwort vorgeschlagen Thorsten DörflerModerator Montag, 30. Mai 2011 05:40
- Als Antwort markiert Thorsten DörflerModerator Sonntag, 12. Juni 2011 09:25
Alle Antworten
-
Hallo Hermie,
ich würde das Migrieren solcher Methoden gleich ganz sein lassen.
Das veraltete FileGet/Put API sollte man nur für strukturierte Dateien aus VB Classic verwenden,
und empfehlenswert wäre, diese baldmöglichst zu Konvertieren auf einen BinaryReader/Writer.
Für neueren Code sollte man sie tunlichst meiden.Um eine Datei zu kopieren reicht im einfachsten Falle: File.Copy,
was direkt über das Windows-API CopyFile arbeitet.Willst Du mit Chunks arbeiten willst so kannst über zwei FileStreams arbeiten
und dort die mit .NET 4.0 verfügbare Methode Stream.CopyTo verwenden.
(Karl E. Petersons Ratschlag ist aber auch etwas in die Jahre gekommen ;-)Ich verzichte zunächst mal auf ein Beispiel, da es eine gute Gelegenheit
zum Einarbeiten ist. Für mehr siehe: Grundlegende Datei-E/AVerursacher für Deine Ausnahme dürfte hier die verwendeten Arrays sein -
- auch wenn ich es jetzt nicht geprüft habe:Arrays in .NET sind im Standard 0-basiert und Visual Basic .NET übernimmt als Parameter
Da eine Array Variable anfangs nicht initialisiert ist (Nothing),
die obere Grenze - Du würdest bei ReDim bChunk(4000000) ein Array mit 4000001 Bytes erhalten.
ist das ReDim hier überflüssig und in VB.NET sinnvoller formuliert als:
siehe dazu: Gewusst wie: Ändern der Größe eines Arrays ff.Dim bChunk = new Byte(lBytesToGet - 1) {}
Gruß Elmar
- Als Antwort vorgeschlagen Thorsten DörflerModerator Montag, 30. Mai 2011 05:39
- Als Antwort markiert Thorsten DörflerModerator Sonntag, 12. Juni 2011 09:25
-
Hallo Elmar,
danke für Deine Antwort.
Ich habe auch schon andere Methoden ausprobiert, die sehr komfortabel in .NET sind, aber es geht mir darum, dass die Datei möglicherweise gelockt ist.
Ich habe in .NET noch keine Möglichkeit gefunden, eine gesperrte Datei zu kopieren, deshalb muss ich das Ding hier zum Laufen kriegen (denke ich).
Liebe Grüße.
-
Hallo Hermie,
eine gesperrte Datei kann man nicht kopieren, das geht auch mit dem obigen Code nicht,
denn dahinter steckt ebenso ein FileStream - in Verbindung mit einem BinaryReader/Writer (für Randomdateien)
bzw. StringReader/Writer (für Textdateien).
Das Gesamte API in dem Bereich existiert einzig und allein zur Kompatbilität mit VB 6 Dateien.Ob - hier lesender - Zugriff auf eine Datei gewährt wird, hängt vielmehr davon ab andere Programme
die Datei eröffnet haben und wird durch die FileShare-Enumeration festgelegt.
Und die wiederum findet man auch im Windows API bei OpenFile/CreateFile (was am Ende aufgerufen wird).
Wenn ein Programm kein kooperatives Lesen ermöglicht gibt es auch kein darum herummogeln.Zum selber Probieren einmal ein CopyFile (IMHO wie gesagt relativ sinnfrei, File.Copy tuts auch)
mit einer kleinen Testroutine die Dateien von einem Verzeichnis in ein anderes kopiert:Public Shared Sub TestCopyFile() Dim watch = Stopwatch.StartNew() For Each sourcefileName In Directory.EnumerateFiles("C:\TEMP") Dim destinationFileName = Path.Combine("E:\TEMP", Path.GetFileName(sourcefileName)) Try ' Die einfachere Variante 'File.Copy(sourcefileName, destinationFileName, True) ' Wenn man das ganze kompliziert mag (viel bringen tut es kaum) CopyFile(sourcefileName, destinationFileName) Console.WriteLine("CopyFile {0} => {1}", sourcefileName, destinationFileName) Catch ex As Exception Console.WriteLine("CopyFile Fehler: {0}: {1}", sourcefileName, ex.Message) End Try Next watch.Stop() Console.WriteLine("TestCopyFile: {0} ms", watch.ElapsedMilliseconds) End Sub Public Shared Sub CopyFile(ByVal sourceFileName As String, ByVal destinationFileName As String) Const BufferSize = 64 * 1024 ' 64 KB If String.IsNullOrEmpty(sourceFileName) Then Throw New ArgumentNullException("sourceFileName") End If If String.IsNullOrEmpty(destinationFileName) Then Throw New ArgumentNullException("destinationFileName") End If Using sourceStream As New FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, FileOptions.SequentialScan) Dim destinationDirectory = Path.GetDirectoryName(destinationFileName) Directory.CreateDirectory(destinationDirectory) Using destinationStream As New FileStream(destinationFileName, FileMode.Create, FileAccess.Write, FileShare.None, BufferSize) sourceStream.CopyTo(destinationStream, BufferSize) End Using End Using End Sub
Will man sicherstellen, dass die Datei geschrieben wurde, wäre noch ein FileOptions.WriteThrough
für den Ausgabestream zu empfehlen, was dann aber nicht mehr vergleichbar (und langsamer) wäre.Für Dich evtl. noch interessant, weil neu zu VB6 ist der Einsatz der Using-Anweisung,
die sicherstellt, dass die Streams bei Fehlern (Ausnahmen) geschlossen werden.
Die eigentliche Ausnahem wird dann, weil nicht behandelt, an den Aufrufer weitergereicht,
wofür in der Testroutine die Try Catch-Finally-Anweisung verwendet wird.Gruß Elmar
- Bearbeitet Elmar BoyeEditor Dienstag, 24. Mai 2011 16:33 Link
- Als Antwort vorgeschlagen Thorsten DörflerModerator Montag, 30. Mai 2011 05:40
- Als Antwort markiert Thorsten DörflerModerator Sonntag, 12. Juni 2011 09:25
-
Oh, Du hast Recht... wenn die Datei so "richtig" gelockt ist, geht es auch mit meinem Code nicht. Ich beziehe mich jetzt z. B. auf die Dateien unter c:\Windows\Temp
Ich bräuchte halt einen Code für das Kopieren einer Datenbank, die gerade verbunden ist. Ginge das mit dem, was Du geschrieben hast?
Das mit dem USING habe ich noch nicht verstanden. Sagen wir mal, eine Funktion endet nicht wie erwartet, aber sie endet doch (sei es über Try-Catch oder On Error Goto).
Wozu verwenden wir dann USING? Geht der FileStream nicht automatisch out-of-scope und wird geschlossen? Ich dachte, das sei bei Managed Code so.
Liebe Grüße.
-
Oder vielleicht habe ich Dich jetzt (nach mehr Nachdenken) richtig verstanden? ->
Wir benutzen USING, weil ich dann nicht auf den GarbageCollector warten muss. So lange der GarbageCollector den FileStream noch nicht freigegeben hat, kann es sein, dass ich keinen Zugriff auf die Datei habe, weil der (noch nicht freigegebene) FileStream die Datei noch in der Mangel hat. Richtig?
-
Hallo,
das hängt zum einen davon ab, mit welchem Eröffnungsmodus
der/die andere Prozess(e)die Datei für sich in Beschlag nehmen
Zum anderen von den Zugriffsrechten durch das Betriebssystem.Ich habe FileShare.Read verwendet, da mehrfaches Lesen kein Problem darstellt,
großzügiger (und riskant) wäre FileShare.ReadWrite.Wenn Du mit Datenbank eine Jet/Access-Datenbank (oder vergleichbare) meinst:
Solange die im Mehrplatzbetrieb eröffnet ist, geht das - im Exklusivzugriff nicht.Nur ist das Kopieren einer Jet-Datenbank im laufenden Betrieb riskant,
Sinnvoller wäre: Die Benutzer zum Trennen auffordern.
da bei parallelen Zugriff die Datei verändert werden könnte => Datenmüll.
(Und bitte nicht argumentieren, es hätte immer geklappt, das habe ich als ehemaliger
Access MVP immer gelesen, wenn jemand seine Kopien verwenden wollte, und die kaputt waren ;-(
Wenn es immer das eigene ist, könnte man mit .NET eine Benachrichtigung (Udp/TcpClient) programmieren.
Weiteres siehe http://support.microsoft.com/kb/198755
Gruß Elmar
-
Hallo,
das Einsammeln des Mülls ist hier die halbe Miete.
Hier stellt es sicher, dass der Datei-Handle, der vom Betriebssystem kommt,
sofort freigegeben wird, und auch dann wenn eine Ausnahme auftritt
(z. B. Platte Voll beim CopyTo).Zudem solltest Du Dich mit Behandeln und Auslösen von Ausnahmen beschäftigen -
um das alte Error Goto/Resume Next vollständig abzulösen.
Denn es wird zwar von VB.NET noch unterstützt, führt aber nur dazu, dass VB.NET umständlichen
Code bastelt, um die strukturierte Ausnahmebehandlung mit den alten Konzepten zu vereinigen.Wie man oben sieht, kann Code sogar deutlich übersichtlicher werden ->
versuch mal Deine Methode mit On Error sauber zum Schließen der File-Handles zu bringen.Gruß Elmar