.NET Framework 2.0 コア機能解説 ~ 第 2 回 シリアルポートのサポート ~
青柳 臣一
Microsoft MVP for Visual Developer - Visual C#
◆シリアルポートのサポート
.NET Framework 2.0ではシリアルポートをサポートする機能が追加されました。ひょっとするとシリアルポートと言われてもピンと来ない方もいらっしゃるかもしれませんね。シリアルポートの代表的なもののひとつにRS-232Cがあります。現在では光回線やADSLなどを使ってインターネットサービスプロバイダと接続するのが一般的となっていますが、以前はモデムと言われる機器を使って電話回線経由で接続していました。このモデムとパソコンとの接続にもRS-232Cが使用されていました。その後USBが普及するにつれてモデムもUSB対応のものが多くなっていき、また、ADSLなどの普及によりモデム自体も使用頻度が減っていきました。今では個人ユースでRS-232Cを使用することはほとんどなくなったと言っていいような状況ではないかと思います。
しかし、その一方で制御機器や検査機器などRS-232Cを使って通信する機器もまだまだ使われています。実際、「.NET Framework でRS-232Cを使った通信をするにはどうしたらいいですか?」というのはよく聞かれる質問でした。.NET Framework 1.0、1.1では残念ながら直接のサポートはありませんでしたからWin32 APIを呼び出すなど他の手段が必要でしたが、2.0ではとても簡単に使用できるようになりました。
◆SerialPortクラス
シリアルポート通信はSystem.IO.Ports名前空間にあるSerialPortクラスで行うことができます。通信に必要な機能はほとんどすべてこのクラスで簡潔しています。また、BaseStreamプロパティを通じてStreamも公開されています。
http://msdn2.microsoft.com/ja-jp/library/system.io.ports.serialport.aspx
◆シリアルポートで通信してみよう
それではさっそくシリアルポートを使って通信してみます。
ここでは2台のパソコンをRS-232Cのクロスケーブル(ヌルモデムケーブルなどとも呼ばれます)で接続して通信を行ってみます。ただ、筆者は実際に2台のパソコンを用意したのではなく、Microsoft Virtual PC 2004を使用して仮想的に接続しました。簡単にご紹介しておくと、Virtual PC 上に2つのゲストOSを用意します。Virtual PCの設定で、この両方のCOM1を「名前付きパイプ」にし、名前付きパイプの名称をたとえば "\\.\pipe\virtualcom1" などとします。必ず同じ名称にしてください。このようにしてから2つのゲストOSを同時に実行すると、それぞれのCOM1がホスト上の "\\.\pipe\virtualcom1" という名称の名前付きパイプで仮想的に接続された状態となります。
(1) シリアルポートのオープン
シリアルポートで通信を行うには、通信条件を設定してからポートをオープンします。以下のコード例をご覧ください。
C# の例
SerialPort port = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
port.Open();
port.DtrEnable = true;
port.RtsEnable = true;
Visual Basic の例
Dim port As SerialPort = New SerialPort("COM1", 9600, Parity.None, 8, StopBits.One)
port.Open()
port.DtrEnable = True
port.RtsEnable = True
ここではSerialPortクラスのコンストラクタでポート名、ボーレート、パリティ、データ長、ストップビット長を指定しています。これらは、コンストラクト後にプロパティに代入することによって設定することも可能です。それぞれPortName、BaudRate、Parity、DataBits、StopBitsプロパティが対応します。通信条件を指定したあと、Open()メソッドを呼び出すことによりポートがオープンされます。
次のDtrEnable、RtsEnable はRS-232Cを使用するにあたって必要となる設定です。これらはRS-232CのDTR、RTSと呼ばれる信号線に対応しています。DTRはData Terminal Readyの頭文字で、データを受信する準備ができたことを相手機器に伝えます。RTSはRequest To Sendの頭文字で、相手機器にデータを送信していいことを伝えます。
ただ、これらは常に必要となるわけではありません。DTR、RTSなどの信号線をどのように扱うかは相手機器次第となりますので、それにあわせる必要があります。上述したようなVirtual PCの名前付きパイプを使ったテスト環境ではDtrEnable、RtsEnableともにTrueとしてやらないと正常に通信することができませんでした。しかし、筆者が所有しているオムロン社製のモデムではDtrEnableのみをTrueとすることで通信を行うことができました。
(2) 文字列を送受信する
それでは文字列を送受信してみましょう。
C# の例
port.WriteLine("HELLO");
string res = port.ReadLine();
Console.WriteLine(res);
Visual Basic の例
port.WriteLine("HELLO")
Dim res As String = port.ReadLine()
Console.WriteLine(res)
このように非常に簡単です。WriteLine()メソッドにより1行の文字列を送信し、ReadLine()メソッドにより1行の文字列を受信することができます。なお、1行とは改行までのことですが、改行はNewLineプロパティで指定することができ、規定値は "\r\n" (vbCrLf) です。
ところで、WriteLine()、ReadLine()メソッドでやり取りされる文字列はどのようなコードになっているのでしょうか?それはEncodingプロパティによって指定されます。規定値はASCIIEncodingとなっています。日本語文字列を送受信する場合は port.Encoding = Encoding.GetEncoding("Shift_JIS") などのようにエンコーディングを指定する必要があります。
(3) シリアルポートを閉じるシリアルポートの使用が終わったらClose()メソッド、および、Dispose()メソッドで閉じます。
◆非同期受信
さて、通信を行うアプリケーションにはよくあることですが、こちらが何かの処理をしている最中であっても、送られてくるデータを受信したいことがあります。上記で紹介したReadLine()メソッドは1行読み込み終わるまで処理がブロックされてしまいます。Read()メソッドなどバイト単位で読み込めばブロックされずにすみます。もちろん、別スレッドでReadLine()を呼び出す、BaseStreamプロパティからStreamを取り出して非同期I/Oを行うなどの方法もあります。また、SerialPortクラスにはデータが到着したことを教えてくれるDataReceivedイベントが用意されています。ここではこのイベントを紹介します。
C# の例
static void Main(string[] args)
{
SerialPort port = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
port.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);
port.Open();
port.DtrEnable = true;
port.RtsEnable = true;
Console.ReadLine();
port.Close();
port.Dispose();
}
private static void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort port = (SerialPort)sender;
byte[] buf = new byte[1024];
int len = port.Read(buf, 0, 1024);
string s = Encoding.GetEncoding("Shift_JIS").GetString(buf, 0, len);
Console.WriteLine(s);
}
Visual Basic の例
Sub Main()
Dim port As SerialPort = New SerialPort("COM1", 9600, Parity.None, 8, StopBits.One)
AddHandler port.DataReceived, AddressOf SerialPort_DataReceived
port.Open()
port.DtrEnable = True
port.RtsEnable = True
Console.ReadLine()
port.Close()
port.Dispose()
End Sub
Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
Dim port As SerialPort = CType(sender, SerialPort)
Dim buf(1024) As Byte
Dim len As Integer = port.Read(buf, 0, 1024)
Dim s As String = Encoding.GetEncoding("Shift_JIS").GetString(buf, 0, len)
Console.WriteLine(s)
End Sub
上記のようにDataReceivedイベントハンドラを登録すると、何かを受信するたびにハンドラが呼び出されるようになります。上記の例ではRead()メソッドを使用してバイト列を読み込み、それをShift_JISとして文字列化しています。
ここで、重要な注意点があります。DataReceivedイベントハンドラはセカンダリスレッドから呼び出されます。そのためWindows.FormsのUI要素にアクセスする場合はControl.Invoke()メソッドを使用する必要があります。また、データ構造などにアクセスする場合は排他制御を行うなどマルチスレッドプログラミングと同様の注意が必要となります。
サンプル3(下記参照)ではDataReceivedイベントハンドラからWindows.FormsのUI要素にControl.Invoke()メソッドを使用してアクセスし、また、バイト単位でのデータの送受信を行っています。
◆サンプルソースコード
サンプル コードのダウンロード
(core2_sample.exe, 257 KB)
-
サンプル 1
ReadLine()、WriteLine()を使った非常にシンプルな通信サンプルです。クライアントとサーバに別れています。1台のパソコンでサーバを起動し、別のもう1台のパソコンでクライアントを起動してください。
SerialPortSample1_CSがC#版、SerialPortSample1_VBがVisual Basic版となっています。 -
サンプル 2
DataReceivedイベントのサンプルです。
SerialPortSample2_CSがC#版、SerialPortSample2_VBがVisual Basic版となっています。 -
サンプル 3
シリアルポートを使ったチャットアプリケーションのサンプルです。Windows.FormsでのDataReceivedイベントの使用例、また、先頭に2バイトの文字列長、続いて文字列本体という単純ではありますが独自のプロトコルでやり取りしますので、バイナリ形式のデータを扱う例にもなっています。2台のパソコンでそれぞれexeファイルを実行してください。
SerialPortSample3_CSがC#版、SerialPortSample3_VBがVisual Basic版となっています。