Структура Marshal с членом массива в C # - PullRequest
4 голосов
/ 07 декабря 2009

Я использую 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-взаимодействия или PInvoke, которые могут повредить стек.

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

Ответы [ 3 ]

4 голосов
/ 07 декабря 2009

Если вы указываете массив в структуре, используемой в качестве параметра 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 битным проблемам.

3 голосов
/ 07 декабря 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);

- или -

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

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

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

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

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

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

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

1 голос
/ 07 декабря 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);
    }
}
...