【问题标题】:How to prevent function pointer created in managed code, and passed to unmanaged code, from getting garbage collected如何防止在托管代码中创建并传递给非托管代码的函数指针被垃圾收集
【发布时间】:2019-11-08 07:22:58
【问题描述】:

我在我的应用程序中使用了一个名为 muParserNET 的库。 muParserNET 是一个函数解析库,我的应用程序从不同的线程多次调用它。

muParserNET 由一个带有托管 C# 包装器的 C++(非托管)dll 组成。在这个包装器中,它在初始化时将指向错误处理例程的指针传递给非托管库。

即在 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 是上面使用 MuParserLibrary.mupSetErrorHandler 传递的 this.ErrorHandler 的类型。

我的理解是,由于在将指针传递给非托管代码后未使用错误处理函数,因此它会被垃圾收集。我怎样才能防止这种情况发生?

更多信息基于第一个回复: 解析器对象是在一个计算例程中创建的,该例程通常可以同时在多达 8 个单独的线程上运行。该对象是在计算例程中创建和处理的。出于这个原因,我不想将解析器对象创建为静态的,因为它会限制我在任何时候都只能有一个线程使用解析器。

【问题讨论】:

  • 在某个静态字段中保留对Parser 类的引用?实际上很难告诉您最好的方法,因为我们不知道应用程序中所有对象的生命周期的上下文,但是在您的应用程序生命周期中存在的对象之一需要直接或间接引用Parser 实例(或者您需要将其放在静态字段中,但这有点全局变量)
  • 谢谢。上面添加了更多信息。

标签: c# garbage-collection unmanaged managed


【解决方案1】:

无法以

所以你是说可以有多个线程都调用“MuParserLibrary.mupSetErrorHandler”?这似乎已经错了。库的规范是否声明了 mupSetErrorHandler,或者实际上库的任何部分都是“线程安全的”?

想象一下这个场景:

  • 线程 A 设置错误处理程序,开始工作。
  • 线程 B 设置错误处理程序,开始工作。库中的当前错误处理程序现在具有对线程 B 错误处理程序的引用。
  • 线程 B 比 A 更早完成工作。
  • A 产生错误。
  • 库仍然有来自 B 的错误处理程序的引用,现在无效。

从您的示例中,尚不清楚 B 是否可以比 A 更早停止,但如果有另一种情况可能让您进入这种状态,那就是会发生什么。 我认为您需要该库始终使用的更全局的错误处理程序。但是,如果您无法跟踪错误的来源(线程),或者该库不是为多线程使用而设计的,那么它不会有太大帮助。 如果这是典型的“本地 C DLL 的静态类 C# 包装器”库的东西,恐怕就是这种情况。 您还需要库状态的多个对象(以及对错误处理程序等的引用),即每个线程一个。如果那个库不能做到这一点,那么你现在做事的方式就行不通了。

【讨论】:

  • 包装类不是静态的,因此每个线程创建自己的实例并相应地设置错误处理程序。错误处理程序使用非静态变量,因此不能是静态的。
【解决方案2】:

我最后想通了。

我创建了两个类变量:一个用于函数处理程序,一个用于 GChandle:

private ErrorFuncType ptrErrHandler; 
private GCHandle gchErrorHandler;

然后使用 GCHandle 防止函数指针在传递给非托管代码之前被垃圾回收:

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

最后,在类析构函数中,您需要释放 GCHandle 以允许它收集垃圾:

  gchErrorHandler.Free();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-27
    • 1970-01-01
    • 2011-01-21
    相关资源
    最近更新 更多