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

Interop using Span and Structures: Error CS8377 and Error CS1663 #48784

Closed
netcorefan1 opened this issue Feb 24, 2021 · 2 comments
Closed

Interop using Span and Structures: Error CS8377 and Error CS1663 #48784

netcorefan1 opened this issue Feb 24, 2021 · 2 comments

Comments

@netcorefan1
Copy link

Hello,
I am trying to learn and take advantage of Spans when dealing with native code and I encountered immediately some problems.
This is how would look getting data using the old Marshalling techniques:

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern void UnmanagedFunction(out StructA lpBuffer);

[StructLayout(LayoutKind.Sequential)]
public struct StructB
{
    public uint val1;
    public uint val2;
}

[StructLayout(LayoutKind.Sequential)]
public struct StructA
{
    public ushort val1;
    public byte val2;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
    public StructB[] ArrayOfStructB;
}

I am trying to convert the above using ReadOnlySpan and combine it with the new generics features of the language:

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern void UnmanagedFunction(out byte lpBuffer);

public T ReadStruct<T>() where T : unmanaged
{
    ReadOnlySpan<byte> buf = new byte[Marshal.SizeOf<T>()];
    UnmanagedFunction(out MemoryMarshal.GetReference(buf))
    return MemoryMarshal.Read<T>(buf);
}

As is I get Error CS8377: The type StructA must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter T in the generic type or method...

So far this is what I have tried:

public unsafe fixed StructB ArrayOfStructB[16];

Although I am not happy to recur to unsafe code, even in this way I get Error CS1663: Fixed size buffer type must be one of the following: bool, byte, short, int, long, char, sbyte, ushort, uint, ulong, float or double
The error is self explanatory, but StructB only contains uint !!
If I replace the declaration with public unsafe fixed uint ArrayOfStructB[16]; it compiles, but this opens the road to new issues when accessing the data from the code, problems with unsafe inside iterators along with another concern: in this sample I can use a fixed buffer because the struct only contains uint, but what if contains a mix of ushort, int, bool etc?

Without unsafe context this is the only way I got it to work.

[StructLayout(LayoutKind.Sequential)]
public struct StructA
{
    public ushort val1;
    public byte val2;

    public uint StructB_val1;
    public uint StructB_val2;

    public uint StructB_val3;
    public uint StructB_val4;

    public uint StructB_val5;
    public uint StructB_val6;

    //...until public uint StructB_val16;
}

This is not feasible, extremely confusing and the debugger shows the data unordered and mixed.

I would have never expected that would have been so complicated the transition and therefore I would like to ask:

  • What is the recommended way to deal with the issues mentioned above without any use of the unsafe code (which is supposed to be what Span free us from)?
  • The data in the sample uses primitive types, but what if contains strings or arrays of string (which is pretty common today) ?
  • I have read somewhere that there are troubles to access that code from async methods or iterators. Does this still applies?
  • I considered for a while to switch into Memory<T> instead of Span<T>, but I see everywhere mostly the latter and several people recommend to try to use it whenever possible.
    Thanks
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@netcorefan1 netcorefan1 changed the title Interop using Span and Structures: type must be a non-nullable value Interop using Span and Structures: Error CS8377 and Error CS1663 Feb 24, 2021
@wli3 wli3 transferred this issue from dotnet/sdk Feb 25, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added area-Interop-coreclr untriaged New issue has not been triaged by the area owner labels Feb 25, 2021
@jkoritzinsky jkoritzinsky removed the untriaged New issue has not been triaged by the area owner label Jul 6, 2021
@jkoritzinsky
Copy link
Member

This is expected. The StructA type is not an unmanaged type by the C# definition. An unmanaged type must have no reference fields, and an array is a reference field.

Additionally, your sample code using MemoryMarshal would not work with StructA as-is. Since StructA is not blittable (which is a subset of unmanaged), the data needs to be marshalled between the managed and native representations. This would also be the case if StructA or StructB contained strings or arrays of strings (marshalling would be required).

Unless your types are blittable, you should not be using Span<T> for any interop scenario. You'll end up in really bad scenarios where things will break pretty severely.

For your scenario, you should either put StructA in the P/Invoke signature directly or provide a blittable type that represents the native representation of StructA (which as you saw with fixed-size buffers isn't the best experience today) and use that with MemoryMarshal.

For a better experience with fixed-size buffers with user-defined types, improvements in that use case are being tracked by dotnet/csharplang#1502.

@ghost ghost locked as resolved and limited conversation to collaborators Aug 5, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants