首页 > 解决方案 > 在 .NET 4.5 或更高版本中具有互操作回调的 C# AccessViolationException

问题描述

我正在研究 C# 中的屏幕阅读器技术,并遇到有关使用 C++ 编写的互操作库捕获事件的问题(Java Access Bridge - https://github.com/openjdk/jdk/tree/05a764f4ffb8030d6b768f2d362c388e5aabd92d/src/jdk。使用 .NET 4.5+(64 位)编译时的可访问性/windows/native/libwindowsaccessbridge )。它在 Debug 和 Release 构建配置中崩溃。然而,该功能在 .NET 4.0 中运行良好

每当发生应触发注册回调的事件时,就会发生 AccessViolationException,从而使应用程序崩溃。我怀疑这与 .NET 4.5 中引入的垃圾收集器更改有关,但希望能够帮助您找到确切的问题。

这里有一个完整的最小可重现代码项目,它订阅了鼠标点击事件 -> https://github.com/needmoreraminbrainnow/screenreadertest
当它使用 .NET 4.5 编译时,它会在基于 Java 的应用程序中发生 mouseclick 事件时崩溃

委托被创建为类变量以维护引用。C# 端的两个重要方法是...

[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);

我相信相关的 C++ 方法是......

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

和...

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")); \
    } \
}

我能找到的与 setMouseClicked 相对应的所有内容都在 C 代码中......

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

并且...

#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)

我尝试使用固定委托,GCHandle.Alloc(_mouseClickDelegate, GCHandleType.Pinned);但运行时会导致Object contains non-primitive or non-blittable data错误。根据对类似问题和微软文章的研究,这似乎没有必要。

我试过使用GC.KeepAlive代表

在设置回调并使用不同的调用约定时,我还尝试了与委托编组相关的更改,但无济于事。

我还尝试使用以下结构作为回调参数类型而不是 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();
        }
    }

如何确认代表被移动或无法访问是否存在问题?

标签: javac#c++interop

解决方案


.NET 4.5中有一些重大变化。如果 4.0 适合您,请使用它。在它编译并发布之后,它应该不重要。


推荐阅读