動機
Tera Term のマクロ言語 (Tera Term Language) の通信コマンドのほとんどはなにがしかのホストに接続していないと実行できません。職場の Windows 10 デスクトップ環境には Tera Term が入っていて TTL の通信コマンドの動作を実行させて確かめたいのですが、開発系でないので気安く接続できるようなホストはありません。
Tera Term は名前付きパイプに接続でき、 Windows に .NET が入っていればローカルに名前付きパイプ サーバーを立てられるそうなので、試みてみました。
参考文献
- Powershellで名前付きパイプを扱う - じゅんじゅんのきまぐれ
- NamedPipeServerStream クラス (System.IO.Pipes) | Microsoft Docs
- エコーバックせずに文字列を入力する(Console.ReadKey) - Programming/.NET Framework/Tips - 総武ソフトウェア推進所
- [C#]処理ループ内でキー入力を受け付ける – エンジニ屋
- 非同期名前付きパイプの基礎 / クライアントSide - Neareal)
- .NETで名前付きパイプを試す(4) - 複数のクライアントに対応したサーバにする - いちろぐ
実装
C# および .NET の学習が不十分なので、これで問題ないのか心もとないですが、動作はしているようです。
基本的に文献 1. の非同期バージョンを C# に書き直しただけですが、クライアントからの入力をエコーするのではなく、双方向から入力できるようにしてみました。その際に参考になったのが文献 3. と 4. です。
なお、文献 1. の同期バージョンの PowerShell スクリプト1を実行すると Ctrl + C も Ctrl + Break もきかなくなることもあり、不慣れな C# にチャレンジしてみました2。
using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
class PipeServer {
private NamedPipeServerStream pipe;
private byte[] buffer;
PipeServer(string name) {
this.pipe = new NamedPipeServerStream(name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
this.buffer = new byte[512];
}
private void ConnectCallback(IAsyncResult result) {
Console.WriteLine("Connected.");
// this.pipe.EndWaitForConnection(result);
((NamedPipeServerStream)result.AsyncState).EndWaitForConnection(result);
}
private void ReadCallback(IAsyncResult result) {
int len = ((NamedPipeServerStream)result.AsyncState).EndRead(result);
if (len > 0)
Console.Write(Encoding.UTF8.GetString(this.buffer, 0, len));
}
async private void WriteCallback(IAsyncResult result) {
((NamedPipeServerStream)result.AsyncState).EndWrite(result);
while (!result.IsCompleted)
await Task.Delay(10);
}
async private void ReadingThread() {
while (this.pipe.IsConnected) {
IAsyncResult ar = this.pipe.BeginRead(this.buffer, 0, this.buffer.Length, this.ReadCallback, this.pipe);
while (!ar.IsCompleted)
await Task.Delay(10);
}
}
static void Main(string[] args) {
PipeServer server = new PipeServer(args[0]);
try {
IAsyncResult ar = server.pipe.BeginWaitForConnection(server.ConnectCallback, server.pipe);
Console.WriteLine("Waiting for client connection at \\\\.\\pipe\\" + args[0] + " ...");
while (!ar.IsCompleted)
Thread.Sleep(100);
Thread t = new Thread(server.ReadingThread);
t.Start();
byte[] buf;
ConsoleKeyInfo cki;
while (server.pipe.IsConnected) {
if (Console.KeyAvailable) {
cki = Console.ReadKey(true);
buf = Encoding.UTF8.GetBytes(new char[1] { (char)cki.KeyChar });
if (buf.Length > 0)
ar = server.pipe.BeginWrite(buf, 0, buf.Length, server.WriteCallback, server.pipe);
while (!ar.IsCompleted)
Thread.Sleep(10);
}
}
} finally {
server.pipe.Close();
}
}
}
これらのコールバック メソッドはそのコールバック メソッドを指定したメソッドの最後の引数のオブジェクトを result.AsyncState
で受け取れるらしいのですが、当該の引数のオブジェクトの型にキャストしてやらなければメンバーにアクセスできないようです。 (文献 5.)
使い方
C>C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe PipeServer.cs
C>PipeServer.exe test
PipeServer.exe
の引数にパイプの名前 (例 test
) を指定します。 Tera Term の 新しい接続 の ホスト に例として \\.\pipe\test
と入力して接続します。
接続後、 Tera Term 側でキー入力すると対応する文字が PipeServer.exe
側のコンソールに出ます。その逆も同様です。
なお、 Enter キーを押しても入力されるのは CR
(Ctrl + M) のみのようです。「改行」するには Ctrl + M, Ctrl + J と入力しなければなりません。
Tera Term で 接続断 を選択するかコンソールで Ctrl + C を入力して接続を切ります。
改良版
名前付きパイプはパイプ ストリームを複数もつことができ、それぞれにクライアントを接続できます。よって、名前付きパイプを介して二つの Tera Term のインスタンスで通信できれば所期の目的は達成できることに気が付きました。また、文献 2. をもうすこし丁寧に見直したら、うってつけのメソッドがありました。
using System;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
class PipeServer {
private static void ConnectCallback(IAsyncResult result) {
((NamedPipeServerStream)result.AsyncState).EndWaitForConnection(result);
}
static void Main(string[] args) {
string name = args[0];
Console.WriteLine("Waiting for client connection at \\\\.\\pipe\\" + name + " ...");
using (NamedPipeServerStream stream1 = new NamedPipeServerStream(name, PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.Asynchronous),
stream2 = new NamedPipeServerStream(name, PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) {
IAsyncResult ar = stream1.BeginWaitForConnection(ConnectCallback, stream1);
while (!ar.IsCompleted)
Thread.Sleep(100);
ar = stream2.BeginWaitForConnection(ConnectCallback, stream2);
while (!ar.IsCompleted)
Thread.Sleep(100);
Console.WriteLine("Connected.");
while (stream1.IsConnected && stream2.IsConnected) {
Task.WaitAll(new [] {
stream1.CopyToAsync(stream2),
stream2.CopyToAsync(stream1),
});
}
}
}
}
使い方
C>C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe PipeServer2.cs
C>PipeServer2.exe test
PipeServer2.exe
の引数にパイプの名前 (例 test
) を指定します。
Tera Term を二つ起動してそれぞれの 新しい接続 の ホスト に例としては \\.\pipe\test
と入力して接続します。パイプ名は同一ですが、それぞれが別々のパイプ ストリームにつながります。
接続後、一方の Tera Term でキー入力すると対応する文字が他方の Tera Term に出ます。
こちらの方法では XMODEM などのファイル転送も可能です3 4。
Tera Term で 接続断 を選択するかコンソールで Ctrl + C を入力して接続を切ります。
改良版 2
using System;
using System.IO.Pipes;
using System.Threading.Tasks;
class PipeServer {
static void Main(string[] args) {
string name = args[0];
NamedPipeServerStream stream1 = new NamedPipeServerStream(name, PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.Asynchronous),
stream2 = new NamedPipeServerStream(name, PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
try {
Console.WriteLine("Waiting for client connection at \\\\.\\pipe\\" + name + " ...");
stream1.WaitForConnection();
stream2.WaitForConnection();
Console.WriteLine("Connected.");
while (true) {
Task.WaitAny(new [] {
stream1.CopyToAsync(stream2),
stream2.CopyToAsync(stream1),
});
if (!stream1.IsConnected) {
stream1.Close();
stream1 = new NamedPipeServerStream(name, PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
stream1.WaitForConnection();
} else if (!stream2.IsConnected) {
stream2.Close();
stream2 = new NamedPipeServerStream(name, PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
stream2.WaitForConnection();
}
}
} finally {
stream1.Dispose();
stream2.Dispose();
}
}
}
これで片方の接続が切れても再接続させることができます。ただし、両方の接続を切ってもパイプ サーバーは終了しません。パイプ サーバー起動中のコンソールで Ctrl + C を入力するしかないです。