Лучшие практики P / Invoke, Pinning и KeepAlive - PullRequest
4 голосов
/ 09 февраля 2009

На работе у нас есть собственный код C, отвечающий за чтение и запись в собственную базу данных плоских файлов. У меня есть оболочка, написанная на C #, которая инкапсулирует вызовы P / Invoke в модель OO. С момента запуска проекта управляемые оболочки для вызовов P / Invoke значительно усложнились. Как ни странно, с текущей оболочкой все в порядке, но я думаю, что мне действительно нужно сделать больше, чтобы обеспечить правильную работу.

Пара замечаний, поднятых ответами:

  1. Вероятно, не нужен KeepAlive
  2. Вероятно, не нужен пиннинг GCHandle
  3. Если вы используете GCHandle, попробуйте ... наконец то дело (хотя вопросы CER не рассматриваются)

Вот пример пересмотренного кода:

[DllImport(@"somedll", EntryPoint="ADD", CharSet=CharSet.Ansi,
           ThrowOnUnmappableChar=true, BestFitMapping=false,
           SetLastError=false)]
[ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)]
internal static extern void ADD(
    [In] ref Int32 id,
    [In] [MarshalAs(UnmanagedType.LPStr)] string key,
    [In] byte[] data, // formerly IntPtr
    [In] [MarshalAs(UnmanagedType.LPArray, SizeConst=10)] Int32[] details,
    [In] [MarshalAs(UnmanagedType.LPArray, SizeConst=2)] Int32[] status);

public void Add(FileId file, string key, TypedBuffer buffer)
{
    // ...Arguments get checked

    int[] status = new int[2] { 0, 0 };
    int[] details = new int[10];

    // ...Make the details array

    lock (OPERATION_LOCK)
    {
        ADD(file.Id, key, buffer.GetBytes(), details, status);
        // the byte[], details, and status should be auto
        // pinned/keepalive'd

        if ((status[0] != 0) || (status[1] != 0))
            throw new OurDatabaseException(file, key, status);

        // we no longer KeepAlive the data because it should be auto
        // pinned we DO however KeepAlive our 'file' object since 
        // we're passing it the Id property which will not preserve
        // a reference to 'file' the exception getting thrown 
        // kinda preserves it, but being explicit won't hurt us
        GC.KeepAlive(file);
    }
}

Мои (исправленные) вопросы:

  1. Будут ли автоматически прикреплены данные, сведения и статус / KeepAlive'd?
  2. Я что-то пропустил, чтобы это работало правильно?

РЕДАКТИРОВАТЬ: Я недавно нашел диаграмму, которая именно то, что вызвало мое любопытство. В основном говорится, что после вызова метода P / Invoke GC может вытеснить ваш нативный код . Таким образом, хотя собственный вызов может выполняться синхронно, GC может выбрать запуск и перемещение / удаление моей памяти. Думаю, теперь мне интересно, достаточно ли автоматического закрепления (или оно вообще работает).

Ответы [ 4 ]

2 голосов
/ 09 февраля 2009

Если ваш неуправляемый код напрямую не манипулирует памятью, я не думаю, что вам нужно прикреплять объект. Пиннинг по существу информирует GC, что он не должен перемещать этот объект в памяти во время компактной фазы цикла сбора. Это важно только для неуправляемого доступа к памяти, когда неуправляемый код ожидает, что данные всегда будут находиться в том же месте, в котором они были переданы. «Режим», в котором работает GC (параллельный или вытесняющий), не должен влиять на закрепленный объекты как поведенческие правила закрепления применяются в любом режиме. Инфраструктура сортировки в .NET пытается быть умной в том, как она маршализирует данные между управляемым / неуправляемым кодом. В этом конкретном случае два создаваемых вами массива будут автоматически закреплены в процессе сортировки.

Вызов GC.KeepAlive, вероятно, также не нужен, если только ваш неуправляемый метод ADD не асинхронный. GC.KeepAlive предназначен только для того, чтобы GC не мог вернуть объект, который он считает мертвым во время длительной работы. Поскольку файл передается в качестве параметра, он, вероятно, используется в другом месте кода после вызова управляемой функции Add, поэтому нет необходимости в вызове GC.KeepAlive.

Вы отредактировали пример кода и удалили вызовы GCHandle.Alloc () и Free (), так значит ли это, что код больше не использует их? Если вы все еще используете его, код внутри вашего блока блокировки (OPERATION_LOCK) также должен быть заключен в блок try / finally. В вашем блоке finally вы, вероятно, захотите сделать что-то вроде этого:

if (dataHandle.IsAllocated)
{
   dataHandle.Free();
}

Кроме того, вы можете убедиться, что вызов GCHandle.Alloc () не должен находиться внутри вашей блокировки. Имея его за пределами блокировки, вы будете иметь несколько потоков, выделяющих память.

Что касается автоматического закрепления, то если данные автоматически закрепляются во время процесса сортировки, они закрепляются и не будут перемещаться во время цикла сбора GC, если они произошли во время выполнения неуправляемого кода. Я не уверен, что полностью понимаю ваш комментарий к коду о причинах продолжения вызова GC.KeepAlive. Неизмененный ли код устанавливает значение для поля file.Id?

1 голос
/ 09 февраля 2009
  1. Я не уверен, в чем смысл вашей KeepAlive, поскольку вы уже освободили GCHandle - кажется, что данные больше не нужны в этот момент?
  2. Подобно # 1, почему вы чувствуете, что вам нужно вообще вызывать KeepAlive? Есть ли что-то за пределами кода, который вы опубликовали, мы не видим?
  3. Вероятно, нет. Если это синхронный P / Invoke, то маршалер будет фактически фиксировать входящие переменные, пока не вернется. На самом деле, вам, вероятно, и не нужно закреплять данные (если только это не асинхронно, но ваша конструкция предполагает, что это не так).
  4. Нет, ничего не пропущено. Я думаю, что вы на самом деле добавили больше, чем нужно.

РЕДАКТИРОВАТЬ в ответ на оригинальный вопрос правки и комментарии:

Диаграмма просто показывает, что режим GC изменяется, режим не влияет на закрепленные объекты. Типы либо закрепляются, либо копируются во время маршалинга , в зависимости от типа. В этом случае вы используете байтовый массив, который, как говорят в документах, имеет тип blittable . Вы также увидите, что в нем также говорится, что «в качестве оптимизации массивы blittable типов и классов, которые содержат только blittable члены, прикрепляются, а не копируются во время маршалинга». Таким образом, это означает, что данные закреплены на время вызова, и если GC запустится, он не сможет переместить или освободить массив. То же самое относится и к статусу.

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

Я до сих пор не вижу смысла вызывать KeepAlive. Предположительно, файл недоступен для сбора, поскольку он был передан методу и имеет некоторый другой корень (где он был объявлен), который сохранит его.

0 голосов
/ 26 февраля 2010
0 голосов
/ 09 февраля 2009

Одной из непосредственных проблем, по-видимому, является то, что вы никогда не вызовете dataHandle.Free (), если выбросите исключение, что приведет к утечке.

...