2

I am using a library called muParserNET in my application. muParserNET is a function parsing library, and my application is calling it multiple times from different threads.

muParserNET consists of a C++ (unmanaged) dll with a managed C# wrapper. In this wrapper, it passes the pointer to an error handling routine to the unmanaged library on initialisation.

i.e. within the Parser class we have this function:

    /// <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);
    }

Here is the initialisation of the parser object, in managed code. it happens on the last line of this function:

    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);
    }

On running this code, sporadically, on calls to evaluate functions (so sometime after initialisation of the object) I get this error:

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

ErrorFuncType is the type of this.ErrorHandler passed above using MuParserLibrary.mupSetErrorHandler.

My understanding is that since the error handler function is not used after its pointer is passed to unmanaged code, it gets garbage collected. How can I prevent this from happening?

More information based on the first reply: The parser object is created inside a calculation routine which may be running simultaneously on up to 8 separate threads, typically. The object is created and disposed of within the calculation routine. For this reason, I didn't want to create the parser object as static as it would limit me to only one thread using the parser at any one time.

BPDESILVA
  • 2,040
  • 5
  • 15
  • 35
bgarrood
  • 419
  • 8
  • 17
  • Keep a reference to the `Parser` class in a static field somewhere? It's actually difficult to tell you the best approach because we don't know the context of the lifetime of all the objects in your application, but one of the objects that lives for the lifetime of your application needs to have a direct or indirect reference to the Parser instance (or you need to put it in a static field, but that's a bit global-variable ish) – Matthew Watson Jun 26 '19 at 11:03
  • Thanks. a bit more information added above. – bgarrood Jun 26 '19 at 11:26

2 Answers2

0

Can't comment with < 50 reputation, but I don't have a clear answer, only hints.

So you are saying there can be multiple threads which all call "MuParserLibrary.mupSetErrorHandler"? That already seems wrong. Do the specs of the library declare mupSetErrorHandler, or in fact any part of the library as "thread safe"?

Imagine this scenario:

  • Thread A sets the error handler, starts work.
  • Thread B sets error handler, starts work. The current error handler in the library now has a reference to the error handler for thread-B.
  • Thread B finishes work earlier than A.
  • A produces an error.
  • Library still has a reference to error handler from B, which is now invalid.

From your example it's not clear whether B can stop earlier than A, but if there is another scenario possible getting you into such a state, that's what would happen. I think you'd need a more global error handler that the library always uses. But if you have no way of tracking the source (thread) of the error, or the library is just not made for multi-threaded use, it won't be of much help. If this is the typical "static class C# wrapper around native C DLL" library thing, I'm afraid that that's the case. You'd need several objects of the library state (with its references to error handlers and such), too, i.e. one per thread. If that library can't do that, the way you are doing things now won't work.

sktpin
  • 317
  • 2
  • 15
  • The wrapper class isn't static, so each thread creates its own instance and sets the error handler accordingly. Error handler uses non-static variables so can't be static. – bgarrood Jun 27 '19 at 04:49
0

I figured this out in the end.

I create two class variables: one for function handler, and one for a GChandle:

private ErrorFuncType ptrErrHandler; 
private GCHandle gchErrorHandler;

Then use GCHandle to prevent the function pointer from being garbage collected before passing it to unmanaged code:

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

Finally in the class destructor you need to free the GCHandle to allow it to get garbage collected:

  gchErrorHandler.Free();
bgarrood
  • 419
  • 8
  • 17