Skip to content

IDataTransferManagerInterop with void** parameter #246

Open

Description

@Marv51

Actual behavior

When I try to use IDataTransferManagerInterop the generated code uses a void** to return the requested DataTransferManager. I believe more comfortable code can and should be generated e.g. using an out DataTansferManager parameter.

Generated code:

[Guid("3A3DCD6C-3EAB-43DC-BCDE-45671CE800C8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), ComImport]
public interface IDataTransferManagerInterop
{
    unsafe void GetForWindow(HWND appWindow, global::System.Guid*riid, [Optional] void **dataTransferManager);
    void ShowShareUIForWindow(HWND appWindow);
}

as well as

    public static partial class FriendlyOverloadExtensions
    {
        public static unsafe void GetForWindow(this IDataTransferManagerInterop @this, HWND appWindow, in global::System.Guid riid, void **dataTransferManager)
        {
            // ...
        }
    }

Expected behavior

The generated code works. The unsafe nature of a void** pointer and the complexity to arrive at a working implementation lead me to file this issue.

Repro steps

  1. NativeMethods.txt content:
IDataTransferManagerInterop
  1. NativeMethods.json content (if present):
{
  "$schema": "https://aka.ms/CsWin32.schema.json",
  "public": true
}
  1. Any of your own code that should be shared?
using WinRT;
// riid of DataTransferManager, not IDataTransferManagerInterop 
var guid = Guid.Parse("a5caee9b-8708-49d1-8d36-67d25a8da00c"); 
var iop = DataTransferManager.As<IDataTransferManagerInterop>();
var transferManager = IntPtr.Zero;
iop.GetForWindow(PInvoke.GetActiveWindow(), guid, (void**)&transferManager);
var realManager = DataTransferManager.FromAbi(transferManager);

Context

  • CsWin32 version: 0.1.445-beta
  • Target Framework: .net 5.0

Activity

  1. changed the title [-]IDataTransferManagerInterop[/-] [+]IDataTransferManagerInterop with void** parameter[/+] on Apr 25, 2021
  2. AArnott commented on Apr 26, 2021

    @AArnott
    Member

    The type of the out parameter comes from the metadata, which comes from the header file. As seen in the docs, void** is the output type--it isn't typed as DataTransferManager in the API.

    Now, things can still be improved here. Newer versions of the metadata attribute this output parameter as a COM object pointer, so soon you'll see out object instead of void** or out void* in me CsWin32 method signature. So that will make it much simpler, but you'll still have to cast to DataTransferManager yourself.

    But I'm not sure where you'll get the definition of DataTransferManager from. It's not in the metadata as the metadata is of Win32 but DataTransferManager is a WinRT type. And if WinRT, I wonder if you should actually be getting this IDataTransferManagerInterop interface from a ref assembly produced by CsWinRT instead.

  3. Marv51 commented on Apr 27, 2021

    @Marv51
    Author

    out object is great! Thank you sir. That avoids all the unsafe operations that I was struggling with.

    CsWinRT delivers both IDataTransferManagerInterop and DataTransferManager. The Interop interface acts as a factory for the manager itself if I understand it correctly. Anyway, that is obviously not a topic for this repo.

  4. Marv51 commented on May 27, 2021

    @Marv51
    Author

    So with the newest CsWin32 preview (0.1.476-beta) the signature is now:

    object GetForWindow(this IDataTransferManagerInterop @this, HWND appWindow, in global::System.Guid riid)

    Like you mentioned Andrew Arnott (@AArnott).

    However, how can I cast that object to DataTransferManager? It's a System.__ComObject and I'm trying to cast to Windows.ApplicationModel.DataTransfer.DataTransferManager.

    All these attempts are failing:

    // riid of DataTransferManager
    var guid = Guid.Parse("a5caee9b-8708-49d1-8d36-67d25a8da00c");
    var iop = DataTransferManager.As<IDataTransferManagerInterop>();
    var manager = iop.GetForWindow(PInvoke.GetActiveWindow(), guid);
    
    var isWinRT = manager is IWinRTObject; // false
    var result = manager as DataTransferManager; // null
    var result2 = (DataTransferManager)manager; // throws Exception
    var result3 = manager.As<DataTransferManager>(); // throws ArgumentException
    var result4 = ABI.Windows.ApplicationModel.DataTransfer.DataTransferManager.GetAbi((IObjectReference)manager); // throws InvalidCastException
  5. AArnott commented on Jun 9, 2021

    @AArnott
    Member

    If DataTransferManager is a class, I imagine the cast is failing because that is a COM object and can only be cast to an interface. As to where you get an equivalent interface, I don't know.

  6. Marv51 commented on Jun 10, 2021

    @Marv51
    Author

    Andrew Arnott (@AArnott) That's disappointing, thanks for the reply. There does not seem to be any interface for that class. Could this class/method be reverted to the earlier signature with the void** return method? Or would it be CsWinRT responsibility to generate the needed interface?

  7. AArnott commented on Jun 11, 2021

    @AArnott
    Member

    CsWinRT only generates interfaces that appear in the win32metadata. I find it very odd that the metadata includes this method but for the "interface" you use it with, WinRT is required. So I'm a bit lost here.

    You can always get the void** signature by setting allowMarshaling: false in NativeMethods.json.

    But if I were you, I'd look at the COM class and declare it as an interface myself (complete with [Guid] attribute) as I expect that cast would succeed.

  8. Marv51 commented on Jun 14, 2021

    @Marv51
    Author

    Thank you for all your help here, but I'm ready to give up.

    If I set allowMarshaling: false then

    var iop = DataTransferManager.As<IDataTransferManagerInterop>();

    Results in an exception stating: "Unable to cast object of type 'WinRT.IInspectable' to type 'Windows.Win32.UI.Shell.IDataTransferManagerInterop'."
    I am not aware of any other way to get a class implementing IDataTransferManagerInterop.

    And as far as declaring the interface myself: I tried. But I cannot figure out how to marshal Windows.Foundation.TypedEventHandler<Windows.ApplicationModel.DataTransfer.DataTransferManager, Windows.ApplicationModel.DataTransfer.DataRequestedEventArgs> correctly, so the cast appears to work with my interface declaration, but the event does not.

    I have filed microsoft/Windows-classic-samples#190 in the hope that people there already know how this works.

  9. mikebattista commented on Aug 25, 2021

    @mikebattista
    Contributor

    I think #374 would help with this scenario. Interop with WinRT interop interfaces is something we want to improve on here.

  10. Marv51 commented on Sep 1, 2021

    @Marv51
    Author

    Andrew Arnott (@AArnott) According to microsoft/CsWinRT#970 (comment) returning object in this case is not correct and does not work.
    A suggested return type is IntPtr which can then be read using MarshalInterface<DataTransferManager>.FromAbi(result);.

    I'm unsure how this connects with #374. But I believe the current object return value needs to be fixed.

  11. asklar commented on Sep 1, 2021

    @asklar
    Member
  12. AArnott commented on Sep 2, 2021

    @AArnott
    Member

    Marvin Rühe (@Marv51) I believe in the WinRT object case, #384 will satisfy your concern. Steve Otteson (@sotteson1) can you confirm?

  13. sotteson1 commented on Sep 2, 2021

    @sotteson1
    Contributor

    Marvin Rühe (@Marv51) I believe in the WinRT object case, #384 will satisfy your concern. Steve Otteson (@sotteson1) can you confirm?

    It should work, but I'm going to try it out to make sure.

  14. Marv51 commented on Sep 10, 2021

    @Marv51
    Author

    Steve Otteson (@sotteson1) I tried your new WinRTCustomMarshaler as released a couple days ago in v0.1.560 this does not yet work for DataTransferManager. I managed to fix it by modifing the generated IDataTransferManagerInterop like this:

    // instead of 
    [return: MarshalAs(UnmanagedType.IUnknown)] unsafe object GetForWindow(win32.Foundation.HWND appWindow, global::System.Guid* riid);
    // this:
    [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = "Windows.ApplicationModel.DataTransfer.DataTransferManager", MarshalType = "Windows.Win32.CsWin32.InteropServices.WinRTCustomMarshaler")] unsafe object GetForWindow(win32.Foundation.HWND appWindow, global::System.Guid* riid);

    As well as manually adding your WinRTCustomMarshaler class because it does not get generated automatically.

    I think some metadata on IDataTransferManagerInterop might be missing?

    Maybe an option could be added to manually output the WinRTCustomMarshaler?

  15. mikebattista commented on Sep 29, 2021

    @mikebattista
    Contributor

    Assigning to Steve Otteson (@sotteson1) to investigate if there are any metadata changes required here on IDataTransferManagerInterop and the concerns around WinRTCustomMarshaler not being autogenerated when you need it (#397).

    Also we need to validate if the improved support here addresses issues like microsoft/WindowsAppSDK#1335.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions