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

[ComInterfaceGenerator] Marshalling structs containing strings/interfaces #88284

Closed
lordmilko opened this issue Jul 1, 2023 · 5 comments
Closed

Comments

@lordmilko
Copy link

It currently does not appear to be possible to emit structs from COM interfaces using the new COM interface generator that contain string or interface fields.

According to the UserTypeMarshallingV2 design document, the expectation appears to be that either a completely custom "native" declaration of the type is provided, or a MarshalUsingAttribute should be applied to the relevant field requiring custom marshalling.

To use these marshallers the user would apply either the NativeMarshallingAttribute attribute to their type or a MarshalUsingAttribute at the marshalling location (field, parameter, or return value) with a marshalling type matching the same requirements as NativeMarshallingAttribute's marshalling type.

While the design document states that MarshalUsingAttribute should apply to fields, parameters and return values, the current definition of MarshalUsingAttribute only allows its use on parameters and return values.

Assuming MarshalUsingAttribute should also be permissible on fields, I would assume this would solve the problem of structs containing strings by allowing you to do the following which is currently prohibited:

public struct StructWithString
{
    //Note: not currently valid as MarshalUsingAttribute is disallowed on fields
    [MarshalUsing(typeof(Utf16StringMarshaller))]
    public string field;
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("7DAC8207-D3AE-4C75-9B67-92801A497D44")]
[GeneratedComInterface]
public partial interface Iface
{
    void M1(out StructWithString value);
}

Less clear is how a struct should be marshalled containing an interface type

public struct StructWithIface
{
    public Iface field;
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("7DAC8207-D3AE-4C75-9B67-92801A497D44")]
[GeneratedComInterface]
public partial interface Iface
{
    void M2(out StructWithIface value);
}

The above currently gives an error that StructWithIface is not supported by source-generated COM. Given that Iface contains a source generated interface, I expect that this should "just work". Alternatively, if MarshalUsingAttribute is supposed to be permissible on fields, a custom marshaller for marshalling interfaces could be applied, in which case it would make sense for there to be a built in custom marshaller for marshalling ComWrappers based interfaces, rather than every user having to define a general purpose interface marshaller themselves.

Decorating string/interface fields in structs with MarshalAsAttribute does not have any effect with regards to either string or interface marshalling

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jul 1, 2023
@ghost
Copy link

ghost commented Jul 1, 2023

Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.

Issue Details

It currently does not appear to be possible to emit structs from COM interfaces using the new COM interface generator that contain string or interface fields.

According to the UserTypeMarshallingV2 design document, the expectation appears to be that either a completely custom "native" declaration of the type is provided, or a MarshalUsingAttribute should be applied to the relevant field requiring custom marshalling.

To use these marshallers the user would apply either the NativeMarshallingAttribute attribute to their type or a MarshalUsingAttribute at the marshalling location (field, parameter, or return value) with a marshalling type matching the same requirements as NativeMarshallingAttribute's marshalling type.

While the design document states that MarshalUsingAttribute should apply to fields, parameters and return values, the current definition of MarshalUsingAttribute only allows its use on parameters and return values.

Assuming MarshalUsingAttribute should also be permissible on fields, I would assume this would solve the problem of structs containing strings by allowing you to do the following which is currently prohibited:

public struct StructWithString
{
    //Note: not currently valid as MarshalUsingAttribute is disallowed on fields
    [MarshalUsing(typeof(Utf16StringMarshaller))]
    public string field;
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("7DAC8207-D3AE-4C75-9B67-92801A497D44")]
[GeneratedComInterface]
public partial interface Iface
{
    void M1(out StructWithString value);
}

Less clear is how a struct should be marshalled containing an interface type

public struct StructWithIface
{
    public Iface field;
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("7DAC8207-D3AE-4C75-9B67-92801A497D44")]
[GeneratedComInterface]
public partial interface Iface
{
    void M2(out StructWithIface value);
}

