Структура Marshal с элементом массива в C#

Я использую С# с P/Invoke для доступа к методу DLL. Определение метода следующее:

[DllImport("userManager.dll")]
static extern int GetUsers(out IntPtr userList);

Оригинальные структуры:

typedef struct user_list {
   unsigned short NumUsers;
   USER_LIST_ITEM List[VARLEN];
} USER_LIST

typedef struct user_list_item {
   char name[260];
   unsigned char address[256];
} USER_LIST_ITEM

И макет структуры, который я сделал, следующий:

[StructLayout(LayoutKind.Sequential)]
public class USER_LIST
{
    public uint NumUsers;
    [MarshalAs(UnmanagedType.ByValArray)]
    public USER_LIST_ITEM [] List;
}

[StructLayout(LayoutKind.Sequential)]
public class USER_LIST_ITEM
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string name;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string address;
};

Но я получаю сообщение об ошибке, когда пытаюсь разобрать его:

USER_LIST userList = new USER_LIST();
// Prepare pointer 
IntPtr uList = Marshal.AllocHGlobal(Marshal.SizeOf(userList));
Marshal.StructureToPtr(userList, uList, false);
result = GetUsers(out uList);

Marshal.PtrToStructure(uList, userList); <--

Среда выполнения обнаружила фатальную ошибку. Адрес ошибки был 0x79f82af6, в потоке 0x464. Код ошибки 0xc0000005. Эта ошибка может быть ошибкой в ​​среде CLR или в небезопасных или непроверяемых частях пользовательского кода. Распространенными источниками этой ошибки являются ошибки маршалинга пользователя для COM-interop или PInvoke, которые могут повредить стек.

Я правильно понимаю свойство NumUsers, но кажется, что ошибка возникает при разупорядочении массива. Есть предположения?


