Как использовать SHFileOperation в .NET. На самом деле все просто.
Итак, во-первых нужно объявить структуру SHFILEOPSTRUCT. Это определение из MSDN:
typedef struct _SHFILEOPSTRUCT { HWND hwnd; UINT wFunc; LPCTSTR pFrom; LPCTSTR pTo; FILEOP_FLAGS fFlags; BOOL fAnyOperationsAborted; LPVOID hNameMappings; LPCTSTR lpszProgressTitle; } SHFILEOPSTRUCT, *LPSHFILEOPSTRUCT;На первый взгляд декларация должна быть такой:
//это плохое объявление [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] internal struct SHFILEOPSTRUCT { internal IntPtr hwnd; internal int wFunc; [MarshalAs(UnmanagedType.LPTStr)] internal string pFrom; [MarshalAs(UnmanagedType.LPTStr)] internal string pTo; internal ushort fFlags; internal bool fAnyOperationsAborted; internal IntPtr hNameMappings; [MarshalAs(UnmanagedType.LPTStr)] internal string lpszProgressTitle; }и на 64-битной платформе оно будет работать, но на 32-битной системе оно не прокатывает. Внимательно читаем Shellapi.h и видим
#if !defined(_WIN64) #include <pshpack1.h>#endifВот оно тяжелое наследие. Это означает, что, если программа на с++ будет собираться под платформу, отличную от 64-битной, то структура SHFILEOPSTRUCT будет иметь 1-байтовое выравнивание, в противном случае - автоматическое выравнивание. В нашем случае придется объявлять два варианта с разным выравниванием. С перечислением wFunc и флагом fFlags все просто: берем константы из shellapi.h и превращаем их для удобства в enum'ы.
public enum ShFileOperationFunc { FO_MOVE = 0x0001, FO_COPY = 0x0002, FO_DELETE = 0x0003, FO_RENAME = 0x0004 } public enum ShFileOperationFlag : ushort { FOF_MULTIDESTFILES = 0x0001, /// <summary> /// Не используется /// </summary> FOF_CONFIRMMOUSE = 0x0002, /// <summary> /// не показывать прогресс /// </summary> FOF_SILENT = 0x0004, /// <summary> /// переименовывать файлы в случае совпадения имен /// </summary> FOF_RENAMEONCOLLISION = 0x0008, /// <summary> /// не показывать диалоги подтверждения /// </summary> FOF_NOCONFIRMATION = 0x0010, /// <summary> /// Заполнять поле SHFILEOPSTRUCT.hNameMappings, нужно освобождать вызовом SHFreeNameMappings /// </summary> FOF_WANTMAPPINGHANDLE = 0x0020, /// <summary> /// включает возможность отмены операций, в том числе удаление в корзину /// </summary> FOF_ALLOWUNDO = 0x0040, /// <summary> /// обрабатывать только файлы (но не директории) /// </summary> FOF_FILESONLY = 0x0080, /// <summary> /// не показывать имена файлов в окне прогресса /// </summary> FOF_SIMPLEPROGRESS = 0x0100, /// <summary> /// не показывать подтверждения для создания новых директорий /// </summary> FOF_NOCONFIRMMKDIR = 0x0200, /// <summary> /// не показывать сообщение об ошибке /// </summary> FOF_NOERRORUI = 0x0400, /// <summary> /// не копировать атрибуты безопасности /// </summary> FOF_NOCOPYSECURITYATTRIBS = 0x0800, /// <summary> /// не обходить рекурсивно директории (при удалении один хрен - не пустые директории все равно удаляются) /// </summary> FOF_NORECURSION = 0x1000, /// <summary> /// не обрабатывать присоединенные элементы (типа директорий XXX_files, которые создаются вместе с XXX.htm) /// </summary> FOF_NO_CONNECTED_ELEMENTS = 0x2000, /// <summary> /// предупреждать при безвозвратном удалении (перекрывает FOF_NOCONFIRMATION) /// </summary> FOF_WANTNUKEWARNING = 0x4000, /// <summary> /// устарело /// </summary> FOF_NORECURSEREPARSE = 0x8000, /// <summary> /// не показывать пользователю ничего /// </summary> FOF_NO_UI = 0x0004 | 0x0010 | 0x0400 | 0x0200 }Теперь структуры выглядят так
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)] internal struct SHFILEOPSTRUCT_32 { // 32 битная структура internal IntPtr hwnd; internal ShFileOperationFunc wFunc; [MarshalAs(UnmanagedType.LPTStr)] internal string pFrom; [MarshalAs(UnmanagedType.LPTStr)] internal string pTo; internal ShFileOperationFlag fFlags; internal bool fAnyOperationsAborted; internal IntPtr hNameMappings; [MarshalAs(UnmanagedType.LPTStr)] internal string lpszProgressTitle; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] internal struct SHFILEOPSTRUCT_64 { // 64 битная структура internal IntPtr hwnd; internal ShFileOperationFunc wFunc; [MarshalAs(UnmanagedType.LPTStr)] internal string pFrom; [MarshalAs(UnmanagedType.LPTStr)] internal string pTo; internal ShFileOperationFlag fFlags; internal bool fAnyOperationsAborted; internal IntPtr hNameMappings; [MarshalAs(UnmanagedType.LPTStr)] internal string lpszProgressTitle; }нужно помнить, что поля pFrom и pTo это не совсем строки (опять тяжелое наследие), а как бы массивы строк. Они должны состоять из отдельных строк, разделенных символом '\0' и завершаться дополнительным '\0', показывающим конец. То есть в любом случае завершающих нуль-символов должно быть два. Составляем pFrom и pTo так:
internal static string build_shell_string(string[] inp) { if ((inp == null) || (inp.Length == 0)) { return null; } StringBuilder sb=new StringBuilder(); for (int i=0; i < inp.Length; i++) { sb.Append(inp[i]); sb.Append('\0'); } //завешающий \0 добавит маршализатор return sb.ToString(); }Определение самой функции:
[DllImport("shell32.dll", CharSet = CharSet.Auto, EntryPoint = "SHFileOperation", SetLastError = false)] internal static extern int SHFileOperation32 (ref SHFILEOPSTRUCT_32 lpFileOp); [DllImport("shell32.dll", CharSet = CharSet.Auto, EntryPoint = "SHFileOperation", SetLastError = false)] internal static extern int SHFileOperation64 (ref SHFILEOPSTRUCT_64 lpFileOp);Теперь разберемся с возвращаемым значением. Если отлично от нуля, значит произошла ошибка, причем MSDN велит не вызывать в этом случае GetLastError, а анализировать результат. Возвращается одна из констант ERROR_ из winerror.h или "до-win32" коды ошибок, причем вторые перекрывают первые. Таким образом для человеческой обработки ошибок понадобится сопоставить "до-win32" коды ошибок и коды ERROR_. Это слегка модифицированный код из far_uncode:
private static Win32Exception SHErrorToException(int SHError) { int WinError = SHError; switch (SHError) { case 0x71: WinError = ERROR_ALREADY_EXISTS; break; // DE_SAMEFILE The source and destination files are the same file. case 0x72: WinError = ERROR_INVALID_PARAMETER; break; // DE_MANYSRC1DEST Multiple file paths were specified in the source buffer, but only one destination file path. case 0x73: WinError = ERROR_NOT_SAME_DEVICE; break; // DE_DIFFDIR Rename operation was specified but the destination path is a different directory. Use the move operation instead. case 0x74: WinError = ERROR_ACCESS_DENIED; break; // DE_ROOTDIR The source is a root directory, which cannot be moved or renamed. case 0x75: WinError = ERROR_CANCELLED; break; // DE_OPCANCELLED The operation was cancelled by the user, or silently cancelled if the appropriate flags were supplied to SHFileOperation. case 0x76: WinError = ERROR_BAD_PATHNAME; break; // DE_DESTSUBTREE The destination is a subtree of the source. case 0x78: WinError = ERROR_ACCESS_DENIED; break; // DE_ACCESSDENIEDSRC Security settings denied access to the source. case 0x79: WinError = ERROR_BUFFER_OVERFLOW; break; // DE_PATHTOODEEP The source or destination path exceeded or would exceed MAX_PATH. case 0x7A: WinError = ERROR_INVALID_PARAMETER; break; // DE_MANYDEST The operation involved multiple destination paths, which can fail in the case of a move operation. case 0x7C: WinError = ERROR_BAD_PATHNAME; break; // DE_INVALIDFILES The path in the source or destination or both was invalid. case 0x7D: WinError = ERROR_INVALID_PARAMETER; break; // DE_DESTSAMETREE The source and destination have the same parent folder. case 0x7E: WinError = ERROR_ALREADY_EXISTS; break; // DE_FLDDESTISFILE The destination path is an existing file. case 0x80: WinError = ERROR_ALREADY_EXISTS; break; // DE_FILEDESTISFLD The destination path is an existing folder. case 0x81: WinError = ERROR_BUFFER_OVERFLOW; break; // DE_FILENAMETOOLONG The name of the file exceeds MAX_PATH. case 0x82: WinError = ERROR_WRITE_FAULT; break; // DE_DEST_IS_CDROM The destination is a read-only CD-ROM, possibly unformatted. case 0x83: WinError = ERROR_WRITE_FAULT; break; // DE_DEST_IS_DVD The destination is a read-only DVD, possibly unformatted. case 0x84: WinError = ERROR_WRITE_FAULT; break; // DE_DEST_IS_CDRECORD The destination is a writable CD-ROM, possibly unformatted. case 0x85: WinError = ERROR_DISK_FULL; break; // DE_FILE_TOO_LARGE The file involved in the operation is too large for the destination media or file system. case 0x86: WinError = ERROR_READ_FAULT; break; // DE_SRC_IS_CDROM The source is a read-only CD-ROM, possibly unformatted. case 0x87: WinError = ERROR_READ_FAULT; break; // DE_SRC_IS_DVD The source is a read-only DVD, possibly unformatted. case 0x88: WinError = ERROR_READ_FAULT; break; // DE_SRC_IS_CDRECORD The source is a writable CD-ROM, possibly unformatted. case 0xB7: WinError = ERROR_BUFFER_OVERFLOW; break; // DE_ERROR_MAX MAX_PATH was exceeded during the operation. case 0x402: WinError = ERROR_PATH_NOT_FOUND; break; // An unknown error occurred. This is typically due to an invalid path in the source or destination. This error does not occur on Windows Vista and later. case 0x10000: WinError = ERROR_GEN_FAILURE; break; // ERRORONDEST An unspecified error occurred on the destination. } return new Win32Exception(WinError); }Теперь осталось объединить всё написанное ранее в одну обертку
internal static int ShellFileOperation32 (string[] from, string[] to, ShFileOperationFunc func, ShFileOperationFlag opts, IntPtr hwnd, string progress_title) { //приготовляем аргумент SHFILEOPSTRUCT_32 arg=new SHFILEOPSTRUCT_32(); arg.fAnyOperationsAborted = false; arg.fFlags = opts; arg.hNameMappings = IntPtr.Zero; arg.hwnd = hwnd; arg.lpszProgressTitle = progress_title; arg.pFrom = build_shell_string(from); arg.pTo = build_shell_string(to); arg.wFunc = func; return SHFileOperation32(ref arg); } internal static int ShellFileOperation64 (string[] from, string[] to, ShFileOperationFunc func, ShFileOperationFlag opts, IntPtr hwnd,string progress_title) { //приготовляем аргумент SHFILEOPSTRUCT_64 arg=new SHFILEOPSTRUCT_64(); arg.fAnyOperationsAborted = false; arg.fFlags = opts; arg.hNameMappings = IntPtr.Zero; arg.hwnd = hwnd; arg.lpszProgressTitle = progress_title; arg.pFrom = build_shell_string(from); arg.pTo = build_shell_string(to); arg.wFunc = func; return SHFileOperation64(ref arg); } /// <summary> /// обертка для SHFileOperation /// </summary> /// <param name="from"></param> /// <param name="to"></param> /// <param name="func"></param> /// <param name="opts"></param> /// <param name="hwnd">указатель на родительское окно для показа диалогов</param> /// <param name="progress_title">заголовок окна прогресса, если установлено FOF_SIMPLEPROGRESS</param> public static void ShellFileOperation (string[] from, string[] to, ShFileOperationFunc func, ShFileOperationFlag opts, IntPtr hwnd, string progress_title) { int shell_res=0; if (IntPtr.Size == 4) { //32 бита shell_res = ShellFileOperation32(from, to, func, opts, hwnd, progress_title); } else { //64 бита shell_res = ShellFileOperation64(from, to, func, opts, hwnd, progress_title); } if (shell_res != 0) { throw SHErrorToException(shell_res); } }