CopyFileEx Beispiel von Pascal auf C++ übertragen



  • Hi Leute,

    ich habe mich des Beispieles von der Seite http://www.swissdelphicenter.ch/torry/showcode.php?id=330 bedient und komme beim "übersetzen" nicht weiter und hoffe hier auf Euere Hilfe. 🙄

    Hier mein Code

    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
            CopyFileEx( Edit1->Text.c_str(), Edit2->Text.c_str(), CopyProgressRoutine, ProgressBar1, &bCancel, 0 ) ;
    }
    
    DWORD __stdcall CopyProgressRoutine( TotalFileSize, TotalBytesTransferred, StreamSize, StreamBytesTransferred, dwStreamNumber,
        dwCallbackReason, hSourceFile, hDestinationFile,
        lpData ) {
    
        if ( dwCallbackReason == CALLBACK_STREAM_SWITCH ) {
            TProgressBar(lpData)->Max = TotalFileSize.QuadPart ;
        }
    
        TProgressBar(lpData)->Position = TotalBytesTransferred.QuadPart;
        Application.ProcessMessages;
    
        return PROGRESS_CONTINUE ;
    }
    

    Fehler habe ich in der Zeile 11 mit der TProgressBar. Mit TotalFileSize.QuadPart werde ich wahrscheinlich auch noch Probleme bekommen. Doch dies werde ich erst genau wissen, wenn ich das Problem mit TProgressBar gelöst bekomme. 😞

    @offTopc:
    Wieso bekommme ich eigentlich mit der Suchanfrage "*Datei* *kopieren*" keine Ergebnisse vom Board zurück, da doch der Thread [url=http://www.c-plusplus.net/forum/viewtopic-var-t-is-122755-and-highlight-is-%2Acopyfileex%2A.html] Kopieren von Dateien[/url] gefunden werden müsste. 😕

    Viele Grüße
    Heinz



  • 1. Wäre es toll wenn du uns ein paar Infos über deine Fehler geben könntest.
    2. Sehe ich auf anhieb ein paar die eigentlich jedem Anfänger ins Auge fallen müssten.
    Deswegen sollte man Code nicht einfach so kopieren sondern versuchen ihn zu verstehen.
    3. Zu deinen Fehlern.

    1. Müsste der Compiler schon gleich bei Aufruf von CopyFileEx mosern weil die Callback-Funktion in deinem Beispiel da noch garnicht bekannt ist.
      Die einfachste Lösung wäre die Funktion im Kopf deiner cpp zu deklarieren.
    2. Seit wann hat der Kopf einer Funktion Parameter ohne Typen?
    DWORD CALLBACK CopyProgressRoutine(
      LARGE_INTEGER TotalFileSize,
      LARGE_INTEGER TotalBytesTransferred,
      LARGE_INTEGER StreamSize,
      LARGE_INTEGER StreamBytesTransferred,
      DWORD dwStreamNumber,
      DWORD dwCallbackReason,
      HANDLE hSourceFile,
      HANDLE hDestinationFile,
      LPVOID lpData
    )
    

    So muss der Kopf richtig aussehen.

    1. Der Zugriff auf die ProgressBar muss so aussehen:
    ((TProgressBar*)lpData)
    
    1. ProcessMessages ist eine Funktion:
    Application->ProcessMessages();
    

    Das wären jetzt mal die allergröbsten Fehler.
    Dass du dem int-Wert Max den int64-Wert TotalFileSize.QuadPart zuweist ist zwar auch nicht besonders toll aber bis Dateigrößen von 4 Gigabyte sollte es keine Fehler geben. Darüber würde deine Progessbar halt alle 4 GiB auf 0 springen.

    Weil ich so nett bin geb ich dir sogar die korrigierte Form raus:

    //Das hinter TForm1 *Form1; einfügen:
    
    DWORD CALLBACK CopyProgressRoutine(
      LARGE_INTEGER TotalFileSize,
      LARGE_INTEGER TotalBytesTransferred,
      LARGE_INTEGER StreamSize,
      LARGE_INTEGER StreamBytesTransferred,
      DWORD dwStreamNumber,
      DWORD dwCallbackReason,
      HANDLE hSourceFile,
      HANDLE hDestinationFile,
      LPVOID lpData
    );
    //---------------------------------------------------------------------------
    
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
            CopyFileEx( Edit1->Text.c_str(), Edit2->Text.c_str(), CopyProgressRoutine, ProgressBar1, &bCancel, 0 ) ;
    }
    //---------------------------------------------------------------------------
    
    //irgendwo in deine cpp:
    DWORD CALLBACK CopyProgressRoutine(
      LARGE_INTEGER TotalFileSize,
      LARGE_INTEGER TotalBytesTransferred,
      LARGE_INTEGER StreamSize,
      LARGE_INTEGER StreamBytesTransferred,
      DWORD dwStreamNumber,
      DWORD dwCallbackReason,
      HANDLE hSourceFile,
      HANDLE hDestinationFile,
      LPVOID lpData
    ) {
        if ( dwCallbackReason == CALLBACK_STREAM_SWITCH ) 
        { ((TProgressBar*)lpData)->Max=TotalFileSize.QuadPart; }
    
        ((TProgressBar*)lpData)->Position = TotalBytesTransferred.QuadPart;
        Application->ProcessMessages();
    
        return PROGRESS_CONTINUE ;
    }
    //---------------------------------------------------------------------------
    

    3. Ja die Suchfunktion hat manchmal ihre Macken. Aber dafür gibts zum einen die FAQ und zum anderen die Suche von google die auf der Suchseite ganz oben steht.



  • rean schrieb:

    1. Wäre es toll wenn du uns ein paar Infos über deine Fehler geben könntest.

    Stimmt, war so im Nacheingesehen ziemlich dürftig. Danke, dass Du Dir aber dennoch die Mühe gemacht hast mir zu helfen.

    rean schrieb:

    2. Sehe ich auf anhieb ein paar die eigentlich jedem Anfänger ins Auge fallen müssten.
    Deswegen sollte man Code nicht einfach so kopieren sondern versuchen ihn zu verstehen.

    Damit hast Du natürlich auch recht. Bin da wohl mit der falschen Einstellung heran gegangen und dachte dass dies sich als selbstläufer darstellt. Was aber irgendwie total gegen die Wand gefahren wurde.

    rean schrieb:

    3. Zu deinen Fehlern.

    1. Müsste der Compiler schon gleich bei Aufruf von CopyFileEx mosern weil die Callback-Funktion in deinem Beispiel da noch garnicht bekannt ist.
      Die einfachste Lösung wäre die Funktion im Kopf deiner cpp zu deklarieren.
    2. Seit wann hat der Kopf einer Funktion Parameter ohne Typen?
    DWORD CALLBACK CopyProgressRoutine(
      LARGE_INTEGER TotalFileSize,
      LARGE_INTEGER TotalBytesTransferred,
      LARGE_INTEGER StreamSize,
      LARGE_INTEGER StreamBytesTransferred,
      DWORD dwStreamNumber,
      DWORD dwCallbackReason,
      HANDLE hSourceFile,
      HANDLE hDestinationFile,
      LPVOID lpData
    )
    

    So muss der Kopf richtig aussehen.

    Sorry, dies kam bei dem Kopieren nur in die falsche Reinfolge. Hatte die Callback Funktion direkt hinter dem Konstruktor von TForm1::TForm1 geschrieben.
    Das mit den Typen kam von dem übereilten Kopieren der Funktion von PASCAL zu C++ und wurde auch vom Compilier nicht bemängelt. Daher war mein Augenmerk mehr auf die Fehler gerichtet.

    rean schrieb:

    Dass du dem int-Wert Max den int64-Wert TotalFileSize.QuadPart zuweist ist zwar auch nicht besonders toll aber bis Dateigrößen von 4 Gigabyte sollte es keine Fehler geben. Darüber würde deine Progessbar halt alle 4 GiB auf 0 springen.

    Dies hatte ich erst gar nicht beachtet. Wäre mir bestimmt auch erst bei der ersten Datei dieser Größe aufgefallen, dass dies Fehlerhaft wäre. Doch eine Lösung zu diesem Problem ist mir bis jetzt noch nicht in den Sinn gekommen, außer eventuell es mit Prozentrechnung zu lösen.

    rean schrieb:

    Weil ich so nett bin geb ich dir sogar die korrigierte Form raus

    Da hast Du Dir wirklich sehr viel Mühe gegen und dafür nochmals besten Dank. Da dies auch für mich sehr übersichtlicher wirkte.

    Nur eine Anmerkung zu Deinem Code Vorschlag hatte ich dann doch noch. Warum die doppelte Deklarierung der Callback Funktion? Dient dies dafür, dass die eigentlich Funktion dann überall in der CPP stehen darf?

    Nun aber wieder zurück zum wesentlichen. Die Funktion macht jetzt genau das, was sie auch soll. Doch ein Problem bleibt für mich jetzt noch weiterhin. Das Abbrechen während eines Kopiervorgang. Zum einem war ich überrascht, dass das Form nicht "einfriert" und zum anderen dann erstaunt dass sich das Form während des Vorgangs nicht schließen lässt. Wollte dies dann über die Variable bCancel lösen und bin bis jetzt noch nicht weiter gekommen, da ich anscheint den Typ nicht so wirklich verstehe und auch kaum Verständnis von Pointern habe.

    Dies ist ersteinmal was ich zustande bekommen habe:

    im Header deklariert in der Klasse vom Form:

    private:	// Anwender-Deklarationen
            LPBOOL bCancel ;
    

    in der CPP Datei:

    __fastcall TForm1::TForm1(TComponent* Owner)
            : TForm(Owner)
    {
            *bCancel = FALSE ;
    }
    
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
            CopyFileEx( Edit1->Text.c_str(), Edit2->Text.c_str(), CopyProgressRoutine, CGauge1, bCancel, 0 ) ;
    }
    
    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
            *bCancel = TRUE ;
    }
    

    Leider bekomme ich dann immer eine Exception zwecks der Zugriffsverletzung "EAccessViolation". Also ist die Zuordnung definitiv falsch. Doch solch ein Beispiel habe ich aus dem Netz gezogen. Also bitte dafür nicht schlagen. Besser habe ich es leider noch nicht in Erfahrung bringen können.

    Viele Grüße
    Heinz



  • Jap genau. Deswegen deklariere ich die Callback-Funktion erstmal im Kopf und formuliere sie dann weiter unten aus.
    Ich deklariere sie also bloß einmal.
    Das ist einfach schönerer Programmierstil. Weil wenn du jede Funktion die neu hinzukommt immer bloß in den Kopf schreibst wird das irgendwann sehr unübersichtlich.

    Zu deinem Problem mit dem Abbrechen. Bool-Werte sind in der WinApi ints.
    So müsste das korrekt aussehen:

    private:    // Anwender-Deklarationen
            int bCancel ;
    

    Mit *bCancel würdest du dem Compiler sagen das er bCancel als Pointer betrachten soll. Allerdings ist das natürlich kein Pointer. Daher die Exception.
    So muss es richtig aussehen:

    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
     bCancel=TRUE;
    }
    

    Außerdem solltest du bCancel jedes mal auf FALSE setzen bevor du CopyFileEx aufrufst. Sonst klappt es nichtmehr nachdem du einmal abgebrochen hast weil bCancel dann ja auf TRUE steht.
    Also so:

    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
     bCancel=FALSE;
     CopyFileEx(Edit1->Text.c_str(), Edit2->Text.c_str(),
                CopyProgressRoutine, CGauge1, &bCancel, 0); //Das & vor bCancel nicht vergessen!!!
    }
    

    Dass die Form nicht einfriert verdankst du dem Application->ProcessMessages(). Schau dir dazu mal die Borland-Hilfe an.

    Damit du die Form schließen kannst müssen zunächst alle Operationen die in der Form laufen beendet worden sein.
    Also setzt du bCancel in der OnClose-Routine der Form einfach wieder auf TRUE:

    void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
    {
     bCancel=TRUE;
    }
    

    Ich hab mir noch ein paar Gedanken zur Dateigröße gemacht und bin darauf gekommen, dass es schon ab 2 GB Fehler gibt weil's dann im int einen Überlauf zum negativen hin gibt.
    Das bedeutet, dass man da ein bisschen rumrechnen muss.
    Das wäre mein Vorschlag:

    DWORD CALLBACK CopyProgressRoutine(
      LARGE_INTEGER TotalFileSize,
      LARGE_INTEGER TotalBytesTransferred,
      LARGE_INTEGER StreamSize,
      LARGE_INTEGER StreamBytesTransferred,
      DWORD dwStreamNumber,
      DWORD dwCallbackReason,
      HANDLE hSourceFile,
      HANDLE hDestinationFile,
      LPVOID lpData
    ) {
        if ( dwCallbackReason == CALLBACK_STREAM_SWITCH )
        { ((TProgressBar*)lpData)->Max=TotalFileSize.HighPart+100; }
        //Die Zahl die du hinzuaddierst ist egal, sollte jedoch nicht zu klein sein.
    
        ((TProgressBar*)lpData)->Position = 
           ((TProgressBar*)lpData)->Max*TotalBytesTransferred.QuadPart/TotalFileSize.QuadPart;
    
        Application->ProcessMessages();
    
        return PROGRESS_CONTINUE ;
    }
    

    Das müsste bis zu Dateigrößen von 2^61-1 Byte = 2305843009213693951 Byte = 2147483647 Gigabyte (hoffe, dass ich mich nicht verrechnet habe 😃 ) eigentlich ganz gut funktionieren 😉
    Ich habs jetzt mit einer 7,5 Gig Datei getestet und da gings wunderbar.


Anmelden zum Antworten