【发布时间】: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