Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GCHandle, AppDomains managed code and 3rd party dll

I have looking at many threads about the exception "cannot pass a GCHandle across AppDomains" but I still don't get it....

I'm working with an RFID Reader which is driven by a DLL. I don't have source code for this DLL but only a sample to show how to use it.

The sample works great but I have to copy some code in another project to add the reader to the middleware Microsoft Biztalk.

The problem is that the process of Microsoft Biztalk works in another AppDomain. The reader handle events when a tag is read. But when I run it under Microsoft Biztalk I got this annoying exception.

I can't see any solution on how to make it work...

Here is some code that may be interesting :

// Let's connecting the result handlers.
// The reader calls a command-specific result handler if a command is done and the answer is ready to send.
// So let's tell the reader which functions should be called if a result is ready to send.

// result handler for reading EPCs synchronous
Reader.KSRWSetResultHandlerSyncGetEPCs(ResultHandlerSyncGetEPCs);

[...]

var readerErrorCode = Reader.KSRWSyncGetEPCs();
if (readerErrorCode == tKSRWReaderErrorCode.KSRW_REC_NoError)
{
    // No error occurs while sending the command to the reader. Let's wait until the result handler was called.
    if (ResultHandlerEvent.WaitOne(TimeSpan.FromSeconds(10)))
    {
        // The reader's work is done and the result handler was called. Let's check the result flag to make sure everything is ok.
        if (_readerResultFlag == tKSRWResultFlag.KSRW_RF_NoError)
        {
             // The command was successfully processed by the reader.
             // We'll display the result in the result handler.
        }
        else
        {
            // The command can't be proccessed by the reader. To know why check the result flag.
            logger.error("Command \"KSRWSyncGetEPCs\" returns with error {0}", _readerResultFlag);
        }
    }
    else
    {
        // We're getting no answer from the reader within 10 seconds.
        logger.error("Command \"KSRWSyncGetEPCs\" timed out");
    }
}

[...]

private static void ResultHandlerSyncGetEPCs(object sender, tKSRWResultFlag resultFlag, tKSRWExtendedResultFlag extendedResultFlag, tKSRWEPCListEntry[] epcList)
{
    if (Reader == sender)
    {
        // Let's store the result flag in a global variable to get access from everywhere.
        _readerResultFlag = resultFlag;

        // Display all available epcs in the antenna field.
        Console.ForegroundColor = ConsoleColor.White;
        foreach (var resultListEntry in epcList)
        {
            handleTagEvent(resultListEntry);
        }

        // Let's set the event so that the calling process knows the command was processed by reader and the result is ready to get processed.
        ResultHandlerEvent.Set();
    }
}
like image 505
hurtauda Avatar asked Aug 22 '13 11:08

hurtauda


1 Answers

You are having a problem with the gcroot<> helper class. It is used in the code that nobody can see, inside that DLL. It is frequently used by C++ code that was designed to interop with managed code, gcroot<> stores a reference to a managed object. The class uses the GCHandle type to add the reference. The GCHandle.ToIntPtr() method returns a pointer that the C++ code can store. The operation that fails is GCHandle.FromIntPtr(), used by the C++ code to recover the reference to the object.

There are two basic explanations for getting this exception:

  1. It can be accurate. Which will happen when you initialized the code in the DLL from one AppDomain and use it in another. It isn't clear from the snippet where the Reader class object gets initialized so there are non-zero odds that this is the explanation. Be sure to keep it close to the code that uses the Reader class.

  2. It can be caused by another bug, present in the C++ code inside the DLL. Unmanaged code often suffers from pointer bugs, the kind of bug that can accidentally overwrite memory. If that happens with the field that stores the gcroot<> object then nothing goes wrong for a while. Until the code tries to recover the object reference again. At that point the CLR notices that the corrupted pointer value no longer matches an actual object handle and generates this exception. This is certainly the hard kind of bug to solve since this happens in code you cannot fix and showing the programmer that worked on it a repro for the bug is very difficult, such memory corruption problems never repro well.

Chase bullet #1 first. There are decent odds that Biztalk runs your C# code in a separate AppDomain. And that the DLL gets loaded too soon, before or while the AppDomain is created. Something you can see with SysInternals' ProcMon. Create a repro of this by writing a little test program that creates an AppDomain and runs the test code. If that reproduces the crash then you'll have a very good way to demonstrate the issue to the RFID vendor and some hope that they'll use it and work on a fix.

Having a good working relationship with the RFID reader vendor to get to a resolution is going to be very important. That's never not a problem, always a good reason to go shopping elsewhere.

like image 185
Hans Passant Avatar answered Nov 15 '22 16:11

Hans Passant