【问题标题】:Unity3D C++ unmanaged DLL crash on windows 8/10, works on 7Windows 8/10 上的 Unity3D C++ 非托管 DLL 崩溃,适用于 7
【发布时间】:2015-11-02 14:58:42
【问题描述】:

我使用带有 HandleReference 的 IDisposable 继承为我的 Unity 项目编写了一个 C++ 非托管 DLL,如最后所述 here

目的是使用 C 库 nifti2c 加载大脑 IRM,以及用于创建实时大脑切割的网格,这些切割稍后会转换为 GameObject 并显示在 Unity 3D 场景中。

我所有的 C++ 对象(卷,...)都有一个等效的 C# IDisposable 类,以便与 Unity C# 脚本一起使用。

C++ DLL:

extern C
{
    Q_DECL_EXPORT hbp::Volume<float,float>* create_Volume(){return new hbp::Volume<float,float>();}
    Q_DECL_EXPORT void delete_Volume(hbp::Volume<float,float> *volume);
    Q_DECL_EXPORT void center_Volume(hbp::Volume<float,float> *volume, float *center);
    ...
}

Q_DECL_EXPORT 是来自 QtGlobals 的 Qt 宏

#  define Q_DECL_EXPORT __declspec(dllexport)

C# 脚本:

    public class Volume_dll : IDisposable 
    {
        [DllImport("hbp_export", EntryPoint = "create_Volume", CallingConvention = CallingConvention.Cdecl)]
        static private extern IntPtr create_Volume();  
        [DllImport("hbp_export", EntryPoint = "delete_Volume", CallingConvention = CallingConvention.Cdecl)]
        static private extern void delete_Volume(HandleRef handleVolume);
        [DllImport("hbp_export", EntryPoint = "center_Volume", 
    ...

    private HandleRef _handle;

    public Volume_dll()
    {
        _handle = new HandleRef(this, create_Volume());
    }

    ~Volume_dll()
    {
        Cleanup();
    }

    public void Dispose()
    {
        Cleanup();
        GC.SuppressFinalize(this);
    }

    private void Cleanup()
    {
        delete_Volume(_handle);
        _handle = new HandleRef(this, IntPtr.Zero);
    }

    public HandleRef getHandle()
    {
        return _handle;
    }

    public Vector3 center()
    {        
        float[] center = new float[3];
        center_Volume(_handle, center);
        return new Vector3(center[0],center[1],center[2]);
    }
   ...
}

我目前正在使用带有 VS2013 编译器的 c++11 和其他一些外部 C++ 库。

我使用 QtCreator 来生成我的 Dll:

.pro:

TARGET = hbp_export
TEMPLATE = lib

############# CONFIG
CONFIG   += dll
CONFIG   -= app_bundle
CONFIG   += warn_on

QT += core gui widgets
CONFIG += opengl

############# DEFINES
DEFINES += _CRT_SECURE_NO_WARNINGS

############# CFG
CFG = ""
CONFIG(debug, debug|release){
    CFG = "Debug"
}else{
    CFG = "Release"
}

############# ARCH
ARCH = ""
win32-msvc*:contains(QMAKE_TARGET.arch, x86_64):{
    ARCH = "x64"
}
win32-msvc*:contains(QMAKE_TARGET.arch, x86):{
    ARCH = "x86"
}

############# INCLUDEPATH
# hbp
HBP_DIR = "D:/_projects/HBP/HBP-*****/qt-creator/"
HBP_BASE_DIR = $$HBP_DIR"hbp_base/"
INCLUDEPATH += $$HBP_BASE_DIR"include"
INCLUDEPATH += $$HBP_BASE_DIR"include/niftilib"
INCLUDEPATH += $$HBP_BASE_DIR"gpc"
# boost
INCLUDEPATH += "D:/_software/boost_1_57_0/boost_1_57_0"
# opencv
INCLUDEPATH += "D:/_software/opencv/build/include"

############# LIBS
# opencv
CV_BASE = "D:/_software/opencv/build"

CV = "opencv_"
CV_VER = "2411"
equals(ARCH, "x86"){
    CV_BASE = $$CV_BASE"/x86/vc12"
}else{
    CV_BASE = $$CV_BASE"/x64/vc12"
}
CV_BIN = $$CV_BASE"/bin"
CV_LIB = $$CV_BASE"/lib"
CV_EXT = ""
equals(CFG, "Debug"){
    CV_EXT = "d"
}
LIBS += -L$$CV_BIN -L$$CV_LIB -l$$CV"imgproc"$$CV_VER$$CV_EXT -l$$CV"core"$$CV_VER$$CV_EXT# -l$$CV"highgui"$$CV_VER$$CV_EXT#
# hbp
OBJ_D = $$HBP_DIR"hbp_base_"
equals(ARCH, "x86"){
    OBJ_D = $$OBJ_D"x86/"
}else{
    OBJ_D = $$OBJ_D"x64/"
}
equals(CFG, "Debug"){
    OBJ_D = $$OBJ_D"debug/"
}else{
    OBJ_D = $$OBJ_D"release/"
}

LIBS += $$OBJ_D"nifti1_io.obj"  $$OBJ_D"Volume.obj"  ... \

SOURCES += \
    VolumeExport.cpp \
    ... \

HEADERS += \
    VolumeExport.h \
    ....h \

它在 Windows 7 上运行良好,我没有任何泄漏,并且 C++ 对象析构函数在适当的时候被调用。 但它在加载 IRM 后片刻在 Windows 8.1 和 Windows 10 上崩溃。

我创建了一个 C++ 调试类,用于在 txt 文件中编写所有函数调用,但是当崩溃发生时,这里没有写入任何内容。

unity 独立构建崩溃生成的输出日志并没有太大帮助:

========== OUTPUTING STACK TRACE ==================

0x00007FFEBA2E73C3 (hbp_export) delete_DebugSettings
0x00007FFEBA2BDC5F (hbp_export) center_Volume
0x00000000053A2B5E (Mono JIT Code) (wrapper managed-to-native) Volume_dll:center_Volume (System.Runtime.InteropServices.HandleRef,single[])
0x00000000053A29B3 (Mono JIT Code) [D:\_projects\HBP\HBP-flo\unity3D\testImportDll\Assets\Scripts\objects\Volume.cs:277] Volume_dll:center () 
0x00000000053A25CC (Mono JIT Code) [D:\_projects\HBP\HBP-flo\unity3D\testImportDll\Assets\Scripts\scenes\Base3DScene.cs:405] Base3DScene:resetNIIBrainVolumeFile (string) 
0x00000000053A1791 (Mono JIT Code) [D:\_projects\HBP\HBP-flo\unity3D\testImportDll\Assets\Scripts\scenes\SinglePatient3DScene.cs:106] SinglePatient3DScene:reset (string,System.Collections.Generic.List`1<string>,string,string,string,string) 
0x00000000053A16CC (Mono JIT Code) [D:\_projects\HBP\HBP-flo\unity3D\testImportDll\Assets\Scripts\scenes\ScenesManager.cs:53] ScenesManager:setSinglePatientPaths (string,System.Collections.Generic.List`1<string>,string,string,string,string) 
0x00000000053A12C8 (Mono JIT Code) [D:\_projects\HBP\HBP-flo\unity3D\testImportDll\Assets\Scripts\HBP_3D_Visu.cs:222] HBP_3D_Visu:testUi1 () 
0x00000000053A0F8D (Mono JIT Code) [D:\_projects\HBP\HBP-flo\unity3D\testImportDll\Assets\Scripts\HBP_3D_Visu.cs:168] HBP_3D_Visu:<Awake>m__0 () 
0x00000000053A0F3A (Mono JIT Code) [C:\buildslave\unity\build\Runtime\Export\UnityEvent.cs:149] UnityEngine.Events.InvokableCall:Invoke (object[]) 
0x000000000533DEA5 (Mono JIT Code) [C:\buildslave\unity\build\Runtime\Export\UnityEvent.cs:626] UnityEngine.Events.InvokableCallList:Invoke (object[]) 
0x000000000533D784 (Mono JIT Code) [C:\buildslave\unity\build\Runtime\Export\UnityEvent.cs:766] UnityEngine.Events.UnityEventBase:Invoke (object[]) 
0x00000000053A0ED4 (Mono JIT Code) [C:\buildslave\unity\build\Runtime\Export\UnityEvent_0.cs:54] UnityEngine.Events.UnityEvent:Invoke () 
0x00000000053A0E81 (Mono JIT Code) [C:\buildslave\unity\build\Extensions\guisystem\UnityEngine.UI\UI\Core\Button.cs:35] UnityEngine.UI.Button:Press () 
0x00000000053A0DE3 (Mono JIT Code) [C:\buildslave\unity\build\Extensions\guisystem\UnityEngine.UI\UI\Core\Button.cs:44] UnityEngine.UI.Button:OnPointerClick (UnityEngine.EventSystems.PointerEventData) 
0x00000000053A0D88 (Mono JIT Code) [C:\buildslave\unity\build\Extensions\guisystem\UnityEngine.UI\EventSystem\ExecuteEvents.cs:52] UnityEngine.EventSystems.ExecuteEvents:Execute (UnityEngine.EventSystems.IPointerClickHandler,UnityEngine.EventSystems.BaseEventData) 
0x000000000539BAC2 (Mono JIT Code) [C:\buildslave\unity\build\Extensions\guisystem\UnityEngine.UI\EventSystem\ExecuteEvents.cs:269] UnityEngine.EventSystems.ExecuteEvents:Execute<object> (UnityEngine.GameObject,UnityEngine.EventSystems.BaseEventData,UnityEngine.EventSystems.ExecuteEvents/EventFunction`1<object>) 
0x000000000539A5DB (Mono JIT Code) [C:\buildslave\unity\build\Extensions\guisystem\UnityEngine.UI\EventSystem\InputModules\StandaloneInputModule.cs:378] UnityEngine.EventSystems.StandaloneInputModule:ProcessMousePress (UnityEngine.EventSystems.PointerInputModule/MouseButtonEventData) 
0x000000000539454C (Mono JIT Code) [C:\buildslave\unity\build\Extensions\guisystem\UnityEngine.UI\EventSystem\InputModules\StandaloneInputModule.cs:277] UnityEngine.EventSystems.StandaloneInputModule:ProcessMouseEvent (int) 
0x000000000539447F (Mono JIT Code) [C:\buildslave\unity\build\Extensions\guisystem\UnityEngine.UI\EventSystem\InputModules\StandaloneInputModule.cs:265] UnityEngine.EventSystems.StandaloneInputModule:ProcessMouseEvent () 
0x0000000005393B75 (Mono JIT Code) [C:\buildslave\unity\build\Extensions\guisystem\UnityEngine.UI\EventSystem\InputModules\StandaloneInputModule.cs:179] UnityEngine.EventSystems.StandaloneInputModule:Process () 
0x0000000005367DE1 (Mono JIT Code) [C:\buildslave\unity\build\Extensions\guisystem\UnityEngine.UI\EventSystem\EventSystem.cs:277] UnityEngine.EventSystems.EventSystem:Update () 
0x00000000053189AB (Mono JIT Code) (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr)
0x00007FFEAB024087 (mono) mono_set_defaults
0x00007FFEAAF783DD (mono) mono_runtime_invoke
0x00007FF677A485C5 (hbp1) ScriptingInvocationNoArgs::Invoke
0x00007FF677A482D7 (hbp1) ScriptingInvocationNoArgs::Invoke
0x00007FF6779337E0 (hbp1) MonoBehaviour::CallMethodIfAvailable
0x00007FF67793632A (hbp1) MonoBehaviour::Update
0x00007FF67790117F (hbp1) BaseBehaviourManager::CommonUpdate<BehaviourManager>
0x00007FF6779F3F24 (hbp1) PlayerLoop
0x00007FF677B35820 (hbp1) PlayerMainWndProc
0x00007FF677B39463 (hbp1) PlayerWinMain
0x00007FF67803995C (hbp1) stricmp
0x00007FFED4EF2D92 (KERNEL32) BaseThreadInitThunk
0x00007FFED5FF9F64 (ntdll) RtlUserThreadStart

========== END OF STACKTRACE ===========

我同意崩溃发生在 center_Volume 中,但 delete_DebugSettings 是我所有代码中最后调用的函数! 所以我修改了我的调试函数以在控制台中显示每个 DLL 函数调用,但我们从未看到 delete_DebugSettings。

加载成功,就在我可以可视化所有数据并计算体积中心之后。

我认为函数 center_volume 尝试使用已删除的指针访问内存。上面的函数 convert_to_volume 从我的 C# 脚本中读取 nii 脑容量并按预期存储数据:

public bool resetNIIBrainVolumeFile(string pathNIIBrainVolumeFile)
            {
                m_.isVolumeLoaded = false;    

                // checks parameter
                    if (pathNIIBrainVolumeFile.Length == 0)
                    {
                        Debug.LogError("-ERROR : Base3DScene::resetNIIBrainVolumeFile -> path NII brain volume file is empty. ");
                        return (m_.isSurfaceLoaded = false);
                    }

                // load volume
                    bool loadingSuccess = m_.DLLNiiLoader.loadNiftiFile(pathNIIBrainVolumeFile);
                    if (loadingSuccess)
                    {     
                        m_.DLLNiiLoader.convertToVolume(m_.DLLVolume);
                        // HERE the irm is loaded and i can call volume_center without any problem
                    }
                    else
                    {
                        Debug.LogError("-ERROR : Base3DScene::resetNIIBrainVolumeFile -> load NII file failed. " + pathNIIBrainVolumeFile);
                        return m_.isVolumeLoaded;                        
                    }

                    // set bbox and center
                    if (loadingSuccess)
                    {
                        // CRASH HERE when trying to access irm data !
                        m_.volumeCenter = m_.DLLVolume.center();   
                    }

                m_.isVolumeLoaded = loadingSuccess;
                updatePlanesEvent.Invoke(true);
                return m_.isVolumeLoaded;
            }

音量中心的详细信息:

void center_Volume(Volume<float,float> *volume, float *center)
{
    DebugSettings::displayDebug("(VolumeExport) center_Volume " + getHour() + "\n");
    if(volume == nullptr)
        return;

    Point3<float> centerSurface = volume->center(); // CRASH HERE
    center[0] = centerSurface.x();
    center[1] = centerSurface.y();
    center[2] = centerSurface.z();
    DebugSettings::displayDebug("      -> : " + to_string(centerSurface.x()) + " " + to_string(centerSurface.y()) + " " + to_string(centerSurface.z()) + "\n");
}

    template<class T1, class T2>
    Point3<T1> hbp::Volume<T1,T2>::center() const
    {
        Point3<T1> p1 = m_voxels[0].pos; // CRASH HERE, m_voxels is a vector of  
//points initialized once during the loading an never deleted
        Point3<T1> p2 = m_voxels[dimX() * dimY() * dimZ()-1].pos;
        return Point3<T1>((p1.x() + p2.x())/2, (p1.y() + p2.y())/2,(p1.z() +   p2.z())/2);
    }

我不明白为什么在加载数据后就删除了卷(或者指针被取消引用)。目前只有一个进程,我使用 GC.SuppressFinalize(this) 来禁用 c# 卷脚本中的垃圾收集器。

因为它对我没有意义,所以我更多地考虑 Windows 7 和 Windows 8/10 之间的 DLL 不兼容。 现在我没有想法,谢谢你的建议。

【问题讨论】:

  • 这可能是一个简单的释放后使用。堆在以后的 Windows 中变得更加严格,因此 use-after-free 更有可能导致崩溃。
  • 我同意这一点,但我的析构函数都没有被调用,我的指针也没有被取消引用。在 DLL 中删除后,我总是将指针设置为 nullptr。
  • 你试过在调试器中运行它吗?
  • 我暂时不能这样做,我的配置只安装在 Windows 7 计算机上,我没有完全访问 w8/10 计算机的权限,我只能提供新的 dll 和 C#代码。
  • 您可能会看到重复的 comdat 折叠(即,如果多个函数生成相同的机器代码,它们将被折叠为一个)。这可能意味着您在调用堆栈上获得了不合适的名称。所以有可能top函数根本就不是delete_DebugSettings。此外,优化的代码可能会掩盖调用堆栈...尝试关闭所有优化,看看是否可以获得更好的调用堆栈数据。

标签: c# c++ windows dll unity3d


【解决方案1】:

当我尝试读取 IRM 时,我通过从 DLL 中删除 Volume 构造函数调用解决了我的问题,现在我在必要时始终使用具有干净函数的相同对象。我不知道为什么,但是当我们在 DLL 中取消引用指针时,IDisposable 模式似乎无法正常工作。

【讨论】:

    猜你喜欢
    • 2017-03-07
    • 2017-10-16
    • 1970-01-01
    • 2010-10-18
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 2011-10-28
    • 2012-04-28
    相关资源
    最近更新 更多