This code runs fine on older acad versions, but crashes acad 2025

This code runs fine on older acad versions, but crashes acad 2025

TLatev
Participant Participant
656 Views
6 Replies
Message 1 of 7

This code runs fine on older acad versions, but crashes acad 2025

TLatev
Participant
Participant

I have this code that runs fine in acad versions prior to 2025, now trying to compile against acad 2025 and dot net 8, the cad session freezes than crashes even though my routine is wrapped in a "try...catch" block which acad promptly ignores

internal void CleanUpTransientObjects()
{
TM.EraseTransients(TransientDrawingMode.DirectShortTerm, 0, []); // this executes ok, but does nothing
foreach (Drawable d in Letters) d.Dispose(); // this causes crash
Letters.Clear(); // this causes crash too, any of the last two lines will freeze/crash the cad session
}

Exception thrown is:

System.AccessViolationException Attempted to read or write protected memory. This is often an indication that other memory is corrupt

 

Again - only v2025 seems to be affected by this for some reason, does not make a difference whether i run the .net 4.6 framework version or the .net 8 version

Any hints?

AutoCAD and C3D 2016+ on Win7x64
0 Likes
Accepted solutions (1)
657 Views
6 Replies
Replies (6)
Message 2 of 7

norman.yuan
Mentor
Mentor
Accepted solution

@TLatev ,

Here are what I tested (see code below).

 

Notice that the line of code that erases the Transient graphics could be different

 

1. If erasing is done by calling EraseTransient() (that is, erase each Transient);

2. Erase all Transient by calling EraseTransients(), with the second argument being 128, which is what I ALWAYS use.

3. Erase all Transient by calling EraseTransients(), with the second argument being 0 (as you did);

 

The result:

 

1. Build the code against .NET 4.8 (so, it runs with all versions of AutoCAD, including Acad2025). No problem of testing all 3 lines of code tested with Acad2024. When testing with Acad2025 (.NET 4.8 based code), the 3 of the above code crashes AutoCAD2025;

 

2. Build the code with .NET 8, thus the code will only run Acad2025. Again, code 1 and 2 work OK, and code 3 crashes Acad2025.

 

So, the issue is the second argument used for EraseTransients() cannot be 0. (note, it is OK for Acad2024). Due to the notoriously lacking of documentation, I am not sure  what values of the second argument means, but I always used 128🤔. Since it works with Acad2024 or older when the value being 0, I'd consider it is a bug introduced by Acad2025, be the code is based on .NET 4.X or .NET 8. So, while you may want to hold your breath waiting Autodesk to fix it, you can try to simply pass 128 to the method instead to make the code work in all versions of AutoCAD.

 

Here is my text code, which can be built against both .NET4.X or .NET 8:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;

[assembly: CommandClass(typeof(Transients2024.MyCommands))]

namespace Transients2024
{
    public class MyCommands 
    {
        private Point3d _center;
        private Circle _circle = null;
        private TransientManager _ts = TransientManager.CurrentTransientManager;

        [CommandMethod("MyCmd")]
        public void RunMyCommand()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;

            var res = ed.GetPoint("\nSelect center point:");
            if (res.Status != PromptStatus.OK) return;

            
            _center = res.Value;

            try
            {
                
                _circle = new Circle() { Center = _center, Radius = 1, ColorIndex = 2 };
                _ts.AddTransient(
                    _circle, TransientDrawingMode.DirectShortTerm, 128, new IntegerCollection());

                ed.PointMonitor += Ed_PointMonitor;
                res = ed.GetPoint("\nPick radius:");
            }
            finally
            {
                ed.PointMonitor -= Ed_PointMonitor;
                ed.WriteMessage("\nIn finally clause!");
                
                // ===== this line always work ========================
                //_ts.EraseTransient(_circle, new IntegerCollection());
                // ====================================================

                // ==== This line always work: the second argument is 120 ==============================
                _ts.EraseTransients(TransientDrawingMode.DirectShortTerm, 128, new IntegerCollection());
                // =====================================================================================

                // ==== This is the problematic line, with the second argument being 0==================
                // it works with Acad2024 or older, but crash Acad2025, be the code is built against
                // .NET 4.x, or against .NET 8 (of course, for Acad2024, no .NET 8 build)
                //_ts.EraseTransients(TransientDrawingMode.DirectShortTerm, 0, new IntegerCollection());
                // =====================================================================================

                _circle.Dispose();
            }
        }

