I have been trying to use .NET remoting to pass a Serializable data object from an Excel Add-in to an AutoCAD command. This has been working decently well with my Excel Add-in exposing a MarshalByRefObject, then calling NetLoad and SendCommand to run my AutoCAD command and receive the serialized data at the other end.
What I have found is that when my AutoCAD command is running in a document context, I can use Activator.GetObject to get the exposed object, call methods on it, and get the required data out of it. When my command is running in the application context (CommandFlags.Session), however, I can get the exposed object, but when I try to call one of its methods, I get a SocketException where it says that "No connection could be made because the target machine actively refused it 127.0.0.1:8000". I have confirmed that the sever is still running and available, it is just being blocked. Normally I would expect this kind of error to be caused by a firewall or similar security setting, but there is no doubt that in this case, the error is exactly correlated to what context my command is running in.
The reason that I want to use application context is because my command should create a new drawing from a template file and then perform actions on that new drawing. If I try to run DocumentCollectionExtension.Add(Core.Application.DocumentManager, DrawingSpecification.AutoCADTemplatePath) when this is in document context, I get a filer error but it works correctly in application context.
In short, the two requirements are conflicting. I need to either connect to the remote socket when in application context or open a document when in document context.
I have tried to use ExecuteInApplicationContext without the session attribute to run a document create function, but that will not open the document now, in the current command. It queues the request and opens it after my command has completed. I am not sure I would try to do the remote socket call from a document context when CommandFlags.Session.
Main Command:
<CommandMethod("AutoDraw", CommandFlags.Session)>
Public Sub CreateDrawingsFromRemoteData()
Using dataChannel = New AutoDrawDataChannel
Try
For Each dwgSpec In dataChannel.GetDrawingSpecifications
Dim drawing = New GeneratedDrawing(dwgSpec) 'this constructor tries to create a new drawing from template using DocumentCollectionExtension.Add
'TODO: read drawing.Errors
dataChannel.RecordDrawingResult(Result.CreateSuccess)
Next
Catch ex As System.Exception
dataChannel.RecordDrawingResult(Result.CreateFailure(ex))
End Try
End Using
End Sub
Wrapper around channel and remote object
Friend Class AutoDrawDataChannel
Implements IDisposable
Private _ServerObject As IAutoDrawDataProvider
Private _Channel As TcpChannel
Public Sub New()
Dim IsRegistered As Boolean
Try
Dim clientProv = New BinaryClientFormatterSinkProvider
Dim serverProv = New BinaryServerFormatterSinkProvider
serverProv.TypeFilterLevel = TypeFilterLevel.Full
Dim props As IDictionary = New Hashtable
props("port") = 0
'props("name") = privServiceName
_Channel = New TcpChannel(props, clientProv, serverProv)
ChannelServices.RegisterChannel(_Channel, True)
IsRegistered = True
_ServerObject = DirectCast(Activator.GetObject(GetType(IAutoDrawDataProvider), "tcp://localhost:8000/Autodraw"), IAutoDrawDataProvider)
Catch ex As Exception
If IsRegistered Then ChannelServices.UnregisterChannel(_Channel)
Throw New RemotingException("Cannot connect to AutoDraw.Excel from AutoDraw.AutoCAD.", ex)
End Try
If _ServerObject Is Nothing Then
ChannelServices.UnregisterChannel(_Channel)
Throw New RemotingException("Cannot connect to AutoDraw.Excel from AutoDraw.AutoCAD. Object returned is null.")
End If
End Sub
Public Iterator Function GetDrawingSpecifications() As IEnumerable(Of DrawingSpecification)
For Each dwgSpec In _ServerObject.GetDrawingSpecifications ' crashes on this method call when CommandFlags.Session
Yield dwgSpec
Next
End Function
Private disposedValue As Boolean ' To detect redundant calls
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
ChannelServices.UnregisterChannel(_Channel)
End If
Me.disposedValue = True
End Sub
Protected Overrides Sub Finalize()
Dispose(False)
MyBase.Finalize()
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
End Class