Tuesday, November 23, 2004

Serial Ports

Serial Ports are a common tool we use to communicate with devices at work. Usually, if a device has a serial interface, you can breath a sigh of relief that you are going to have success with communicating with it. There are still some things to watch out for though. Getting the baud rate, parity, data bits, and stop bits configured properly is critical. I helped a colleague at work today with just such a problem. He was trying to receive data in his .NET client from a bar code scanner that we had configured in the Descarte OmniServer OPC server. He would get the initial data change event when the OPC item was created, but nothing after that. I wrote the prototype he was basing it on, and I knew that it worked, as I had used the same barcode scanner in testing it. So we started poking around. First, we brought up HyperTerm and looked to see if we were getting any data. Sure enough, data was coming across. Then we went back to the OmniServer configuration to be sure that our topic was configured to use COM1, and that the device was using the right protocol, in this case an Intermek protocol. It was, but still, no data. So then I got to thinking, "I wonder if HyperTerm is using a different configuration". That was it! In HyperTerm, we were setup for 9600 baud 7-e-1 configuration. In OmniServer, we were looking at 9600 baud 8-n-1. Big difference! The barcode scanner was sending 7 data bits, but our OPC server was looking for 8 data bits. That's why it never sent a data change event. We got it reconfigured and it worked beautifully from there.

Sunday, November 7, 2004

It Works!

I've won my fight with .NET Remoting! Thank goodness for Ingo Rammer and Google, as those two resources provided the answers to all of the "gotchas" that bit me on this. My final solution involves a shared base class assembly, which defines the abstract base class for each of the objects exposed by my web service. Next, there is the server implementation, which is composed of a server activated class factory serving up the client activated remoted objects. And finally, there is the client piece, which first gets an instance of the class factory, and uses that reference to get instances of the client activated objects.


First, let's talk about the shared assembly. This is the easiest one. I needed to create, at a minimum, two abstract classes in my shared assembly. The first abstract class describes my class factory.

public abstract class ClassFactoryBase : MarshalByRefObject
{
   public abstract GetCAO();
}

So far so good. Now comes the first gotcha I experienced. My client activated object is exposing the functionality of an existing COM object on the server. So my first idea was to simply implement interface exposed by this COM object. This was a bad idea, and caused remoting to barf big time. So instead, my base class for the client activated implements the interface, but doesn't put it in the declaration.

public abstract class CAOBase : MarshalByRefObject
{
   public abstract bool COMObjectMethod();
}

All of this code went into a file I named Shared.cs, and I compiled it to an assembly, Shared.dll.
Next, I needed to implement the factory and the client activated object.

public class ClassFactory : ClassFactoryBase
{
   // Generic constructor required for Remoting
   public ClassFactory()
   {
   }

   public override GetCAO()
   {
      return new CAO();
   }
}

public class CAO : CAOBase
{
   internal Interop.ComObject comroot;

   // Generic constructor required for Remoting
   public CAO()
   {
      comroot = new Interop.ComObject();
   }

   public override COMObjectMethod()
   {
      return comroot.COMObjectMethod()
   }
}

Here, the ClassFactory creates a new instance of the CAO object on demand and passes it back. The CAO object creates an instance of the COM object to be exposed. The methods of the CAO object then pass through the COM object layer. These classes I put in a file called server.cs and compiled to an assembly named Server.dll

I wanted to use IIS as the host for my remote classes, so I needed to do a couple of things to enable this. First, I created a virtual directory in the IIS admin. In that virtual directory, I created a bin directory. I copied my Shared.dll and Server.dll into the bin directory, along with the interop.ComObject.dll. Finally, I created a web.config file and placed this in the root of the virtual directory. The web.config looked like this:

<configuration>
   <system.runtime.remoting>
      <application>
         <service>
            <wellknown
               mode="SingleCall"
               type="ClassFactory,Server"
               objectUri="ClassFactoryURI.soap" />
            </service>
         </application>
      </system.runtime.remoting>
   <system.web>
</configuration>

This was another stumbling block for me. Originally, I had excluded the ".soap" extension from my objectURI. I had read something that indicated that it was unnecessary when hosting your object in IIS. This was dead wrong. Unless your object URI ends with ".soap" or ".rem", IIS will not pass the method calls on to the remoted object. This took me a while to figure out, so don't make the same mistake. Fortunately, everything else was cake from this point, as IIS takes care of all of the nastiness of load balancing, connection pooling, and security for connecting to your remote object.

Finally, it's time to implement the client. Here is my quick and dirty client code:

public class Client
{
   public static void Main( string[] args )
   {
      ClassFactoryBase factory =
         (ClassFactoryBase)Activator.GetObject(
            typeof(ClassFactoryBase),
            "http://remotinghostserver/VirtualDirectory/ClassFactoryURI.soap" );
      CAOBase cao = factory.GetCAO();
      if ( cao.COMObjectMethod() )
      {
         MessageBox.Show( "Success!" )
      }
   }
}

This code goes in Client.cs, compiles to Client.exe, and is deployed with only the Client.exe and Shared.dll. I didn't even need a config file! This is because I'm using the Activator.GetObject method to create an instance of my object. Why am I doing this? Well, another option would be to use soapsuds.exe to generate the metadata for my remoted object and reference this when compiling my client. When done this way, the client can simply use the new keyword when appropriate remoting configuration information is in the app.config file. Very convenient for the code. Unfortunately, soapsuds.exe is broken. For some reason, when you host your remoting classes in IIS, soapsuds makes mistakes when attempting to generate the metadata. The result is that you will successfully expose your class factory, but will get a type case exception when attempting to get an instance of your CAO. This is seriously bad news for the solution I needed to make. Another option would be to use share interfaces (as opposed to share base abstract classes). When using share interfaces, you can again configure the app.config file to allow for the new keyword to be used. However, there is another gotcha here. When using shared interfaces, the CAOs instances can not be passed as arguments to other methods on other remote objects. For our solution, several of the CAOs need to interact with each other. Using abstract base classes allows us to pass these references as arguments to our other remote objects. The only drawback here is that we are forced to use the Activator.GetObject() call to instantiate our remote object rather than the new keyword. It's a small price to pay I think.


So what do I do from here? The next thing I need to do is verify that the object lifetime is being managed properly. I don't want to strand a bunch of instances of my remote objects on the server. So my next effort will be to investigate CAO object lifetime leases. Once I get there and interesting information to pass on, I'll be sure to post it here.

Thursday, November 4, 2004

My Continuing Fight with .NET Remoting

So I went for the sleep option around 3:15am. I should have picked option two (coffee) because my brain was still way too active to let me fall asleep. I kept thinking about different options to try. In any case, I'm a couple of steps close to getting my solution to work. For one of our systems, we have a set of COM objects that are hosted in COM+. These objects are exported through COM+ / DCOM to several clients across a LAN / WAN environment. The clients create an instance of a broker object exposed through COM+, and then use that broker to create instances of other server business objects (SBOs). Each of these instances is intended to hold state (they connect to a variety of ERP and database systems, so opening and closing them often has a high transaction cost). Our objective with this project is two-fold. First, the client that activates these distributed components is Wonderware's InTouch 7.x. The license costs for our customer are getting a little high, so they are looking to replace the InTouch client with a .NET client written in C#. I had originally considered using XML Web Services hosted in IIS to wrap the functionality of the SBOs. This was before I learned that the SBOs held state. XML Web Services is a stateless architecture, so it wouldn't due for accomplishing our task. So that is why I chose to go the .NET Remoting route. I'm still going to use IIS to host the remoting component, as it will save me time by acting as the hosting control, provide security, and also take care of channel and load management.


So after banging my head on the wall last night, I got this far: I have a Client Activated Object (CAO) that is hosted by IIS. I've written the web.config file to expose the CAO. I used "soapsuds.exe -nowp -ia:MyRemoteCode -oa:MyRemoteCode_Proxy.dll" to generate a meta-data proxy for the remote object. I created a client that registers the remote object, and then creates an instance of it. So far so good. Here is where the trouble starts though. I create a second remoteable class that also inherits from MarshalByRefObject. This one is instantiated by making a method call on the first remoted object. However, when the client calls "firstRemoteObject.GetSecondRemoteObject()" I get a type mismatch error on the return type. Evidently, the meta-data generated by soapsuds.exe doesn't exactly match the typing information passed back by the remote call. So this is where I am stuck tonight. I spent the day working on another project, so I haven't been able to touch it yet. If I can stay concious long enough I may try again tonight. If I get it solved, I'll post sample code.

CAOs hosted by IIS that implement COM Interop Interfaces

Wow, did I have an intrigueing assignment today. I have a client that is going to be developed in C# (Microsoft .NET technology). This client is going to remotely access another .NET assembly on a server machine, across the network. The server side assembly is going to be client activated (CAO), and the CAO needs to expose the functionality of a COM component through interop. I thought I could just create a class that inherits from MarshalByRefObject and implements the interface of the interop component.... but NOOOOOO! Couldn't be that simple could it? So instead, I'm sitting here at 2am still banging out the intricacies of creating and passing references around across application domains. So far, I've learned that implenting the interface for an interop component is seriously bad news. Forget about remoting your object after you've done that. Next, don't every install the .NET Framework 2.0 beta on your primary development box. It did me the courtesy of modifying all of my IIS virtual directories to automatically select the 2.0 revision rather than the stable 1.1 revision. So now I need to manually select 1.1 for all existing and newly created virtual directories. Thanks .NET 2.0. So now my stickler is trying to figure out how to get two remoted objects that are hosted in the same application domain to interact. Not a simple task so far. More on this wonderful story once I get a cup of coffee... or sleep.