Object lifetimes
A proxy owns its native object, or for a transient one reference on it, and releases it in Dispose or its finalizer. The garbage collector sees only the small proxy, not the native memory behind it: dispose large or scarce objects when you're done with them.
flowchart LR
subgraph managed [".NET heap"]
p1["Geom_Circle proxy"]
p2["another proxy<br/>of the same circle"]
p3["BRepMesh_IncrementalMesh proxy"]
end
subgraph native ["native heap"]
t["Geom_Circle<br/>reference count 2"]
m["BRepMesh_IncrementalMesh"]
end
p1 -->|one reference| t
p2 -->|one reference| t
p3 -->|owns| m
// using disposes at the end of the scope: the native object goes right away
using (var mesh = new BRepMesh_IncrementalMesh(shape, 0.01))
{
// the triangulation stays on the shape; the mesher itself is done
}
// without, the finalizer releases it when the garbage collector gets to it
var another = new BRepPrimAPI_MakeBox(10, 10, 10);
| Dispose promptly | Why |
|---|---|
| views, drivers, viewers | GPU resources, and they belong to one thread |
| documents | close through the application, see Documents |
| meshers, large algorithms | megabytes of intermediate data |
| shapes, points, small objects | no need: the finalizer is fine |
What stays alive with what
C++ code keeps raw references the garbage collector doesn't see: an algorithm referring to the shape it was given, a proxy for an element inside a map. NetOcc keeps the C# side alive as long as the native object may use it, so no GC.KeepAlive is needed:
TopoDS_Shape first;
{
var edges = new TopTools_IndexedMapOfShape();
TopExp.MapShapes(new BRepPrimAPI_MakeBox(10, 10, 10).Shape(), TopAbs_ShapeEnum.TopAbs_EDGE,
edges);
first = edges.FindKey(1); // const TopoDS_Shape&: a proxy into the map
}
// the map is out of reach, but the proxy keeps it alive: no GC.KeepAlive needed
GC.Collect();
GC.WaitForPendingFinalizers();
var type = first.ShapeType(); // TopAbs_EDGE
flowchart LR
first["first<br/>(a proxy into the map)"] -->|keeps alive| map["edges<br/>(TopTools_IndexedMapOfShape)"]
map -->|owns| element["the native element"]
first -.->|points into| element
| The native object refers to | Kept alive by |
|---|---|
its owner, for a reference a member returned (map.FindKey(1), block.ChangeShapes()) |
the returned proxy |
a constructor's argument (new BRepGraph_FaceIterator(graph), a boolean's shared BOPAlgo_PaveFiller) |
the new proxy |
a member's argument (extrema.Initialize(surface, ...)) |
the proxy, until the member's next call replaces it |
the object it came from (editor.Mut(vertex) returns a guard into the graph) |
the returned proxy |
the object a returned copy refers to (graph.Topo() returns a view of the graph) |
the returned proxy |
What a constructor keeps, and the object behind a returned guard, also outlive the finalizer: finalizers run in any order, and a destructor may still use them.
Guards
A guard locks an item until it's disposed. One dropped without Dispose is safe, but its item stays locked until the collector finalizes it, so dispose guards with using:
// Mut returns a guard that locks the vertex until it's disposed
var editor = graph.Editor().Vertices();
using (var guard = editor.Mut(vertex))
{
editor.SetPoint(guard, new gp_Pnt(-1, -1, -1));
}
Finalization
sequenceDiagram
participant App as your code
participant P as proxy
participant GC as garbage collector
participant N as native object
App->>P: new
P->>N: create
alt disposed
App->>P: Dispose()
P->>N: delete (or release the reference)
else dropped
GC->>P: finalizer, on the finalizer thread
P->>N: delete (or release the reference)
end
OCAF documents
- Labels, attributes and
TNaming_Builderkeep their document's data alive, so a proxy never points into a freed tree. - Close documents through the application (
application.Close(document)): OCCT keeps a raw pointer to the document that onlyCloseclears. - Undo needs
document.SetUndoLimit(n)withn > 0; with 0,OpenCommandandAbortCommanddo nothing. XCAFApp_Application.GetApplication()is one per process.
Threads
Finalizers run on the collector's thread. Objects with a GL context (views, drivers) belong to the thread that made them: dispose them there, don't leave them to the finalizer. OCCT's modeling objects aren't thread-safe: share a shape between threads only for reading.