microsoft / win32metadata

Tooling to generate metadata for Win32 APIs in the Windows SDK.
Other
1.32k stars 113 forks source link

Recognize COM _NewEnum method as IEnumerable (possibly by parsing .idl file) #1918

Open TSlivede opened 2 months ago

TSlivede commented 2 months ago

Is your feature request related to a problem? Please describe. CsWin32 doesn't seem to recognise _NewEnum() as equivalent to IEnumerable, which makes it hard to implement an Interface like FolderItems which CsWin32 generates as:

[Guid("744129E0-CBE5-11CE-8350-444553540000"),InterfaceType(ComInterfaceType.InterfaceIsDual),ComImport()]
[global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Windows.CsWin32", "0.3.106+a37a0b4b70")]
internal interface FolderItems
{
    // ... other properties and methods here ...

    [return: MarshalAs(UnmanagedType.IUnknown)]
    object _NewEnum();
}

As far as I can tell that would require me to implement my own IEnumVARIANT to return from _NewEnum.

Describe the solution you'd like If I instead add Shell32.dll as COM Reference, Visual studio generates the same interface as:

[ComImport]
[Guid("744129E0-CBE5-11CE-8350-444553540000")]
[TypeLibType(4160)]
public interface FolderItems : IEnumerable
{
    // ... other properties and methods here ...

    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    [DispId(-4)]
    [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalType = "System.Runtime.InteropServices.CustomMarshalers.EnumeratorToEnumVariantMarshaler, CustomMarshalers, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
    new IEnumerator GetEnumerator();
}

This makes it much more convenient to implement the interface. (No member named _NewEnum is generated at all, instead the [DispId(-4)] attribute is used to map to the correct IDispatch member (see DISPID_NEWENUM).)

Describe alternatives you've considered I could probably use both cswin32 and Visual Studio Add COM Reference in the same project, but that does not seem like the right way to do it...

Additional context Similar: https://github.com/microsoft/CsWin32/issues/824

AArnott commented 2 months ago

The win32metadata expresses the result of _NewEnum as just an IUnknown*, so CsWin32 is doing the best it can here. But I see what you mean. The docs say that IEnumVARIANT is always returned. So the metadata should probably special case this and deviate from the header definition in order to make this more user-friendly.

riverar commented 2 months ago

I don't think there's anything to do in metadata for this. CsWin32 should probably instead detect that it is in an IDispatch/OLE context (T : IDispatch) and transform _NewEnum to whatever method name is desired (e.g., GetEnumerator).

AArnott commented 2 months ago

This isn't about the method name. This is about the return type. And there's nothing in the metadata today that could serve as a hint to CsWin32 that IEnumVARIANT is the more appropriate return type. The docs say it though. Can we special case it in the metadata?

kennykerr commented 2 months ago

Are you referring to these docs?

image

Note that when a COM object "supports" an interface it means the caller must query for that interface using QueryInterface. It does not imply that the interface pointer you happen to have is actually that interface. If you change the metadata, it will imply that the returned interface actually points to that vtable which may not be correct.

riverar commented 2 months ago

Additional more explicit docs here: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/automat/implementing-the--newenum-property

AArnott commented 2 months ago

Ok, I see what you mean. Good points. Then I guess we should Won't Fix this. Maybe one day we'll have capacity in CsWin32 to do a custom marshaler as the OP requests to workaround this, but I don't see it happening in the foreseeable future.

riverar commented 2 months ago

@TSlivede Can you share a bit more information on what you're trying to do that necessitates implementing FolderItems?

TSlivede commented 2 months ago

I already solved my specific problem, this issue was more intended to improve the state of generated interface declarations. There are probably more interfaces for which _NewEnum could better be handled by the EnumeratorToEnumVariantMarshaler, right?

Regarding the current Metadata:

Based on what I can see via ILSpy, currently the Metadata is:

[Documentation("https://learn.microsoft.com/windows/win32/shell/folderitems--newenum")]
unsafe HRESULT _NewEnum([Out][RetVal] IUnknown* ppunk);

The type can of course not simply be changed to IEnumVARIANT*, because (as @kennykerr mentioned) just because the object supports the interface, doesn't mean that the pointer points to that interface.

Would it instead be possible for the metadata to include some Attribute, that documents that the object supports additional interfaces? Sadly the header file ShlDisp.h doesn't actually contain any information, that could be used to find out, that _NewEnum returns a pointer that supports IEnumVARIANT* (except guessing from the name, but that seems like a bad idea). My suggestion would be to implement parsing of the IDL files in addition to the c/c++ headers, as shldisp.idl defines the _NewEnum member of FolderItems as

[id(-4), helpstring("Enumerates the figures")]
HRESULT _NewEnum([out, retval] IUnknown **ppunk);

As the dispatch id of -4 (DISPID_NEWENUM) unambiguously indicates that the member returns a pointer that supports the IEnumVARIANT interface, this could then be reflected via some attribute in the metadata. (Which could later be used by CsWin32 to improve the generated code.)

I do understand, that it's a lot to ask and that it probably won't happen in the near (or even far) future, but maybe this issue could remain until someone who has too much time wants to tackle it? 😉

Additional motivation to parse IDL files: The IDL can contain helpstrings, which could be used to generate documentation comments for the c# code. I know that for the win32 APIs there are already links to the actual microsoft online documentation. But this is not only the project for the Metadata of the Win32 APIs but also AFAIK the home of the more general WinmdGenerator which can be used for other c libraries. And in that case it could be really helpful to pass through the helpstrings from IDL files...

What I was actually doing: "necessitates implementing" might be a bit strong, but I was essentially trying to do the same as this guy: [Copying multiple items with Powershell and shell.application.copyHere](https://stackoverflow.com/questions/60381325/copying-multiple-items-with-powershell-and-shell-application-copyhere) As I'm using powershell, I only wanted to use CsWin32 to generate the c# interface signature and embed the generated c# code via `Add-Type` into the powershell script. As I don't have any dependencies to other code generated by CsWin32, I simply used the code generated by adding Shell32.dll as COM Reference in Visual Studio. I'm aware, that I could use `SHFileOperation` or `IFileOperation` instead, but that seems to require even more embedded c# code within the powershell script.