CopyFileEx With Progress Callback in C# using PInvoke

Platform Invocation Services (PInvoke) allows managed code to call unmanaged functions that are implemented in a DLL.

This article will show you how to call unmanaged DLL functions from C#. We’ll specifically walk through how to call CopyFileEx with a callback to report progress.

The MSDN CopyFileEx Function is described as:

Copies an existing file to a new file, notifying the application of its progress through a callback function.

Syntax

BOOL WINAPI CopyFileEx(
    __in      LPCTSTR lpExistingFileName,
    __in      LPCTSTR lpNewFileName,
    __in_opt  LPPROGRESS_ROUTINE lpProgressRoutine,
    __in_opt  LPVOID lpData,
    __in_opt  LPBOOL pbCancel,
    __in      DWORD dwCopyFlags
);

The First thing we need to do is apply the DllImportAttribute to a method. Since CopyFileExA is defined in Kernel32.dll, we apply the DllImport attribute and specify “Kernel32.dll” as the dll that contains the method.
(Be sure to reference the System.Runtime.InteropServices namespace so we have access to DllImport)

Then we define the CopyFileEx method.

[DllImport("kernel32.dll", EntryPoint = "CopyFileExA", SetLastError = true)]
private static extern Int32 CopyFileEx(
    string lpExistingFileName,
    string lpNewFileName,
    [MarshalAs(UnmanagedType.FunctionPtr)] CopyCallBackDelegate copycallback,
    long lpData,
    bool pbCancel,
    Int32 dwCopyFlags);

Now, We need to declare a delegate callback for the CopyFileExA method to call. As you can see in the function definition above, CopyCallBackDelegate is the delegate we need to create.

private delegate Int32 CopyCallBackDelegate(uint TotalFileSize, uint BytesTransfered, uint StreamSize, uint StreamBytesTransfered, uint DwStreamNumber, long dwCallbackReason, long hSourceFile, long hDestinationFile, long lpData);

Now We can start copying files! (or at least attempt to)

I created a backgroundWorker component called FileCopy and set it’s DoWork Message Handler to FileCopy_DoWork

private void FileCopy_DoWork(object sender, DoWorkEventArgs e)
{
    string outputPath = null;
    if ((String)e.Argument != null)
    {
        outputPath = (String)e.Argument;
    }
    if (this.cpDataGrid.Rows.Count > 0)
    {
        foreach (DataGridViewRow row in this.cpDataGrid.SelectedRows)
        {
            String fullname = row.Cells["FullName"].Value.ToString();
            String filename = row.Cells["Name"].Value.ToString();
            try
            {
                CopyCallBackDelegate copycallback = new CopyCallBackDelegate(CopyProgressUpdate);
                UpdateProgressUi(0, 0, "Copying File: " + filename);
                CopyFileEx(fullname, outputPath + @"\" + filename, copycallback, 0, false, 0x00000001); // 0x00000001 = FAIL IF FILE EXISTS
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}

Basically I’m looping through a datagrid’s selected rows (which is a list of filenames\filepaths)
On Line 16 I create an instance of my CopyCallBackDelegate and pass in a method named CopyProgressUpdate. Which takes the same parameters as the delegate. This is the holy callback which will report it’s progress back to the CopyProgressUpdate method.

private Int32 CopyProgressUpdate(uint TotalFileSize, uint BytesTransfered, uint StreamSize, uint StreamBytesTransfered, uint DwStreamNumber, long dwCallbackReason, long hSourceFile, long hDestinationFile, long lpData)
{
    // This takes the current stream size (amount transfered) and creates a percentage based on file size
    UpdateProgressUi(1, Convert.ToInt64(((float)StreamSize / (float)TotalFileSize) * 100), "");
    return (Int32)0;
}

Line 18 i call the CopyFileEx() method which we pass the original file, output file, the CopyCallBackDelegate we created, and the last parameter as  0×00000001 which means the copy fails if the file already exists.

Line 17 I call UpdateProgressUI which is just method with a select/case statement to determine what control needs updating.

private delegate void UpdateProgressDelegate(int update, Int64 percent, String status);

private void UpdateProgressUi(int update, Int64 percent, String status)
{
    switch (update)
    {
        case 0:
            if (this.tsMain.InvokeRequired)
            {
                // we need access to the UI thread -- progress toolstrip doesn't have an invoke method
                // this is a hack.. sort of
                this.Invoke(new UpdateProgressDelegate(UpdateProgressUi), update, percent, status);
            }
            this.tsProgressBar.Value = (int)percent;
            this.tsStatusLbl.Visible = true;
            this.tsStatusLbl.Text = status;
            break;
        case 1:
            if (this.tsMain.InvokeRequired)
            {
                // we need access to the UI thread -- progress toolstrip doesn't have an invoke method
                // this is a hack.. sort of
                this.Invoke(new UpdateProgressDelegate(UpdateProgressUi), update, percent, status);
            }
            this.tsProgressBar.Value = (int)percent;
            break;
    }
}

That wraps it up! If you have any questions or corrections please feel free to express them. This is by no means the best way to do this. Only my feeble attempt.
Enjoy.

Finished Product

Download the Solution for this tutorial: TechScene Copy File With Progress

Share:
  • Digg
  • Technorati
  • Slashdot
  • Reddit
  • del.icio.us

7 Comments »

  1. cEBIK Said:

    Excelent !!

    But I’ve got an error “PInvokeStackImbalance was detected” on the end of copying file :(

    comment-bottom
  2. cEBIK Said:

    I found the problem..
    Changed param in function

    [DllImport("kernel32.dll"...

    [MarshalAs(UnmanagedType.AsAny)] object lpData,

    comment-bottom
  3. D Said:

    Well, this is all well and good but I think it would be more useful if the sample actually worked?

    I click to find a directory and I get nothing in the list to choose from so I can’t test the copy code.

    comment-bottom
  4. D:

    You have to type the path to the folder you wish to copy files from in the edit box next to the copy files button. the browse for folder dialog when you click the copy files button is where the files will be copied to.

    I’m sorry I didn’t clarify that.

    comment-bottom
  5. Jakub Said:

    I have problem with very big files (over 4GB). Problem is with CopyProgressUpdate method, because parameters are uint but size of file is bigger then max value of unit. I’ve tried using long instead uint, but it doesn’t help.
    Do you have any idea?

    comment-bottom
  6. deciacco Said:

    Thank you very much for posting this. It was very helpful!!!

    comment-bottom
  7. Sheri Said:

    Hi there!
    I need a cancel button to cancel this operation when user would like to cancel the copy operation.

    I cannot catch any event when the copying is in progress!

    Is there any solution?
    Thanks in advance
    Sheri

    comment-bottom

RSS feed for comments on this post. TrackBack URL

Leave a comment