.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Is dll a AutoCAD plug-in?

18 REPLIES 18
Reply
Message 1 of 19
alex_b
2267 Views, 18 Replies

Is dll a AutoCAD plug-in?

Hi

 

I'm looking for a way to programmatically check if a dll file is a AutoCAD plug-in, preferably getting the version too.

 

The only thing I can think of is using

 

System.Reflection.Assembly.ReflectionOnlyLoad()

 

to load the dll, search thru its referrences looking for Acad-specific (acdbmgd etc.) get the reference version, compare it to a list....

 

Is there a simpler way?

 

Thanks

 

alex

18 REPLIES 18
Message 2 of 19
norman.yuan
in reply to: alex_b

Using whether acdbmgd/acmgd.dll is referenced in a DLL or not to decide if a DLL is an AutoCAD plug-in DLL is not reliable, IMO. Actually, the term of "AutoCAD Plug-in" isn't quite clear enough to classify DLLs.

 

For example, we can build a few dlls, one does file/folder manupulation, another does data read/write to file/database...They do not have to have reference to acad .NET API assemblies at all. Then we can simply create a acad .NET API dll that defines coomands that execute the functions in those DLLs. In this case, can you say only the last DLL is AutoCAD Plug-in DLL, while others are not?

 

Maybe, you want to explain what exactly you want to achieve, then someone would have better advice/suggestion.

Message 3 of 19
alex_b
in reply to: norman.yuan

Norman

 

I'm trying to identify which dll's on my disk drive can be loaded into AutoCAD, and into which Acad version.

As such, I do not need to identify referenced dll's. Those will be pulled in by the plug-in once ir is loaded.

So I guess I have to identify those dll's which define a Acad command.

 

Thanks

alex

Message 4 of 19
khoa.ho
in reply to: alex_b

Based on your explanation, I wrote the following code to check if a DLL is an AutoCAD plug-in or not. If it is, get the AutoCAD version.

 

I used System.Reflection.Assembly.ReflectionOnlyLoadFrom() to load context of a DLL file from its location.

 

[CommandMethod("GetAutocadPluginVersion")]
public static void GetAutocadPluginVersion()
{
	string dllPath = @"C:\Temp\AutocadPlugin.dll";
	string acadVersion = GetAutocadPluginVersion(dllPath);

	Editor editor = Application.DocumentManager.MdiActiveDocument.Editor;
	string message = string.IsNullOrEmpty(acadVersion)
		? "This DLL is not an AutoCAD plug-in"
		: "This DLL is an AutoCAD plug-in that supports AutoCAD " + acadVersion;
	editor.WriteMessage(message);
}

private static string GetAutocadPluginVersion(string dllPath)
{
	var AutoCADVersion = new Dictionary<string, string>
		{
			{ "15.0", "2000" },
			{ "15.1", "2000i" },
			{ "15.6", "2002" },
			{ "16.0", "2004" },
			{ "16.1", "2005" },
			{ "16.2", "2006" },
			{ "17.0", "2007" },
			{ "17.1", "2008" },
			{ "17.2", "2009" },
			{ "18.0", "2010" },
			{ "18.1", "2011" },
			{ "18.2", "2012" },
			{ "19.0", "2013" }
		};
	string dllName = "acdbmgd";
	string acadVersion = string.Empty;

	Assembly assembly = System.Reflection.Assembly.ReflectionOnlyLoadFrom(dllPath);
	AssemblyName[] assemblyNames = assembly.GetReferencedAssemblies();
	foreach (AssemblyName assemblyName in assemblyNames)
	{
		string name = assemblyName.Name;
		if (name.ToLower() == dllName.ToLower())
		{
			string version = assemblyName.Version.Major + "." + assemblyName.Version.Minor;
			if (AutoCADVersion.ContainsKey(version))
			{
				acadVersion = AutoCADVersion[version];
			}
			break;
		}
	}
	return acadVersion;
}

 

-Khoa

Message 5 of 19
alex_b
in reply to: khoa.ho

khoa:

 

Excelent. Works perfectly. I had some trouble initializing the Dictionary in .NET 2.0 but it's OK now.

