背景
PLCでステージ動作を制御し、ラインカメラで撮影した画像で外観検査する装置を立ち上げることになりました。ラインカメラで大きな画像を扱うので64ビットでやりたいのですが、PLCは32ビットにしか対応していません。そこで、画像を扱う部分とPLC制御を別々のアプリにし、アプリ間で連携させることにしました。
色々調べたところ、WCF(Windows Communication Foundation)というのが割と新しく、良さそうでした。 コメントでご指摘をいただきました。 将来的にサポートされないことが決まっているので、いい方法ではないかもしれません。
どのようにして実現したのかを紹介します。
環境はWindows10、C#を使います。
概要
WCFは双方向通信もできますが、今回の用途ではクライアント側からサーバー側にリクエストを出す一方向通信で目的は達成できるので、無用にコードが複雑になるのを避けるため、一方向通信でやります。
大雑把な流れは下記のようになります。
サーバー側(32ビット)
- PLCの一連の機能を1つのクラス(devPlc)にまとめておきます
- 上記のクラス内でPLCとの通信を確立します(actUtlType.Open())
- WCFのサービスホストを立上げ、クライアントからのリクエストを受けられるようにして待機します
クライアント側(64ビット)
- チャネルファクトリを作ってサーバー側で動いているWCFにアクセスします。
- このWCFで通信確立済みのdevPlcをつかみ、これ以降、devPlcをWCF経由で操作します。
正しい説明かどうかは分かりませんが、大雑把なイメージで言うと、WCFではサーバー側とクライアント側で一つの共通のクラスを共有し、このクラスの実際の動作はサーバー側で行われ、クラスのメソッドの呼び出しはクライアント側から行われるもの、ということができると思います。
この「一つのクラスを共有する」の実現のために、サーバー側とクライアント側で共通のインターフェースを持ち、その中身はサーバー側にだけ記述する、というスタイルがとられているのだと思います。
実装
インターフェースの定義
System.ServiceModel.dll
の参照を追加します。
上述のように、インターフェースはサーバー側、クライアント側の両方で同じものを記述します。
下記の例ではサーバー側で通信確立済みのPLCをつかむためにgrabPlc()
, ステージ位置への移動と、現在位置の問い合わせのメソッドがあります。
using System.ServiceModel;
[ServiceContract]
public interface IHost
{
[OperationContract]
void grabPlc(); // 通信確立済みのPLCをつかむ
[OperationContract]
bool stageCoord_Q(out int x, out int y, out int z); // ステージの現在位置問い合わせ
[OperationContract]
bool stageCoord(int x, int y, int z); // ステージを移動
}
インターフェースの中身の実装
サービスコントラクトの各メソッドはクライアント側で呼び出されますが、中身が実行されるのはサーバー側です。インターフェースの中身はサーバー側にだけ記述します。
grabPlc()
以外のメソッドはdevPlc
のメソッドをラップしているだけです。
public class Host : IHost
{
devPlc devPlc; // 通信確立済みのPLC制御パッケージ
public void grabPlc() // 通信確立済みのPLCをつかむ
{
devPlc = Form1.devPlc;
}
public bool stageCoord_Q(out int x, out int y, out int z)
{
return devPlc.stageCoord_Q(out x, out y, out z);
}
public bool stageCoord(int x, int y, int z)
{
return devPlc.stageCoord(x, y, z);
}
}
サーバー側
サーバー側ではPLCとの通信を確立した後、サービスホストをスタートしてクライアントからのリクエスト待ちの状態で待機します。
devPlc
をstaticにすることで上記のgrabPlc()
が通信確立済みのPLCをつかむことができています。
クライアントからのリクエストがしばらくない状態が続くとエラーが発生します。デフォルトでは10分間になっていてちょっと短いので、下記の例ではReceiveTimeout
を10日間に設定しています。
public partial class Form1 : Form
{
static public devPlc devPlc; // PLC制御のパッケージ
ServiceHost svc;
public Form1()
{
InitializeComponent();
// PLCとの通信を確立する
devPlc = new devPlc(axActUtlType1, 1);
// 名前付きパイプのバインディング
const string addr = "net.pipe://localhost/wcfInterProcessComm";
var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
// 10日間クライアントからのリクエストがなくてもタイムアウトしない
binding.ReceiveTimeout = new TimeSpan(240, 0, 0);
// エンドポイントを作成
svc = new ServiceHost(typeof(Host));
svc.AddServiceEndpoint(typeof(IHost), binding, addr);
// サービスホストをスタート
svc.Open();
}
}
クライアント側
クライアント側ではサーバー側と同じ名前付きパイプのバインディングを作り、これを使ってチャネルファクトリを作ってサービスコントラクトhost
を作っています。
これ以降host
はローカルなクラスと同じ感覚で使用できます。
public partial class Form1 : Form
{
IHost host;
public Form1()
{
InitializeComponent();
// 名前付きパイプのバインディング
const string addr = "net.pipe://localhost/wcfInterProcessComm";
var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
// チャネルファクトリを作る
ChannelFactory<IHost> factory
= new ChannelFactory<IHost>(binding, new EndpointAddress(addr));
// サービスコントラクトのインターフェースにチャネルを割り当てると、
host = factory.CreateChannel();
// これ以降、サーバー側のサービスコントラクトがローカルなクラスのように扱える
host.grabPlc();
host.stageCoord(100, 100, 20);
int x, y, z;
host.stageCoord_Q(out x, out y, out z);
}
}
参考
以下のサイトを参考にさせて頂きました。ありがとうございました。