c# - 如何防止在托管代码中创建并传递给非托管代码的函数指针被垃圾收集

标签 c# garbage-collection unmanaged managed

我在我的应用程序中使用了一个名为 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 是上面使用 MuParserLibrary.mupSetErrorHandler 传递的 this.ErrorHandler 的类型。

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

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

最佳答案

无法以 < 50 声望发表评论,但我没有明确的答案,只有提示。

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

想象一下这个场景:

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

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

关于c# - 如何防止在托管代码中创建并传递给非托管代码的函数指针被垃圾收集,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56771249/

相关文章:

c# - 在 winforms 应用程序中缓存 GDI+ 对象 : is it worth it and how to do it right?

c# - 使用 C# 反射从字典生成动态对象

java - ArrayList.remove(index),垃圾回收可行吗?

c# - 返回时导致 SEGFAULT 的非托管函数指针

c# - 调用非托管函数,该函数采用指向指针参数的指针

c# - 如何在C#中的一个循环中运行两个foreach循环

c# - 没有安装程序的 XNA 游戏应用程序

java - final 变量在匿名类中使用时的垃圾收集

garbage-collection - 将 D 用于实时应用程序?

c# - 在 C# 中使用 DLL