We have a .NET tool that uses AutoCAD to automatically process graphics. This tool uses a method identical to this article from Kean Walmsley’s blog: http://through-the-interface.typepad.com/through_the_interface/2009/05/interfacing-an-external-com-a...
I have been asked to make this tool work with a variety of different AutoCAD versions from AutoCAD 2010 to 2015, both 32 and 64 bit variants.
The ProgID's of the AutoCAD versions I need to CreateInstance for are:
"AutoCAD.Application.18" "AutoCAD.Application.18.1" "AutoCAD.Application.18.2"
"AutoCAD.Application.19" "AutoCAD.Application.19.1"
"AutoCAD.Application.20"
I am experiencing two problems:
First there are different versions of the Autodesk.AutoCAD.Interop.dll in ObjectARX SDK, corresponding to the different versions of AutoCAD. I cannot include references to all the different versions in the same Visual Studio project. Most importantly the interface ID’s for the IAcadApplication interface is different in all of them.
ObjectARX 2010 32-bit: 84F323FC-C179-4704-87E7-E3D576C2599E
64-bit: 2959C1CC-8577-4EDB-ADDC-6EBBAB147926
ObjectARX 2013 32-bit: 7558007D-8677-4FF1-BD48-B66281BD3DD7
64-bit: 070AA05D-DFC1-4E64-8379-432269B48B07
ObjectARX 2015 32-bit: 9FD28580-7461-4A57-AF4F-695AF5FDDA35
64-bit: 1C9CCE52-F48B-42CC-877B-F74905EC7DBC
Is there some method using Reflection that might enable me to create an instance of AutoCAD, and then reflect on the AutoCAD object to obtain the IAcadApplication interface at runtime?
The second problem I’m having is that when multiple versions of AutoCAD are installed side-by-side there is some confusion with the CreateInstance call. For Example, if AutoCAD 2013 and AutoCAD 2014 are both installed and I try to create an AutoCAD.Application.19 instance, I always get an AutoCAD.Application.19.1 instance. Is there a way I can prevent this, getting the AutoCAD.Application.19 instance I asked for.
Any information or strategy would be most helpful.
Thanks,
Eric Matthews
Hi,
First, to avoid COM versions dependency, you can use the dynamic type (requires framework 4.0 or later) or late binding with Reflection, these way you won't have to reference any AutoCAD COM library.
The main inconvenient is that you won't have no more help from Visual Studio intellisense, but you can build your program referencing the COM libraries and the convert it to dynamic or late binding.
Here're three examples of the same little Console Application which creates a new instance of AutoCAD, make it visible and change the cursor size to 100.
Using COM libraries reference and strong typing, the progId have to be same version à the referenced libraries.
static void Main() { AcadApplication acadApp; try { acadApp = (AcadApplication)Activator.CreateInstance(System.Type.GetTypeFromProgID("AutoCAD.Application.18.1")); } catch (Exception) { MessageBox.Show("Unable to launch AutoCAD"); return; } WaitForAcadIsQuiescent(acadApp); acadApp.Visible = true; WaitForAcadIsQuiescent(acadApp); acadApp.Preferences.Display.CursorSize = 100; } static void WaitForAcadIsQuiescent(AcadApplication acadApp) { while (true) { try { AcadState state = acadApp.GetAcadState(); if (state.IsQuiescent) break; } catch { } } }
Converting to dynamic is the easiest way: remove references to the COM references and replace all AutoCAD COM strong types in the code with the 'dynamic' keyword.
Using dynamic typing, requires the Framework 4
static void Main() { dynamic acadApp; try { acadApp = Activator.CreateInstance(System.Type.GetTypeFromProgID("AutoCAD.Application.18")); } catch (Exception) { MessageBox.Show("Unable to launch AutoCAD"); return; } WaitForAcadIsQuiescent(acadApp); acadApp.Visible = true; WaitForAcadIsQuiescent(acadApp); acadApp.Preferences.Display.CursorSize = 100; } static void WaitForAcadIsQuiescent(dynamic acadApp) { while (true) { try { dynamic state = acadApp.GetAcadState(); if (state.IsQuiescent) break; } catch { } } }
Using late binding is a little more tricky, but to avoid all the object.GetType().InvokeMember( ...) stuff, you can use the below extension methods which allow to write code in a frienly way (LISP vlax-* syntax like).
Using late bindig with Reflection, requires the LateBindingHelper extension methods
static void Main(string[] args) { object acadApp; try { acadApp = LateBindingHelper.CreateInstance("AutoCAD.Application"); } catch (Exception) { MessageBox.Show("Unable to launch AutoCAD"); return; } WaitForAcadIsQuiescent(acadApp); acadApp.Set("Visible", true); WaitForAcadIsQuiescent(acadApp); acadApp.Get("Preferences").Get("Display").Set("CursorSize", 100); } static void WaitForAcadIsQuiescent(object acadApp) { while (true) { try { object state = acadApp.Invoke("GetAcadState"); if ((bool)state.Get("IsQuiescent")) break; } catch { } } }
Late binding helper extension methods, requires reference to System.Reflection and System.runtime.InteropServices.
public static class LateBindingHelper { public static object GetInstance(string appName) { return Marshal.GetActiveObject(appName); } public static object CreateInstance(string appName) { return Activator.CreateInstance(System.Type.GetTypeFromProgID(appName)); } public static object GetOrCreateInstance(string appName) { try { return GetInstance(appName); } catch { return CreateInstance(appName); } } public static void ReleaseInstance(this object obj) { Marshal.ReleaseComObject(obj); } public static object Get(this object obj, string propName) { return obj.GetType().InvokeMember(propName, BindingFlags.GetProperty, null, obj, new object[0]); } public static void Set(this object obj, string propName, object value) { obj.GetType().InvokeMember(propName, BindingFlags.SetProperty, null, obj, new object[1] { value }); } public static object Invoke(this object obj, string methName, params object[] parameters) { return obj.GetType().InvokeMember(methName, BindingFlags.InvokeMethod, null, obj, parameters); } }
For the second problem, as far as I know, using the ProgId without specifying version ("AutoCAD.Application") launches the latest opened version. Using only the major version ("AutoCAD.Application.19") launches the latest opened AutoCAD for this major version.
Hi,
Greetings..Thanks for your post.
Could you please guide me, how we operate Acaddocuments commands in LateBindingHelper method.
Thanks,
vallimanalan.T
public void CREATE_polyline(double[] Point_list) { AcadPolyline polyline = ACAD_Document.ModelSpace.AddPolyline(Point_list); ACAD_Application.ZoomExtents(); }
Hi Gilles Chanteau,
Greetings. Am struggling do handle lot of functions through LateBindingHelper (like this approach). Could you please guide me, how i can use this same function thorough LateBindingHelper.
If possible, could you please give any tutorial for better understanding of LateBindingHelper
Wish you a happy new year in advance
Thank you
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.Runtime; using System.Reflection; using System.IO; [assembly:CommandClass(typeof(loading_netload_automatically.Class1))] namespace loading_netload_automatically { public class Class1 { [CommandMethod("autoloading")] public static void autoloading() { Assembly.LoadFrom(@"C:\Users\Naveen-Pc\Documents\Visual Studio 2017\Projects\cadd customization\autocadd should be closed everytime\multicircle\multicircle\bin\Debug\multicircle.dll"); Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; Application.SetSystemVariable("cmdecho", 0); ed.Command("mc",206); Application.SetSystemVariable("cmdecho", 1); String[] blocks = new string[3] { "b1", "b2", "b3" }; string ip = "0,0,0"; foreach (string block in blocks) { ed.Command("-insert", block, ip, "", "", ""); } } } }
This helper just provides extension methods to make late binding less noisy.
This is useless since .NET Framework 4.0 brings the dynamic type.
From the code you're showing (using Editor.Command), it looks like you're targerting AutoCAD 2015 or later. That means you have at least the NET Framework 4.5 installed, so you absolutely do not need to use this helper and should use dynamic type instead.
Hi,
Thanks. If we use dynamic type then its a dependency of particular version like "AutoCAD.Application.18".So we cant achieve multiple version type.
Am right?
thanks,
vallimanalan.T
Sorry, I cannot understand what you're trying to achieve.
The code you are showing does not use the COM interface, so there is no reason to use late binding or the dynamic type to get rid of dependencies on AutoCAD versions. However, you use the Editor.Command () method, which only exists for versions of AutoCAD 2015 and later.
Hi Gilles Chanteau,
Greetings.
In my project Scope, i need to prepare window application with the following features
1.through "COM" method , i need open different AutoCAD (invisible mode) and load dll file (list of custom commands design through "CommandMethod")
2.Execute the custom command with SQlite data. [Example: for creating polyline, sqlite data will provide points]
Could you please guide me
1.How i need to achieve "loading dll file in my application" through latebinderhelp. i unable to understand the dynamic type.if am using dynamic method, am unable to create autocad instance in program.
2.How to handle custom commands in latebinderhelp. the reason is am using prompt for user input to transfer sqlite data to command prompt.
like this method for creating layer am passing layer name
[CommandMethod("CreateLayer_DWG")] public static void CreateLayer() { // Get the current document and database Document acadDoc = Application.DocumentManager.MdiActiveDocument; Database acadCurDb = acadDoc.Database; // Start a transaction using (Transaction acadTrans = acadCurDb.TransactionManager.StartTransaction()) { // Open the Layer table for read LayerTable acadLyrTbl; acadLyrTbl = acadTrans.GetObject(acadCurDb.LayerTableId, OpenMode.ForRead) as LayerTable; // prompt receive the Layer name PromptStringOptions pLyrName = new PromptStringOptions("\nEnter Layer name: "); pLyrName.AllowSpaces = true; PromptResult pLyrRes = acadDoc.Editor.GetString(pLyrName); string sLayerName = pLyrRes.StringResult; // prompt receive the Layer color PromptIntegerOptions pLyrColor = new PromptIntegerOptions("\nEnter Color Index: "); pLyrColor.AllowNone = false; pLyrColor.AllowZero = false; PromptIntegerResult pLyrCol = acadDoc.Editor.GetInteger(pLyrColor); if ((pLyrRes.Status != PromptStatus.Cancel) && (pLyrCol.Status == PromptStatus.OK)) { if (acadLyrTbl.Has(sLayerName) == false) { LayerTableRecord acadLyrTblRec = new LayerTableRecord(); short colorIndex = Convert.ToInt16(pLyrCol.Value); // Assign the layer the ACI color acadLyrTblRec.Color = Color.FromColorIndex(ColorMethod.ByAci, colorIndex); acadLyrTblRec.Name = sLayerName; // Upgrade the Layer table for write acadLyrTbl.UpgradeOpen(); // Append the new layer to the Layer table and the transaction acadLyrTbl.Add(acadLyrTblRec); acadTrans.AddNewlyCreatedDBObject(acadLyrTblRec, true); } } // Save the changes and dispose of the transaction acadTrans.Commit(); } }
2.Is it possible to bind the dll with my window application (because we are not providing any setup file to users)
Thank you,
vallimanalan.T
I don't clearly understand your request.
The process to get or create an AutoCAD instance and call custom .NET commands is described in this topic.
To load a DLL just use SendCommand with a string figuring the (command "NETLOAD" ...) LISP expression as shown in the upper link.
acadDoc.SendCommand("(command \"NETLOAD\" \"C:\\NET commands\\CreateLayer.dll\") ");
To call a command with inputs, just build a string with the command name and the inputs as you'd do at AutoCAD Command line and pass it to SendCommand (example to call your command to create a layer named "Layer_1" with color index = 7):
acadDoc.SendCommand("CreateLayer_DWG Layer_1 7 ");
And please, create a new post. You'd get more help.
@tvmanalan wrote:
Hi,
Thanks. If we use dynamic type then its a dependency of particular version like "AutoCAD.Application.18".So we cant achieve multiple version type.
Am right?
thanks,
vallimanalan.T
The only requirement for using the dynamic type is the Framework 4.0 or upper is installed.
You can use "AutoCAD.Application" as progId to launch the last opened version of AutoCAD.
acadApp = Activator.CreateInstance(System.Type.GetTypeFromProgID("AutoCAD.Application"));
Hi,
only i have confusion on how i need to write the below commands in LateBinderHelp with parameters
acadDoc.SendCommand("CreateLayer_DWG Layer_1 7 ")
In the above example, Layer & color index are parameters
acadDoc.SendCommand("(command \"NETLOAD\" \"C:\\NET commands\\CreateLayer.dll\") ");
similarly i have more commands in "CommandMethod".could you please correct me.
Thanks
Basically I want to access the autocad command line through your latebindhelper.
acDoc.SendStringToExecute("._circle 2,2,0 4 ", true, false, false); acDoc.SendStringToExecute("._zoom _all ", true, false, false);
if you can send the equivalent code for the above two line it will be very useful for me.
thanks a lot.
@tvmanalan wrote:
Hi,
only i have confusion on how i need to write the below commands in LateBinderHelp with parameters
acadDoc.SendCommand("CreateLayer_DWG Layer_1 7 ")
In the above example, Layer & color index are parameters
You're confusing method parameters and command inputs.
There's only one parameter for the SendCommand method which is a string containing the command inputs.
Using the dynamic type (you can safely use this for AutoCAD 2012 and later versions):
try { dynamic acadApp; try { acadApp = LateBindingHelper.CreateInstance("AutoCAD.Application"); } catch (Exception) { Console.WriteLine("Unable to launch AutoCAD"); return; } WaitForAcadIsQuiescent(acadApp); acadApp.Visible = true; WaitForAcadIsQuiescent(acadApp); dynamic acadDoc = acadApp.ActiveDocument; acadDoc.SendCommand(@"(command ""NETLOAD"" ""C:\\Temp\\AcadStandaloneLateBinding\\CreateLayerSample.dll"") "); acadDoc.SendCommand("CreateLayer_DWG Layer_1\n7 "); } catch (System.Exception e) { Console.WriteLine(e.Message); }
Using late binding (only required if the .NET Framework is lower than 4.0):
try { object acadApp; try { acadApp = LateBindingHelper.CreateInstance("AutoCAD.Application"); } catch (Exception) { Console.WriteLine("Unable to launch AutoCAD"); return; } WaitForAcadIsQuiescent(acadApp); acadApp.Set("Visible", true); WaitForAcadIsQuiescent(acadApp); object acadDoc = acadApp.Get("ActiveDocument"); acadDoc.Invoke("SendCommand", @"(command ""NETLOAD"" ""C:\\NET Commands\\AcadStandaloneLateBinding\\CreateLayerSample.dll"") "); acadDoc.Invoke("SendCommand", "CreateLayer_DWG Layer_1\n7 "); } catch(System.Exception e) { Console.WriteLine(e.Message); }
@tvmanalan wrote:
Basically I want to access the autocad command line through your latebindhelper.
acDoc.SendStringToExecute("._circle 2,2,0 4 ", true, false, false); acDoc.SendStringToExecute("._zoom _all ", true, false, false);
if you can send the equivalent code for the above two line it will be very useful for me.
thanks a lot.
Using dynamic:
acadDoc.SendCommand("._circle 2,2,0 4 "); acadDoc.SendCommand("._zoom _all ");
Using late binding helper
acadDoc.Invoke("SendCommand", "._circle 2,2,0 4 "); acadDoc.Invoke("SendCommand", "._zoom _all ");