Bob Tway
Bob Tway

Reputation: 9603

Tracing source of access violation in unmanaged C# code

I am currently working on some C# code that talks to a C++ dll. This is not an area in which I - or anyone else at my company - has any experience. It's been an eye-opener to say the least.

After a lot of reading, trial and error, and frustration, I've managed to iron out most of the kinks and get something that's largely functional. However, from time to time, it still throws this at me ...

System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

.. and then dies. This error only appears when I run the call on parallel threads - it's fine single threaded. This dll is supposed to be thread safe and we've good reason to believe it ought to be, if handled correctly.

The cause of this error is always a call to the same function:

[DllImport(DLL, SetLastError = true, CharSet = CharSet.Ansi)]
public static extern int QABatchWV_Close(IntPtr vi1);

I have the header file for the library, which defines this function as:

__declspec(dllimport) int __stdcall QABatchWV_Close(int);

From what I understand there are additional tools at my disposal like SafeHandle and MarshalAs. But, frankly, I'm unsure as to how to best deploy them in this situation.

This error tends to take several hours of use time to show up, so tweaking and hoping isn't going to be a productive approach here. Can anyone point me as to what I might be doing wrong in calling down to the C++ function?

Upvotes: 0

Views: 650

Answers (2)

mortysporty
mortysporty

Reputation: 2889

Have a look at the following code which I use to call a c (not c++) dll. I know it is not really an answer to your question, but perhaps you can use some of this going foreward.

Note the "CallingConvention"-specifier in the dll declaration and also the "FreeGlobal" in the "finally" part of the try catch.

public class csInterface
{
    [DllImport(@"myDLL.dll", EntryPoint = "dllFunc", CallingConvention = CallingConvention.StdCall)]
    private static extern void dllFunc(IntPtr inp, IntPtr outp);

    public static int myDll(ref MyInput myInput, ref MyOutput myOutput)
    {
        int sizeIn, sizeOut;
        IntPtr ptr_i = IntPtr.Zero, ptr_u = IntPtr.Zero;

        sizeIn = Marshal.SizeOf(typeof(myInput));
        sizeOut = Marshal.SizeOf(typeof(myOutput));
        /* Calling C */
        try
        {
            ptr_i = Marshal.AllocHGlobal(sizeIn);
            ptr_u = Marshal.AllocHGlobal(sizeOut);

            Marshal.StructureToPtr(myInput, ptr_i, true);
            Marshal.StructureToPtr(myOutput, ptr_u, true);

            dllFunc(ptr_i, ptr_u);

            myOutput = (MyOutput)(Marshal.PtrToStructure(ptr_u, typeof(MyOutput)));
        }
        catch (Exception)
        {
            //Return something meaningful (or not)
            return -999;
        }
        finally
        {
            //Free memory
            Marshal.FreeHGlobal(ptr_i);
            Marshal.FreeHGlobal(ptr_u);
        }
        //Return something to indicate it all went well
        return 0;
    }
}

In C# I declare my types

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MySubType
    {
        public int a;
        public double b;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MyInput
    {
        [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 4)]
        public string aString; //A string of length 3 
        public bool aBoolean;       
        public int anInt;
        public char aChar;
        public double aDouble;

        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 12)]
        public MySubType[] aSubType; //Array of struct of length 12
    }

And something similar for the output.

Now in C (its probably the same or similar in c++) i declare my dll

__declspec(dllexport) void _stdcall dllFunc(MyCInput *myCInput, MyCOutput *myCOutput)
{
    //Code
}

And the corresponding C types which obviously have to mirror the C# types exactly

typedef struct
{
    int a;
    double b;
} MyCSubType;

typedef struct
{
    char aString[4];
    int aBoolean;   //This needs to be cast over to your C boolean type
    int anInt;
    char aChar;
    double aDouble;
    MyCSubType myCSubType[12];
} MyCType;

Now the types I have used in this example do not exactly match what I have used in my code, and i have not tested this code. So there may be typos and such, but the "principle" is ok.

Upvotes: 1

Adam Jachocki
Adam Jachocki

Reputation: 2125

Well, first of all you don't need setting Charset here, because there are no strings.

Second of all - function in cpp should be declared as exported not imported, so it should look like:

__declspec(dllimport) int __stdcall QABatchWV_Close(int);

Next, you should set calling convention in your C# code to stdcall:

[DllImport(DLL, SetLastError = true, CallingConvention=CallingConvention.Stdcall)]

Next you should have int instead of IntPtr in C# code. And I'm nearly sure that name of this function (in C++ dll) is mangled and it's not QABatchWV_Close but rather something like QABatchWV_Close@32. You should check it using "dll export viewer".

Upvotes: 1

Related Questions