С#: обычно конвертировать неуправляемый массив в управляемый список

Я имею дело с набором нативных функций, которые возвращают данные через динамически распределяемые массивы. Функции принимают в качестве входных данных ссылочный указатель, а затем указывают его на результирующий массив.

Например:

typedef struct result
{
   //..Some Members..//
}

int extern WINAPI getInfo(result**);

После вызова 'result' указывает на массив результата*, оканчивающийся нулем.

Я хочу создать управляемый список из этого неуправляемого массива. Я могу сделать следующее:

struct Result
{
   //..The Same Members..//
}

public static unsafe List<Result> getManagedResultList(Result** unmanagedArray)
{
    List<Result> resultList = new List<Result>();

    while (*unmanagedArray != null)
    {
       resultList.Add(**unmanagedArray);
       ++unmanaged;
    }
    return result;
}

Это работает, будет утомительно и некрасиво переопределять для каждого типа структуры, с которой мне придется иметь дело (~ 35). Мне нужно решение, которое является общим для типа структуры в массиве. С этой целью я попытался:

public static unsafe List<T> unmanagedArrToList<T>(T** unmanagedArray)
{ 
    List<T> result = new List<T>();
    while (*unmanagedArray != null)
    {
        result.Add((**unmanagedArray));
        ++unmanagedArray;
    }
    return result;
}

Но это не скомпилируется, потому что вы не можете «взять адрес, получить размер или объявить указатель на управляемый тип ('T')».

Я также пытался сделать это без использования небезопасного кода, но столкнулся с проблемой, что Marshal.Copy() должен знать размер неуправляемого массива. Я мог определить это только с помощью небезопасного кода, поэтому в этом случае использование Marshal.Copy() не дает никаких преимуществ.

Что мне не хватает? Может ли кто-нибудь предложить общий подход к этой проблеме?


person Odrade    schedule 10.06.2009    source источник


Ответы (2)


Вы можете сделать разумное предположение, что размер и представление всех указателей одинаковы (не уверен, гарантирует ли это спецификация C#, но на практике вы обнаружите, что это так). Таким образом, вы можете относиться к своему T** как к IntPtr*. Кроме того, я не вижу, как Marshal.Copy поможет вам здесь, поскольку у него есть перегрузки только для встроенных типов. Так:

public static unsafe List<T> unmanagedArrToList<T>(IntPtr* p)
{ 
    List<T> result = new List<T>();
    for (; *p != null; ++p)
    {
        T item = (T)Marshal.PtrToStructure(*p, typeof(T));
        result.Add(item);
    }
    return result;
}

Конечно, вам потребуется явное приведение к IntPtr* всякий раз, когда вы вызываете это, но, по крайней мере, в противном случае нет дублирования кода.

person Pavel Minaev    schedule 10.06.2009

Ты сказал:

Marshal.Copy() должен знать размер неуправляемого массива. Я мог определить это только с помощью небезопасного кода

Кажется, вам не хватает Marshal.SizeOf ().

Из того, что вы упомянули в посте, этого может быть достаточно для решения вашей проблемы. (Кроме того, параметр вашей функции может быть Object** вместо T**.)

person John Fisher    schedule 10.06.2009
comment
Marshal.Copy нужно знать, сколько элементов находится в исходном массиве, но я не знаю этого во время компиляции, потому что массив динамически выделяется собственным кодом. Я не знаю, как определить количество элементов в неуправляемом массиве без использования небезопасного кода. - person Odrade; 10.06.2009
comment
Кроме того, объект является управляемым типом. Как указано выше, вы не можете выполнять небезопасные операции с управляемым типом. - person Odrade; 10.06.2009
comment
В худшем случае вы можете использовать IntPtr для параметра вашей функции, а затем вызывать Marshal.Copy() один раз для каждого элемента в массиве, пока не дойдете до конца. Вы должны иметь возможность сократить это, сделав копию переданного IntPtr, используя Marshal.SizeOf() для определения правильного размера, а затем добавляя этот размер в цикле, пока не найдете нулевую запись. - person John Fisher; 10.06.2009