Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
TSlivede opened this issue Jun 2, 2024 · 8 comments
Labels
enhancement New feature or request

Comments

@TSlivede
Copy link

TSlivede commented Jun 2, 2024

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: microsoft/CsWin32#824

@TSlivede TSlivede added the enhancement New feature or request label Jun 2, 2024
@AArnott
Copy link
Member

AArnott commented Jun 3, 2024

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.

@AArnott AArnott transferred this issue from microsoft/CsWin32 Jun 3, 2024
@riverar
Copy link
Collaborator

riverar commented Jun 14, 2024

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
Copy link
Member

AArnott commented Jun 14, 2024

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
Copy link
Contributor

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
Copy link
Collaborator

riverar commented Jun 14, 2024

@AArnott
Copy link
Member

AArnott commented Jun 14, 2024

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
Copy link
Collaborator

riverar commented Jun 15, 2024

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

@TSlivede
Copy link
Author

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

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.

@TSlivede TSlivede changed the title Recognize COM _NewEnum method as IEnumerable Recognize COM _NewEnum method as IEnumerable (possibly by parsing .idl file) Jun 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants