如何将IFileOperation用于虚拟文件(IStream)?从IStream创建IShellItem
在C#中通过IFileOperation复制网络流文件(借助Shell原生UI)
核心思路
IFileOperation依赖Windows Shell对实际文件系统对象的处理逻辑,无法直接将IStream转换为可用于Copy()的IShellItem。必须先将网络流写入本地临时文件,再将该临时文件转为IShellItem,之后通过IFileOperation完成复制,最后清理临时文件。这种方式能让Shell自动处理覆盖询问、权限验证等原生交互逻辑。
实现步骤
1. 将网络流写入临时文件
利用系统临时目录生成唯一临时文件,把网络流内容写入该文件:
string tempFilePath = Path.GetTempFileName(); using (var tempFileStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.Write)) { await networkStream.CopyToAsync(tempFileStream); }
2. 将临时文件转换为IShellItem
通过P/Invoke调用SHCreateItemFromParsingName函数,将文件路径转为IShellItem实例。首先需要定义相关COM接口和P/Invoke声明:
using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; [ComImport] [Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IShellItem { void BindToHandler(IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); void GetParent(out IShellItem ppsi); void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName); void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); void Compare(IShellItem psi, uint hint, out int piOrder); } [ComImport] [Guid("947AAB5F-0A5C-4C13-B4D6-4BF7836FC9F8")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IFileOperation { void Advise([MarshalAs(UnmanagedType.Interface)] IFileOperationProgressSink pfops, out uint pdwCookie); void Unadvise(uint dwCookie); void SetOperationFlags(uint dwOperationFlags); void SetProgressMessage([MarshalAs(UnmanagedType.LPWStr)] string pszMessage); void SetProgressDialog([MarshalAs(UnmanagedType.Interface)] IOperationsProgressDialog popd); void SetProperties([MarshalAs(UnmanagedType.Interface)] IPropertyStore pStore); void SetOwnerWindow(IntPtr hwndOwner); void ApplyPropertiesToItem(IShellItem psi); void ApplyPropertiesToItems([MarshalAs(UnmanagedType.LPArray)] IShellItem[] apItems); void RenameItem(IShellItem psi, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, [MarshalAs(UnmanagedType.Interface)] IFileOperationProgressSink pfops); void RenameItems([MarshalAs(UnmanagedType.LPArray)] IShellItem[] apItems, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName); void MoveItem(IShellItem psi, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, [MarshalAs(UnmanagedType.Interface)] IFileOperationProgressSink pfops); void MoveItems([MarshalAs(UnmanagedType.LPArray)] IShellItem[] apItems, IShellItem psiDestinationFolder); void CopyItem(IShellItem psi, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, [MarshalAs(UnmanagedType.Interface)] IFileOperationProgressSink pfops); void CopyItems([MarshalAs(UnmanagedType.LPArray)] IShellItem[] apItems, IShellItem psiDestinationFolder); void DeleteItem(IShellItem psi, [MarshalAs(UnmanagedType.Interface)] IFileOperationProgressSink pfops); void DeleteItems([MarshalAs(UnmanagedType.LPArray)] IShellItem[] apItems); void NewItem(IShellItem psiDestinationFolder, uint dwFileAttributes, [MarshalAs(UnmanagedType.LPWStr)] string pszName, [MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName, [MarshalAs(UnmanagedType.Interface)] IFileOperationProgressSink pfops); void PerformOperations(); void GetAnyOperationsAborted(out bool pfAnyOperationsAborted); } [ComImport] [Guid("0002DF0A-0000-0000-C000-000000000046")] public class FileOperation { } public enum SIGDN : uint { SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, SIGDN_FILESYSPATH = 0x80058000 } [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void SHCreateItemFromParsingName( [MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv);
然后执行转换:
IShellItem tempItem; Guid shellItemGuid = typeof(IShellItem).GUID; SHCreateItemFromParsingName(tempFilePath, IntPtr.Zero, shellItemGuid, out tempItem);
3. 初始化IFileOperation并执行复制
创建IFileOperation实例,设置操作标志(保留Shell交互弹窗),指定目标目录和文件名后执行复制:
// 获取目标目录的IShellItem IShellItem destFolder; SHCreateItemFromParsingName(@"C:\Your\Target\Directory", IntPtr.Zero, shellItemGuid, out destFolder); // 创建IFileOperation实例 IFileOperation fileOp = (IFileOperation)new FileOperation(); // 设置操作标志:允许撤销、显示进度UI、自动创建目标目录(可按需调整) const uint FOF_ALLOWUNDO = 0x40; const uint FOF_SHOWPROGRESS = 0x00000040; const uint FOF_NOCONFIRMMKDIR = 0x200; fileOp.SetOperationFlags(FOF_ALLOWUNDO | FOF_SHOWPROGRESS | FOF_NOCONFIRMMKDIR); // 设置弹窗父窗口(可选) fileOp.SetOwnerWindow(IntPtr.Zero); // 执行复制:第三个参数为最终保存的文件名 fileOp.CopyItem(tempItem, destFolder, "YourFinalFileName.ext", null); // 完成操作 fileOp.PerformOperations();
4. 清理临时文件
复制完成后删除临时文件:
File.Delete(tempFilePath);
关键说明
- 临时文件的必要性:IFileOperation仅能处理已存在的文件系统对象(本地文件、网络共享文件等),无法直接识别内存流或网络流,因此必须先落地为临时文件。
- Shell原生交互:只要不设置
FOF_NOCONFIRMATION标志,Shell会自动弹出覆盖确认、权限验证等系统原生对话框,无需额外编码。 - 错误处理:可通过
try-catch捕获COM异常,或实现IFileOperationProgressSink接口监听操作进度和错误详情。
内容的提问来源于stack exchange,提问作者sbarrac
相关产品推荐
相关产品推荐

