Tuesday, October 28, 2008

Inter-Process Communication (IPC) with Autodesk Map3d

Author: Jonio, Dennis

Prospective and Environment
Everything that follows was done on and with Windows XP SP2, Visual Studio 2005 Professional SP1, Oracle XE 10.2, Oracle ODP.NET 11g(2.111.6.20), Autodesk Map3d 2008 SP1, Autodesk ObjectARX 2008 and lots of reading and lots of research. Since I have not done it myself I cannot attest as to the viability of this solution in a vanilla AutoCAD 200x environment. I am however inclined to believe that it will work just fine. I make neither guarantees nor claims of fitness for any purpose. It is “as-is” so proceed at your own risk.

Overview
Simply stated, this was the problem, I wished to have a generic way in which to communicate with a DWG. All I wished to do was PUT an SDO_GEOMETRY and GET a selected entity back as an SDO_GEOMETRY. This seemed to me to be a not unreasonable request. Just think about this a second: AutoCAD as a COLUMN editor? I could build whatever user interface I wished, using whatever tools I wished. Include all the business logic needed and then just “plumb” this stand-alone application. I didn’t have to solve the world’s problems either. Points, lines and surfaces were all I was interested in. Well good luck! First off there was no .NET DWG to SDO_GEOMETRY translator that I could get my hands on. Second, this just is not how Autodesk has written their applications. Don’t misunderstand me here. This is not a flame against Autodesk. Their applications do what they were designed to do and do this very well. So this series is then about getting one of two major things accomplished: 1) A 2-dimensional, SDO_GEOMETRY translator that supported arcs and 2) A communications interface.

This is about the communications piece. In my mind this would be the most technically challenging and if I was going to fail it would be on this.

Of course the issue of multithreading arose numerous times but it always seemed, by its nature, to be somewhat limiting. Inter-process communication (IPC) was the answer but I found no ready made solution. I had to piece this together myself. I did a fair amount of investigating and found named pipes to be the most generic and robust approach to take.

This is not really a story about pipes but you must look at this article: “Inter-Process Communication in .NET Using Named Pipes” by Ivan Latunov.
link to article The author explains it all much better than I ever could and his implementation is the backbone of this implementation. By the way, I believe that Microsoft has now included Named Pipe support in the 3.5 .NET framework. Since MS pipes are a Client/Server architecture and I was totally intimidated by “overlapping” ,“bidirectional” and the other more complex implementations I decided to really simplify and incorporate, in each executable or dll, a pipe Server and a pipe Client. Thank you Ivan. The significant change that I incorporated was the use of Events on the Server side( the “owner” side if you will ). When you compare Ivan’s code to mine you will see some other minor changes. A simple piccy to illustrate the idea.


Since experience has taught me that the Autodesk API’s are not trivial especially when it comes to the MDI interface structure I really needed to isolate this application as much as possible. The solution was a simple data exchange mechanism, a data “bridge”, if you will. Let the bridge become the generic between the other executables! My NETLOADed module and the “bridge” will communicate with simple Events and Queue objects! The Autodesk API’s understands Forms, Forms are, by nature, multi-threaded! The bridge will then communicate to whatever is at the other end of the pipe. BINGO! We have it all!

Below is a clip where these ideas are put together. Very simple and straightforward but it should give you some "food for thought":

Full-size video clip is available for download at:


The Communications Data Definition

OK… so now we have settled on the communications “plumbing”, a full blown IO channel no less, what is it that we wish to communicate?
Well in reading Ivan’s work and the work of some others I became aware that pipes can move megabytes of data in sub-second times. Thusly then, the amount was not the issue the format was. Whatever that format was it had to be serializable and it had to be generic. I had to accomplish two(2) things: 1) construct a request that I wished my NETLOADed module to perform 2) provide for data interchange. The solution here is a plain’ol .NET dataset. In fact, all I really had to do was encapsulate a dataset inside a field in a “control” dataset. I could then pass one(1) serializabe object that had both a request/task and corresponding data. In my mind at least, very clean, very straightforward, and very generic.

The following is the structure I decided upon for my environment:



To support the manipulation of this structure there is one(1) class. All of the serialization/de-seriaization and encapsulations are done with this class. Below are the “putter” and “getter” methods for the dataset that is encapsulated within the “PAYLOAD” field. In addition there are numerous other helper methods within the class.
”putter”

“getter”

No, I did not implement any compression of the datasets, just the BinaryFormatter.

The Named Pipes
Ivan’s implementation is a work of art. I really hated to touch it. (Not only because it was well put together but I really did not fully comprehend his threading architecture). However, I really needed event hooks because this wasn’t going to be a “classic” Client/Server application. The applications that were going to be incorporating this were going to be operating in and supporting the user interface world. Since Ivan had architected this with interfaces I went about some touch up. (four lines of code with some big ideas ...)
Source code (C#):

Since I had decided to have both a Client and a Server incorporated within each app I made it easier on myself and just constructed an “A” and “B” dispatcher (I wanted to get rid of this Client/Server label). One would be owned by the “bridge” the other would be owned by the “other” application.
They are really nothing more than containers for Ivan’s public static IChannelManager PipeManager; I just added a few constants to make life easier and to set the “ReadOn” pipe and the “SendOn” pipe. I really didn’t want any confusion later on.

PipeManager’s most significant modifications had to do with the Events hooks:
Source code (C#):

Now, I just pass it on up the chain to “whatever” has hooked up.
Source code (C#):

Finally public sealed class ServerNamedPipe had to be tweaked. In this implementation I always send an acknowledgment string after a receipt. It is client/server after all. So it is a receive binary / send a string.
Source code (C#):

Believe me named pipes and threads are NOT my specialty. I just had to know and do just enough to make it work for me. Yes, I am sure there are different and probably better ways. But I am a pragmatist and was not doing this for fun.

So we now have our pipes and we have an A and a B configuration. It matters not who is who but obviously partners cannot have the same configuration. As a final note these configurations are coded to operate on one workstation. You can of course run across different machines but that is a whole different topic.

The Data Bridge
This is the Form that will be instantiated from our NETLOADed module. As I mentioned before I wanted this to be as simple and generic as possible. Just a couple public Queue objects and a few events. I did add some UI Controls to play with during debug and never took them out.

How it works:
  1. Receive an entry on “my” pipe
  2. EnQueue the entry into the Suspense Queue
  3. Fire off an OnReceivedQueueEntry event
OR
  1. Receive an OnCompletedTask event
  2. DeQueue the entry from the Process queue
  3. Send it on to the other “Server’
If there is a simpler approach I could not think of it. Since a picture is worth so many words…



And the entire source sans the UI controls for the bridge:
The last piece of the puzzle is this interface object for the DataBridge so it knows that there was something to be done.
And a skeleton of a NETLOADable dll:
Finally … we receive our task and do it

Thursday, October 23, 2008

Harvesting Autodesk DWG Entities

Author: Jonio, Dennis

This kicks off a series of articles on building SDO_GEOMETRY types from DWG entities. By series end you should have at least one working NETLOADable module for a template. I do not pretend that this is THE solution. Since this was completed, I have, of course, realized better ways to do some things and I am sure you will be able to find more. I strongly recommend that you look over the Oracle OTN forums in particular .NET and Spatial. The heart and soul of these UDT’s came from there. Another indispensable forum is the Autodesk Discussion Groups. Their .NET forum is also indispensable.

Oracle SDO_GEOMETRY and a few others as User Defined Types (UDT’s)

Included below are a couple projects NetSdoGeometry and NetSdoDimArray. These are the fundamental classes and I have posted these in the past. However since that time I have added an AsText property and the ToString() method. It became necessary to actually visualize the objects during development. I must also point out that SDO_GEOMETRY is now marked as [Serializable]. You will see later on that this allows for the inclusion of this type into a native .NET serializable dataset. I have included a fair number of non-essential members and methods. They are for convenience so the purist may wish to strip them.

Triplets and Triplet Management

In order to make any sense of this article you will have to have a copy of Oracle’s SDO_GEOMETRY specification and my implementation of the class NetSdoGeometry.sdogeometry.

Let me clarify, in general terms, what this implementation does and does not do in regards the specification

  1. Is Limited to two(2) dimensional geometries. Points, lines, surfaces(polygons)
  2. Does not include “compound-type” geometries
  3. Does include support for curves/arcs

After reviewing the spec you will realize it is all about “triplets”. The SDO_DIM_ELEM_ARRAY is of course a 1-> n series of 3 element integers groups. OFFSET - ETYPE -INTERPRETATION as the named components.

The manipulation of the ordinates array I found to be rather trivial both in terms of composition and decomposition. It is nothing more than that a left to right series of numbers.

“Triple Management” becomes the focus. Since I was learning the spec and writing code to it as I went along, the first class I created was, of course, “Triplet”. This class helped me visualize and understand the key elements of the specification. It is nothing more than codifying the obvious. The only oddity is my use of a GUID to track external or internal rings. The idea here will be explained later. In most cases a triplet acts as a kind’a sort’a header. A header that explains the ordinate array that is part of the SDO_GEOMETRY class. It would be just that simple if all one wished to do was work with linestrings. It is the support for curves/arcs that really adds a level of complexity by using a series of triplets to denote the transitions from line to arc, arc to line, etc.

Class “Pts” is a simple container for holding the ordinates. Of note here is the BulgeValue member. This acts as the placeholder for the arc information gotten from, in my case Map3d.

Finally class “TripletMgr” or Triplet Manager if you will. Before I discuss this class I will give you a chance to see how it is used. I do not use it for points and simple lines. Really unnecessary in those cases. This code decomposes as MPolygon into an sdogeometry. Frankly if you understand this one the rest is easy.

Here is the basic idea:


The method SetPts is the workhorse. Once again it is the management of the triplets. The concept of “ownership” can from Pro Oracle Spatial for Oracle Database 11g, by Kothuri, Godfrind, Beinat. This really helped me put some of the pieces together. I recommend it highly.

Source code (C#) available for download at: