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

Support arrays of types with simple custom marshalling #379

Conversation

jkoritzinsky
Copy link
Member

@jkoritzinsky jkoritzinsky commented Nov 23, 2020

Add support for marshalling arrays of types with simple custom marshalling (no Value property) and gracefully fail when an array of a type with complex custom marshalling (Value property) is introduced.

To do so, we create a new ArrayMarshallingInfo subtype of MarshallingInfo for the default array marshalling case and pre-calculate the element type's marshalling information instead of calculating it in MarshallingGenerator.cs

Snippet example (BoolStruct is defined as in the SharedTypes library)
User-Provided:
            [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "and_all_members")]
            [return:MarshalAs(UnmanagedType.U1)]
            public static partial bool AndAllMembers(BoolStruct[] pArray, int length);

Generated:

namespace DllImportGenerator.IntegrationTests
{
    partial class NativeExportsNE
    {
        public partial class Arrays
        {
            public static partial bool AndAllMembers(global::SharedTypes.BoolStruct[] pArray, int length)
            {
                unsafe
                {
                    global::SharedTypes.BoolStructNative*__pArray_gen_native = default;
                    bool __retVal = default;
                    byte __retVal_gen_native = default;
                    //
                    // Setup
                    //
                    bool pArray__allocated = false;
                    try
                    {
                        //
                        // Marshal
                        //
                        if (pArray == null)
                        {
                            __pArray_gen_native = null;
                        }
                        else
                        {
                            int pArray__bytelen = sizeof(global::SharedTypes.BoolStructNative) * pArray.Length;
                            if (pArray__bytelen > 512)
                            {
                                __pArray_gen_native = (global::SharedTypes.BoolStructNative*)System.Runtime.InteropServices.Marshal.AllocCoTaskMem(pArray__bytelen);
                                pArray__allocated = true;
                            }
                            else
                            {
                                byte *pArray__stackptr = stackalloc byte[pArray__bytelen];
                                ;
                                __pArray_gen_native = (global::SharedTypes.BoolStructNative*)pArray__stackptr;
                            }
                        }

                        if (pArray != null)
                            for (int __i = 0; __i < pArray.Length; ++__i)
                            {
                                __pArray_gen_native[__i] = new global::SharedTypes.BoolStructNative(pArray[__i]);
                            }

                        //
                        // Invoke
                        //
                        __retVal_gen_native = AndAllMembers__PInvoke__(__pArray_gen_native, length);
                        //
                        // Unmarshal
                        //
                        __retVal = __retVal_gen_native != 0;
                    }
                    finally
                    {
                        //
                        // Cleanup
                        //
                        if (pArray__allocated)
                        {
                            System.Runtime.InteropServices.Marshal.FreeCoTaskMem((System.IntPtr)__pArray_gen_native);
                        }
                    }

                    return __retVal;
                }
            }

            [System.Runtime.InteropServices.DllImportAttribute("NativeExportsNE", EntryPoint = "and_all_members")]
            extern private static unsafe byte AndAllMembers__PInvoke__(global::SharedTypes.BoolStructNative*pArray, int length);
        }
    }
}
By Value, In, Ref, Out, Return Value sample
User provided:
using System.Runtime.InteropServices;
partial class Test
{
    [GeneratedDllImport("DoesNotExist")]
    [return:MarshalAs(UnmanagedType.LPArray, SizeConst=10)]
    public static partial IntStructWrapper[] Method(
        IntStructWrapper[] p,
        in IntStructWrapper[] pIn,
        int pRefSize,
        [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)] ref IntStructWrapper[] pRef,
        [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=5, SizeConst=4)] out IntStructWrapper[] pOut,
        out int pOutSize
        );
}
[NativeMarshalling(typeof(IntStructWrapperNative))]
public struct IntStructWrapper
{
    public int Value;
}

public struct IntStructWrapperNative
{
    private int value;

    public IntStructWrapperNative(IntStructWrapper managed)
    {
        value = managed.Value;
    }

    public IntStructWrapper ToManaged() => new IntStructWrapper { Value = value };
}

Generated:

