Закрепление делегата в структуре перед передачей в неуправляемый код - PullRequest
2 голосов
/ 06 апреля 2009

Я пытаюсь использовать неуправляемый Cll для загрузки данных изображения в приложение C #. Библиотека имеет довольно простой интерфейс, в котором вы передаете структуру, содержащую три обратных вызова: один для получения размера изображения, другой для получения каждой строки пикселей и, наконец, один для вызова после завершения загрузки. Вот так (управляемое определение C #):

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct st_ImageProtocol
{
   public st_ImageProtocol_done Done;    
   public st_ImageProtocol_setSize SetSize;    
   public st_ImageProtocol_sendLine SendLine;
}

Типами, начинающими st_ImageProtocol, являются delgates:

public delegate int st_ImageProtocol_sendLine(System.IntPtr localData, int rowNumber, System.IntPtr pixelData);

При использовании тестового файла, который я использую, SetSize должен вызываться один раз, затем SendLine будет вызываться 200 раз (по одному разу для каждой строки пикселей в изображении), и, наконец, вызывается обратный вызов Done. На самом деле происходит то, что SendLine вызывается 19 раз, а затем выдается AccessViolationException, утверждая, что библиотека пыталась получить доступ к защищенной памяти.

У меня есть доступ к коду библиотеки C (хотя я не могу изменить функциональность), и во время цикла, когда он вызывает метод SendLine, он не выделяет и не освобождает новую память, поэтому я предполагаю, что делегат проблема сама по себе, и мне нужно закрепить ее, прежде чем передать (в настоящее время у меня нет кода внутри самого делегата, кроме счетчика, чтобы увидеть, как часто он вызывается, поэтому я сомневаюсь, что что-то сломалось на управляемой стороне) , Проблема в том, что я не знаю, как это сделать; метод, который я использовал для объявления структур в неуправляемом пространстве, не работает с делегатами (Marshal.AllocHGlobal ()), и я не могу найти другой подходящий метод. Сами делегаты являются статическими полями в классе Program, поэтому их не следует собирать, но я думаю, что среда выполнения могла бы их перемещать.

Эта запись в блоге Криса Брамма говорит, что делегаты не должны быть закреплены перед передачей в неуправляемый код:

Очевидно, что указатель неуправляемой функции должен ссылаться на фиксированный адрес. Было бы катастрофой, если бы GC переместил это! Это приводит к тому, что многие приложения создают дескриптор закрепления для делегата. Это совершенно не нужно. Указатель неуправляемой функции фактически ссылается на заглушку собственного кода, которую мы динамически генерируем для выполнения перехода и маршалинга. Эта заглушка существует в фиксированной памяти вне кучи GC.

Но я не знаю, верно ли это, когда делегат является частью структуры. Это означает, что их можно закрепить вручную, и меня интересует, как это сделать, или какие-либо более полезные предложения относительно того, почему цикл будет выполняться 19 раз, а затем внезапно завершится неудачей.

Спасибо.


Отредактировано для ответа на вопросы Йохана ...

Код, который выделяет структуру, выглядит следующим образом:

_sendLineFunc = new st_ImageProtocol_sendLine(protocolSendLineStub);

_imageProtocol = new st_ImageProtocol()
                     {
                          //Set some other properties...
                          SendLine = _sendLineFunc
                     };

int protocolSize = Marshal.SizeOf(_imageProtocol);
_imageProtocolPtr = Marshal.AllocHGlobal(protocolSize);
Marshal.StructureToPtr(_imageProtocol, _imageProtocolPtr, true);

Где переменные _sendLineFunc и _imageProtocol являются статическими полями класса Program. Если я правильно понимаю внутренности этого, это означает, что я передаю неуправляемый указатель на копию переменной _imageProtocol в библиотеку C, но эта копия содержит ссылку на статический _sendLineFunc. Это должно означать, что GC не трогает копию, поскольку она неуправляема, и делегат не будет собран, поскольку он все еще находится в области действия (статическая).

Структура фактически передается в библиотеку как возвращаемое значение из другого обратного вызова, но как указатель:

private static IntPtr beginCallback(IntPtr localData, en_ImageType imageType)
{
    return _imageProtocolPtr;
}

По существу, существует другой тип структуры, который содержит имя файла изображения и указатель на функцию для этого обратного вызова, библиотека выясняет, какой тип изображения хранится в файле, и использует этот обратный вызов для запроса правильной структуры протокола для данного типа. Моя структура имени файла объявляется и управляется так же, как и протокол, описанный выше, поэтому, вероятно, содержит те же ошибки, но поскольку этот делегат вызывается только один раз и вызывается быстро, у меня еще не было проблем с ним.


Изменено для обновления

Спасибо всем за ответы, но, потратив еще пару дней на проблему и не добившись прогресса, я решил отложить ее. На случай, если кому-то будет интересно, я пытался написать инструмент для пользователей приложения рендеринга Lightwave 3D, и хорошей особенностью была бы возможность просмотра всех различных форматов изображений, которые поддерживает Lightwave (некоторые из которых довольно экзотичны). Я подумал, что лучший способ сделать это - написать оболочку C # для архитектуры плагинов, которую Lightwave использует для манипулирования изображениями, чтобы я мог использовать их код для фактической загрузки файлов. К сожалению, после того, как я попробовал несколько плагинов для моего решения, у меня было множество ошибок, которые я не мог понять или исправить, и я предполагаю, что Lightwave не вызывает методы для плагинов стандартным способом, вероятно, для повышения безопасности. запуска внешнего кода (дикий удар в темноте, я признаю). Пока я собираюсь отбросить функцию изображения, и если я решу восстановить ее, я подойду к ней по-другому.

Еще раз спасибо, я многому научился благодаря этому процессу, хотя и не получил желаемого результата.

Ответы [ 4 ]

2 голосов
/ 18 апреля 2009

У меня была похожая проблема при регистрации делегата обратного вызова (он будет вызван, затем пуф!). Моя проблема заключалась в том, что объект с делегируемым методом получал GC. Я создал объект в более глобальном месте, чтобы он не мог быть скопирован.

Если что-то подобное не работает, обратите внимание на другие вещи:

В качестве дополнительной информации взгляните на GetFunctionPointerForDelegate из класса Marshal. Это еще один способ сделать это. Просто убедитесь, что делегаты не GC'ed. Затем, вместо делегатов в вашей структуре, объявите их как IntPtr.

Это может не решить проблему закрепления, но взгляните на ключевое слово fixed, хотя это может не сработать для вас, поскольку вы имеете дело с более длительным сроком службы, чем то, что обычно используется.

Наконец, посмотрите на stackalloc для создания памяти без GC. Эти методы потребуют использования unsafe и, следовательно, могут наложить некоторые другие ограничения на ваши сборки.

0 голосов
/ 08 мая 2014

Если функция c является функцией __cdecl, тогда вы должны использовать Attribut [UnmanagedFunctionPointer (CallingConvention.Cdecl)] до объявления делегата.

0 голосов
/ 18 апреля 2009

Вы не говорите точно, как обратный вызов объявлен в библиотеке C. Если явно не объявлено __stdcall, вы медленно повредите свой стек. Вы увидите, что ваш метод вызывается (возможно, с измененными параметрами), но в какой-то момент в будущем программа завершится сбоем.

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

0 голосов
/ 06 апреля 2009

Было бы интересно узнать чуть больше:

  • Как создать структуру ImageProtocol? Это локальная переменная или член класса, или вы выделяете ее в неуправляемой памяти с помощью Marshal.AllocHGlobal?

  • Как это отправляется в функцию C? Прямо как переменная стека или как указатель?


Действительно сложная проблема! Такое ощущение, что данные делегата перемещаются GC, что вызывает нарушение доступа. Интересно то, что тип данных делегата является эталонным типом данных, который хранит свои данные в куче GC. Эти данные содержат такие вещи, как адрес вызываемой функции (указатель на функцию), а также ссылку на объект, который содержит функцию. Это должно означать, что даже если фактический код функции хранится вне кучи GC, данные, содержащие указатель функции, сохраняются в куче GC и, следовательно, могут перемещаться GC. Я много думал о проблеме прошлой ночью, но не нашел решения ....

...