Search code examples
c#marshalling.net-5c#-native-library

How to Marshal a nested array of pointers in c#


Library uses nested array of pointer to struct, but result in an exception when using. When there is no nested array of pointers to struct paramB, then there is no exception.

Original code for use of native library in C++:

    typedef struct _paramB
    {
        int order;
        double value;
    }paramB;

    typedef struct _paramA
    {
        char name[SIZE_NAME]; 
        double a;
        double b;
        double c;
        double d;
        paramB* pointerToParamB[SIZE_VECTOR]; 
    }paramA;
    
    int native_ParamA(paramA *params[], int size);

Adapted code to call native method from c# that results in an error

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
        public struct paramA
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SIZE_NAME)]
            public string name; 
            public double a;
            public double b;
            public double c;
            public double d;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = SIZE_VECTOR)]
            public IntPtr[] pointerToParamB;
        }
        
        public static paramA Factory()
        {
            paramA instance = new paramA();

            instance.name = "UUUU";
            instance.pointerToParamB = new IntPtr[SIZE_VECTOR];
            
            for (int i = 0; i < SIZE_VECTOR; i++)
            {
                instance.pointerToParamB[i] = Marshal.AllocHGlobal(Marshal.SizeOf<paramB>());

                paramB element = paramB.Factory();
                                    
                Marshal.StructureToPtr<paramB>(element, instance.pointerToParamB[i], false);
            }

            return instance;
        }
        
        [DllImport("native_dll.dll", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, CharSet = CharSet.Ansi)]
        internal static extern int native_ParamA([In] IntPtr[] params, int size);
        
        static int main()
        {
            var instance = new IntPtr[100];
            paramA[] datos = paramA.Factory(3);

            for (int i = 0; i < 3; i++)
            {
                instance[i] = Marshal.AllocHGlobal(Marshal.SizeOf<paramA>());

                //==> Next Marshal operation uses an struct that also conatains an inner Marshaled vecotr of IntPtr to structures.
                Marshal.StructureToPtr<paramA>(datos[i], instance[i], false);
            }
        
            native_ParamA(instance, 3); //System.AccessViolationException: 'Attempted to read or write protected memory. 
        }

Note: This example doesnt include the needed memory free after calling Marshal.AllocHGlobal


Solution

  • Pack = 1 is almost certainly wrong, although it does depend on your C/C++ compiler.

    You don't need to mess around with any of this manual marshalling. Your existing code anyway has a major memory leak, as it doesn't do Marshal.FreeHGlobal in a finally.

    Just make paramB and paramAinto a class and the marshaller will pass it through as a pointer (as an array of pointers)

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class paramA
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SIZE_NAME)]
        public string name; 
        public double a;
        public double b;
        public double c;
        public double d;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = SIZE_VECTOR)]
        public paramB[] pointerToParamB;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    public class paramB
    {
        public int order;
        public double value;
    }
    
    [DllImport("native_dll.dll", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, CharSet = CharSet.Ansi)]
    internal static extern int native_ParamA([In] ParamA[] @params, int size);
    
    public static paramA Factory()
    {
        var instance = new paramA
        {
             name = "UUUU",
             pointerToParamB = new paramB[SIZE_VECTOR],
        };
        for (var i = 0; i < instance.pointerToParamB.Length; i++)
        {
            instance.pointerToParamB[i] = paramB.Factory();
        }
    
        return instance;
    }
    
    static int main()
    {
        var datos = new paramA[3];
        for (var i = 0; i < datos.Length; i++)
        {
            datos[i] = paramA.Factory();
        }
        native_ParamA(datos, datos.Length);
    }