开发者

FatalExecutionEngineError during C# Marshalling

I'm running into an issue when trying to read c++ structures from memofields in a number of DBase IV files into C# (.Net 4) and then insert them into MSSQL 2008. The data is being extracted ok from the DBase files but I seemed to be doing something wrong with managing the unsafe calls because I randomly get the following error:

FatalExecutionEngineError was detected
Message: The runtime has encountered a fatal error. The address of the error was
at 0x791fa62c, on thread 0x16c0. The error code is 0xc0000005. This error may be
a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common
sources of this bug include user marshaling errors for COM-interop or PInvoke, 
which may corrupt the stack. 

The following is the code I use to read the data. This code completes successfully and the data I get from the files are correct. The error occurs a number of minutes after this function is called, when the objects are being inserted into the database using nhibernate (I left it out because I didn't think it really matter to the issue).

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Event
{        
    public int      Number;
    public int      Month;
    public int      Day;
    public int      Year;
    public int      Hour;
    public int      Minute;
    public int      Second;
    public UInt32   UPCTime;
    public int      BlobSize;
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = DataLengths.MAX_EVENT_TITLE_LENGTH)]
    public string   Title;
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = DataLengths.MAX_TRIGGER_LENGTH)]
    public string Trigger;
 }

public struct Trigger
{
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
    public string Control;
    public int Index;
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
    public string Mode;
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
    public string RecordFreq;
    public int Pre;
    public int Post;
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
    public string Source;
    public int Delay;
    public int EventUserNotify;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
    public int[] Spare;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = DataLengths.MAX_EVENT_SENSORS)]
    public int[] Sensors;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Value
{
    public Trigger Trigger;
    public string[] SensorLabels;
    public Point[] Point;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Point
{
    public Single Value;
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
    public string State;
}

//The dbf is from the java xBasej library that I compiled into a dll using IKVM.net
public Dictionary<Event, Value> Read(DBF dbf)
{     
Dictionary<Event, Value> eventData = new Dictionary<Event, Value>();

try
{
    for (int i = 1; i <= dbf.getRecordCount(); i++)
    {
         dbf.gotoRecord(i);
         MemoField memofield = (MemoField)dbf.getField("MemoField");

         // Perform the conversion from one encoding to the other.
         byte[] blob = memofield.getBytes();

         if (blob.Length == 0)
         {
             continue;
         }

         MemoryStream memoryStream = null;
         BinaryReader binaryReader = null;
         GCHandle eventHandle = new GCHandle();
         GCHandle triggerHandle = new GCHandle();
         GCHandle sensorLabelHandle = new GCHandle();
         GCHandle pointHandle = new GCHandle();

         try
         {
             memoryStream = new MemoryStream(blob);
             binaryReader = new BinaryReader(memoryStream);

             //The data was orignally C++ structures so we read the bytes back into C# equivalent
             //structures.
             int eventDataSize = Marshal.SizeOf(typeof(Event));

             eventHandle = GCHandle.Alloc(binaryReader.ReadBytes(eventDataSize), GCHandleType.Pinned);
             Event @event = (Event)Marshal.PtrToStructure(eventHandle.AddrOfPinnedObject(), typeof(Event));

             //Read the event trigger data
             int triggerDataSize = Marshal.SizeOf(typeof(Trigger));
             triggerHandle = GCHandle.Alloc(binaryReader.ReadBytes(triggerDataSize), GCHandleType.Pinned);
             Trigger trigger = (Trigger)Marshal.PtrToStructure(triggerHandle.AddrOfPinnedObject(), typeof(Trigger));

             Value value = new Value();
             value.Trigger = trigger;

             triggerHandle.Free();

             //Read all the sensor labels
             List<string> sensorLableList = new List<string>();
             for (int sensorIndex = 0; sensorIndex < DataLengths.MAX_EVENT_SENSORS; sensorIndex++)
             {
                   int sensorLableDataSize = DataLengths.MAX_LABEL_LENGTH;
                   sensorLabelHandle = GCHandle.Alloc(binaryReader.ReadBytes(sensorLableDataSize), GCHandleType.Pinned);
                  string label = Marshal.PtrToStringAnsi(sensorLabelHandle.AddrOfPinnedObject(), sensorLableDataSize).Trim();
                   sensorLableList.Add(label);

                   sensorLabelHandle.Free();
             }
             value.SensorLabels = sensorLableList.ToArray();
             //Read all the recorded sensor data
             List<Point> pointList = new List<Point>();
             for (int pointIndex = 0; pointIndex < DataLengths.MAX_EVENT_SENSORS; pointIndex++)
             {
                  int pointDataSize = Marshal.SizeOf(typeof(Point));

                  pointHandle = GCHandle.Alloc(binaryReader.ReadBytes(pointDataSize), GCHandleType.Pinned);
                  Point point = (Point)Marshal.PtrToStructure(pointHandle.AddrOfPinnedObject(), typeof(Point));
                  pointList.Add(point);

                  pointHandle.Free();
             }

             value.Point = pointList.ToArray();

             eventData.Add(@event, value);
             eventHandle.Free();
        }
        final开发者_如何学JAVAly
        {
             //Free all the resources used to get the data
             if (memoryStream != null) { memoryStream.Close(); }
             if (binaryReader != null) { binaryReader.Close(); }
             if (eventHandle.IsAllocated) { eventHandle.Free(); }
             if (triggerHandle.IsAllocated) { triggerHandle.Free(); }
             if (sensorLabelHandle.IsAllocated) { sensorLabelHandle.Free(); }
             if (pointHandle.IsAllocated) { pointHandle.Free(); }

             GC.Collect();
         }
    }

}
finally
{
     if (dbf != null)
     {
        dbf.close();
     }
}                

return eventData;
}


Interesting failure mode, this is not supposed to happen. You get the FEEE typically because the garbage collected heap is getting destroyed. This is normally easily explained by a misbehaving pinvoked native function that does something like overflow a buffer. No pinvoke here though.

There's however an excellent candidate for this mishap:

[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
public string Control;

The marshaling for this is designed to marshal a C string, a zero-terminated array of characters. A SizeConst = 1 cannot work properly by design, that only leaves room for the zero terminator, not any character. On top of which, a .dbf file doesn't contain zero terminated strings, they are fixed-width according to the field declaration.

Whether the access violation is actually caused by the marshaller getting this wrong is an open question though, it can just as easily bomb on trying to find the zero terminator. Not find one and blunder into an unmapped memory page.

Anyhoo, you're doing it wrong. You must use a char[] instead of a string in the structure declaration and use ByValArray in the [MarshalAs] attribute. Pretty painful coding btw.


Within your Trigger structure, are you sure you have the right packing? It looks like the Index member would be at offset 4 where as you might have intended it to be at offset 1? Same with Pre and Post.

Also, what size is Control supposed to be? If it is a string, usually unmanaged strings are null-terminated. By specifying the length of 1, that would indicate to me a single character. If that is the case, why not use char for it instead of string?

Error 0xc0000005 is an access violation.

Have you tried running under the debugger and enabling unmanaged code debugging? You can then set the access violation to be caught right when thrown as it might be happening inside .NET framework code. You then could look at memory and get an idea of what it might be doing.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