C# AccessViolationException with interop callbacks in .NET 4.5 or above

needmoreraminbrain 6 Reputation points
2021-03-08T10:22:03.9+00:00

I'm investigating screen reader technology in C# and experiencing an issue regarding the capturing of events using an interop library written in C++ (Java Access Bridge - https://github.com/openjdk/jdk/tree/05a764f4ffb8030d6b768f2d362c388e5aabd92d/src/jdk.accessibility/windows/native/libwindowsaccessbridge) when compiled using .NET 4.5+ (64-bit). It crashes in both Debug and Release build configurations. The functionality works fine in .NET 4.0 however.

Whenever an event occurs that should trigger the registered callback, an AccessViolationException occurs, crashing the application. I suspect it has something to do with the Garbage Collector changes introduced in .NET 4.5 but would appreciate assistance in being able to track down the exact issue.

There is a complete minimal reproducible code project located here that subscribes to a mouseclick event -> https://github.com/needmoreraminbrainnow/screenreadertest.
When this is compiled with .NET 4.5 it will crash when a mouseclick event occurs within a Java-based application

The delegate is created as a class variable to maintain a reference. The two important methods on the C# side are...

[DllImport(WINDOWS_ACCESS_BRIDGE, SetLastError = true, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
private static extern void setMouseClickedFP(MouseClickedDelegate fp);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MouseClickedDelegate(System.Int64 vmID, IntPtr jevent, IntPtr ac);

I believe the relevant C++ methods are...

void fireMouseClicked(long vmID, JOBJECT64 event, JOBJECT64 source);

and...

void AccessBridgeEventHandler::method(long vmID, JOBJECT64 event, JOBJECT64 source) { \
    DEBUG_CODE(char debugBuf[255]); \
    DEBUG_CODE(sprintf(debugBuf, fireEventDebugString, #method, event, source, vmID)); \
    DEBUG_CODE(AppendToCallInfo(debugBuf)); \
    if (eventFP != (FPprototype) 0) { \
        eventFP(vmID, event, source); \
    } else { \
        DEBUG_CODE(AppendToCallInfo("[ERROR]: eventFP == 0")); \
    } \
}

All I can find that corresponds to setMouseClicked is in C code here...

void SetMouseClicked(AccessBridge_MouseClickedFP fp) {
    if (theAccessBridgeInitializedFlag == TRUE) {
        theAccessBridge.SetMouseClicked(fp);
    }
}

and also...

#define CALL_SET_EVENT_FP(function, callbackFP)         \
    void WinAccessBridge::function(callbackFP fp) { \
            eventHandler->function(fp, this);                       \
            /* eventHandler calls back to winAccessBridgeDLL to set eventMask */    \
    }

CALL_SET_EVENT_FP(setMouseClickedFP, AccessBridge_MouseClickedFP)

I've tried pinning the delegate using GCHandle.Alloc(_mouseClickDelegate, GCHandleType.Pinned); but when run this results in a Object contains non-primitive or non-blittable data error. And based on research of similar questions and Microsoft articles it appears this should not be necessary.

I've tried using GC.KeepAlive for the delegate

I have also tried changes related to Marshalling of the delegate when setting the callback and using a different calling convention but to no avail.

I have also tried using the following struct as the callback argument type instead of IntPtr...

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public class JOBJECT64
    {
        public static JOBJECT64 Zero = default(JOBJECT64);

        private readonly long _value;

        public JOBJECT64(long value)
        {
            _value = value;
        }

        public long Value
        {
            get { return _value; }
        }

        public static bool operator ==(JOBJECT64 x, JOBJECT64 y)
        {
            return x._value == y._value;
        }

        public static bool operator !=(JOBJECT64 x, JOBJECT64 y)
        {
            return x._value == y._value;
        }

        public override bool Equals(object obj)
        {
            if (obj is JOBJECT64)
            {
                return this == (JOBJECT64) obj;
            }

            return false;
        }

        public override int GetHashCode()
        {
            return _value.GetHashCode();
        }
    }

How can I confirm that it is a problem with the delegate being moved or inaccessible?

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,626 questions
C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,628 questions
{count} vote