C# - CopyFileEx API 사용 예제 코드
닷넷에서, 파일 복사의 진행 여부를 보여주려면 복사 과정을 직접 구현해야 합니다. 반면, Win32 API로 제공하는 CopyFileEx를 사용하면,
CopyFileExW function (winbase.h)
; https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfileexw
C#으로 이렇게 만들어 볼 수 있습니다.
// https://www.pinvoke.net/default.aspx/kernel32.copyfileex
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
class Program
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
unsafe static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName,
delegate* unmanaged[Stdcall]<long, long, long, long, uint, CopyProgressCallbackReason, IntPtr, IntPtr, IntPtr, CopyProgressResult> lpProgressRoutine,
IntPtr lpData, ref Int32 pbCancel, CopyFileFlags dwCopyFlags);
[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvStdcall) })]
static CopyProgressResult CopyProgressRoutineCallback(
long TotalFileSize,
long TotalBytesTransferred,
long StreamSize,
long StreamBytesTransferred,
uint dwStreamNumber,
CopyProgressCallbackReason dwCallbackReason,
IntPtr hSourceFile,
IntPtr hDestinationFile,
IntPtr lpData)
{
Console.WriteLine($"{TotalBytesTransferred} / {TotalFileSize}");
return CopyProgressResult.PROGRESS_CONTINUE;
}
static void Main(string[] args)
{
string srcFile = @"C:\temp\test.vhdx";
string dstFile = Path.ChangeExtension(srcFile, ".tmp");
int bCancel = 0;
unsafe
{
CopyFileEx(srcFile, dstFile, &CopyProgressRoutineCallback, IntPtr.Zero, ref bCancel, CopyFileFlags.COPY_FILE_RESTARTABLE);
}
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
}
[Flags]
enum CopyFileFlags : uint
{
COPY_FILE_NONE = 0,
COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
COPY_FILE_RESTARTABLE = 0x00000002,
COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008,
COPY_FILE_COPY_SYMLINK = 0x00000800, //NT 6.0+
COPY_FILE_NO_BUFFERING = 0x00001000 //NT 6.0+
}
enum CopyProgressCallbackReason : uint
{
CALLBACK_CHUNK_FINISHED = 0x00000000,
CALLBACK_STREAM_SWITCH = 0x00000001
}
enum CopyProgressResult : uint
{
PROGRESS_CONTINUE = 0,
PROGRESS_CANCEL = 1,
PROGRESS_STOP = 2,
PROGRESS_QUIET = 3
}
#if !NET5_0
namespace System.Runtime.InteropServices
{
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class UnmanagedCallersOnlyAttribute : Attribute
{
public Type[] CallConvs;
public string EntryPoint;
}
}
#endif
그런데, 유의해야 할 점이 있는데 위의 코드는 64비트 환경에서만 잘 동작합니다. 왜냐하면, 이전에 언급했던,
UnmanagedCallersOnly + C# 9.0 함수 포인터 사용 시 x86 빌드에서 오동작하는 문제
; https://www.sysnet.pe.kr/2/0/12431
이유 때문입니다. 따라서, 만약 32비트 빌드로도 동작하게 만들고 싶다면 UnmanagedCallersOnly + 함수 포인터가 아닌 델리게이트를 이용하는 것으로 코드를 변경해야 합니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]