logo-beycons
  • Product
    Plugins
    Download
  • Service
    Courses
    Outsource
    Structural Design
  • Community
    Forums
    Blogs
  • User
    Register
    Sign In

Detail Blog

Nhu.Truong
Nhu.Truong
8Blogs
3Followers
930Views
0Like
Created on 17/12/2023
Category - AutoCAD API

How to debug without restarting AutoCAD?

The approach is applicable not only to AutoCAD but also to the majority of software that supports .NET, like Revit or Navisworks, aiming to simplify the add-in development process.

Overview

The application executes commands developed by users through System.Reflection in .NET.

During software startup, it loads .dll files (referred to as assemblies, compiled by users using an integrated development environment) as configured in the manifest file, following the specific format of each software.

This loading process is accomplished through Assembly.LoadFile, and the assembly references that users have included are also brought into the software's domain.

What will happen next?

Issue

If, during the debugging process, we attempt to apply new code after compiling while the software is still open, it will lead to the following error:

Could not copy "..." to "...". Exceeded retry count of 10. Failed. The file is locked by: "AutoCAD Application (6824)".
Unable to copy file "..." to "...". The process cannot access the file '...' because it is being used by another process.

  • We cannot modify the application's assembly loading method.
  • We do not want to restart the application after applying new code.

So, is there any solution to solve this problem?

Solution


Load the BeyCons.Debug assembly directly into the AutoCAD domain. In BeyCons.Debug, the BeyCons.App assembly will be loaded into the AutoCAD domain using Assembly.Load(byte[] rawAssembly).

Now, BeyCons.App can edit and compile code easily without being blocked.

Invoke the commands of BeyCons.App within the commands of BeyCons.Debug using Reflection as follows:

#region Using
using Autodesk.AutoCAD.Runtime;
using System;
using System.IO;
using System.Reflection;
#endregion
#nullable enable
namespace BeyCons.Debug.Commands
{
    public class DebugCommand
    {
        #region Fields
        private const string _assemblyName = "BeyCons.App";
        #endregion

        #region Commands
        [CommandMethod("LC", CommandFlags.Session | CommandFlags.UsePickSet)]
        public static void LoadCommand()
        {
            var arrayByte = File.ReadAllBytes(@$"<The folder path of BeyCons.App>\{_assemblyName}.dll");
            var assembly = Assembly.Load(arrayByte);

            if (assembly.GetType($"{_assemblyName}.Commands.TestingCommand") is not { } type)
                return;

            if (type.GetMethod("ShowDialog") is not { } methodInfo)
                return;

            var instance = Activator.CreateInstance(type);
            methodInfo.Invoke(instance, null);
        }
        #endregion
    }
}
#region Using
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
#endregion
namespace BeyCons.App.Commands
{
    internal class TestingCommand
    {
        [CommandMethod("ShowDialog", CommandFlags.Session)]
        public static void ShowDialog()
        {
            Application.ShowAlertDialog("Hello World!");
        }
    }
}

Another arising issue is that Assembly.Load(byte[] rawAssembly) will not load the assembly references used in BeyCons.App.

How do we resolve it?

You have to implement the AssemblyResolve event of the AppDomain to load the assembly references when BeyCons.App requests them and modify the code as follows:

#region Using
using Autodesk.AutoCAD.Runtime;
using System;
using System.IO;
using System.Reflection;
#endregion
#nullable enable
namespace BeyCons.Debug.Commands
{
    public class DebugCommand
    {
        #region Fields
        private const string _assemblyName = "BeyCons.App";
        #endregion

        #region Commands
        [CommandMethod("LC", CommandFlags.Session | CommandFlags.UsePickSet)]
        public static void LoadCommand()
        {
            var folderPath = @"<The folder path of BeyCons.App>";
var arrayByte = File.ReadAllBytes(Path.Combine(folderPath, $"{_assemblyName}.dll")); var assembly = Assembly.Load(arrayByte); if (assembly.GetType($"{_assemblyName}.Commands.TestingCommand") is not { } type) return; if (type.GetMethod("ShowDialog") is not { } methodInfo) return; var instance = Activator.CreateInstance(type, folderPath); methodInfo.Invoke(instance, null); } #endregion } }
#region Using
using Autodesk.AutoCAD.Runtime;
using System;
using System.IO;
using System.Reflection;
#endregion
#nullable enable
namespace BeyCons.App.Commands
{
    internal class TestingCommand
    {
        #region Fields
        private readonly string _folderPath; 
        #endregion

        public TestingCommand(string folderPath)
        {
            _folderPath = folderPath;
            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;            
        }

        #region Events
        private Assembly? AssemblyResolve(object sender, ResolveEventArgs args)
        {            
            if (args.RequestingAssembly is null)
                return null;

            var assemblyDependentPath = string.Empty;
            foreach (var referencedAssembly in args.RequestingAssembly.GetReferencedAssemblies())
            {
                var assemblyName = args.Name.Substring(0, args.Name.IndexOf(","));
                if (referencedAssembly.FullName.Substring(0, referencedAssembly.FullName.IndexOf(",")) != assemblyName)
                    continue;

                assemblyDependentPath = Path.Combine(_folderPath, $"{assemblyName}.dll");
                break;
            }

            if (string.IsNullOrEmpty(assemblyDependentPath))
                return null;

            return Assembly.Load(File.ReadAllBytes(assemblyDependentPath));
        }
        #endregion

        #region Commands
        [CommandMethod("ShowDialog", CommandFlags.Session)]
        public static void ShowDialog()
        {
            BeyCons.UI.Notification.ShowDialog("Hello BeyCons!");
        } 
        #endregion
    }
}

As you can see, now BeyCons.App successfully executes the function located in the BeyCons.UI assembly that BeyCons.App references.

Conclusion

Utilizing .NET Reflection allows you to modify the loading process of assemblies into the application's domain, preventing potential blocks during source code recompilation.

Additionally, it empowers the execution of commands from a separate assembly at runtime, simplifying add-in development and speeding up the debugging process.

C#
AutoCAD
Modified on 23/04/2024
Back
Summary

    Related Blogs

    How to store complex data in AutoCAD?

    Created by Nhu.Truong on 21/04/2024
    950 Views - 0 Like
    In some situations, we may need to attach additional data to an AutoCAD document for the purpose of identification, storage, and facilitating easier data management. So, how do we store the data?

    How to convert text in a link/import DWG file to Revit?

    Created by Nhu.Truong on 23/12/2023
    1017 Views - 0 Like
    The Revit API does not provide any information about Text objects in a link/import DWG file. This post shares a solution that can read Text objects in a link/import DWG and convert them to Revit.

    Developing a Dynamo package using DI pattern

    Created by Nhu.Truong on 15/12/2023
    939 Views - 0 Like
    Developing a Dynamo package to accurately position Soffit Corner elements using Dynamo API, Revit API, and C# with the Dependency Injection pattern.
    Previous Next
    About Us
    logo-beycons
    Technology Makes Construction Better
    EIN 0316964547
    Address
    Thu Duc City, Ho Chi Minh City
    (+84) 33 248 2470
    contact.beycons@gmail.com
    Follow Us
    Terms & Privacy
    © 2022 - 2025 BeyCons Co.,Ltd.
    Developed by Nhu.Truong.