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

Add support for LPArray when marshalling struct fields #8719

Closed
Tragetaschen opened this issue Aug 9, 2017 · 13 comments
Closed

Add support for LPArray when marshalling struct fields #8719

Tragetaschen opened this issue Aug 9, 2017 · 13 comments

Comments

@Tragetaschen
Copy link
Contributor

Today, I have finally found the reason, why my DBus implementation works with Mono, but doesn't with .NET Core:

System.TypeLoadException: Cannot marshal field 'control' of type 'msghdr': Unknown error.
    at System.StubHelpers.ValueClassMarshaler.ConvertToNative(IntPtr dst, IntPtr src, IntPtr pMT, CleanupWorkList& pCleanupWorkList)
    at Dbus.Connection.recvmsg(IntPtr sockfd, msghdr& buf, Int32 flags)
    at Dbus.Connection.receive()

Here's a quick copy of the code from here

[DllImport("libc")]
private static extern int recvmsg(IntPtr sockfd, [In] ref msghdr buf, int flags);

private unsafe struct iovec
{
    public byte* iov_base;
    public int iov_len;
}

private unsafe struct msghdr
{
    public IntPtr name;
    public int namelen;
    public iovec* iov;
    public int iovlen;
    public int[] control;
    public int controllen;
    public int flags;
}

When calling recvmsg, Mono is fine with marshalling the control member of msghdr. For .NET Core (1.1 and 2.0-preview2), I had to change int[] to int* and add another fixed statement to my code.

The Unknown error makes me wonder if there's something missing in .NET Core.

@tannergooding
Copy link
Member

@Tragetaschen, could you try explicitly marking control with [MarshalAs(UnmanagedType.LPArray)]?

The default marshalling for arrays is described here: https://docs.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-for-arrays

You will note that under the Arrays within Structures subsection that By default, these embedded array fields are marshaled as a SAFEARRAY.

When marshalling from managed to unmanaged, the size of the array is determined by the managed length. When marshalling from unmanaged to managed, the size of the array is determined by the SizeConst attribute parameter.

For P/Invoke methods, there exists a SizeParamIndex parameter, but there is no corresponding parameter for specifying the unmanaged size based on another field in the struct (in this case, controllen).

@Tragetaschen
Copy link
Contributor Author

With this declaration:

public unsafe struct msghdr
{
    public IntPtr name;
    public NativeInt namelen;
    public iovec* iov;
    public NativeInt iovlen;
    [MarshalAs(UnmanagedType.LPArray)]
    public int[] control;
    public NativeInt controllen;
    public int flags;
}

the exception is the same.

@tijoytom-zz
Copy link
Contributor

@Tragetaschen I will take a look at why it says "unklnown' , but you can marshal the field as ByValArray.
Note marshaling as ByValArray require a SizeConst and from here it appears that you always use 16 as size.

[MarshalAs(UnmanagedType.ByValArray,SizeConst=16)]
public int[] control;

@tijoytom-zz tijoytom-zz self-assigned this Aug 24, 2017
@Tragetaschen
Copy link
Contributor Author

Thanks for looking into it.

The C API expects a pointer to the first array element. Wouldn't ByValArray marshal the 16 integers as 16 struct fields?

You can use this UnmanagedType only on an array that whose elements appear as fields in a structure.
(https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.unmanagedtype(v=vs.110).aspx)

@tannergooding
Copy link
Member

tannergooding commented Aug 25, 2017

@Tragetaschen, it looks like some of your types aren't accurate representations of the native side, which might be causing some problems.

The native declarations are:

#define __STD_TYPE           typedef
#define __U32_TYPE           unsigned int

__STD_TYPE __U32_TYPE __socklen_t;
typedef __socklen_t socklen_t;

struct iovec
{
    void *iov_base;           /* Pointer to data.  */
    size_t iov_len;           /* Length of data.  */
};

struct msghdr
{
    void *msg_name;           /* Address to send to/receive from.  */
    socklen_t msg_namelen;    /* Length of address data.  */

    struct iovec *msg_iov;    /* Vector of data to send/receive into.  */
    size_t msg_iovlen;        /* Number of elements in the vector.  */

    void *msg_control;        /* Ancillary data (eg BSD filedesc passing). */
    size_t msg_controllen;    /* Ancillary data buffer length.
                              !! The type should be socklen_t but the
                                 definition of the kernel is incompatible
                                 with this.  */

    int msg_flags;            /* Flags on received message.  */
};

extern ssize_t recvmsg (int __fd, struct msghdr *__message, int __flags);

So I would expect the most accurate representation to be:

private static extern IntPtr recvmsg(   // You return int, can cause issues for 64-bit
    int __fd,                           // You use IntPtr, can cause issues for 64-bit
    ref msghdr __message,               //
    int __flags                         //
);

private unsafe struct iovec
{
    public void*    iov_base;           // You use byte*, shouldn't be a problem
    public IntPtr   iov_len;            // You use int, can cause issues for 64-bit
}

private unsafe struct msghdr
{
    public void*    msg_name;           // You use IntPtr, shouldn't be a problem
    public uint     msg_namelen;        // You use int, shouldn't be a problem
    public iovec*   msg_iov;            //
    public IntPtr   msg_iovlen;         // You use int, can cause issues for 64-bit
    public void*    msg_control;        // You use int[], byte[] would be more accurate, I don't think this should cause any problems in either case. If you use an array, you must specify MarshalAs(UnmanagedType.LPArray)
    public IntPtr   msg_controllen;     // You use int, can cause issues for 64-bit -- This is defined in x86_64-linux_gnu\bits\socket.h and is explicitly documented to be different from the POSIX declaration
    public int      msg_flags;          //
}

@Tragetaschen
Copy link
Contributor Author

@tannergooding I had already refactored the code to split 32- and 64-bit operations. Luckily it's possible to hide those details behind an interface.

As mentioned, decorating the mgs_control with [MarshalAs(UnmanagedType.LPArray)] results in the same runtime exception.

@tijoytom-zz
Copy link
Contributor

decorating the mgs_control with [MarshalAs(UnmanagedType.LPArray)]
@Tragetaschen
Unfortunately LPArray is not supported for struct fields , and it's same on Desktop CLR. Given mono support it this might be something worth adding.Hope this is not blocking you in any ways ? , as you mentioned you can pin it and marshal as pointer.

@Tragetaschen
Copy link
Contributor Author

With the manual fixed statement, I'm good.

I'll update the title accordingly

@Tragetaschen Tragetaschen changed the title Missing marshalling capability with a struct containing an array? Add support for LPArray when marshalling struct fields Sep 7, 2017
@letmaik
Copy link

letmaik commented Jan 31, 2019

I'm having the same issue. I expected something like: [MarshalAs(UnmanagedType.LPArray, SizeFieldName: "count")] but this doesn't exist. It makes interop a nightmare in certain cases :/

@mrEDitor
Copy link

+1, looking forward for MarshalAs(UnmanagedType.LPArray)] and MarshalAs(UnmanagedType.LPStruct)].

@msftgits msftgits transferred this issue from dotnet/coreclr Jan 31, 2020
@msftgits msftgits added this to the Future milestone Jan 31, 2020
@garrynewman
Copy link

This would save a ton of energy, thanks for looking into it

@AaronRobinsonMSFT
Copy link
Member

@elinor-fung and @jkoritzinsky Let's make sure the struct marshalling for DllImport source generation can handle this case. #46838.

@AaronRobinsonMSFT
Copy link
Member

Now that the LibraryImport generator (i.e., source generated DllImports) has been exposed as a publicly consumable tool, this issue should be explored relative to that effort. This specific issue is being close due to lower priority relative to other interop needs and should be solved using the source generator.

Support for marshalling customized types can be found in the new API described at #66121.

@ghost ghost locked as resolved and limited conversation to collaborators Jun 6, 2022
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

8 participants