【发布时间】: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 有效。
-
你的代码坏了,不应该出局,你必须分配并传递一个数组,并且只释放获取的项目