Могу ли я маршалировать C-структуру с 2d массивом без использования «unsafe»? - PullRequest
0 голосов
/ 30 мая 2018

У меня есть C DLL, для которой я пишу класс взаимодействия C #.

В C DLL один из ключевых методов заполняет 2d структуру;структура распределяется и освобождается вспомогательными методами, например:

// Simple Struct Definition -- Plain Old Data
typedef struct MyPodStruct_s
{
    double a;
    double b;
} MyPodStruct;

typedef struct My2dArray_s
{
    MyPodStruct** arr; // allocated by Init2d;
                       // array of arrays.
                       // usage: arr[i][j] for i<n,j<m
    int n;
    int m;
} My2dArray;

void Init2d(My2dArray* s, int n, int m);
void Free2d(My2dArray* s);

// fill according to additional work elsewhere in the code:
void Fill2dResult(My2dArray* result);

Просто маршалинг My2dArray.arr в качестве указателя на указатель выглядит как проблема. Есть ли какой-нибудь способ, которым я могу упорядочить это для C #, чтобы я не не нуждался в коде C #, чтобы быть небезопасным?
(я бы настоятельно предпочел избегать изменения моегоC API, если это возможно, или, по крайней мере, сохранение минимальных изменений, но это вариант, если это единственный способ.)

Вот небезопасный код C #, который я в настоящее время (немного упрощенный от реального).Он отлично работает и делает то, что я хочу, но требует небезопасного использования:

class FooInterop
{
    public struct MyPodStruct // Plain Old Data
    {
        public double a;
        public double b;
    };

    [StructLayout(LayoutKind.Sequential)]
    private unsafe struct unmanaged2d
    {
        public MyPodStruct** arr;
        public int n;
        public int m;
    };

    [DllImport("Foo.DLL", EntryPoint = "Init2d", CallingConvention = CallingConvention.Cdecl)]
    private static extern void unsafe_Init2d(ref FooInterop.unmanaged2d, int n, int m);
    [DllImport("Foo.DLL", EntryPoint = "Free2d", CallingConvention = CallingConvention.Cdecl)]
    private static extern void unsafe_Free2d(ref FooInterop.unmanaged2d);
    [DllImport("Foo.DLL", EntryPoint = "Fill2dResult", CallingConvention = CallingConvention.Cdecl)]
    private static extern void unsafe_Fill2dResult(ref FooInterop.unmanaged2d);

    public static FooInterop.MyPodStruct[,] Fill2dResult()
    {
        unmanaged2d unsafeRes = new unmanaged2d();
        FooInterop.MyPodStruct[,] res;

        unsafe_Init2d(ref unsafeRes, n, m); // I have n, m from elsewhere
        unsafe_Fill2dResult(ref unsafeRes );
        res = new FooInterop.MyPodStruct[n,m];

        for (int i=0; i<n; ++i)
        {
            for (int j=0; j<m; ++j)
            {
                unsafe
                {
                    res[i, j] = unsafeRes.arr[i][j];
                }
            }
        }

        unsafe_Free2d(ref unsafeRes );

        return res;
    }
}

1 Ответ

0 голосов
/ 30 мая 2018

Мммм ... Я выложу код, который, вероятно, вам не нужен: -)

Я использую последний компилятор (C # 7.0) ( nuget )плюс небезопасная библиотека ( nuget ).

Дело в том, что я не хочу выполнять маршалирование путем копирования структуры Unmanaged2d, а также не хочу копировать массив.Я хочу использовать их "на месте".Я буду использовать ref return плюс некоторые Unsafe.As* методы для чтения одного MyPodStruct, когда его спросят, и двумерный индексатор, чтобы скрыть все.к сожалению, Unsafe.As* требует ключевое слово unsafe, потому что его методы принимают void* вместо принятия IntPtr.

[StructLayout(LayoutKind.Sequential, Size = 16)]
public struct MyPodStruct // Plain Old Data
{
    public double a;
    public double b;
};

[StructLayout(LayoutKind.Sequential)]
public struct Unmanaged2d
{
    public IntPtr arr;
    public int n;
    public int m;

    public unsafe ref MyPodStruct this[int x, int y]
    {
        get
        {
            if (x < 0 || x >= n)
            {
                throw new ArgumentOutOfRangeException(nameof(x));
            }

            if (y < 0 || y >= m)
            {
                throw new ArgumentOutOfRangeException(nameof(y));
            }

            IntPtr ptr = Marshal.ReadIntPtr(arr, x * sizeof(IntPtr));
            IntPtr ptr2 = ptr + y * 16; // 16 == sizeof(MyPodStruct)
            return ref Unsafe.AsRef<MyPodStruct>(ptr2.ToPointer());
        }
    }
}

unsafe_Init2d(ref unsafeRes, n, m);

// We increase all the values of a and b, just to show that we can!
for (int i = 0; i < u.n; i++)
{
    for (int j = 0; j < u.m; j++)
    {
        u[i, j].a += 10;
        u[i, j].b++;
    }
}

// We print them
for (int i = 0; i < u.n; i++)
{
    Console.WriteLine(string.Join(";", Enumerable.Range(0, u.m).Select(x => string.Format($"({u[i, x].a},{u[i, x].b})"))));
}

В качестве идентификатора кажется, что использование IntPtr делает "небезопасным""код" сейф "не одобряется.Смотрите, например, здесь , где запрос на перегрузку на Span<T>(void*), который принимает Span<T>(IntPtr) и был закрыт, потому что:

Мы хотим, чтобы операции с указателями были явной операциейс указателями, а не прятать их за IntPtr, которые, как правило, дают людям ложное чувство безопасности.

и здесь .

В общем, что вы хотитеdo можно сделать с некоторыми Marshal.ReadIntPtr плюс BitConverter.Int64BitsToDouble(Marshal.ReadInt64(...)), например:

[StructLayout(LayoutKind.Sequential)]
public struct Unmanaged2d
{
    public IntPtr arr;
    public int n;
    public int m;

    public static MyPodStruct[,] Fill2dResult()
    {
        Unmanaged2d unsafeRes = new Unmanaged2d();

        //unsafe_Init2d(ref unsafeRes, n, m); // I have n, m from elsewhere
        //unsafe_Fill2dResult(ref unsafeRes);

        MyPodStruct[,] res = new MyPodStruct[unsafeRes.n, unsafeRes.m];

        for (int i = 0; i < unsafeRes.n; i++)
        {
            IntPtr row = Marshal.ReadIntPtr(unsafeRes.arr, i * IntPtr.Size);

            for (int j = 0, offset = 0; j < unsafeRes.m; j++)
            {
                // Automatic marshaling of MyPodStruct
                // res[i, j] = Marshal.PtrToStructure<MyPodStruct>(row + j * (sizeof(double) + sizeof(double)));

                // Manual marshaling

                // a
                long temp1 = Marshal.ReadInt64(row, offset);
                double dbl1 = BitConverter.Int64BitsToDouble(temp1);
                offset += sizeof(double);

                // b
                long temp2 = Marshal.ReadInt64(row, offset);
                double dbl2 = BitConverter.Int64BitsToDouble(temp2);
                offset += sizeof(double);

                res[i, j] = new MyPodStruct { a = dbl1, b = dbl2 };
            }
        }

        //unsafe_Free2d(ref unsafeRes);

        return res;
    }
}

Технически этот код не содержит ничего, что составляет unsafe, но он настолько же небезопасен, как и ваш код.

Ах ... а в C # то, что у вас есть в C, называется зубчатым массивом.Это массив массивов (массив уровней указателей первого уровня, которые указывают на множество элементов второго уровня).Это не многомерный массив.

...