You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 19:29:44