2

I'm writing a (fairly) simple C# application using .NET 4 to check for updates before running an executable. If a newer version of an exe exists on a network share, simply copy it over to the local folder and start it. It's all working perfectly, except while reading about the limitations of File.Copy() I realized that I wasn't going to be able to show a progress bar while I did this, and everything I saw said to use CopyFileEx, so I'm trying to do that.

I used the sample code found here and it compiles fine (although I'm still a little unsure of exactly how the backgroundworker comes into play), except when I actually go to run the application, the CopyFilEx() method returns false, with the error being "The parameter is incorrect".

My code (relevant sections only, I'll add more if need be)

Calling the function:

XCopy.Copy(strServerAppPath + strExeName, strLocalAppPath + strExeName, true, true, (o,    pce) =>
{
worker.ReportProgress(pce.ProgressPercentage, strServerAppPath + strExeName);
});

(the source path evaluates to "C:\test.txt" and the destination path to "C:\test\test.txt")

Where the error occurs in the code linked above:

bool result = CopyFileEx(Source, Destination, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, ref IsCancelled, copyFileFlags);
            if (!result)
                throw new Win32Exception(Marshal.GetLastWin32Error());

Thanks in advance for the help, I've been struggling with this for a few hours now...

Community
  • 1
  • 1
Mansfield
  • 14,445
  • 18
  • 76
  • 112

2 Answers2

3

Rather than deal with all that Marshalling, it's pretty trivial to just "roll your own" copier that goes chunk by chunk:

private static void CopyFile(string source, string destination, int bytesPerChunk)
{
    int bytesRead = 0;

    using (FileStream fs = new FileStream(source, FileMode.Open, FileAccess.Read))
    {
        using (BinaryReader br = new BinaryReader(fs))
        {
            using (FileStream fsDest = new FileStream(destination, FileMode.Create))
            {
                BinaryWriter bw = new BinaryWriter(fsDest);
                byte[] buffer;

                for (int i = 0; i < fs.Length; i += bytesPerChunk)
                {
                    buffer = br.ReadBytes(bytesPerChunk);
                    bw.Write(buffer);
                    bytesRead += bytesPerChunk;
                    ReportProgress(bytesRead, fs.Length);  //report the progress
                }
            }
        }
    }
}
Steve Danner
  • 21,818
  • 7
  • 41
  • 51
  • Looks interesting...I had seen similar answers before but general consensus seemed to be that copyfileex was better (if one could actually get it to work). I'll give it a try though at any rate :) – Mansfield Apr 27 '12 at 16:53
  • 2
    You may be better off using the `ReadBytes(byte[],int,int)` method, which can reuse the same buffer instead of creating a new array for every read operation. (http://msdn.microsoft.com/en-us/library/ms143295.aspx) – David R Tribble Apr 27 '12 at 20:34
  • How can we boost the speed up? It's very slow compared to the regular File.Copy. (I'm using 20 MB as chunk value) – SepehrM Jun 01 '14 at 20:56
  • @DavidRTribble Could you please explain more? How exactly can it be used? – SepehrM Jun 01 '14 at 20:56
  • Generally-speaking I'd say CopyEx is better in the sense that it supports real optimizations behind the scene while streams are not (e.g. DMA). – Natalie Perret Feb 06 '17 at 00:33
3

Instead of calling ReadBytes(), which allocates a new byte[] buffer array on every call, allocate a single buffer (of, say 64KB in size) and call Read(buf, 0, buf.Length), which will read up to buf.Length bytes into the array and then return the actual number of bytes read. Then re-use that same buffer array for every read (after writing out its contents to the target stream). This saves having to re-allocate a new buffer for each read/write operation.

Example

For example, the inner loop of a stream copying method would look something like this:

byte[]  buf;

// Allocate an I/O data buffer
buf = new byte[64*1024];

// Copy the contents of the input stream to the output stream
for (;;)
{
    int     len;

    // Read a block of data from the input stream
    len = inp.ReadBytes(buf, 0, buf.Length);
    if (len <= 0)
        break;

    // Write the data to the output stream
    outp.Write(buf, 0, len);
}

The loop reads up to 64KB of bytes from the input stream into the buffer, then writes out the actual number of bytes read to the output stream. The same buffer is used for each read/write operation, so we're not doing unnecessary allocations and deallocations of buffers. When the read operation fails, we've reached the end of the input stream, so we exit the loop.

David R Tribble
  • 11,918
  • 5
  • 42
  • 52
  • @David R Tribble Pls. provide the implementation of `ReportProgress(bytesRead, fs.Length)` or should it use the same background worker as in CopyFileEx? –  Khan Feb 09 '15 at 09:37