One more question: how can we retrieve the command names defined in the plug-in?

 

Thanks

 

alex

Message 6 of 19
khoa.ho
in reply to: alex_b

AutoCAD command is defined by CommandMethod attribute. We use reflection MethodInfo.GetCustomAttributes() to get all custom attributes including AutoCAD CommandMethodAttribute.

 

Assembly.ReflectionOnlyLoadFrom() is used to get all references, so it is faster to load only context in the DLL.

Assembly.LoadFile() is used to get all contents of the DLL, so it loads all methods and their custom attributes.

 

I rewrote the code a little bit complicated to make it a nice custom command in AutoCAD. When user types a new command AcadPluginDetails, it will prompt to select a DLL file to check if it is an AutoCAD plug-in or not, and list all found commands in this plug-in.

 

[CommandMethod("AcadPluginDetails")]
public static void GetAutocadPluginDetails()
{
	Editor editor = Application.DocumentManager.MdiActiveDocument.Editor;

	var promptOpenFile = new PromptOpenFileOptions("Select an DLL file")
		{
			Filter = "Dynamic libraries (*.dll)|*.dll"
		};
	PromptFileNameResult res = editor.GetFileNameForOpen(promptOpenFile);
	string dllPath = res.StringResult;
	if (string.IsNullOrEmpty(dllPath))
		return;

	string dllName = Path.GetFileName(dllPath);
	string acadVersion = GetAutocadPluginVersion(dllPath);
	List<string> acadCommands = GetAllAutocadCommands(dllPath);

	string message = string.IsNullOrEmpty(acadVersion)
		? "File " + dllName + " is not an AutoCAD plug-in"
		: "File " + dllName + " is an AutoCAD plug-in that supports AutoCAD " + acadVersion +
			(acadCommands.Count == 0
			? string.Empty
			: "\nFound commands in this plug-in: " + String.Join(", ", acadCommands.ToArray()));

	editor.WriteMessage(message);
}

private static string GetAutocadVersion(AssemblyName assemblyName)
{
	var AutoCADVersion = new Dictionary<string, string>
		{
			{ "15.0", "2000" },
			{ "15.1", "2000i" },
			{ "15.6", "2002" },
			{ "16.0", "2004" },
			{ "16.1", "2005" },
			{ "16.2", "2006" },
			{ "17.0", "2007" },
			{ "17.1", "2008" },
			{ "17.2", "2009" },
			{ "18.0", "2010" },
			{ "18.1", "2011" },
			{ "18.2", "2012" },
			{ "19.0", "2013" }
		};
	string acadVersion = string.Empty;
	string version = assemblyName.Version.Major + "." + assemblyName.Version.Minor;
	if (AutoCADVersion.ContainsKey(version))
	{
		acadVersion = AutoCADVersion[version];
	}
	return acadVersion;
}

// Get the AutoCAD version of a given DLL file,
// returns empty string if it is not an AutoCAD plug-in
public static string GetAutocadPluginVersion(string dllPath)
{
	string acadVersion = string.Empty;
	// Indicate the most common DLL name referencing in an AutoCAD plug-in
	const string dllName = "acdbmgd";
	// Loads an assembly into the reflection-only context, given its path
	try
	{
		Assembly assembly = Assembly.ReflectionOnlyLoadFrom(dllPath);
		// Check if this assembly is the defined dllName
		AssemblyName loadedAssemblyName = assembly.GetName();
		if (loadedAssemblyName.Name.ToLower() == dllName.ToLower())
		{
			acadVersion = GetAutocadVersion(loadedAssemblyName);
		}
		else // this assembly is not acdbmgd.dll
		{
			AssemblyName[] assemblyNames = assembly.GetReferencedAssemblies();
			foreach (AssemblyName assemblyName in assemblyNames)
			{
				string name = assemblyName.Name;
				if (name.ToLower() == dllName.ToLower())
				{
					acadVersion = GetAutocadVersion(assemblyName);
					break;
				}
			}
		}
	}
	catch (System.Exception ex)
	{
	}
	return acadVersion;
}

// Retrieve all AutoCAD commands from a given DLL plug-in file
public static List<string> GetAllAutocadCommands(string dllPath)
{
	var commandList = new List<string>();
	try
	{
		// Loads the contents of an assembly file
		Assembly assembly = Assembly.LoadFile(dllPath);
		// Get all types/classes in this assembly
		Type[] types = assembly.GetTypes();
		foreach (Type type in types)
		{
			// Get all methods in this class
			MethodInfo[] methodInfos = type.GetMethods();
			foreach (MethodInfo methodInfo in methodInfos)
			{
				// Get AutoCAD command's attribute
				object[] attributes = methodInfo.GetCustomAttributes(typeof(CommandMethodAttribute), true);
				if (attributes.Length > 0)
				{
					// Get AutoCAD command name
					string commandName = ((CommandMethodAttribute)attributes[0]).GlobalName;
					commandList.Add(commandName);
				}
			}
		}
	}
	catch (System.Exception ex)
	{
	}
	return commandList;
}

 

-Khoa

Message 7 of 19
alex_b
in reply to: khoa.ho

Khoa

 

The code you posted is fine, thank you.

My problem is, I do this in an out-of-process COM.

The 1st part is OK, it identifies the Acad plug-ins and thei versions.

But, when calling GetAllAutocadCommands() I get a File Not Found exception, presumably because the CommandMethodAttribute is defined in one of the Autocad dll's, the JIT tries to pull in all the referrenced dll's and can't find or load them.

I tried using CustomAttributeData.GetCustomAttributes(Assembly) but it raises the same exception.

Is there a workaround?

 

Thanks

 

alex

I

Message 8 of 19
khoa.ho
in reply to: alex_b

Hi Alex,

Can you give more details about the error of "File Not Found" exception. What specific code causes that error?

CommandMethodAttribute is a class inside acmgd.dll, so make sure you reference this AutoCAD assembly in your out-of-process COM program.

Assembly.LoadFile() will raise error if it loads native DLL files (BadImageFormatException with no manifest of an unmanaged assembly), or managed assemblies built with higher .NET framework. My latest code will handle this error.

-Khoa

Message 9 of 19
alex_b
in reply to: khoa.ho

Hi Khoa

 

Yes, I do reference acmgd.dll and acdbmgd.dll

The exception is:

System.IO.FileNotFoundException: The specified module could not be found. (Exception from HRESULT: 0x8007007E)

on the line:

GetAllAutocadCommands(dllPath);

My understanding is that the JIT compiler tries to compile the method, looks for the CommandMethodAttribute type, tries to load acmgd.dll and fails (on it or one of its dependencies). I haven't been able to find what's missing (in any case acmgd references only system dll's and acdbmgd).

 

Thanks

 

alex

Message 10 of 19
khoa.ho
in reply to: alex_b

I guess your program may use .NET framework 2.0, but it references acdbmgd.dll and acmgd.dll in .NET higher version, such as 3.0 (A2009), 3.5 (A2010 & A2011), or 4.0 (A2012 & A2013). AutoCAD 2007 & 2008 use .NET 2.0.

Make sure your program is using equal or higher .NET version than framework of acmgd.dll. Otherwise, the reflection of lower runtime framework cannot read assemblies of higher framework, and raises errors.

-Khoa

Message 11 of 19
alex_b
in reply to: khoa.ho

Khoa

 

It uses NET 2.0 indeed, but the references are to A2008, so it should be OK.

One more detail: which I missed pasting: The FileNotFound is thrown as:

 

Could not load file or assembly 'acmgd, Version=17.1.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

And I noticed that the version shown in Add Reference is 17.1.51.0. Could that be it?

 

I even tried to put the executable in the A2008 folder, so it won't have any trouble loading dll's.

This is what happened:

******************************************************************************

System.TypeInitializationException: The type initializer for '<Module>' threw an exception. ---> <CrtImplementationDetails>.ModuleLoadException: The C++ module failed to load during process initialization.
 ---> System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

  at AcPlPlotReactor.{ctor}(AcPlPlotReactor* )
   at ?A0x860c6007.??__EgPlotReactor@@YMXXZ()
   at _initterm_m((fnptr)* pfbegin, (fnptr)* pfend)
   at <CrtImplementationDetails>.LanguageSupport.InitializePerProcess(LanguageSupport* )
   at <CrtImplementationDetails>.LanguageSupport._Initialize(LanguageSupport* )
   at <CrtImplementationDetails>.LanguageSupport.Initialize(LanguageSupport* )
   --- End of inner exception stack trace ---
   at <CrtImplementationDetails>.ThrowModuleLoadException(String errorMessage, Exception innerException)
   at <CrtImplementationDetails>.ThrowModuleLoadException(String , Exception )
   at <CrtImplementationDetails>.LanguageSupport.Initialize(LanguageSupport* )
   at .cctor()
   --- End of inner exception stack trace ---
   at LL.Form1.GetAutocadPluginVersion(String dllPath)

*******************************************************************************************

Does this mean anyrhing to you?

 

THanks

 

alex

Message 12 of 19
khoa.ho
in reply to: alex_b

Alex,

The error of "Could not load file or assembly 'acmgd, Version=17.1.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies." is the problem of reading unmanaged DLL dependencies of acmgd.dll. When your .NET program references acmgd.dll to use CommandMethodAttribute, it fails to load its native unmanaged dependencies. This is a problem of .NET reflection to load native C++ assemblies. I still don't know how to fix that. I will come to find the solution later.

-Khoa

Message 13 of 19
alex_b
in reply to: khoa.ho

Khoa,

 

>>I will come to find the solution later.

So there is still hope..

I admit that I don't understand much of this, but just a thought: What about using CustomAttributeData class and thus avoiding loading acmgd.dll at all?

 

Thanks

 

alex

Message 14 of 19
khoa.ho
in reply to: alex_b

Alex,

 

I made a new console app (without any references to AutoCAD assemblies such as acmgd.dll) and got exactly the same problem with your program. I revised my posted code to capture error events. But it is not finished to post here.

 

“The type initializer for '<Module>' threw an exception...” indicates that the .NET reflector fails when trying to reference unmanaged assemblies inside acmgd.dll

 

CustomAttributeData.GetCustomAttributes() has the same error of "Could not load file or assembly 'acmgd, Version=17.2.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies."

 

CustomAttributeData.GetCustomAttributes(methodInfo) is similar to methodInfo.GetCustomAttributes(true). CustomAttributeData class is more generic to accept many object types to retrieve their attributes.

 

The problem as I mentioned previously is .NET reflection tries to load dependent C++ assemblies during reading the AutoCAD plug-in file. It can read acmgd.dll at the AutoCAD installation folder but fails to read all its unmanaged dependencies. I still try to figure how to prevent reading those unnecessary C++ dependencies of acmgd.dll.

 

There is no problem if the posted code works inside AutoCAD. But outside AutoCAD as a console app, it will cause a problem.

 

-Khoa

Message 15 of 19
alex_b
in reply to: khoa.ho

Khoa

 

1.My Reflector 6 program suceeds in doing what I'm looking for: It finds the methods having the CommandMethod attribute, which shows that it is possible to do it.

 

2. From MSDN CustomAttributeData class docu:

CustomAttributeData can be used in the execution context as well as in the reflection-only context. For example, you might want to avoid loading the assembly that contains the code for a custom attribute.

 

2Maybe?

 

To make a hard task harder (for me, not you), what about the same problem for .arx files (here we know they are Acad plug-ins, we just have to find the commands).

 

alex

Message 16 of 19
khoa.ho
in reply to: alex_b

Alex,

 

CustomAttributeData.GetCustomAttributes() always throws exception when reading attributes. I still don't know how to prevent loading acmgd.dll and its dependencies. The .NET reflection throws errors when loading unmanaged assemblies.

 

.NET Reflector software and other disassembly tools can read custom attributes (of assembly, class, method) nicely without loading dependencies. I don't know how it can do that. This is something we need to learn. I found Mono.Cecil is a great library to support .NET reflection but it requires learning curve and time.

 

To keep you moving forward, I post the code of a sample console app that can read an AutoCAD plug-in to find its AutoCAD version, but it fails to get all AutoCAD commands. In Visual Studio, create a new simple console app (prefer .NET 4.0 to read all AutoCAD plug-in versions) without any extra references, and then copy the code to the file Program.cs.

 

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace AcadReflection
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                string dllPath = @"C:\Program Files\AutoCAD PID 2009\AcCalcUi.dll";
                string output = GetAutocadPluginDetails(dllPath);
                Console.WriteLine(output);
            }
            catch (BadImageFormatException ex)
            {
                Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
            }
        }

        #region Reflection
        public static string GetAutocadPluginDetails(string dllPath)
        {
            try
            {
                if (string.IsNullOrEmpty(dllPath))
                    return string.Empty;

                string dllName = Path.GetFileName(dllPath);
                string acadVersion = GetAutocadPluginVersion(dllPath);
                List<string> acadCommands = GetAllAutocadCommands(dllPath);

                string message = string.IsNullOrEmpty(acadVersion)
                    ? "File " + dllName + " is not an AutoCAD plug-in"
                    : "File " + dllName + " is an AutoCAD plug-in that supports AutoCAD " + acadVersion +
                        (acadCommands.Count == 0
                        ? string.Empty
                        : "\nFound commands in this plug-in: " + String.Join(", ", acadCommands.ToArray()));
                return message;
            }
            catch (BadImageFormatException ex)
            {
                throw;
            }
        }

        private static string GetAutocadVersion(AssemblyName assemblyName)
        {
            var AutoCADVersion = new Dictionary<string, string>
                {
                    { "15.0", "2000" },
                    { "15.1", "2000i" },
                    { "15.6", "2002" },
                    { "16.0", "2004" },
                    { "16.1", "2005" },
                    { "16.2", "2006" },
                    { "17.0", "2007" },
                    { "17.1", "2008" },
                    { "17.2", "2009" },
                    { "18.0", "2010" },
                    { "18.1", "2011" },
                    { "18.2", "2012" },
                    { "19.0", "2013" }
                };
            string acadVersion = string.Empty;
            string version = assemblyName.Version.Major + "." + assemblyName.Version.Minor;
            if (AutoCADVersion.ContainsKey(version))
            {
                acadVersion = AutoCADVersion[version];
            }
            return acadVersion;
        }

        // Get the AutoCAD version of a given DLL file,
        // returns empty string if it is not an AutoCAD plug-in
        public static string GetAutocadPluginVersion(string dllPath)
        {
            string acadVersion = string.Empty;
            // Indicate the most common DLL name referencing in an AutoCAD plug-in
            const string dllName = "acdbmgd";
            // Loads an assembly into the reflection-only context, given its path
            try
            {
                Assembly assembly = Assembly.ReflectionOnlyLoadFrom(dllPath);
                AssemblyName loadedAssemblyName = assembly.GetName();
                // Check if this assembly is the defined dllName
                if (loadedAssemblyName.Name.ToLower() == dllName.ToLower())
                {
                    acadVersion = GetAutocadVersion(loadedAssemblyName);
                }
                else // this assembly is not acdbmgd.dll
                {
                    AssemblyName[] assemblyNames = assembly.GetReferencedAssemblies();
                    foreach (AssemblyName assemblyName in assemblyNames)
                    {
                        string name = assemblyName.Name;
                        if (name.ToLower() == dllName.ToLower())
                        {
                            acadVersion = GetAutocadVersion(assemblyName);
                            break;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw;
            }
            return acadVersion;
        }

        // Retrieve all AutoCAD commands from a given DLL plug-in file
        public static List<string> GetAllAutocadCommands(string dllPath)
        {
            var commandList = new List<string>();
            try
            {
                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomainOnReflectionOnlyAssemblyResolve;
                // Loads the contents of an assembly file
                Assembly assembly = Assembly.ReflectionOnlyLoadFrom(dllPath);
                // Get all types/classes in this assembly
                Type[] types;
                try
                {
                    types = assembly.GetTypes();
                }
                catch (ReflectionTypeLoadException typeException)
                {
                    types = typeException.Types;
                }
                foreach (Type type in types)
                {
                    if (type == null)
                        continue;

                    // Get all methods in this class
                    MethodInfo[] methodInfos = type.GetMethods();
                    foreach (MethodInfo methodInfo in methodInfos)
                    {
                        try
                        {
                            // Get AutoCAD command's attribute
                            //object[] attributes = methodInfo.GetCustomAttributes(false);
                            IList<CustomAttributeData> attributeDataList = CustomAttributeData.GetCustomAttributes(methodInfo);
                            foreach (CustomAttributeData customAttributeData in attributeDataList)
                            {
                                if (customAttributeData.GetType().Name == "CommandMethodAttribute")
                                {
                                    // Get AutoCAD command name
                                    string commandName = ""; // ((CommandMethodAttribute)attributes[0]).GlobalName;
                                    commandList.Add(commandName);
                                }
                            }
                        }
                        catch (FileNotFoundException fileException)
                        {
                            //throw;
                        }
                    }
                }
            }
            catch (BadImageFormatException ex)
            {
                throw;
            }
            return commandList;
        }

        private static Assembly CurrentDomainOnReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
        {
            return System.Reflection.Assembly.ReflectionOnlyLoad(args.Name);
        }
        #endregion
    }
}

 

-Khoa

Message 17 of 19
alex_b
in reply to: khoa.ho

Khoa

 

>>...I found Mono.Cecil is a great library to support .NET reflection but it requires learning curve and time.

 

Surprisingly (or maybe not) there was almost no learning curve, due in part probably to the complete lack of documentation.

I downloaded the library and it took 15 minutes to get the results.

Here is the code, and it works:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Mono.Cecil;
using System.Text;
//code not extensively tested yet
//TODO add error checking
//TODO fix it for Unicode. for now CustomAttribute.Blob is just raw ASCII data with some obscure formatting (additional ASCII 0 and ASCII 8 added)

namespace ReflectionAcadPlugins
{
	class Program
	{
		public static void Main()
		{
			//Creates an AssemblyDefinition from the "MyLibrary.dll" assembly
			AssemblyDefinition myLibrary = AssemblyFactory.GetAssembly(@"MyLibrary.dll");
			//Gets all the program modules
			ModuleDefinitionCollection modules = myLibrary.Modules;
			foreach (ModuleDefinition mod in modules)
			{
				//Gets all types/classes which are declared in the module
				TypeDefinitionCollection types = mod.Types;
				foreach (TypeDefinition type in mod.Types)
				{
					if (type == null) continue;
					//gets all methods in this class
					MethodDefinitionCollection methods = type.Methods;
					//gets all custom attributes for this method
					foreach (MethodDefinition met in methods)
					{
						CustomAttributeCollection attribs = met.CustomAttributes;
						//checks it the attribute is what we want
						foreach (CustomAttribute attr in attribs)
						{
							if (attr.Constructor.ToString().Contains("CommandMethodAttribute"))
							{
								Byte[] bytes = attr.Blob;
								string cmd = System.Text.ASCIIEncoding.ASCII.GetString(bytes);
								cmd = ReplaceNonPrintableCharacters(cmd);
								Console.WriteLine(cmd);
							}
						}
					}
				}
			}
		}
		static string ReplaceNonPrintableCharacters(string s)
		{
			StringBuilder result = new StringBuilder();
			for (int i = 0; i < s.Length; i++)
			{
				char c = s[i];
				byte b = (byte)c;
				if (b >= 32)
					result.Append(c);
			}
			return result.ToString();
		}

 So your tip about Mono.Cecil was great.

 

Thank you

 

alex

Message 18 of 19
alex_b
in reply to: alex_b

Just remembered:

I still need to do the same (extracting the command names) for arx files.

I haven't a clue how to go about it.

Any ideas?

 

Message 19 of 19
khoa.ho
in reply to: alex_b

Hi Alex,

 

I am happy that you could make it work with Mono.Cecil library, as I found .NET framework has some limitations for reflection. Your topic is also very interesting to me to start playing with .NET Reflection.

 

-Khoa

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report

”Boost