        private void Ed_PointMonitor(object sender, PointMonitorEventArgs e)
        {
            var pt = e.Context.RawPoint;
            var r = pt.DistanceTo(_center);
            if (r > 0)
            {
                _circle.Radius = r;
                _ts.UpdateTransient(_circle, new IntegerCollection());
            }
        }
    }
}

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 3 of 7

TLatev
Participant
Participant

Thanks Norman,

This is really helpful - indeed I suspected that some kind of regression was introduced in v2025, but wouldn't have thought to experiment with random powers of two for the XXXTransient functions parameters.

The code runs fine now, I'll stop by to buy you a beer next time I find myself in Edmonton 😉

AutoCAD and C3D 2016+ on Win7x64
0 Likes
Message 4 of 7

Yehoshua.Shore
Contributor
Contributor

Hi! 

 

I'm new at this so sorry if I'm asking dumb questions. 

 

When calling the class below I can create my triangles anywhere I like, but they do not get cleared when I call Clear Graphics and AutoCAD runs incredibly unstably and crashes frequently. I can't figure out why. Any help would be much apricated!

public class TransientGraphics
{
    private TransientManager tm = TransientManager.CurrentTransientManager;
    private Entity transientEntity;

    public void DrawTriangle(Point3d location, double size, short colorIndex)
    {
        // Clear any existing triangle before drawing a new one
        ClearGraphics();

        using (Autodesk.AutoCAD.DatabaseServices.Polyline triangle = new Autodesk.AutoCAD.DatabaseServices.Polyline(3))
        {
            triangle.AddVertexAt(0, new Point2d(location.X, location.Y + size), 0, 0, 0);
            triangle.AddVertexAt(1, new Point2d(location.X - size, location.Y - size), 0, 0, 0);
            triangle.AddVertexAt(2, new Point2d(location.X + size, location.Y - size), 0, 0, 0);
            triangle.Closed = true;
            triangle.ColorIndex = colorIndex;

            transientEntity = triangle;  // Store the new triangle entity
            tm.AddTransient(triangle, TransientDrawingMode.DirectShortTerm, 128, new IntegerCollection());
        }
    }

    public void ClearGraphics()
    {
        if (transientEntity != null)
        {
            try
            {
                // Erase the transient graphic
                tm.EraseTransient(transientEntity, new IntegerCollection());

                // Ensure it is disposed of safely
                transientEntity.Dispose();
                transientEntity = null;  // Reset reference

                // Logging for debug
                Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nTransient graphics cleared successfully.");
            }
            catch (Exception ex)
            {
                // Catch and log any potential issues during the erase process
                Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage($"\nError clearing transient graphics: {ex.Message}");
            }
        }
        else
        {
            // Log when there are no transient graphics to clear
            Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nNo transient graphics to clear.");
        }
    }
}

 

0 Likes
Message 5 of 7

Yehoshua.Shore
Contributor
Contributor

AutoCAD even crashes when ive hit a break point and I'm just inspecting the code and the variables, just looking around.

0 Likes
Message 6 of 7

norman.yuan
Mentor
Mentor

The reason the ClearGraphics() method does not clear the Transient (and even cause crashing) is the drawable (referenced by class-level variable transientEntity ) has been disposed in the scope of the ClearGraphics() method when TransientManager calls EraseTransient(), because the drawable is created in a "using..." block!

 

You need to keep the drawable alive after the transient graphics is drawn, in order to update it, or erase it. Of cause, since the drawable is a non-database-residing entity, you need to dispose it when all the Transient thing is done.

 

So, one of the proper approach to do this type of work is to implement the class as IDisposable. Something like:

 

public class TransientGraphics : IDisposable
{
    private TransientManager tm = TransientManager.CurrentTransientManager;
    private Entity transientEntity;
	
	public void Dispose()
	{
		if (transientEntity!=null)
		{
			ClearGraphics(true);
		}
	}

    public void DrawTriangle(Point3d location, double size, short colorIndex)
    {
        // Clear any existing triangle before drawing a new one
        ClearGraphics();

		triangle.AddVertexAt(0, new Point2d(location.X, location.Y + size), 0, 0, 0);
		triangle.AddVertexAt(1, new Point2d(location.X - size, location.Y - size), 0, 0, 0);
		triangle.AddVertexAt(2, new Point2d(location.X + size, location.Y - size), 0, 0, 0);
		triangle.Closed = true;
		triangle.ColorIndex = colorIndex;

		transientEntity = triangle;  // Store the new triangle entity
		tm.AddTransient(triangle, TransientDrawingMode.DirectShortTerm, 128, new IntegerCollection());
    }

    public void ClearGraphics(bool disposeDrawble = false)
    {
        if (transientEntity != null)
        {
            try
            {
                // Erase the transient graphic
                tm.EraseTransient(transientEntity, new IntegerCollection());

                // Ensure it is disposed of safely
				if (disposeDrawable)
				{
					transientEntity.Dispose();
					transientEntity = null;  // Reset reference
				}
				
                // Logging for debug
                Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nTransient graphics cleared successfully.");
            }
            catch (Exception ex)
            {
                // Catch and log any potential issues during the erase process
                Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage($"\nError clearing transient graphics: {ex.Message}");
            }
        }
        else
        {
            // Log when there are no transient graphics to clear
            Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nNo transient graphics to clear.");
        }
    }
}

 

You would use the class like:

...

using (var graphics = new TransientGraphics())

{

   // draw the transient

   // get user input

   // update the transient, or erase it and redraw a new one

   // get user input

} // at the end, the transient is erased automatically because of the Dispose()

 

Notice

1. The drawable is created and should be alive during the entire instance of TransientGraphics class.

2. I added an optional input for the ClearGraphics() to allow it decide whether the drawable is to be disposed or not.

3. You can also create the drawable entity in the TransientGraphics's constructor (i.e. only create it once and let it to be disposed at the end of "using..." block. This way, you can change/transform the drawable entity inside the scope of the "using..." and call TransientManager.UpdateTransient(), or call ClearGraphics(false) to clear the graphcs without dispose the entity, so you can redraw it.

4. Of course you can always create a new drawable and show it, and then clear the graphics and also dispose it at the same time. Just remember, the drawable should be alive before the graphics is erased.

 

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 7 of 7

mszanto
Enthusiast
Enthusiast

Hi Norman, that was very helpful. After upgrading an older project to 2025, the application would cause AutoCAD to shut down continuously, and it took me some time to determine that transient objects were the cause. This code has run for many years over many versions with no issues until the 2025 upgrade with no code changes. The project is huge therefore a lot of code was affected by this issue, but your example helped me derive a solution that others might find helpful.

The solution for me was to ensure that all transients are erased when my tools are disposed therefore, I created a base class that my tools inherit from and use the _transientManager member in the base class to Add or Erase transients.

 

/// <summary>
/// The class that inherits from this class must be instantiated 
/// with a 'using' for the Dispose method to be fired.
/// Ex: using MyTool tool = new MyTool();
/// </summary>
public abstract class MyBaseClass : IDisposable
{
    TransientManager _transientManager = TransientManager.CurrentTransientManager;

    public void Dispose()
    {
        _transientManager.EraseTransients(TransientDrawingMode.Main, 128, new IntegerCollection());
    }
}

 

 

 

0 Likes