partial class Test
{
    public static partial global::IntStructWrapper[] Method(global::IntStructWrapper[] p, in global::IntStructWrapper[] pIn, int pRefSize, ref global::IntStructWrapper[] pRef, out global::IntStructWrapper[] pOut, out int pOutSize)
    {
        unsafe
        {
            global::IntStructWrapperNative*__p_gen_native = default;
            global::IntStructWrapperNative*__pIn_gen_native = default;
            global::IntStructWrapperNative*__pRef_gen_native = default;
            pOut = default;
            global::IntStructWrapperNative*__pOut_gen_native = default;
            pOutSize = default;
            global::IntStructWrapper[] __retVal = default;
            global::IntStructWrapperNative*__retVal_gen_native = default;
            //
            // Setup
            //
            bool p__allocated = false;
            bool pIn__allocated = false;
            try
            {
                //
                // Marshal
                //
                if (p == null)
                {
                    __p_gen_native = null;
                }
                else
                {
                    int p__bytelen = sizeof(global::IntStructWrapperNative) * p.Length;
                    if (p__bytelen > 512)
                    {
                        __p_gen_native = (global::IntStructWrapperNative*)System.Runtime.InteropServices.Marshal.AllocCoTaskMem(p__bytelen);
                        p__allocated = true;
                    }
                    else
                    {
                        byte *p__stackptr = stackalloc byte[p__bytelen];
                        ;
                        __p_gen_native = (global::IntStructWrapperNative*)p__stackptr;
                    }
                }

                if (p != null)
                {
                    new System.Span<global::IntStructWrapperNative>((global::IntStructWrapperNative*)__p_gen_native, p.Length).Clear();
                    for (int __i = 0; __i < p.Length; ++__i)
                    {
                        __p_gen_native[__i] = new global::IntStructWrapperNative(p[__i]);
                    }
                }

                if (pIn == null)
                {
                    __pIn_gen_native = null;
                }
                else
                {
                    int pIn__bytelen = sizeof(global::IntStructWrapperNative) * pIn.Length;
                    if (pIn__bytelen > 512)
                    {
                        __pIn_gen_native = (global::IntStructWrapperNative*)System.Runtime.InteropServices.Marshal.AllocCoTaskMem(pIn__bytelen);
                        pIn__allocated = true;
                    }
                    else
                    {
                        byte *pIn__stackptr = stackalloc byte[pIn__bytelen];
                        ;
                        __pIn_gen_native = (global::IntStructWrapperNative*)pIn__stackptr;
                    }
                }

                if (pIn != null)
                {
                    new System.Span<global::IntStructWrapperNative>((global::IntStructWrapperNative*)__pIn_gen_native, pIn.Length).Clear();
                    for (int __i = 0; __i < pIn.Length; ++__i)
                    {
                        __pIn_gen_native[__i] = new global::IntStructWrapperNative(pIn[__i]);
                    }
                }

                __pRef_gen_native = null;
                if (pRef != null)
                {
                    int pRef__bytelen = sizeof(global::IntStructWrapperNative) * pRef.Length;
                    __pRef_gen_native = (global::IntStructWrapperNative*)System.Runtime.InteropServices.Marshal.AllocCoTaskMem(pRef__bytelen);
                }

                if (pRef != null)
                {
                    new System.Span<global::IntStructWrapperNative>((global::IntStructWrapperNative*)__pRef_gen_native, pRef.Length).Clear();
                    for (int __i = 0; __i < pRef.Length; ++__i)
                    {
                        __pRef_gen_native[__i] = new global::IntStructWrapperNative(pRef[__i]);
                    }
                }

                //
                // Invoke
                //
                fixed (int *__pOutSize_gen_native = &pOutSize)
                {
                    __retVal_gen_native = Method__PInvoke__(__p_gen_native, &__pIn_gen_native, pRefSize, &__pRef_gen_native, &__pOut_gen_native, __pOutSize_gen_native);
                }

                //
                // Unmarshal
                //
                if (__retVal_gen_native != null)
                {
                    __retVal = new global::IntStructWrapper[10];
                    for (int __i = 0; __i < __retVal.Length; ++__i)
                    {
                        __retVal[__i] = __retVal_gen_native[__i].ToManaged();
                    }
                }
                else
                    __retVal = null;
                if (__pRef_gen_native != null)
                {
                    pRef = new global::IntStructWrapper[checked((int)pRefSize)];
                    for (int __i = 0; __i < pRef.Length; ++__i)
                    {
                        pRef[__i] = __pRef_gen_native[__i].ToManaged();
                    }
                }
                else
                    pRef = null;
                if (__pOut_gen_native != null)
                {
                    pOut = new global::IntStructWrapper[checked(4 + (int)pOutSize)];
                    for (int __i = 0; __i < pOut.Length; ++__i)
                    {
                        pOut[__i] = __pOut_gen_native[__i].ToManaged();
                    }
                }
                else
                    pOut = null;
            }
            finally
            {
                //
                // Cleanup
                //
                if (p__allocated)
                {
                    System.Runtime.InteropServices.Marshal.FreeCoTaskMem((System.IntPtr)__p_gen_native);
                }

                if (pIn__allocated)
                {
                    System.Runtime.InteropServices.Marshal.FreeCoTaskMem((System.IntPtr)__pIn_gen_native);
                }

                System.Runtime.InteropServices.Marshal.FreeCoTaskMem((System.IntPtr)__pRef_gen_native);
                System.Runtime.InteropServices.Marshal.FreeCoTaskMem((System.IntPtr)__pOut_gen_native);
            }

            return __retVal;
        }
    }

