Wednesday, February 3, 2010

Autodesk Map3d 2010 with WCF (III)

Author: Jonio, Dennis

The snippets below are an example for a client. It did take some time to research the parameters required for a long lived(8 hour) service. It turned out to be that two(2) parameters have to be set, InactivityTimeout and ReceiveTimeout. Needless to say these should match the service’s ServiceEndpoint configuration.

In general the way the service works is that you can send it data with any one(1) of three(3) different methods. I only employ one(1) of those methods in this case. If your application has “Subscribed” you will be sent via the callback method any and all output from the service. I really do not attempt to track the “state” of anything. As the book explains, and to say the least, state tracking is probably not the best way to expend your time and energy.

From Map3d's perspective the new, improved DataBridge is really a non issue. For all intent and purpose Map3d will see things the same way. I am sure that as time moves on the complexity of "if" and "case" statements will grow.
I plan on following this article with the complete DataBridge Version II.
Source code snippets for a client(C#):


public static IByteSrvcWCallback SrvcHost;
public static ByteSrvcCallbackImpl callbackImpl;
public static DuplexChannelFactory dcf;
public static Guid MyServicedName;
public static string MyPrefixForMap3dDQ = "MDADCLSSFYIO";
public static string AddressOfReliableSessionService = "net.tcp://localhost:9080/DataService";


public MainForm()
{
InitializeComponent();
NetTcpBinding ntb = new NetTcpBinding(SecurityMode.None, true);
ntb.ReliableSession.Enabled = true;
ntb.ReliableSession.Ordered = true;
ntb.ReliableSession.InactivityTimeout = new TimeSpan(0, 8, 0, 0);
ntb.ReceiveTimeout = new TimeSpan(0, 8, 0, 0);
callbackImpl = new ByteSrvcCallbackImpl();
InstanceContext ic = new InstanceContext(callbackImpl);
dcf = new DuplexChannelFactory(
ic,
ntb,
new EndpointAddress(AddressOfReliableSessionService));
SrvcHost = dcf.CreateChannel();
callbackImpl.OnGotControlPacket +=
new ByteSrvcCallbackImpl.GotControlPacket(WCF_RcvdSrvcControlPacket);
MyServicedName = Guid.Empty;
}

void WCF_RcvdSrvcControlPacket(byte[] ba)
{
byte[] x = callbackImpl.ControlPacket;
//
//Here is the callback
//Do something useful with the data that the Service has sent to you
//
}

public int WCF_SendToMap3dDataBridge(byte[] ba)
{
return SrvcHost.DQPipedIn(MyPrefixForMap3dDQ, ba);
}

private void btnSUBSCRIBE_Click(object sender, EventArgs e)
{
try
{
if (MyServicedName == Guid.Empty)
MyServicedName = SrvcHost.Subscribe();
}
catch (EndpointNotFoundException _epnfX)
{
AppendMessage("EndPoint ERROR: " + _epnfX.Message);
}
catch (System.Exception _eX)
{
AppendMessage("ERROR: " + _eX.Message);
}
}

private void btnUNSUBSCRIBE_Click(object sender, EventArgs e)
{
try
{
if (MyServicedName != Guid.Empty)
SrvcHost.UnSubscribe(MyServicedName);
MyServicedName = Guid.Empty;
}
catch (EndpointNotFoundException _epnfX)
{
AppendMessage("EndPoint ERROR: " + _epnfX.Message);
}
catch (System.Exception _eX)
{
AppendMessage("ERROR: " + _eX.Message);
}
}

Monday, February 1, 2010

Autodesk Map3d 2010 with WCF (II)

Author: Jonio, Dennis

I make absolutely no attempt here to explain why I have done the things the way I have done them in relationship to the grand scheme of WCF. There is just too much to explain and you really should get the book and improve on what I have done. I stopped a long time ago in trying to force Map3d to do multi-threading. As it turns out this is not debilitating at all, just use a Form.
The following are my concrete classes for the WCF interfaces found in the previous article. Making no value judgments, you do have to be impressed with how much Microsoft has done in producing the “behind the scenes” transport functionality. Since I wanted “required, reliable sessions with binary transport” I have coded towards the NetTcpBinding and the NetNamedPipeBinding.
As you can see there are two(2) methods for getting data to the service and the service has one(1) method to send data back to the client. During research I happened upon this multiple client subscription concept with a simple Guid identifier. I desperately wanted some kind of "session identifier" that was all ready in place, generic and robust. If you do the reading you will discover WCF has the concept of SessionId but in my mind's eye it is too crippled to be of any use for my purpose. This subscription paradigm struck me as a good, simple and straightforward idea.
The service itself.
Source code (C#):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace DplXByteSrvcs
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class ByteSrvcWCallbackImpl : IByteSrvcWCallback
{
public delegate void DataIsReady(byte[] hotData);
public DataIsReady DataReady = null;

public delegate void DQDataIsReady(string destinationQ, byte[] hotData);
public DQDataIsReady DQDataReady = null;

public delegate void GotControlPacket(byte[] hotPacket);
public GotControlPacket OnGotControlPacket = null;

public readonly Dictionary SrvcClients =
new Dictionary();

public void ControlPacket(byte[] ba)
{
int rtnval = 0;
if (ba != null)
rtnval = ba.Length;
if (OnGotControlPacket != null)
OnGotControlPacket(ba);

IByteSrvcCallback callback = OperationContext.Current.GetCallbackChannel();
if (!SrvcClients.ContainsValue(callback))
{
Guid clientId = Guid.NewGuid();
if (callback != null)
{
lock (SrvcClients)
{
SrvcClients.Add(clientId, callback);
}
}
}
}
public int PipedIn(byte[] data)
{
int rtnval = 0;
if (data != null)
rtnval = data.Length;

if (DataReady != null)
DataReady(data);

return rtnval;
}
public int DQPipedIn(string destinationQ, byte[] data)
{
int rtnval = 0;
if (data != null)
rtnval = data.Length;

if (DQDataReady != null)
DQDataReady(destinationQ, data);

return rtnval;
}
Guid IByteSrvcWCallback.Subscribe()
{
IByteSrvcCallback callback =
OperationContext.Current.GetCallbackChannel();
Guid clientId = Guid.NewGuid();
if (callback != null)
{
lock (SrvcClients)
{
SrvcClients.Add(clientId, callback);
}
}
return clientId;
}
void IByteSrvcWCallback.UnSubscribe(Guid clientGID)
{
lock (SrvcClients)
{
if (SrvcClients.ContainsKey(clientGID))
{
SrvcClients.Remove(clientGID);
}
}
}
void IByteSrvcWCallback.NoOpButKeepAlive() { }

}//EOC
}//EONS


You will discover as I did that the communications piece is really all about the client. Nothing much happens without them.
Now for the Client the callback class.
Source code (C#):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DplXByteSrvcs
{
public class ByteSrvcCallbackImpl : IByteSrvcCallback
{
public delegate void GotControlPacket(byte[] hotPacket);
public GotControlPacket OnGotControlPacket = null;

private byte[] m_ba;
public byte[] ControlPacket
{
get { return m_ba; }
}
public void SentControlPacket(byte[] ba)
{
m_ba = ba;
if (OnGotControlPacket != null)
OnGotControlPacket(ba);
}
}
}

Autodesk Map3d 2010 and WCF (I)

Author: Jonio, Dennis

Well, I finally finished my first Windows Communication Foundation(WCF) project. Ever since .NET Framework 3.0 came out I had been curious but until now the chance to delve into it just never appeared. The prompt was to get some flexibility into the IPC chores between my custom Autodesk application modules(netloads) and my .NET applications.
First off, I certainly join the chorus that promotes getting Juval Lowy’s work Programming WCF Services (in my case 2nd Edition). There is a multitude of nuance and subtlety that would be left to chance without reading this work. In addition I do not have a clue where else a person would go to get his kind of depth of understanding. A real "must have" if you are serious about WCF.
As I spent more time with WCF I certainly learned of some of its strengths and weakness’. I really did waiver a lot concerning implementing the callback interface versus just using the simplest of operation contracts with each executable acting as a hosting service and likewise a client. This would have made this new interface very similar to my previous NamedPipe interface in that both sides act as host and client. In the end I choose the WCF DuplexChannel/callback paradigm because I figured out the rather straightforward way to keep the session(s) alive over an extended period of time.
If you are at all familiar with my previous work with IPC you know that I pass serialized datasets around as opposed to passing more granular objects. I do not stray from this paradigm. It has served me well and I cannot think of any reason to change.

The following are my interface definitions for the service and callback. I will leave the class implementation for a follow up.
Source code (C#):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace DplXByteSrvcs
{
[ServiceContract(Namespace = "WCF.MDAD.SERVICES",
CallbackContract = typeof(IByteSrvcCallback),
SessionMode = SessionMode.Required)]
public interface IByteSrvcWCallback
{
[OperationContract(IsOneWay = true)]
void ControlPacket(byte[] baIn);

[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
int PipedIn(byte[] data);

[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
int DQPipedIn(string destinationQ, byte[] data);

[OperationContract]
Guid Subscribe();
[OperationContract(IsOneWay = true)]
void UnSubscribe(Guid clientId);
[OperationContract(IsOneWay = true)]
void NoOpButKeepAlive();

}
public interface IByteSrvcCallback
{
[OperationContract(IsOneWay = true)]
void SentControlPacket(byte[] baOut);
}
}