The above currently gives an error that StructWithIface is not supported by source-generated COM. Given that Iface contains a source generated interface, I expect that this should "just work". Alternatively, if MarshalUsingAttribute is supposed to be permissible on fields, a custom marshaller for marshalling interfaces could be applied, in which case it would make sense for there to be a built in custom marshaller for marshalling ComWrappers based interfaces, rather than every user having to define a general purpose interface marshaller themselves.

Decorating string/interface fields in structs with MarshalAsAttribute does not have any effect with regards to either string or interface marshalling

Author: lordmilko
Assignees: -
Labels:

area-System.Runtime.InteropServices, untriaged

Milestone: -

@DaZombieKiller
Copy link
Contributor

As I understand it, permitting [MarshalUsing] on fields would be a bad idea because not all fields are necessarily public, and if they aren't public then they can't be seen by the source generator outside of the current project without InternalsVisibleTo (and if they're private, the source generator can't even touch the fields when operating outside of the type).

You would instead need to have a marshaller for StructWithString itself and mark the parameter in your COM interface with [MarshalUsing].

With that in mind, it seems to me that a better approach might be to have some kind of [GeneratedCustomMarshaller] attribute combined with a special attribute for fields that describes how the generated marshaller should behave:

public partial struct StructWithString
{
    [MarshalFieldUsing(typeof(Utf16StringMarshaller))]
    public string field;
    
    [GeneratedCustomMarshaller(typeof(StructWithString))]
    public static partial class Marshaller
    {
    }
}

// ...

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("7DAC8207-D3AE-4C75-9B67-92801A497D44")]
[GeneratedComInterface]
public partial interface Iface
{
    void M1([MarshalUsing(typeof(StructWithString.Marshaller))] out StructWithString value);
}

@lordmilko
Copy link
Author

lordmilko commented Jul 2, 2023

Rather than have to declare an empty marshaller yourself, I would imagine what should happen is similar to what is described in the V1 StructMarshalling document

The user can apply the GeneratedMarshallingAttribute to their structure S. The source generator will determine if the type requires marshalling. If it does, it will generate a representation of the struct that does not require marshalling with the aforementioned required shape and apply the NativeMarshallingAttribute and point it to that new type. This generated representation can either be generated as a separate top-level type or as a nested type on S.

// User code
[GeneratedMarshalling]
public partial struct StructWithString
{
    [MarshalFieldUsing(typeof(Utf16StringMarshaller))]
    public string field;
}
//Generated code
file struct NativeStructWithString
{
    //whatever the native representation of the string field should be
}

public partial StructWithString
{
    //Inside StructWithString so the marshaller can access private fields
    [CustomMarshaller(typeof(StructWithString), ...]
    public static class Marshaller
    {
        public static StructWithString ConvertToManaged(NativeStructWithString unmanaged);

        public static NativeStructWithString ConvertToUnmanaged(StructWithString managed);

        //any other required methods
    }
}

If a struct is decorated with some attribute indicating it has generated custom marshalling, I would imagine it should "just work" at a given parameter site, using the generated custom marshaller by default without having to explicitly specify the custom marshaller to use

@AaronRobinsonMSFT
Copy link
Member

The currently supported struct marshalling logic is either define the struct as blittable or implement a custom marshaller with CustomMarshallerAttribute.

@AaronRobinsonMSFT AaronRobinsonMSFT removed the untriaged New issue has not been triaged by the area owner label Jul 15, 2023
@AaronRobinsonMSFT AaronRobinsonMSFT added this to the 9.0.0 milestone Jul 15, 2023
@jkoritzinsky
Copy link
Member

Due to time constraints, we never implemented a "custom struct marshalling" generator. That work is tracked by #81656. I'm going to close this issue as a duplicate of that one (and make that the central issue for generated struct marshalling).

@jkoritzinsky jkoritzinsky closed this as not planned Won't fix, can't repro, duplicate, stale Jun 20, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Jul 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
Archived in project
Development

No branches or pull requests

4 participants