    [System.Runtime.InteropServices.DllImportAttribute("DoesNotExist", EntryPoint = "Method")]
    extern private static unsafe global::IntStructWrapperNative*Method__PInvoke__(global::IntStructWrapperNative*p, global::IntStructWrapperNative**pIn, int pRefSize, global::IntStructWrapperNative**pRef, global::IntStructWrapperNative**pOut, int *pOutSize);
}

…lling and gracefully fail when an array of a type with complex custom marshalling (Value property) is introduced.
@jkoritzinsky jkoritzinsky added the area-DllImportGenerator Source Generated stubs for P/Invokes in C# label Nov 23, 2020
@jkoritzinsky jkoritzinsky changed the title Support arrays of types with simple custmo marshalling Support arrays of types with simple custom marshalling Nov 23, 2020
@jkotas
Copy link
Member

jkotas commented Nov 23, 2020

Could you please share code-snippets that this generates?

@jkoritzinsky
Copy link
Member Author

I've added the example from the integration tests into the PR description. The only part that's "custom marshalling"-specific is the body of the for loop. The rest is the same array code as in the main branch.

@jkotas
Copy link
Member

jkotas commented Nov 23, 2020

What is it going to do if the marshaller has FreeNative method?

@jkoritzinsky
Copy link
Member Author

jkoritzinsky commented Nov 23, 2020

If the marshaller has a FreeNative method, the code-gen will have another for loop that calls FreeNative in the finally block right before the FreeCoTaskMem if block. The loop would look like the following:

                        if (pArray != null)
                            for (int __i = 0; __i < pArray.Length; ++__i)
                            {
                                __pArray_gen_native[__i].FreeNative();
                            }

@jkotas
Copy link
Member

jkotas commented Nov 23, 2020

Does this need to account for error handling? What if the initialization of the array failed half-way through?

@jkoritzinsky
Copy link
Member Author

If the initialization failed halfway through, the native structs should be default initialized. The implementor of the native struct should handle this scenario.

I may have to add handling for that case.

@AaronRobinsonMSFT
Copy link
Member

Does this need to account for error handling? What if the initialization of the array failed half-way through?

I thought we were going to assume non-exception cases only at least custom marshalling. In this case that would seem to imply that if the custom marshalling throws, we can either let them handle the possible clean or determine which of the elements were initialized and only free them. @jkotas are you implying the latter?

…ntify when an element is empty due to not being marshalled.
Signed-off-by: Jeremy Koritzinsky <jekoritz@microsoft.com>
@jkoritzinsky
Copy link
Member Author

I've added in some basic handling for this case similar to the old system where we zero-init the memory before array marshalling in case we only partially marshal. I also fixed a bug around marshalling arrays of pointers since I found it along the way.

@jkotas
Copy link
Member

jkotas commented Nov 24, 2020

we zero-init the memory before array marshalling in case we only partially marshal

This works, but it may be more efficient to keep track of how many elements of the array were marshalled successfully and cleanup only those.

@jkotas
Copy link
Member

jkotas commented Nov 24, 2020

Also, what does would the code look for [in,out] ref BoolStruct[] pArray?

