【问题标题】:Enumerating the shell with Next > 1使用 Next > 1 枚举 shell
【发布时间】:2017-12-21 19:30:29
【问题描述】:

网络上任何地方的所有示例代码都不会质疑将 1 作为第一个参数传递给枚举接口 Next() 调用的习惯。然而,文档清楚地承诺,一次通话可以获得不止一件物品。从这段代码片段中可以看出,这样做会极大地加快文件夹中文件的计数过程(实际上,基本相似的 WPD 界面就是这样工作的)。

string FolderPath = @"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\<Phone-USB-ID>\Internal storage\Pictures\Test";
SHCreateItemFromParsingName(FolderPath, IntPtr.Zero, typeof(IShellItem).GUID, out IShellItem item);
item.BindToHandler(IntPtr.Zero, BHID_SFObject, typeof(IShellFolder).GUID, out IShellFolder folder);
folder.EnumObjects(IntPtr.Zero, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, out IEnumIDList list);
uint count = 0;
try {
  do {
    //var ObjectIDs = new ObjectIDLargeArray();
    //var pidl = Marshal.AllocHGlobal(Marshal.SizeOf(ObjectIDs));
    //Marshal.StructureToPtr(ObjectIDs, pidl, true);
    //var pidl = Marshal.AllocCoTaskMem(100 * IntPtr.Size);
    int hr = list.Next(100, out var pidl, out uint fetched); // <<<<<
    if (hr == 0)
      count += fetched;
    if (fetched == 0)
      break;

    //Marshal.FreeHGlobal(pidl);
    Marshal.FreeCoTaskMem(pidl);
  }
  while (true);
}
catch (Exception e) {
  Console.WriteLine(e);
}

[StructLayout(LayoutKind.Sequential)]
internal class ObjectIDLargeArray {
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
  public IntPtr[] IDs;
}

网络上的文档和用法并不完全清楚调用者是否必须为返回的 ID 分配缓冲区,但从代码中可以看出,我尝试了所有方法,包括特定数组和分配的块的记忆。对于像 2 和 3 这样非常小的值,shell 可能会简单地在 fetched 中返回垃圾。对于更大的情况,访问冲突异常。

只是为了说明它的重要性:使用新连接的手机,之前没有缓存,计算包含 500 张图片的文件夹中的文件大约需要 15 (!) 秒。第二次已经缓存了数据,从 3 到 4。不用说,即使是第二次,对于单纯的文件计数来说也是不可接受的,但 15 秒绝对是愚蠢的。使用 WPD 的平庸速度,但 100 步计数,最多几百毫秒。

【问题讨论】:

  • 我不知道您的问题的答案,但是没有必要为作为out 参数传递给函数的局部变量分配任何内容。
  • 嗯,khhm,你在哪里看到这样的东西? - 哦是的。其中,如果 pidl 是预定义的,则那里没有类型。这就是为什么我说我不能绝对肯定地确定 MSDN 文档是完美的。
  • 文档明确指出必须分配 celt 元素的数组,但只能分配数组。数组中的项目可以为空,也可以不为空,但它们将在返回时分配(或不分配)(因此您需要遍历获取的项目并一一释放)。此外,即使接口实现者可能支持 celt > 1,但这并不意味着它必须这样做。如果没有,您将要求 10,它会将 1 放入 fetched(或 0)中
  • 是的,我知道假定的操作,但我的问题是它不这样做。 :-) 随着数组正式通过,我得到的只是一个很好的访问错误。只有 celt = 1 有效。
  • 你的代码坏了,不应该出局,你必须分配并传递一个数组,并且只释放获取的项目

标签: c# shell com


【解决方案1】:

由新的 C# 7.2 功能解决,in 参数:

internal interface IEnumIDList {
  [PreserveSig]
  [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
  HResult Next(uint celt, in IntPtr rgelt, out uint pceltFetched);
}

这使得它在预分配数组和单值情况下都可以正常运行。不幸的是,它并没有那么快...

【讨论】:

    猜你喜欢
    • 2014-06-22
    • 1970-01-01
    • 1970-01-01
    • 2012-01-30
    • 1970-01-01
    • 1970-01-01
    • 2012-11-05
    • 1970-01-01
    相关资源
    最近更新 更多