none
FileGet (von VB6 nach VB.NET) RRS feed

  • 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)

    Dienstag, 24. Mai 2011 12:22

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/A

    Verursacher 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
    die obere Grenze - Du würdest bei ReDim bChunk(4000000) ein Array mit 4000001 Bytes erhalten.

    Da eine Array Variable anfangs nicht initialisiert ist (Nothing),
    ist das ReDim hier überflüssig und in VB.NET sinnvoller formuliert als:
    Dim bChunk = new Byte(lBytesToGet - 1) {}
    
    siehe dazu: Gewusst wie: Ändern der Größe eines Arrays ff.

    Gruß Elmar

    Dienstag, 24. Mai 2011 13:39
    Beantworter
  •  

    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


    Dienstag, 24. Mai 2011 16:08
    Beantworter

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/A

    Verursacher 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
    die obere Grenze - Du würdest bei ReDim bChunk(4000000) ein Array mit 4000001 Bytes erhalten.

    Da eine Array Variable anfangs nicht initialisiert ist (Nothing),
    ist das ReDim hier überflüssig und in VB.NET sinnvoller formuliert als:
    Dim bChunk = new Byte(lBytesToGet - 1) {}
    
    siehe dazu: Gewusst wie: Ändern der Größe eines Arrays ff.

    Gruß Elmar

    Dienstag, 24. Mai 2011 13:39
    Beantworter
  • 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.

    Dienstag, 24. Mai 2011 13:56
  •  

    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


    Dienstag, 24. Mai 2011 16:08
    Beantworter
  • 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.

     

     

    Mittwoch, 25. Mai 2011 14:07
  • 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?

    Mittwoch, 25. Mai 2011 14:29
  • 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,
    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 ;-(

    Sinnvoller wäre: Die Benutzer zum Trennen auffordern.
    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

    Mittwoch, 25. Mai 2011 17:35
    Beantworter
  • 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

    Mittwoch, 25. Mai 2011 17:48
    Beantworter