@jkotas
Copy link
Member

jkotas commented Nov 24, 2020

we can either let them handle the possible clean or determine which of the elements were initialized and only free them

Agree with that.

@jkoritzinsky
Copy link
Member Author

I've added a more comprehensive sample to the PR description that covers the ref case.

I prefer to use the mem-clear method initially since that's what the old system used and it still leaves open the option for switching methods in the future.

@jkotas
Copy link
Member

jkotas commented Nov 24, 2020

for (int __i = 0; __i < pRef.Length; ++__i)

This is prone to buffer overruns. pRef can change between the time the buffer is allocate and the marshalling happens.

The length should be cached to avoid this problem.

@jkotas
Copy link
Member

jkotas commented Nov 24, 2020

sizeof(global::IntStructWrapperNative) * pRef.Length

This is prone to overflow. It should be checked(...)

@jkoritzinsky
Copy link
Member Author

for (int __i = 0; __i < pRef.Length; ++__i)

This is prone to buffer overruns. pRef can change between the time the buffer is allocate and the marshalling happens.

The length should be cached to avoid this problem.

Which loop is this referring to? There's multiple loops with this condition (one in marshal, one in unmarshal).

Since both of these comments are relating to general array marshalling, not marshalling of arrays of custom marshaled types, I'm going to put out a separate PR for these fixes.

@jkotas
Copy link
Member

jkotas commented Nov 24, 2020

The problem is in the loops that operate on arrays passed in via ref. The user code can be changing the value on other thread.

@jkotas
Copy link
Member

jkotas commented Nov 24, 2020

                        if (pArray == null)
                        {
                            __pArray_gen_native = null;
                        }
                        else
                        {

This can be just if (pArray != null). __pArray_gen_native is initialized to null from the prolog.

@jkoritzinsky
Copy link
Member Author

@jkotas I've opened #384 with the array marshalling improvements.

…elab into arrays-with-custom-marshalled-elements
Signed-off-by: Jeremy Koritzinsky <jekoritz@microsoft.com>
…elab into arrays-with-custom-marshalled-elements
@jkoritzinsky
Copy link
Member Author

Up to date stub example with BoolStruct (taken from the integration tests):

namespace DllImportGenerator.IntegrationTests
{
    partial class NativeExportsNE
    {
        public partial class Arrays
        {
            [System.Runtime.CompilerServices.SkipLocalsInitAttribute]
            public static partial bool AndAllMembers(global::SharedTypes.BoolStruct[] pArray, int length)
            {
                unsafe
                {
                    global::SharedTypes.BoolStructNative*__pArray_gen_native = default;
                    bool __retVal = default;
                    byte __retVal_gen_native = default;
                    //
                    // Setup
                    //
                    bool pArray__allocated = false;
                    try
                    {
                        //
                        // Marshal
                        //
                        if (pArray != null)
                        {
                            int pArray__bytelen = checked(sizeof(global::SharedTypes.BoolStructNative) * pArray.Length);
                            if (pArray__bytelen > 512)
                            {
                                __pArray_gen_native = (global::SharedTypes.BoolStructNative*)System.Runtime.InteropServices.Marshal.AllocCoTaskMem(pArray__bytelen);
                                pArray__allocated = true;
                            }
                            else
                            {
                                byte *pArray__stackptr = stackalloc byte[pArray__bytelen];
                                ;
                                __pArray_gen_native = (global::SharedTypes.BoolStructNative*)pArray__stackptr;
                            }
                        }

                        if (pArray != null)
                        {
                            new System.Span<global::SharedTypes.BoolStructNative>((global::SharedTypes.BoolStructNative*)__pArray_gen_native, pArray.Length).Clear();
                            for (int __i = 0; __i < pArray.Length; ++__i)
                            {
                                __pArray_gen_native[__i] = new global::SharedTypes.BoolStructNative(pArray[__i]);
                            }
                        }

                        //
                        // Invoke
                        //
                        __retVal_gen_native = AndAllMembers__PInvoke__(__pArray_gen_native, length);
                        //
                        // Unmarshal
                        //
                        __retVal = __retVal_gen_native != 0;
                    }
                    finally
                    {
                        //
                        // Cleanup
                        //
                        if (pArray__allocated)
                        {
                            System.Runtime.InteropServices.Marshal.FreeCoTaskMem((System.IntPtr)__pArray_gen_native);
                        }
                    }

                    return __retVal;
                }
            }

            [System.Runtime.InteropServices.DllImportAttribute("Microsoft.Interop.Tests.NativeExportsNE", EntryPoint = "and_all_members")]
            extern private static unsafe byte AndAllMembers__PInvoke__(global::SharedTypes.BoolStructNative*pArray, int length);
        }
    }
}

@jkoritzinsky
Copy link
Member Author

jkoritzinsky commented Dec 10, 2020

Here's a more comprehensive one taken from the unit tests:

Source:

using System.Runtime.InteropServices;
partial class Test
{
    [GeneratedDllImport("DoesNotExist")]
    [return:MarshalAs(UnmanagedType.LPArray, SizeConst=10)]
    public static partial IntStructWrapper[] Method(
        IntStructWrapper[] p,
        in IntStructWrapper[] pIn,
        int pRefSize,
        [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)] ref IntStructWrapper[] pRef,
        [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=5, SizeConst=4)] out IntStructWrapper[] pOut,
        out int pOutSize
        );
}
[NativeMarshalling(typeof(IntStructWrapperNative))]
public struct IntStructWrapper
{
    public int Value;
}

