- Table of Contents
- .NET Book Recommendations
- Getting Started with .NET
- The Microsoft .NET Framework
- The Common Language Runtime (CLR), the Common Type System (CTS), and the Common Language Specification (CLS)
- .NET Framework Class Library
- Visual Studio .NET
- .NET Enterprise Servers and .NET My Services
- .NET Compliant Languages
- C#
- Visual Basic .NET (VB .NET)
- ASP.NET
- XML Web Services
- ADO.NET
- XML.NET
- Windows Forms
- Why .NET?
- Displaying Errors with the Error Provider
- COM Interoperability
- Comparing Java and .NET
- Calling Unmanaged Code
- .NET Application Security
- Code Access Security
- .NET Standards Support
- Numeric Types in the .NET Framework
- Working with Strings
- Formatting Strings
- Trimming Character Strings
- Comparing Strings in .NET 2.0
- Arrays and Collections
- Arrays as Class Members
- Sorting a Multi-Dimensional Array
- Sorting a Multi-Dimensional Array with LINQ
- File I/O (System.IO)
- Working with File Names
- Using the File System
- Working with Files and Directories
- Monitoring the File System
- Working with Streams
- Working with Text Encodings
- Working with Date and Time
- Extending the DateTime Class
- Using DateTimeOffset
- Fun with Dates
- Exceptions
- Delegates
- Events
- Asynchronous Programming
- Asynchronous File I/O
- Timers
- Random Numbers
- Cryptographically Secure Random Numbers
- Serialization
- MultiThreading (System.Threading)
- Multi-Threading Overview
- The Managed Thread Pool
- Managed Threading
- Thread Synchronization
- Synchronizing Data Access
- Trace Debugging
- Tracing in .NET 2.0
- ASP.NET Trace
- Validating User Input in ASP.NET Web Pages
- Event Logging
- Monitoring Application Performance
- Accessing the Registry
- Accessing Environment Information
- Environment Variables in .NET 2.0
- Managing Windows Forms Applications
- Working with Email
- Working with Graphics
- Animating a Background
- Working with Images
- Drawing Cycloid Curves
- Simulating the Spirograph
- Building International Web Applications
- .NET Compact Framework
- Mobile Web Development with ASP.NET
- Speech Technologies
- Microsoft MapPoint Web Service
- Working with Typed DataSets
- Using Relationships in DataSets
- DataColumn Expressions
- Playing Simple Sounds
- Playing Sounds with .NET 2.0
- Returning an Image in a Web Page
- RSS
- Best Practices Project Structure
- Best Practices Application Blocks
- The Data Access Application Block
- The Exception Management Application Block
- Best Practices — Performance
- Best Practices — Performance and Scalability
- Best Practices - Testing
- Reading the Tea Leaves, 2005
- Predictions: A Look Back at 2005, and a Look Ahead to 2006
- .NET Downloads
- Application Deployment Overview
- Application Deployment — Versioning
- Application Deployment — Version Policy
- Application Deployment — Packaging and Distribution
- .NET Remoting Overview
- A Remoting Demonstration
- Remoting Configuration
- Remoting: Lifetimes and Leases
- Remoting: Other Issues
- Attributes
- Writing Custom Attributes
- Accessing Attributes in Code
- Reflection
- Class Design: Inheritance, Interface, or Composition?
- The TriTryst Game
- Console Applications in .NET 2.0
- New File I/O Methods in .NET 2.0
- Building Projects with MSBuild
- Unmanaged Callbacks in .NET 2.0
- Timer Troubles
- Non-Rectangular Windows Forms
- Windows Forms Transparency
- 10 Things I Hate About Visual Basic
- 10 Things I Hate About C#
- Background Processing with Idle Time
- Scaling Windows Forms
- Reading and Writing Binary Data
- New Memory Management Functions in .NET 2.0
- Compatibility Between .NET 1.1 and .NET 2.0
- Managed Debugging Assistants in .NET 2.0
- XDir: A Program for Viewing Directory Sizes
- The Microsoft.VisualBasic Namespace
- Operator Overloading
- Working with GPS Data
- Hidden Visual Studio Tools
- .NET 3.0
- The .NET 2.0 Stopwatch Class
- Nullable Types
- Drawing Rotated Text
- Unsafe Code
- Other .NET Languages
- Compiler Directives
- Safe Handles
- Predictions, 2007 Edition
- New Features in C# 3.0
- Generics
- Network Client Programming
- On the Misuse of Exceptions
- Maximum Object Size in .NET
- More on Maximum Object Sizes
- Keyed Collection Memory Limitations
- Matching String Endings
- Allocating Small Data Structures
- Grumbling About Limitations
- Some Thoughts on the Nature of What We Do
- Working with Predicates in Collections
- Working with DataReaders
- Outputting XML with XmlWriter
- Writing XML Data
- Working with Compression
- Another Look at Compressed Streams
- Compressing a Very Large File
- Canonical URIs
- Constructing URIs
- Using OneWayAttribute for Remote Calls
- Selecting a Garbage Collector
- Linked List
- Linked List Application - The MRU List
- Auto-implemented Properties in C#
- The HashSet Collection
- Looking Ahead: 2018
- An Experiment in Optimization
- A Larger Integer
- Extension Methods
- Language Integrated Query (LINQ)
- Variable Length Parameter Lists
- The ReaderWriterLockSlim Synchronization Primitive
- Sorting a Text File
- Sorting a Large Text File
- Using ListView with Large Data Sets
- LINQ One-Liners
- Regular Expression Optimization
- Random File I/O
- Computing the Size of a Structure
- More on Computing Structure Sizes
- UnmanagedMemoryStream
- Dynamically Loading Code
- Building a String Table
- Delegates Versus Function Pointers
- Visual Studio Editor Features
- A Simple Profile Timer
- New Features in C# 4.0
- IEnumerator or IList?
- New Features in .NET 4.0
- Set Operations with IEnumerable and HashSet
- Using File Locks
- Extending Object Functionality
- Clearing a HashSet
- When Hash Codes Matter
- Parsing Command Line Options
- Creating a Single-Instance Program
- Asynchronous Windows Forms Events
- The BackgroundWorker Component
- Fixing a Dumb Mistake
- Thinking About Multi-Threaded Programs
- JavaScript Object Notation
- Better JSON Processing with JSON.Net
- Useful .NET-related Sites
- Markov Models
- Building an Order 0 Markov Model
- Higher Order Markov Models
- Webmaster's Guide to robots.txt
- An Overview of the Parallel Extensions to .NET
- Parallel Extensions Synchronization Objects
- Thread Safe Collections
- A Bug and a Conundrum
- Another Bug and an Answer
- Task Parallel Library
- Good and Bad Ideas in C#
- Parallel LINQ
- Copying Large Files
- Replacing File.Copy
- Learning from Our Mistakes
- Symbolic Links
- There Is No Easy Fix
- Tracking Hurricanes
- Examining Hurricane Data
- Searching for Multiple Strings
- Simple JSON Processing
- Aho-Corasick String Searching
- Writing a Web Crawler
- Web Crawler Politeness
- Source Control Management
- Subversion
- Communicating with Datagrams
- Fun with Actions and Funcs
- The Future of Media
- The Importance of Metadata
- Of Comparison and IComparer
- IComparer, Comparer, IComparable, Oh My!
- Comparing Generic Types
- A Simple HTTP Server
- Quantizing DateTime Fields
- More Fun with the Garbage Collector
- Refactor, Don't Rewrite
- A Generic BinaryHeap Class
- A Generic File Sorter
- Birthdays, Random Numbers, and Hash Keys
- Random Selection from Large Groups
- Command Line Tools for Windows
- Reading and Writing, Bit by Bit
- Selecting the Top N Items from a Group
- Determining Website Content Encoding
- Benefits and Drawbacks of Syndication
- Pubsubhubbub
- Memory Use Misconceptions
- Risk, Lost Opportunity, and Other Hidden Upgrade Costs
- Culture Shock: from .NET to JavaScript
- Using .NET for a Startup
- Tracking Wikipedia Changes with IRC
- Browser Applications and the Same Origin Policy
- Handling the Unexpected
- Dealing with Growth
- Deleting the Oldest File
- Where Do I Put Stuff?
- .NET Timer Resolution
- Exploring Options for Better Timers
- Using the Windows Timer Queue API
- Locks Aren't Slow
- Alternatives to Locks
- Lock Free Concurrent Collections
- The BlockingCollection Class
- Customizing BlockingCollection
- What Time Is It? Daylight Saving Time and Computers
- Using enums to Save Memory
- New File Operations in .NET 4.0
- Building a Hierarchy of Rectangles
- A Faster File Copy
- Constants Are Forever
- The Dangers of Floating Point
- Goto is Not Inherently Evil
- The Weakest Link
- Reducing Memory Required for Strings
- Grouping with LINQ
- HttpListener "Gotchas"
- Extension Methods Are Evil
- Finding the Registered Domain in a URL
- Drawing Text
- Obfuscating Sequential Keys
- Properties of Obfuscated Keys
- Finding Changes Between Two Lists
- Using the ConcurrentBag Collection
- Never Sleep!
- Shuffling and Sorting
- Viewing Large Text Files
- Use the Right Tool
- Why GetHashCode Matters
- Optimization Guidelines
- Timer Differences
- The Mutex
- Modifying a Working System
- Building a New Type of Stream
- More Large File Problems
- A Better File.Copy Replacement
- Throwing the Wrong Exception
- Approximate Counters
- Monitoring a Timer
- Combining Consoles and Forms
- Embedding a Text Resource
- Handling Concurrent Downloads
- The Importance of Domain Knowledge
- Stupid Programmer Tricks
- Aho-Corasick Revisited
- Expressiveness is the Soul of Brevity
- Fun with Anonymous Types
- Simplifying a Multi-Threaded Application
- Work Smarter
- The Skip List Data Structure
- A More Memory-Efficient Skip List
- Selection Revisited
- Why Async?
- What the Future Holds
- The "Roslyn" CTP
- Where We've Been
- Informit Reference Library
A Better File.Copy Replacement
Last updated Jun 17, 2011.
As useful as it is, the .NET File.Copy method has its drawbacks. Perhaps most important from my perspective is that using it to copy a very large file from a server can cause the server to exhibit the pathological caching behavior that I described in More Large File Problems.
Another drawback is that File.Copy doesn't expose the full functionality of the CopyFileEx API function. CopyFileEx lets you monitor progress and also lets you cancel the copy operation--things that File.Copy doesn't let you do because it calls the older CopyFile API function, which lacks those features.
In A Faster File Copy, I presented a method that outperforms File.Copy, but it too lacks the advanced features of CopyFileEx, and also can trigger the bad caching behavior.
In this article, I'll look more closely at CopyFileEx and show how to expose its full functionality to .NET programs.
The CopyFileEx API function
The C prototype for CopyFileEx looks like this:
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 two parameters are simply the source and destination file names. The last parameter is used to specify flags that control how the file is to be copied. The documentation has full descriptions of how those flags affect the copy.
The third parameter is the address of a function that is called by CopyFileEx to report progress. In the API, it's called CopyProgressRoutine:
DWORD CALLBACK CopyProgressRoutine( __in LARGE_INTEGER TotalFileSize, __in LARGE_INTEGER TotalBytesTransferred, __in LARGE_INTEGER StreamSize, __in LARGE_INTEGER StreamBytesTransferred, __in DWORD dwStreamNumber, __in DWORD dwCallbackReason, __in HANDLE hSourceFile, __in HANDLE hDestinationFile, __in_opt LPVOID lpData );
Most of those parameters are self explanatory. dwCallbackReason will be one of these two values:
- CALLBACK_CHUNK_FINISHED (0) - Another part of the file was copied.
- CALLBACK_STREAM_SWITCH (1) - Another stream was created and is about to be copied. This is the value passed when the callback is first invoked.
The return value of the function should be one of four values:
- PROGRESS_CANCEL (1) - Cancel the copy operation and close the destination file.
- PROGRESS_CONTINUE (0) - Continue the copy operation
- PROGRESS_QUIET (3) - Continue the copy operation, but stop calling the progress routine.
- PROGRESS_STOP (4) - Stop the copy operation. It can be restarted at a later time.
The lpData parameter passed to the CopyProgressRoutine is the same as the lpData parameter that the caller passes to CopyFileEx. It is user-specified data that the API function doesn't use, but instead passes to the progress routine.
The pbCancel parameter to CopyFileEx is a pointer to a BOOL (a 32-bit integer value) that, if set at any time during the copy progress, will cancel the operation.
You'll note that there are two ways to cancel a copy: you can set the value referred to by pbCancel to 1, or you can return PROGRESS_CANCEL or PROGRESS_STOP from the progress function. Having two ways to cancel means that you don't have to implement the progress function if you want the ability to cancel.
The lpProgressRoutine, pbCancel, and lpData, parameters to CopyFileEx are optional. If set to NULL, the corresponding functionality is not available.
Calling CopyFileEx from C#
Ultimately, I want to create a method called CopyFile that has this signature:
static public void CopyFile(
string sourceFilename,
string destinationFilename,
CopyProgressDelegate progressHandler,
Object userData,
CopyFileOptions copyOptions,
CancellationToken cancelToken)
And, of course, simplified versions that let you dispense with the unnecessary things if you want to. For example, you should be able to replace calls to File.Copy with this:
FileUtil.CopyFile(srcFilename, destFilename);
But I'm getting a little ahead of myself. First we have to create a managed wrapper for the Windows API.
In order to call CopyFileEx from C#, we need to create a managed prototype for the function, and also provide definitions for the progress routine. I showed a very simplified version in More Large File Problems, but that didn't provide the progress or cancel functionality.
CopyFileEx makes use of several sets of constants. When converting to .NET, we typically gather related constants and create an enum. So there are three separate enumerations:
[Flags]
public enum CopyFileOptions
{
None = 0,
AllowDecryptedDestination = 0x00000008,
CopySumlink = 0x00000800,
FailIfExists = 0x00000001,
NoBuffering = 0x00001000,
OpenSourceForWrite = 0x00000004,
Restartable = 0x00000002
}
public enum CopyCallbackReason
{
ChunkFinished = 0,
StreamSwitch = 1
}
public enum ProgressCallbackResult
{
Continue = 0,
Cancel = 1,
Stop = 2,
Quiet = 3
}
The CopyProgressRoutine has a lot of parameters. A literal translation to C# would look like this:
public delegate Int32 APICopyProgressRoutine(
Int64 TotalFileSize,
Int64 TotalBytesTransferred,
Int64 StreamSize,
Int64 StreamBytesTransferred,
Int32 StreamNumber,
Int32 CallbackReason,
IntPtr SourceHandle,
IntPtr DestinationHandle,
Object UserData);
Given those definitions, the managed prototype for CopyFileEx becomes:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CopyFileEx(
string lpExistingFileName,
string lpNewFileName,
APICopyProgressRoutine lpProgressRoutine,
Object lpData,
ref Int32 lpCancel,
CopyFileOptions dwCopyFlags);
Whereas it's possible to call that directly, there are some decidedly unfriendly things lurking in the call. First, I think it's too much to ask a client of my new file copy method to create a callback function that takes nine parameters. So I've created a class, CopyProgressArgs to contain all of that data.
public class CopyProgressArgs
{
public readonly Int64 TotalFileSize;
public readonly Int64 TotalBytesTransferred;
public readonly Int64 StreamSize;
public readonly Int64 StreamBytesTransferred;
public readonly Int32 StreamNumber;
public readonly Int32 CallbackReason;
public readonly IntPtr SourceHandle;
public readonly IntPtr DestinationHandle;
public readonly Object UserData;
public ProgressCallbackResult Result { get; set; }
public CopyProgressArgs(Int64 fsize, Int64 xferBytes, Int64 strmSize, Int64 strmXferBytes,
Int32 strmNum, Int32 reason, IntPtr srcHandle, IntPtr destHandle, Object uData)
{
TotalFileSize = fsize;
TotalBytesTransferred = xferBytes;
StreamSize = strmSize;
StreamBytesTransferred = strmXferBytes;
StreamNumber = strmNum;
CallbackReason = reason;
SourceHandle = srcHandle;
DestinationHandle = destHandle;
UserData = uData;
}
}
The progress delegate, then, is a simple method that takes that one parameter:
public delegate void CopyProgressDelegate(CopyProgressArgs e);
Forcing the user to pass a reference to a memory location in order to support cancellation is not a particularly .NET-friendly way to do things, either. .NET 4.0 introduced a new unified model for cancellation, which is more flexible and easier to use. The new file copy method will make use that, instead.
Internally, the code has to translate calls to the .NET-friendly method:
static public void CopyFile(
string sourceFilename,
string destinationFilename,
CopyProgressDelegate progressHandler,
Object userData,
CopyFileOptions copyOptions,
CancellationToken cancelToken)
To the Windows API function:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CopyFileEx(
string lpExistingFileName,
string lpNewFileName,
APICopyProgressRoutine lpProgressRoutine,
Object lpData,
ref Int32 lpCancel,
CopyFileOptions dwCopyFlags);
That's all done by a private method called CopyFileInternal:
struct CopyFileResult
{
public readonly bool ReturnValue;
public readonly int LastError;
public CopyFileResult(bool rslt, int err)
{
ReturnValue = rslt;
LastError = err;
}
}
static private CopyFileResult CopyFileInternal(
string sourceFilename,
string destinationFilename,
CopyProgressDelegate progressHandler,
Object userData,
CopyFileOptions copyOptions,
CancellationToken cancelToken)
{
// On error, throw IOException with the value from Marshal.GetLastWin32Error
CopyProgressDelegate handler = progressHandler;
int cancelFlag = 0;
APICopyProgressRoutine callback;
if (handler == null)
{
callback = null;
}
else
{
callback = new APICopyProgressRoutine((tfSize, xferBytes, strmSize, strmXferBytes,
strmNum, cbReason, srcHandle, dstHandle, udata) =>
{
var args = new CopyProgressArgs(tfSize, xferBytes, strmSize, strmXferBytes,
strmNum, cbReason, srcHandle, dstHandle, udata);
handler(args);
return (Int32)args.Result;
});
}
if (cancelToken.CanBeCanceled)
{
cancelToken.Register(() => { cancelFlag = 1; });
}
bool rslt = CopyFileEx(
sourceFilename,
destinationFilename,
callback,
userData,
ref cancelFlag,
copyOptions);
int err = 0;
if (!rslt)
{
err = Marshal.GetLastWin32Error();
}
return new CopyFileResult(rslt, err);
}
CopyFileInternal returns a CopyFileResult, which contains a return value as well as a LastError value. The reason for this is so that the code can properly handle asynchronous requests, as you'll see later.
CopyFileInternal takes care of translating parameters, for the call to CopyFileEx, creating the Windows-friendly callback method, and marshaling those callbacks to the .NET-friendly callback delegate. It also handles setting up the pbCancel flag and making it work with the CancellationToken passed by the client.
When the copy operation is done, CopyFileInternal gets the return value and, if necessary, the Win32 error code, and returns them to the caller.
With CopyFileInternal doing all the heavy lifting, the public CopyFile method is quite simple:
static public void CopyFile(
string sourceFilename,
string destinationFilename,
CopyProgressDelegate progressHandler,
Object userData,
CopyFileOptions copyOptions,
CancellationToken cancelToken)
{
var rslt = CopyFileInternal(
sourceFilename,
destinationFilename,
progressHandler,
userData,
copyOptions,
cancelToken);
if (!rslt.ReturnValue)
{
throw new IOException(string.Format("Error copying file. GetLastError returns {0}.",
rslt.LastError));
}
}
Error handling here could be better. As it is, CopyFile will throw IOException on error, and just report the last Win32 error code. More robust error handling would give a better description of the particular error. For example, the destination drive is full, cannot access the source file, etc.
In the complete class shown below, you'll see that I've added some convenience methods like CopyFileNoBuffering, which takes just the source and destination file names and automatically supplies the CopyFileOptions.NoBuffering flag.
I also added BeginCopyFile and EndCopyFile methods so that clients can easily start an asynchronous copy using the typical .NET asynchronous call semantics.
Here's the full source for the FileUtil class that contains the various CopyFile overloads and the associated definitions.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Threading;
namespace Mischel.IO
{
[Flags]
public enum CopyFileOptions
{
None = 0,
AllowDecryptedDestination = 0x00000008,
CopySumlink = 0x00000800,
FailIfExists = 0x00000001,
NoBuffering = 0x00001000,
OpenSourceForWrite = 0x00000004,
Restartable = 0x00000002
}
public enum CopyCallbackReason
{
ChunkFinished = 0,
StreamSwitch = 1
}
public enum ProgressCallbackResult
{
Continue = 0,
Cancel = 1,
Stop = 2,
Quiet = 3
}
public class CopyProgressArgs
{
public readonly Int64 TotalFileSize;
public readonly Int64 TotalBytesTransferred;
public readonly Int64 StreamSize;
public readonly Int64 StreamBytesTransferred;
public readonly Int32 StreamNumber;
public readonly Int32 CallbackReason;
public readonly IntPtr SourceHandle;
public readonly IntPtr DestinationHandle;
public readonly Object UserData;
public ProgressCallbackResult Result { get; set; }
public CopyProgressArgs(Int64 fsize, Int64 xferBytes, Int64 strmSize, Int64 strmXferBytes,
Int32 strmNum, Int32 reason, IntPtr srcHandle, IntPtr destHandle, Object uData)
{
TotalFileSize = fsize;
TotalBytesTransferred = xferBytes;
StreamSize = strmSize;
StreamBytesTransferred = strmXferBytes;
StreamNumber = strmNum;
CallbackReason = reason;
SourceHandle = srcHandle;
DestinationHandle = destHandle;
UserData = uData;
}
}
public delegate void CopyProgressDelegate(CopyProgressArgs e);
public class FileUtil
{
private delegate Int32 APICopyProgressRoutine(
Int64 TotalFileSize,
Int64 TotalBytesTransferred,
Int64 StreamSize,
Int64 StreamBytesTransferred,
Int32 StreamNumber,
Int32 CallbackReason,
IntPtr SourceHandle,
IntPtr DestinationHandle,
Object UserData);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CopyFileEx(
string lpExistingFileName,
string lpNewFileName,
APICopyProgressRoutine lpProgressRoutine,
Object lpData,
ref Int32 lpCancel,
CopyFileOptions dwCopyFlags);
struct CopyFileResult
{
public readonly bool ReturnValue;
public readonly int LastError;
public CopyFileResult(bool rslt, int err)
{
ReturnValue = rslt;
LastError = err;
}
}
static private CopyFileResult CopyFileInternal(
string sourceFilename,
string destinationFilename,
CopyProgressDelegate progressHandler,
Object userData,
CopyFileOptions copyOptions,
CancellationToken cancelToken)
{
// On error, throw IOException with the value from Marshal.GetLastWin32Error
CopyProgressDelegate handler = progressHandler;
int cancelFlag = 0;
APICopyProgressRoutine callback;
if (handler == null)
{
callback = null;
}
else
{
callback = new APICopyProgressRoutine((tfSize, xferBytes, strmSize, strmXferBytes,
strmNum, cbReason, srcHandle, dstHandle, udata) =>
{
var args = new CopyProgressArgs(tfSize, xferBytes, strmSize, strmXferBytes,
strmNum, cbReason, srcHandle, dstHandle, udata);
handler(args);
return (Int32)args.Result;
});
}
if (cancelToken.CanBeCanceled)
{
cancelToken.Register(() => { cancelFlag = 1; });
}
bool rslt = CopyFileEx(
sourceFilename,
destinationFilename,
callback,
userData,
ref cancelFlag,
copyOptions);
int err = 0;
if (!rslt)
{
err = Marshal.GetLastWin32Error();
}
return new CopyFileResult(rslt, err);
}
static public void CopyFile(
string sourceFilename,
string destinationFilename,
CopyProgressDelegate progressHandler,
Object userData,
CopyFileOptions copyOptions,
CancellationToken cancelToken)
{
var rslt = CopyFileInternal(
sourceFilename,
destinationFilename,
progressHandler,
userData,
copyOptions,
cancelToken);
if (!rslt.ReturnValue)
{
throw new IOException(string.Format("Error copying file. GetLastError returns {0}.",
rslt.LastError));
}
}
static public void CopyFile(
string sourceFilename,
string destinationFilename,
CopyProgressDelegate progressHandler,
Object userData,
CopyFileOptions copyOptions)
{
CopyFile(sourceFilename, destinationFilename, progressHandler, userData, copyOptions,
CancellationToken.None);
}
static public void CopyFile(
string sourceFilename,
string destinationFilename)
{
CopyFile(sourceFilename, destinationFilename, null, null, CopyFileOptions.None);
}
static public void CopyFileNoBuffering(
string sourceFilename,
string destinationFilename)
{
CopyFile(sourceFilename, destinationFilename, null, null, CopyFileOptions.NoBuffering);
}
private delegate CopyFileResult CopyFileInvoker(
string sourceFilename,
string destinationFilename,
CopyProgressDelegate progressHandler,
Object userData,
CopyFileOptions copyOptions,
CancellationToken cancelToken);
static public IAsyncResult BeginCopyFile(
string sourceFilename,
string destinationFilename,
CopyProgressDelegate progressHandler,
Object userData,
CopyFileOptions copyOptions,
CancellationToken cancelToken,
AsyncCallback callback = null)
{
var caller = new CopyFileInvoker(CopyFileInternal);
return caller.BeginInvoke(
sourceFilename,
destinationFilename,
progressHandler,
userData,
copyOptions,
cancelToken,
callback,
null);
}
static public void EndCopyFile(IAsyncResult ar)
{
AsyncResult rslt = (AsyncResult)ar;
var caller = (CopyFileInvoker)rslt.AsyncDelegate;
var copyResult = caller.EndInvoke(ar);
if (!copyResult.ReturnValue)
{
throw new IOException(string.Format("Error copying file. GetLastError returns {0}.",
copyResult.LastError));
}
}
}
}
Because EndCopyFile will be called at some unknown time after CopyFileEx returns, there's no guarantee that a call to Marshal.GetLastWin32Error would return the correct value. That's the reason why CopyFileInternal gets the error code and returns it in the CopyFileResult structure. Otherwise, EndCopyFile wouldn't return the correct error value.
Using the new CopyFile method
In the simplest case, calling the new CopyFile method is just like calling File.Copy. That is, to copy from srcFilename to destFilename, you can write:
FileUtil.CopyFile(srcFilename, destFilename);
Or, if you want to avoid locking up a server that contains a very large file:
FileUtil.CopyFileNoBuffering(srcFilename, destFilename);
If you want to monitor progress and support cancellation, you have to do just slightly more work. The code below starts an asynchronous copy and reports progress as each chunk is copied. At any point, you can press Enter to cancel the copy operation.
static int Main(string[] args)
{
string srcFilename = args[0];
string destFilename = args[1];
Console.WriteLine("Copying {0} to {1}", srcFilename, destFilename);
Console.WriteLine("Press Enter to cancel the copy operation.");
var cts = new CancellationTokenSource();
try
{
var ar = FileUtil.BeginCopyFile(
srcFilename,
destFilename,
MyCopyProgress,
"Hello, world",
CopyFileOptions.NoBuffering,
cts.Token,
CopyDoneCallback);
// Wait for user to press Enter.
Console.ReadLine();
// set the cancel flag
cts.Cancel();
// wait for the async call to finish
ar.AsyncWaitHandle.WaitOne();
}
finally
{
cts.Dispose();
}
return 0;
}
static void CopyDoneCallback(IAsyncResult ar)
{
Console.WriteLine();
Console.WriteLine("Copy done.");
try
{
FileUtil.EndCopyFile(ar);
}
catch (IOException ex)
{
Console.WriteLine(ex.Message);
}
}
static void MyCopyProgress(CopyProgressArgs e)
{
Console.Write("\r{0:N0}/{1:N0} ({2:P2})", e.TotalBytesTransferred, e.TotalFileSize,
(double)e.TotalBytesTransferred / e.TotalFileSize);
e.Result = ProgressCallbackResult.Continue;
}
It's a little more work, but not a lot more work. The benefits are huge. You can start a file copy in the background, monitor its progress, and cancel it at any time. In addition (and most important from my perspective), the ability to specify no buffering means that my programs won't inadvertently crash a server when it tries to copy large files.