person Sergi    schedule 07.12.2009    source источник
comment
Какова подпись оригинальной функции GetUsers?   -  person dtb    schedule 07.12.2009
comment
Зачем вы выделяете место/подготавливаете указатель? out uList означает, что uList будет перезаписано, но в собственный код не будет передано никакого значения.   -  person dtb    schedule 07.12.2009
comment
Исходная подпись: STATUS UMAPI GetUsers(OUT USER_LIST** userList);   -  person Sergi    schedule 07.12.2009
comment
Хорошо. Следующий вопрос: Кто отвечает за выделение памяти? Вызывающий (код С#) или функция (собственный код)?   -  person dtb    schedule 07.12.2009
comment
Что такое OUT USER_LIST**? Тройной указатель!?   -  person dtb    schedule 07.12.2009
comment
Я предполагаю, что нативный код, как и в простом C, отлично работает без выделения памяти.   -  person Sergi    schedule 07.12.2009
comment
Зачем тогда двойной (тройной?) указатель? У вас есть пример того, как функция должна вызываться (на C)?   -  person dtb    schedule 07.12.2009
comment
OUT ничего, просто украшение. Это указатель на список USER_LIST. Функция будет просто писать по указанному указателю.   -  person leppie    schedule 07.12.2009
comment
Вот как это работает с простым C: USER_LIST* user_list; res = GetUsers(&user_list);   -  person Sergi    schedule 07.12.2009
comment
THE USER_LIST — это структура, определенная выше.   -  person Sergi    schedule 07.12.2009
comment
А как вы освобождаете user_list? Звучит болезненно.   -  person leppie    schedule 07.12.2009
comment
USER_LIST* user_list; res = GetUsers(&user_list); -- это не выделяет память для элементов пользовательского списка, не так ли?   -  person dtb    schedule 07.12.2009
comment
В Dll есть метод освобождения памяти (UMFree(user_list))   -  person Sergi    schedule 07.12.2009
comment
После этого вызова я получаю USER_LIST, заполненный всеми данными, так что я думаю, что это так. Затем я могу перебирать внутренний список   -  person Sergi    schedule 07.12.2009
comment
Я считаю, что вся память выделяется GetUsers, поэтому нет необходимости выделять память в управляемом коде. Просто используйте UMFree, чтобы освободить память. Пожалуйста, взгляните на мой обновленный ответ.   -  person dtb    schedule 07.12.2009
comment
Так как же узнать, когда список пользователей остановится? У вас есть какое-то секретное соглашение, что последняя запись будет NULL?   -  person leppie    schedule 07.12.2009


Ответы (3)


Если вы указываете массив в структуре, используемой в качестве параметра out, вам нужно сообщить маршалеру, какой длины будет массив. В вашем коде маршалер, вероятно, выделяет массив нулевой длины или просто использует null, что приводит к сбою. К сожалению, кажется, нет способа указать массив переменной длины out в качестве члена структуры, потому что MarshalAs.SizeParamIndex работает только для методов. Вам может сойти с рук указание большого массива постоянного размера с помощью MarshalAs.SizeConst, но обычно вам придется анализировать буфер возврата (предположительно выделенный вызываемым пользователем) следующим образом:

var count = Marshal.ReadInt32 (uList) ;
var users = new List<USER_LIST_ITEM>  () ;
var ptr   = (long)uList + 4 ;
for (int i = 0 ; i < count ; ++i)
{
    users.Add (Marshal.PtrToStructure (typeof (USER_LIST_ITEM), 
        new IntPtr (ptr))) ;
    ptr += Marshal.SizeOf (typeof (USER_LIST_ITEM)) ;
}

Вам придется уделить особое внимание выравниванию и заполнению, а также проблемам с 32/64-битными версиями.

person Anton Tykhyy    schedule 07.12.2009

Это потому, что List еще не выделено.

Вам нужно будет инициализировать все поля.

Еще одна проблема, которую я вижу, заключается в следующем:

IntPtr uList = Marshal.AllocHGlobal(Marshal.SizeOf(userList));
...
result = GetUsers(out uList);

Вы уверены, что out не должно быть ref? В противном случае нет смысла (тоже не уверен, что ref правильно).

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

IntPtr uList;
var result = GetUsers(out uList);

var userlist = (USER_LIST) Marshal.PtrToStructure(ulist, typeof(USER_LIST));

Marshal.FreeHGlobal(ulist); // pray here or shoot the author of the C function

Обновите снова:

Ваша подпись p/invoke, вероятно, неверна или вы неправильно ее интерпретируете.

Я могу предположить, что это, вероятно, что-то вроде:

int GetUsers(USER_LIST* ulist);

И то, что у вас есть, это не то же самое.

Если это так, решение легко.

Измените USER_LIST на класс (но сохраните последовательную компоновку) и используйте

// pinvoke sig
int GetUsers(USER_LIST ulist);

var ulist = new USER_LIST();
// initialize fields
var r = GetUsers(ulist);

-- or --

Позвоните по ref.

// pinvoke sig
int GetUsers(ref USER_LIST ulist);

var ulist = new USER_LIST();
// initialize fields
var r = GetUsers(ref ulist);

Таким образом, вам не нужно возиться с ручным маршаллингом, и я больше не вижу потенциальных утечек памяти.

Окончательное обновление:

Учитывая опубликованную вами подпись, похоже, что GetUsers возвращает указатель на список USER_LIST с возвращаемым значением, являющимся счетчиком. Хорошая утечка памяти.

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

person leppie    schedule 07.12.2009
comment
Возврат либо 0, либо 1, просто чтобы показать успех вызова - person Sergi; 07.12.2009
comment
@Sergi: тогда у вас большая утечка и хорошее переполнение буфера для загрузки! - person leppie; 07.12.2009
comment
@Sergi: переход в небезопасное состояние, вероятно, будет выглядеть так же, как C, и может быть самым простым. - person leppie; 07.12.2009

Я думаю, что ваш исходный код, вероятно, не так уж неверен. Вероятно, вы только что использовали неправильную перегрузку Marshal.PtrToStructure.

Вы пробовали это?

[DllImport("userManager.dll")]
static extern int GetUsers(out IntPtr userList);

[DllImport("userManager.dll")]
static extern void UMFree(IntPtr userList);

static void Main()
{
    IntPtr userList;               // no need to allocate memory in managed code;
    GetUsers(out userList);        // memory is allocated by native function
    USER_LIST u = (USER_LIST)Marshal.PtrToStructure(userList, typeof(USER_LIST));
    UMFree(userList);
}

Использование небезопасного кода:

public unsafe struct USER_LIST
{
    public uint numUsers;
    public USER_LIST_ITEM* list;
}

public unsafe struct USER_LIST_ITEM
{
    public fixed byte name[260];
    public fixed byte address[256];
}

class Program
{
    [DllImport("userManager.dll")]
    static unsafe extern int GetUsers(USER_LIST** userList);

    [DllImport("userManager.dll")]
    static unsafe extern int UMFree(USER_LIST* userList);

    private static unsafe void Main()
    {
        USER_LIST* list;
        GetUsers(&list);
        UMFree(list);
    }
}
person dtb    schedule 07.12.2009
comment
Спасибо за фрагмент, но он не работает, я получаю ту же ошибку, когда PtrToStructure, я не знаю, верно ли определение структуры, потому что код мне тоже кажется правильным - person Sergi; 07.12.2009
comment
Я думаю, проблема в массиве переменного размера в USER_LIST. Как маршаллер может узнать его размер (он не знает, что размер хранится в NumUsers). Использование небезопасного кода, вероятно, является лучшим решением. - person dtb; 07.12.2009
comment
Кстати, ваши структуры в С# фактически объявлены как классы. Вы пытались изменить их на struct? - person dtb; 07.12.2009