上周处理了一个有趣的问题,与垃圾回收机制有关。当获取Revit中对象的几何信息,在一个函数来获得构件的Solid。然后在Caller中对solid包含的面进行遍历访问。加载运行编译的debug版时,没有问题,当用Release版时,出现SEHExpection异常。
--AccessViolationException - Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
这个问题的本质是有关垃圾回收机制的。因为大家常常用到几何数据访问,这里给大家分享下产生的原因和修改方法。
源代码大概样子是这样的:
注:在你运行的时候不一定出现这个错误,与机器的资源,内存,计算过程等有关。
[TransactionAttribute(Autodesk.Revit.Attributes.TransactionMode.Manual)][RegenerationAttribute(Autodesk.Revit.Attributes.RegenerationOption.Manual)]public class Lab1PlaceGroup : IExternalCommand{ public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements) { //Get application and document objects UIApplication uiApp = commandData.Application; Document doc = uiApp.ActiveUIDocument.Document;
Transaction trans = new Transaction(doc, "ExComm"); trans.Start();
//Define a Reference object to accept the pick result. Reference pickedRef = null;
//Pick an object Selection sel = uiApp.ActiveUIDocument.Selection; pickedRef = sel.PickObject(ObjectType.Element, "Please select a family instance"); Element elem = pickedRef.Element; Options geoOptions = uiApp.Application.Create.NewGeometryOptions(); SolidArray solids = new SolidArray(); GeometryElement geoElement = elem.get_Geometry(geoOptions);
//get the transformed solid elements GetSolids(geoElement, ref solids);
SolidArrayIterator solidIter = solids.ForwardIterator(); solidIter.Reset(); FaceArray faces = null; while (solidIter.MoveNext()) { faces = null; Autodesk.Revit.DB.Solid sol = solidIter.Current as Autodesk.Revit.DB.Solid; faces = sol.Faces; if (faces.Size > 0) //抛出 SEHException错误,没有规律的出现。 { TaskDialog.Show("Get faces", "the solid has " + faces.Size.ToString() + "faces"); } }
trans.Commit();
return Result.Succeeded; }
public void GetSolids(GeometryElement geomElem, ref SolidArray solids) { foreach (Autodesk.Revit.DB.GeometryObject geomObj in geomElem.Objects) {
Autodesk.Revit.DB.Solid solid = geomObj as Autodesk.Revit.DB.Solid; if (null != solid) { if (solid.Faces.Size > 0) { { solids.Append(solid); continue; } } }
Autodesk.Revit.DB.GeometryInstance geomInst = geomObj as Autodesk.Revit.DB.GeometryInstance; if (null != geomInst) { Autodesk.Revit.DB.GeometryElement transformedGeomElem = geomInst.GetInstanceGeometry((geomInst.Transform); GetSolids(transformedGeomElem, ref solids); } } } }
错误原因是solid对象从GetInstanceGeometry 函数返回的transformedGeomElem 中获得。当退出拷贝solid的GetSolids()函数回到主调用函数后,操作系统把GetInstanceGeometry 返回的transformedGeomElem 的内存回收了。这一垃圾回收,导致返回到主条用函数的solidArray失去了数据源数据。在debug版,所有的变量占用的内存收保护没有被释放掉,所以debug版运行正常。在Release版垃圾回收机制起作用,把有用的内存数据释放了。所以我们再访问时,这块内存被保护起来了。所以错误信息包含 Attempted to read or write protected memory。
解决办法有两个:
1. 把读取solid数据的代码放在遍历solid的代码后面,处于同一个函数中。这样可能transformedGeomElem 还不能释放。
2. 增加一个对象引用,指向返回的transformedGeomElem 对象,这样也不会释放,因为有人正引用它。
下面是修改后的代码。
[TransactionAttribute(Autodesk.Revit.Attributes.TransactionMode.Manual)][RegenerationAttribute(Autodesk.Revit.Attributes.RegenerationOption.Manual)]public class Lab1PlaceGroup : IExternalCommand{ public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements) { //Get application and document objects UIApplication uiApp = commandData.Application; Document doc = uiApp.ActiveUIDocument.Document;
Transaction trans = new Transaction(doc, "ExComm"); trans.Start();
//Define a Reference object to accept the pick result. Reference pickedRef = null;
//Pick an object Selection sel = uiApp.ActiveUIDocument.Selection; pickedRef = sel.PickObject(ObjectType.Element, "Please select a family instance"); Element elem = pickedRef.Element;
Options geoOptions = uiApp.Application.Create.NewGeometryOptions(); SolidArray solids = new SolidArray(); GeometryElement geoElement = elem.get_Geometry(geoOptions);
GeometryElement refGeometry = null; //对象保护,防止几何数据被释放掉了,后面产生SEHException. //get the transformed solid elements GetSolids(geoElement, ref solids,ref refGeometry);
SolidArrayIterator solidIter = solids.ForwardIterator(); solidIter.Reset(); FaceArray faces = null; while (solidIter.MoveNext()) { faces = null; Autodesk.Revit.DB.Solid sol = solidIter.Current as Autodesk.Revit.DB.Solid; faces = sol.Faces; if (faces.Size > 0) //这里再也不会出现问题了。 { TaskDialog.Show("Get faces", "the solid has " + faces.Size.ToString() + "faces"); } }
trans.Commit();
return Result.Succeeded; }
public void GetSolids(GeometryElement geomElem, ref SolidArray solids, ref GeometryElement refGeometry) { foreach (Autodesk.Revit.DB.GeometryObject geomObj in geomElem.Objects) {
Autodesk.Revit.DB.Solid solid = geomObj as Autodesk.Revit.DB.Solid; if (null != solid) { if (solid.Faces.Size > 0) { { solids.Append(solid); continue; } } } Autodesk.Revit.DB.GeometryInstance geomInst = geomObj as Autodesk.Revit.DB.GeometryInstance; if (null != geomInst) { refGeometry = geomInst.GetInstanceGeometry(geomInst.Transform); GetSolids(refGeometry, ref solids,ref refGeometry); } } }}
这里增加了refGeometry 来指向GetInstanceGeometry所返回的对象到主调用函数。保护了目标对象不释放。
