Saturday, September 15, 2007

A simple GreatestCommonDivisor service in C# - Part 1

I was finally able to get the GCD service up and running. Essentially it does nothing different than my previously posted GCD service, which is implemented in VPL. So to recall, this service computes the greatest common divisor of two positive integer numbers (inputx, inputy) and outputs the result as greatestcommondivisor.

Based on this description of what the service does, the remainder of this post will guide you through the process of how I built such a rather simple DSS service using the MRS object model and C# as implementation language (as mentioned you could also choose VB.NET, C++).

This kind of tutorial is divided into distinct steps beginning at creating a new service project to the final result being the service we want.


  1. Create new DSS Service
    The first thing one has to do in order to set up a basic GreatestCommonDivisor service is to create a new DSS service.We will do so by creating a new Visual Studio 2005 Project. More specifically it is a "Simple DSS Service (1.5)" project (it is assumed that you have MRS 1.5 installed). Select the mentioned project type and name it as you please (e.g., "GCDService") (see here for more detail). This will create a file for the service (e.g., GCDService.cs) a manifest file (e.g., *.manifest.xml) and a file for the types used by the service (e.g., GCDServiceTypes.cs).

  2. Define what is the objective of the service
    The service should do nothing special, just compute the greatest common divisor of two non-negative integer numbers m and n.This can be easily accomplished via euclids algorithm. In a previous post about python, I gave a recoursive implementation for this algorith, which is why I won't do it here again. So let us assume we have built a function, which implements euclids algorithm. The function's signature might look like this

    public static int ComputeGreatestCommonDivisor(int m, int n)
    {/*function's implementation not shown*/}


  3. Define the input message
    A DSS Service accepts input messages of some form coming from some input port, handles these messages and sends some output message as a response to its ouput port. For example, to get information about the state of a DSS service, this service has to handle a Get request, i.e. a "Get" message sent to the input port of this service. The DSS service receives the "Get" message does something and might respond to this message by sending "Hello World" as answer to its output port.
    Similarly, our "GreatestCommonDivisor" service might get a request to compute the greatest common divisor (GCD) of two non-negative integer numbers n and m. Therefore, such a request might incorporate the n, and m sent to our service and the expected response might be the GCD of m and n. Consequently, we have to define such a request or how such a GCD message must look like to be accepted as GCD message by our service and to be handled appropriately. The following lines of code, which we add to the types source code file (e.g., GCDServiceTypes.cs), define such a GCD request

    [DataContract(Name="ComputeGreatestCommonDivisor")]
    public class ComputeGCDRequest
    {
    private int _inputX;

    [DataMember(IsRequired = true, Name = "InputX")]
    public int InputX
    {
    get { return (this._inputX); }
    set { this._inputX = value; }
    }

    private int _inputY;

    [DataMember(IsRequired = true, Name = "InputY")]
    public int InputY
    {
    get { return (this._inputY); }
    set { this._inputY = value; }
    }

    public ComputeGCDRequest()
    {
    this.InputX = 1;
    this.InputY = 1;
    }
    public ComputeGCDRequest(int x, int y)
    {
    this._inputX = x;
    this._inputY = y;
    }
    }

    The ComputeGCDRequest defines how a request to our service has to look like. It defines m and n (InputX, InputY). Each request has to be marked with the DataContractAttribute attribute, so DSS can wire things up for us. Fields we want to incorporate in our request have to be marked with DataMemberAttribute attribute (those fields are part of the message serialization process when the ComputeGCDRequest message is serialized over some channel). These two attributes take some optional arguments such as "Name" and "IsRequired".
  4. Define a message handler for the input message
    The sheer presence of such a GCD request message type won't bring us far. We have to do define a GCD request (I), and add this GCD request to list of requests understood by our service (II). Afterwards we have to set up a mechanism that whenever a GCD request is issued to our service, takes the message, which abstracts this request and acts accordingly (III). So defining a GCD request serive, involves defining what the incoming GCD request message body looks like and defining what the result of processing a GCD request is, may it be success or failure. The following lines of source code, added to the GCDServiceTypes.cs, define a ComputeGCD request type ...

    public class ComputeGCD :
    Query<ComputeGCDRequest, PortSet<GCDState, Fault>>{

    public ComputeGCD(int m, int n) :
    base(new ComputeGCDRequest(m, n))
    {
    this.Body.InputX = m;
    this.Body.InputY = n;
    }

    public ComputeGCD() :
    base()
    { }

    }

    The ComputeGCD request type, subclasses the Query<> type, and defines the incoming message to be our previously declared ComputeGCDRequest message and the result will be a GCDState on success and the pre-defined Fault on failure. A Portset is a collection of ports (a set of ports) and a port is just an abstraction that allows services to communicate with each other. So to signal that our GCDService actually understands the ComputeGCD type, we have to add this type to the GCDService service's operations port set, which is defined in the GCDServiceTypes.cs file.

    [ServicePort()]
    public class GCDServiceOperations :
    PortSet<DsspDefaultLookup, DsspDefaultDrop, Get, ComputeGCD>
    {}

    Now that our GCDService declares being able to understand GCD requests, we have to make him do so. More specifically we have to set up a message handler that jumps in whenever a GCDRequest is sent to our GCDService. The following lines of code, added to the GCDService.cs will be in charge of this.

    [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
    public virtual IEnumerator ComputeGCDHandler(ComputeGCD computeGCD)
    {
    int M = computeGCD.Body.InputX;
    int N = computeGCD.Body.InputY;

    int gcd = ComputeGreatestCommonDivisor(M, N);

    this._state.GCD = gcd;

    LogInfo("=========\r\nGcdTest\r\n=========");
    LogInfo(string.Format("M = {0}\r\nN = {1}\r\nGCD= {2}",
    computeGCD.Body.InputX,
    computeGCD.Body.InputY,
    this._state.GCD));

    computeGCD.ResponsePort.Post(new GCDState(gcd));

    yield break;
    }

    The ComputeGCDHandler handler takes the ComputeGCD request as input and unwraps the necessary InputX and InputY fields from message's body. It then uses these two numbers as input to our ComputeGreatestCommonDivisor method, which computes the greatest common divisor and stores it in the gcd parameter. This gcd parameter is stored into the state of the service, which is of type GCDState. This GCDState encapsulates the state of the service, which is the greatest common divisor computed by the service. Actually the state is not really necessary, because we don't do much with it. At last, the message handler outputs the result of the operation to the output port for success, which was of type GCDState. The LogInfo calls are merely for debugging. Since this service alters its internal state, we have to make sure that this happens in an exclusive fashion, i.e. while updating the state no access to the state is granted. This is what the

    [ServiceHandler(ServiceHandlerBehavior.Exclusive)]

    Attribute declaration is for. It ensures that no race condition can occur by exclusively granting access to the internals our service.
  5. Define the GCDState type
    What is left, is to define what the state of our GCDService looks like. As mentioned, our service doesn't need to have a state but I have to decided to give it one (mainly for learning purposes). The GCDState type is define in the GCDServiceTypes.cd and can be modified like this

    [DataContract()]
    public class GCDState
    {
    private int _GCD;

    [DataMember(IsRequired=true, Name = "GreatestCommonDivisor")]
    public int GCD
    {
    get { return (this._GCD); }
    set { this._GCD = value; }
    }

    public GCDState()
    {
    this._GCD = 1;
    }

    public GCDState(int gcd)
    {
    this._GCD = gcd;
    }

    }

  6. Let's test
    Now that we have set things up. We are ready to build the service and test it. Therefore compile everything. Open the MRS Visual Programming Language to build a new test service using our GCDService. I have done that and the test service can be seen here (my GCDService is named "GreatestCommonDivisor").


I hope you enjoyed building this service as much as I did and I hope to have something ready for you asap.

Christian

No comments: