Как предотвратить указатель функции, созданный в управляемом коде и переданный в неуправляемый код, от сбора мусора - PullRequest
2 голосов
/ 26 июня 2019

Я использую библиотеку с именем muParserNET в моем приложении. muParserNET - это библиотека парсинга функций, и мое приложение вызывает ее несколько раз из разных потоков.

muParserNET состоит из C ++ (неуправляемой) библиотеки DLL с управляемой оболочкой C #. В этой оболочке при инициализации он передает указатель на подпрограмму обработки ошибок в неуправляемую библиотеку.

т.е. внутри класса Parser у нас есть эта функция:

    /// <summary>
    /// Error handler. It loads the ParserError exception.
    /// </summary>
    private void ErrorHandler()
    {
        IntPtr ptrMessage = MuParserLibrary.mupGetErrorMsg(this.parserHandler);
        string message = Marshal.PtrToStringAnsi(ptrMessage);

        IntPtr ptrToken = MuParserLibrary.mupGetErrorToken(this.parserHandler);
        string token = Marshal.PtrToStringAnsi(ptrToken);

        string expr = this.Expr;
        ErrorCodes code = (ErrorCodes)MuParserLibrary.mupGetErrorCode(this.parserHandler);
        int pos = MuParserLibrary.mupGetErrorPos(this.parserHandler);

        // lança a exceção
        throw new ParserError(message, expr, token, pos, code);
    }

Вот инициализация объекта анализатора в управляемом коде. это происходит в последней строке этой функции:

    public Parser()
    {
        // inicializa o parser
        this.parserHandler = MuParserLibrary.mupCreate(0);

        // inicializa o dicionário com as variáveis
        this.vars = new Dictionary<string, ParserVariable>();

        // inicializa as listas de delegates
        this.identFunctionsCallbacks = new List<ParserCallback>();
        this.funcCallbacks = new Dictionary<string, ParserCallback>();

        this.infixOprtCallbacks = new Dictionary<string, ParserCallback>();
        this.postfixOprtCallbacks = new Dictionary<string, ParserCallback>();
        this.oprtCallbacks = new Dictionary<string, ParserCallback>();

        // inicializa o delegate de factory
        this.factoryCallback = new ParserCallback(new IntFactoryFunction(this.VarFactoryCallback));

        // ajusta a função de tratamento de erros
        MuParserLibrary.mupSetErrorHandler(this.parserHandler, this.ErrorHandler);
    }

При запуске этого кода, время от времени, при вызовах для оценки функций (поэтому иногда после инициализации объекта) я получаю эту ошибку:

A callback was made on a garbage collected delegate of type 'muParserNET!muParserNET.ErrorFuncType::Invoke'

ErrorFuncType - это тип this.ErrorHandler, переданный выше с использованием MuParserLibrary.mupSetErrorHandler.

Насколько я понимаю, поскольку функция обработчика ошибок не используется после того, как ее указатель передан в неуправляемый код, она получает сборщик мусора. Как я могу предотвратить это?

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

Ответы [ 2 ]

0 голосов
/ 27 июня 2019

Я понял это в конце.

Я создаю две переменные класса: одну для обработчика функции и одну для GChandle:

private ErrorFuncType ptrErrHandler; 
private GCHandle gchErrorHandler;

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

  ptrErrHandler = this.ErrorHandler;
  this.gchErrorHandler = GCHandle.Alloc(ptrErrHandler);
  MuParserLibrary.mupSetErrorHandler(this.parserHandler, ptrErrHandler);

Наконец, в деструкторе класса вам нужно освободить GCHandle, чтобы он мог собирать мусор:

  gchErrorHandler.Free();
0 голосов
/ 26 июня 2019

Не можете комментировать с <50 репутацией, но у меня нет четкого ответа, только подсказки. </p>

То есть вы говорите, что может быть несколько потоков, которые все вызывают "MuParserLibrary.mupSetErrorHandler"?Это уже кажется неправильным.Объявляют ли спецификации библиотеки mupSetErrorHandler или фактически любую часть библиотеки как «поточно-ориентированную»?

Представьте себе такой сценарий:

  • Поток A устанавливает обработчик ошибок, запускаетсяработа.
  • Поток B устанавливает обработчик ошибок, начинает работу.Текущий обработчик ошибок в библиотеке теперь имеет ссылку на обработчик ошибок для потока B.
  • Поток B завершает работу раньше, чем A.
  • A выдает ошибку.
  • В библиотеке по-прежнему есть ссылка на обработчик ошибок из B, который теперь недействителен.

Из вашего примера неясно, может ли B остановиться раньше, чем A, но если есть другой сценарий, который может привести вас втакое состояние, вот что будет.Я думаю, вам нужен более глобальный обработчик ошибок, который всегда используется в библиотеке.Но если у вас нет возможности отследить источник (поток) ошибки или библиотека просто не предназначена для многопоточного использования, это не сильно поможет.Если это типичная «библиотека обёрток статического класса C # вокруг нативной библиотеки C DLL», я боюсь, что это так.Вам также понадобится несколько объектов состояния библиотеки (со ссылками на обработчики ошибок и тому подобное), т.е. по одному на поток.Если эта библиотека не может этого сделать, то, как вы сейчас делаете вещи, не сработает.

...