Skip to content

Commit b935451

Browse files
authored
Merge pull request #622 from Juknum/master
Fix IEnumExplorerCommand.Next implementation
2 parents af42874 + dbf8812 commit b935451

3 files changed

Lines changed: 71 additions & 10 deletions

File tree

‎Core/Collections/IEnumFromCom.cs‎

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections;
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Runtime.InteropServices;
45
using Vanara.PInvoke;
56

67
namespace Vanara.Collections;
@@ -45,8 +46,9 @@ public interface ICOMEnum<TElem>
4546
}
4647

4748
/// <summary>
48-
/// Creates an enumerable class from a get next method in the form of HRESULT Next(uint, TItem[], out uint) and a reset method. Useful
49-
/// if a class doesn't support <see cref="IEnumerable"/> or <see cref="IEnumerable{T}"/> like some COM objects.
49+
/// Creates an enumerable class from a get next method and a reset method. Useful if a class doesn't support <see cref="IEnumerable"/>
50+
/// or <see cref="IEnumerable{T}"/> like some COM objects. The reflected Next method can expose its fetched-count parameter either as
51+
/// out uint or as IntPtr.
5052
/// </summary>
5153
/// <typeparam name="TItem">The type of the item.</typeparam>
5254
public class IEnumFromCom<TItem> : IEnumFromNext<TItem>
@@ -104,18 +106,40 @@ private bool TryGet([NotNullWhen(true)] out TItem? item)
104106
return true;
105107
}
106108

107-
private class ComEnumWrapper<T>(T o) where T : class, ICOMEnum<TItem>
109+
private class ComEnumWrapper<T> where T : class, ICOMEnum<TItem>
108110
{
109-
private readonly T obj = o;
111+
private readonly T obj;
112+
private readonly bool nextUsesIntPtr;
113+
114+
public ComEnumWrapper(T o)
115+
{
116+
obj = o;
117+
var nextMethod = typeof(T).GetMethod("Next") ?? throw new MissingMethodException(typeof(T).FullName, "Next");
118+
var nextParams = nextMethod.GetParameters();
119+
nextUsesIntPtr = nextParams.Length > 2 && nextParams[2].ParameterType == typeof(IntPtr);
120+
}
110121

111122
public void ComObjReset() => ComInvoke("Reset");
112123

113124
public HRESULT ComObjTryGetNext(uint celt, TItem?[] rgelt, out uint celtFetched)
114125
{
115-
var para = new object[] { celt, rgelt, 0U };
116-
var hr = (HRESULT?)ComInvoke("Next", para);
117-
celtFetched = (uint)para[2];
118-
return hr ?? HRESULT.S_OK;
126+
IntPtr fetchedPtr = IntPtr.Zero;
127+
try
128+
{
129+
var para = nextUsesIntPtr
130+
? new object[] { celt, rgelt, fetchedPtr = Marshal.AllocHGlobal(sizeof(uint)) }
131+
: new object[] { celt, rgelt, 0U };
132+
if (fetchedPtr != IntPtr.Zero)
133+
Marshal.WriteInt32(fetchedPtr, 0);
134+
var hr = (HRESULT?)ComInvoke("Next", para);
135+
celtFetched = nextUsesIntPtr ? unchecked((uint)Marshal.ReadInt32(fetchedPtr)) : (uint)para[2];
136+
return hr ?? HRESULT.S_OK;
137+
}
138+
finally
139+
{
140+
if (fetchedPtr != IntPtr.Zero)
141+
Marshal.FreeHGlobal(fetchedPtr);
142+
}
119143
}
120144

121145
private object? ComInvoke(string meth, object[]? p = null) => typeof(T).GetMethod(meth)?.Invoke(obj, p);

‎PInvoke/Shell32/ShObjIdl.IExplorerCommand.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public interface IEnumExplorerCommand : Vanara.Collections.ICOMEnum<IExplorerCom
100100
/// <para>Type: <c>ULONG*</c></para>
101101
/// <para>
102102
/// When this method returns, contains a pointer to the number of elements actually retrieved. This pointer can be <c>NULL</c>
103-
/// if this information is not needed.
103+
/// if this information is not needed. In managed code, this optional pointer is represented by <see cref="IntPtr"/>.
104104
/// </para>
105105
/// </param>
106106
/// <returns>
@@ -110,7 +110,7 @@ public interface IEnumExplorerCommand : Vanara.Collections.ICOMEnum<IExplorerCom
110110
// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ienumexplorercommand-next HRESULT Next(
111111
// ULONG celt, IExplorerCommand **pUICommand, ULONG *pceltFetched );
112112
[PreserveSig]
113-
HRESULT Next([In] uint celt, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface, SizeParamIndex = 0)] IExplorerCommand[] pUICommand, out uint pceltFetched);
113+
HRESULT Next([In] uint celt, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface, SizeParamIndex = 0)] IExplorerCommand[] pUICommand, IntPtr pceltFetched);
114114

115115
/// <summary>Not currently implemented.</summary>
116116
/// <param name="celt">

‎UnitTests/PInvoke/Shell32/IShellFolderTests.cs‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using NUnit.Framework;
22
using System.Linq;
3+
using System.Runtime.InteropServices;
34
using Vanara.Collections;
45
using static Vanara.PInvoke.PropSys;
56
using static Vanara.PInvoke.Shell32;
@@ -50,6 +51,15 @@ public void EnumSearchesTest()
5051

5152
}
5253

54+
[Test]
55+
public void EnumFromComIntPtrFetchedCountTest()
56+
{
57+
var enumObj = new IntPtrEnum();
58+
var values = IEnumFromCom<int>.Create<IIntPtrEnum>(enumObj).ToArray();
59+
60+
Assert.That(values, Is.EqualTo(new[] { 1, 2 }));
61+
}
62+
5363
[Test]
5464
public void Issue530Test()
5565
{
@@ -78,4 +88,31 @@ public void Issue581Test()
7888
Assert.That(pFolder!.ParseDisplayName(default, default, System.IO.Path.GetFileName(TestCaseSources.WordDoc), out _, out var ppidl, ref attr), ResultIs.Successful);
7989
Assert.That(ppidl, ResultIs.ValidHandle);
8090
}
91+
92+
private interface IIntPtrEnum : ICOMEnum<int>
93+
{
94+
HRESULT Next(uint celt, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] int[] rgelt, IntPtr pceltFetched);
95+
96+
void Reset();
97+
}
98+
99+
private sealed class IntPtrEnum : IIntPtrEnum
100+
{
101+
private readonly int[] values = [1, 2];
102+
private int index;
103+
104+
public HRESULT Next(uint celt, int[] rgelt, IntPtr pceltFetched)
105+
{
106+
var fetched = 0;
107+
while (fetched < celt && index < values.Length)
108+
rgelt[fetched++] = values[index++];
109+
110+
if (pceltFetched != IntPtr.Zero)
111+
Marshal.WriteInt32(pceltFetched, fetched);
112+
113+
return fetched == celt ? HRESULT.S_OK : HRESULT.S_FALSE;
114+
}
115+
116+
public void Reset() => index = 0;
117+
}
81118
}

0 commit comments

Comments
 (0)