We’ve had a few reports from people implementing external .NET applications to drive AutoCAD – as shown in this previous post – experiencing intermittent failures once AutoCAD 2010 Update 1 has been applied. Here’s a typical error message:
It contains the text “Problem executing component: Call was rejected by callee. (Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED))” (to help people Googling this error message :-).
This “problem” was introduced as we addressed an issue with the way our WPF components in AutoCAD handle inbound messages, largely due to Microsoft’s decision not to support nested message loops in WPF. If WPF is in the middle of performing some kind of layout processing operation (which leads to Dispatcher.DisableProcessing() being called) and there’s an incoming COM call, then we were previously respecting it which could lead to a crash. Now we do the right thing and reject the COM call: effectively asking the caller to try again later.
The problem is that – while the VB6 runtime was very good at automatically retrying calls such as CreateObject() – WinForm applications are not. We need to implement an additional interface from our WinForm application to make sure it can handle failure (I could probably do with adding that one myself ;-).
As it’s hopefully clear: while the problem is likely to be more visible in AutoCAD 2010 once Update 1 has been applied, this is ultimately about correctly inappropriate expectations on the side of the calling application and teaching it to do the right thing.
Here’s the updated C# code from the previous post, with the changed lines in red, and here’s the source project.
1 using Autodesk.AutoCAD.Interop;
2 using System.Windows.Forms;
3 using System.Runtime.InteropServices;
4 using System.Reflection;
5 using System;
6 using LoadableComponent;
7
8 // For more information on IMessageFilter:
9 // http://msdn.microsoft.com/en-us/library/ms693740(VS.85).aspx
10
11 namespace DrivingAutoCAD
12 {
13 [ComImport,
14 InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
15 Guid("00000016-0000-0000-C000-000000000046")]
16 public interface IMessageFilter
17 {
18 [PreserveSig]
19 int HandleInComingCall(
20 int dwCallType, IntPtr hTaskCaller,
21 int dwTickCount, IntPtr lpInterfaceInfo
22 );
23 [PreserveSig]
24 int RetryRejectedCall(
25 IntPtr hTaskCallee, int dwTickCount, int dwRejectType
26 );
27 [PreserveSig]
28 int MessagePending(
29 IntPtr hTaskCallee, int dwTickCount, int dwPendingType
30 );
31 }
32
33 public partial class Form1 : Form, IMessageFilter
34 {
35 [DllImport("ole32.dll")]
36 static extern int CoRegisterMessageFilter(
37 IMessageFilter lpMessageFilter,
38 out IMessageFilter lplpMessageFilter
39 );
40
41 public Form1()
42 {
43 InitializeComponent();
44 IMessageFilter oldFilter;
45 CoRegisterMessageFilter(this, out oldFilter);
46 }
47
48 private void button1_Click(object sender, EventArgs e)
49 {
50 const string progID = "AutoCAD.Application.18";
51
52 AcadApplication acApp = null;
53 try
54 {
55 acApp =
56 (AcadApplication)Marshal.GetActiveObject(progID);
57 }
58 catch
59 {
60 try
61 {
62 Type acType =
63 Type.GetTypeFromProgID(progID);
64 acApp =
65 (AcadApplication)Activator.CreateInstance(
66 acType,
67 true
68 );
69 }
70 catch
71 {
72 MessageBox.Show(
73 "Cannot create object of type \"" +
74 progID + "\""
75 );
76 }
77 }
78 if (acApp != null)
79 {
80 try
81 {
82 // By the time this is reached AutoCAD is fully
83 // functional and can be interacted with through code
84
85 acApp.Visible = true;
86
87 INumberAddition app =
88 (INumberAddition)acApp.GetInterfaceObject(
89 "LoadableComponent.Commands"
90 );
91
92 // Now let's call our method
93
94 string res = app.AddNumbers(5, 6.3);
95
96 acApp.ZoomAll();
97
98 MessageBox.Show(
99 this,
100 "AddNumbers returned: " + res
101 );
102 }
103 catch (Exception ex)
104 {
105 MessageBox.Show(
106 this,
107 "Problem executing component: " +
108 ex.Message
109 );
110 }
111 }
112 }
113 #region IMessageFilter Members
114
115 int IMessageFilter.HandleInComingCall(
116 int dwCallType, IntPtr hTaskCaller,
117 int dwTickCount, IntPtr lpInterfaceInfo
118 )
119 {
120 return 0; // SERVERCALL_ISHANDLED
121 }
122
123 int IMessageFilter.RetryRejectedCall(
124 IntPtr hTaskCallee, int dwTickCount, int dwRejectType
125 )
126 {
127 return 1000; // Retry in a second
128 }
129
130 int IMessageFilter.MessagePending(
131 IntPtr hTaskCallee, int dwTickCount, int dwPendingType
132 )
133 {
134 return 1; // PENDINGMSG_WAITNOPROCESS
135 }
136
137 #endregion
138 }
139 }
There’s nothing really to see, in terms of modified behaviour, other than that you should no longer experience the crash.