Проблема с Interop C # / C: AccessViolationException - PullRequest
3 голосов
/ 17 декабря 2010

и спасибо за совет за любую помощь.

У меня есть эта тривиальная функция в C:

__declspec(dllexport)  Point* createPoint (int x, int y) {
    Point *p;

    p = (Point*) malloc(sizeof(Point)); 
    p->x = x;
    p->y=y;

    return p;       
}

Точка - это очень простая структура с двумя полями типа int, x и y.

Я хотел бы вызвать эту функцию из C #.

Я использую этот код:

[DllImport("simpleC.dll", EntryPoint = "createPoint", CallingConvention = CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.LPStruct)]
public static extern Point createPoint(int x, int y);

Point p = Wrapper.createPoint(1, 2);

Но во время выполнения у меня есть AccessViolationException. Просматривая исключение подробно, я обнаружил, что исключение выдается из метода Marshal.CoTaskMemFree(IntPtr).

Похоже, что этот метод не может освободить память, выделенную C malloc.

Что я делаю не так?

Действительно, спасибо.

Ответы [ 3 ]

2 голосов
/ 17 декабря 2010

CoTaskMemFree нельзя использовать для освобождения памяти, выделенной malloc (потому что они используют разные распределители). Согласно MSDN , «среда выполнения всегда использует метод CoTaskMemFree для освобождения памяти. Если память, с которой вы работаете, не была выделена методом CoTaskMemAlloc, вы должны использовать IntPtr и освободить память вручную, используя соответствующий метод. "

Кроме того, Адам Натан отмечает , что "UnmanagedType.LPStruct поддерживается только для одного конкретного случая: обработка типа значения System.Guid как неуправляемого GUID с дополнительным уровнем косвенности. ... Вы должны вероятно, просто держитесь подальше от UnmanagedType.LPStruct. "

Существует два возможных решения:

  1. Объявите тип возвращаемого значения метода как IntPtr и используйте Marshal.ReadInt32 , чтобы прочитать поля структуры, или используйте Marshal.PtrToStructure , чтобы скопировать данные в Управляемая структура или используйте небезопасный код для приведения значения IntPtr к Point *. Библиотека C должна будет предоставить метод destroyPoint(Point *), который освобождает память.
  2. Измените сигнатуру метода C на void getPoint(int x, int y, Point *). Это позволяет C # распределять структуру, а метод C просто заполняет значения данных. (Большинство Win32 API определяются таким образом).

Последнее замечание: если ваш метод не использует SetLastError Win32 API, вам не нужно указывать SetLastError = true в атрибуте P / Invoke.

1 голос
/ 17 декабря 2010

Как определяется тип Point на стороне C #?
Это должно быть небезопасно, или вам нужно вернуть указатель void (IntPtr).GC не может считать ссылки извне (здесь выделенная память), поэтому ваш код не может рассчитывать на управление внешней памятью, распределенной через GC.
Одна альтернатива - сохранить статическую ссылку, чтобы избежать сборки мусора, есливам необходимо постоянно сохранять объект во время выполнения приложения.

1 голос
/ 17 декабря 2010

Поскольку у вас нет кода, который освобождает «p», трудно сказать.Однако вполне вероятно, что способ совместной работы malloc () и free () полностью отличается от способа управления памятью в C #.Поскольку в C # есть сборка мусора (я полагаю), вполне вероятно, что он использует совершенно другую систему управления памятью.

В любом случае правильное решение состоит в том, что если вы используете свою библиотеку для создания объекта, вам следуетиспользовать его, чтобы уничтожить.Реализуйте функцию destroyPoint, которая освобождает память в вашей библиотеке C, импортируйте ее в код C # и вызывайте ее оттуда для уничтожения объектов, созданных вашей библиотекой C.

Как общее правило проектирования / кодированиякаждая функция «создать» должна иметь соответствующую функцию «освободить / уничтожить / удалить».Помимо всего прочего, это позволяет легко гарантировать, что все созданные предметы будут должным образом уничтожены.

...