Access Violation Exception/Crash from C++ callback

2019-02-17 10:37发布

So I have a native 3rd party C++ code base I am working with (.lib and .hpp files) that I used to build a wrapper in C++/CLI for eventual use in C#.

I've run into a particular problem when switching from Debug to Release mode, in that I get an Access Violation Exception when a callback's code returns.

The code from the original hpp files for callback function format:

typedef int (*CallbackFunction) (void *inst, const void *data);

Code from the C++/CLI Wrapper for callback function format: (I'll explain why I declared two in a moment)

public delegate int ManagedCallbackFunction (IntPtr oInst, const IntPtr oData);
public delegate int UnManagedCallbackFunction (void* inst, const void* data);

--Quickly, the reason I declared a second "UnManagedCallbackFunction" is that I tried to create an "intermediary" callback in the wrapper, so the chain changed from Native C++ > C# to a version of Native C++ > C++/CLI Wrapper > C#...Full disclosure, the problem still lives, it's just been pushed to the C++/CLI Wrapper now on the same line (the return).

And finally, the crashing code from C#:

public static int hReceiveLogEvent(IntPtr pInstance, IntPtr pData)
    {
        Console.WriteLine("in hReceiveLogEvent...");
        Console.WriteLine("pInstance: {0}", pInstance);
        Console.WriteLine("pData: {0}", pData);

        // provide object context for static member function
        helloworld hw = (helloworld)GCHandle.FromIntPtr(pInstance).Target;
        if (hw == null || pData == null)
        {
            Console.WriteLine("hReceiveLogEvent: received null instance pointer or null data\n");
            return 0;
        }

        // typecast data to DataLogger object ptr
        IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataLoggerWrap(pData)));
        DataLoggerWrap dlw = (DataLoggerWrap)GCHandle.FromIntPtr(ip2).Target;

        //Do Logging Stuff

        Console.WriteLine("exiting hReceiveLogEvent...");
        Console.WriteLine("pInstance: {0}", pInstance);
        Console.WriteLine("pData: {0}", pData);
        Console.WriteLine("Setting pData to zero...");
        pData = IntPtr.Zero;
        pInstance = IntPtr.Zero;
        Console.WriteLine("pData: {0}", pData);
        Console.WriteLine("pInstance: {0}", pInstance);

        return 1;
    }

All writes to the console are done and then we see the dreaded crash on the return:

Unhandled exception at 0x04d1004c in helloworld.exe: 0xC0000005: Access violation reading location 0x04d1004c.

If I step into the debugger from here, all I see is that the last entry on the call stack is: > "04d1004c()" which evaluates to a decimal value of: 80805964

Which is only interesting if you look at the console which shows:

entering registerDataLogger
pointer to callback handle: 790848
fp for callback: 2631370
pointer to inst: 790844
in hReceiveLogEvent...
pInstance: 790844
pData: 80805964
exiting hReceiveLogEvent...
pInstance: 790844
pData: 80805964
Setting pData to zero...
pData: 0
pInstance: 0

Now, I know that between debug and release some things are quite different in the Microsoft world. I am, of course worried about byte padding and initialization of variables, so if there is something I am not providing here, just let me know and I'll add to the (already long) post. I also think the managed code may NOT be releasing all ownership and then the native C++ stuff (which I don't have the code for) may be trying to delete or kill off the pData object, thus crashing the app.

More full disclosure, it all works fine (seemingly) in Debug mode!

A real head scratch issue that would appreciate any help!

4条回答
甜甜的少女心
2楼-- · 2019-02-17 11:06

I think the stack got crushed because of mismatching calling conventions: try out to put the attribute

 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]

on the callback delegate declaration.

查看更多
冷血范
3楼-- · 2019-02-17 11:13

I'm with @jdehaan, except CallingConvetion.StdCall could be the answer, especially when the 3rd party lib is written in BC++, for example.

查看更多
对你真心纯属浪费
4楼-- · 2019-02-17 11:15

This doesn't directly answer your question, but it may lead you in the right direction as far as debug mode okay vs. release mode not okay:

Since the debugger adds a lot of record-keeping information to the stack, generally padding out the size and layout of my program in memory, I was “getting lucky” in debug mode by scribbling over 912 bytes of memory that weren’t very important. Without the debugger, though, I was scribbling on top of rather important things, eventually walking outside of my own memory space, causing Interop to delete memory it didn’t own.

What is the definition of DataLoggerWrap? A char field may be too small for the data you are receiving.

查看更多
冷血范
5楼-- · 2019-02-17 11:30

I'm not sure what your are trying to achieve.

A few points:

1) The garbage collector is more aggressive in release mode so with bad ownership the behaviour you describe is not uncommon.

2) I don't understands what the below code is trying to do?

IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataLoggerWrap(pData)));
DataLoggerWrap dlw = (DataLoggerWrap)GCHandle.FromIntPtr(ip2).Target;

You use GCHandle.Alloc to lock an instance of DataLoggerWrap in memory, but then you never pass it out to unmanaged - so why do you lock it? You also never free it?

The second line then grabs back a reference - why the circular path? why the reference - you never use it?

3) You set the IntPtrs to null - why? - this will have no effect outside of the function scope.

4) You need to know what the contract of the callback is. Who owns pData the callback or the calling function?

查看更多
登录 后发表回答