public struct IntStructWrapperNative
{
    private int value;

    public IntStructWrapperNative(IntStructWrapper managed)
    {
        value = managed.Value;
    }

    public IntStructWrapper ToManaged() => new IntStructWrapper { Value = value };
}

Generated code:

partial class Test
{
    [System.Runtime.CompilerServices.SkipLocalsInitAttribute]
    public static partial global::IntStructWrapper[] Method(global::IntStructWrapper[] p, in global::IntStructWrapper[] pIn, int pRefSize, ref global::IntStructWrapper[] pRef, out global::IntStructWrapper[] pOut, out int pOutSize)
    {
        unsafe
        {
            global::IntStructWrapperNative*__p_gen_native = default;
            global::IntStructWrapperNative*__pIn_gen_native = default;
            global::IntStructWrapperNative*__pRef_gen_native = default;
            pOut = default;
            global::IntStructWrapperNative*__pOut_gen_native = default;
            pOutSize = default;
            global::IntStructWrapper[] __retVal = default;
            global::IntStructWrapperNative*__retVal_gen_native = default;
            //
            // Setup
            //
            bool p__allocated = false;
            bool pIn__allocated = false;
            global::IntStructWrapper[] pIn_local = pIn;
            global::IntStructWrapper[] pRef_local = pRef;
            global::IntStructWrapper[] pOut_local = pOut;
            try
            {
                //
                // Marshal
                //
                if (p != null)
                {
                    int p__bytelen = checked(sizeof(global::IntStructWrapperNative) * p.Length);
                    if (p__bytelen > 512)
                    {
                        __p_gen_native = (global::IntStructWrapperNative*)System.Runtime.InteropServices.Marshal.AllocCoTaskMem(p__bytelen);
                        p__allocated = true;
                    }
                    else
                    {
                        byte *p__stackptr = stackalloc byte[p__bytelen];
                        ;
                        __p_gen_native = (global::IntStructWrapperNative*)p__stackptr;
                    }
                }

                if (p != null)
                {
                    new System.Span<global::IntStructWrapperNative>((global::IntStructWrapperNative*)__p_gen_native, p.Length).Clear();
                    for (int __i = 0; __i < p.Length; ++__i)
                    {
                        __p_gen_native[__i] = new global::IntStructWrapperNative(p[__i]);
                    }
                }

                if (pIn_local != null)
                {
                    int pIn__bytelen = checked(sizeof(global::IntStructWrapperNative) * pIn_local.Length);
                    if (pIn__bytelen > 512)
                    {
                        __pIn_gen_native = (global::IntStructWrapperNative*)System.Runtime.InteropServices.Marshal.AllocCoTaskMem(pIn__bytelen);
                        pIn__allocated = true;
                    }
                    else
                    {
                        byte *pIn__stackptr = stackalloc byte[pIn__bytelen];
                        ;
                        __pIn_gen_native = (global::IntStructWrapperNative*)pIn__stackptr;
                    }
                }

                if (pIn_local != null)
                {
                    new System.Span<global::IntStructWrapperNative>((global::IntStructWrapperNative*)__pIn_gen_native, pIn.Length).Clear();
                    for (int __i = 0; __i < pIn.Length; ++__i)
                    {
                        __pIn_gen_native[__i] = new global::IntStructWrapperNative(pIn_local[__i]);
                    }
                }

                __pRef_gen_native = null;
                if (pRef_local != null)
                {
                    int pRef__bytelen = checked(sizeof(global::IntStructWrapperNative) * pRef_local.Length);
                    __pRef_gen_native = (global::IntStructWrapperNative*)System.Runtime.InteropServices.Marshal.AllocCoTaskMem(pRef__bytelen);
                }

                if (pRef_local != null)
                {
                    new System.Span<global::IntStructWrapperNative>((global::IntStructWrapperNative*)__pRef_gen_native, pRef.Length).Clear();
                    for (int __i = 0; __i < pRef.Length; ++__i)
                    {
                        __pRef_gen_native[__i] = new global::IntStructWrapperNative(pRef_local[__i]);
                    }
                }

                //
                // Invoke
                //
                fixed (int *__pOutSize_gen_native = &pOutSize)
                    __retVal_gen_native = Method__PInvoke__(__p_gen_native, &__pIn_gen_native, pRefSize, &__pRef_gen_native, &__pOut_gen_native, __pOutSize_gen_native);
                //
                // Unmarshal
                //
                if (__retVal_gen_native != null)
                {
                    __retVal = new global::IntStructWrapper[10];
                    for (int __i = 0; __i < __retVal.Length; ++__i)
                    {
                        __retVal[__i] = __retVal_gen_native[__i].ToManaged();
                    }
                }
                else
                    __retVal = null;
                if (__pRef_gen_native != null)
                {
                    pRef_local = new global::IntStructWrapper[checked((int)pRefSize)];
                    for (int __i = 0; __i < pRef_local.Length; ++__i)
                    {
                        pRef_local[__i] = __pRef_gen_native[__i].ToManaged();
                    }
                }
                else
                    pRef_local = null;
                pRef = pRef_local;
                if (__pOut_gen_native != null)
                {
                    pOut_local = new global::IntStructWrapper[checked(4 + (int)pOutSize)];
                    for (int __i = 0; __i < pOut_local.Length; ++__i)
                    {
                        pOut_local[__i] = __pOut_gen_native[__i].ToManaged();
                    }
                }
                else
                    pOut_local = null;
                pOut = pOut_local;
            }
            finally
            {
                //
                // Cleanup
                //
                if (p__allocated)
                {
                    System.Runtime.InteropServices.Marshal.FreeCoTaskMem((System.IntPtr)__p_gen_native);
                }

                if (pIn__allocated)
                {
                    System.Runtime.InteropServices.Marshal.FreeCoTaskMem((System.IntPtr)__pIn_gen_native);
                }

                System.Runtime.InteropServices.Marshal.FreeCoTaskMem((System.IntPtr)__pRef_gen_native);
                System.Runtime.InteropServices.Marshal.FreeCoTaskMem((System.IntPtr)__pOut_gen_native);
            }

            return __retVal;
        }
    }

    [System.Runtime.InteropServices.DllImportAttribute("DoesNotExist", EntryPoint = "Method")]
    extern private static unsafe global::IntStructWrapperNative*Method__PInvoke__(global::IntStructWrapperNative*p, global::IntStructWrapperNative**pIn, int pRefSize, global::IntStructWrapperNative**pRef, global::IntStructWrapperNative**pOut, int *pOutSize);
}

Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add some notes about arrays to the compat doc - multi-dimensional not supported and support for nested array types?

@jkoritzinsky
Copy link
Member Author

The compat doc already mentions multidimensional arrays (it says only single dimensional arrays are supported). I've added a little blurb about current support for arrays of arrays.

@jkoritzinsky jkoritzinsky merged commit ada198f into dotnet:feature/DllImportGenerator Dec 10, 2020
@jkoritzinsky jkoritzinsky deleted the arrays-with-custom-marshalled-elements branch December 10, 2020 01:42
jkoritzinsky added a commit to jkoritzinsky/runtime that referenced this pull request Sep 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-DllImportGenerator Source Generated stubs for P/Invokes in C#
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants