.NET & Win Note

主にVisual Basic .NETについてのメモ書きです。
関連するWindows関係のメモも残していきます。

2003年2月12日 2:01:44

[メールチェッカー]画面に表示させるまでは完成

FETCHコマンドとメールヘッダーの日本語デコード処理が完成。
デコード処理はいろんな例外パターンを想定する必要があって、かなりややこしい文字列処理をしたのだけど、いろんなパターンのテストケースを作ってNunitでテストしながら、コーディングしたので、たぶん問題ないでしょう。
あと、暫定でメールの一覧表示画面も作って、表示させることまでできました。

次こそ、設定の保存や、タイマー処理などのユーザーが見える部分を作っていきます。
メールのアドレスや件名などによるフィルタリング機能は、一通り基本の機能がそろってから作成かな?

・・・といっても、Javaで作ったときもここまでは出来てたんだよなぁ・・・まあ、作りは格段に今の方が良くできてるけどね。

2003年2月11日 17:25:54

[NUnit]2.0と1.0の違い

2.0になってテストケースの書き方が変わっているので、ここでチェック
http://www.divakk.co.jp/aoyagi/csharp_tips_nunit.html

2003年2月10日 1:05:28

[メールチェッカー]サーバーとのIO部分はだいたいOK!?

一応、進捗報告→誰に?(笑)
とりあえずメールチェッカーが使うIMAPコマンドはFETCH以外実装完了。FETCHもあと少しの作業で完成。
サーバーからオクテットが指定された返答が来た場合の処理がうまくいってるかはまだわかりませぬ(汗)
この部分が完成すれば技術的な課題はとりあえずクリアなはず。
あとは、どういう画面のインターフェースにするかです。これが一番大変な気もするけどね・・・

2003年1月27日 2:03:51

[メールチェッカー]結局スレッドは採用せず

さっぱり、わけわからなくなってしまって、スレッドを使用するのはやめることにしました。
非同期で受信処理をさせていましたが、そうすると受信したデータを元に何かするときに、すでに受信されたのかどうかのチェックをする必要が出てくるし、なんやかんやで収集がつかなくなりました(T.T)
できることならスレッドは使わずに、どうしても使わなくてはいけない場合は複雑なことはさせないというのが、必勝パターンのような気がします(^_^)

ちゅーことで、スレッド使わずに必要な分だけレシーブするように変えたら、いやーめちゃくちゃプログラムのすすみが早いです。
スレッド使わないようにしてから、接続からログイン、未読のメールのIDを取得するまで二時間程度でできちゃいました(笑)
最初から背伸びせずに簡単な方法でやるべきだったようです。
「IMAPは常にデータを受信できるようにしておく必要がある」とのことでしたが、考えてみればバッファにはたまるわけですから、別にスレッドにする必要もないんです。
これで、ささーっと作ってしまうぞ!!

2003年1月26日 15:08:34

[参照渡し]参照型の変数を引数に渡した場合、実際は何が渡されるか?

基本的なことなんで、いまさらそんなことも理解していなかったのか!!という感じなんですが・・・
Java と一緒で .NET の変数には値型と参照型の二つの変数があります。
値型は Integer や Long などで、変数を作成したメモリに直接値が書き込まれています(表現が合ってるかな?(笑))。
一方参照型は、オブジェクトです。String も .NET ではオブジェクトになります。これらの参照型の変数は変数自体のメモリ領域には実際の値は入っておらず、別の実際の値が書き込まれているメモリのアドレスが書き込まれています。ようは C でいうポインタです。

ここまでは良いとして、参照型の変数をメソッドの引数や他の変数へ代入した場合にどうなるかが少し曖昧だったのです。
たとえば、変数Aは参照型の変数で、これを別の変数Bに入れようとした場合に渡される参照は、Aに入っている参照なのか、それともA自身への参照なのか?
それで、実験してみました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Module Module1

    Sub Main()
        Dim que As New Queue()

        que.Enqueue("一個目")

        Dim que1 As Queue

        que1 = que

        que = New Queue()

        que.Enqueue("二個目")

        Dim que2 As New Queue()

        que2 = que

        Console.WriteLine("1:{0} 2:{1}", CType(que1.Dequeue, String), CType(que2.Dequeue, String))

    End Sub

End Module
結果は・・・
1:一個目 2:二個目
こんな感じになりました。
10行目と18行目で que オブジェクトをそれぞれ代入していますが、もしこの代入で que の中の参照が渡されずに、 que 変数自体の参照が渡されているならば、 que1 と que2 は同じ内容を指すことになるので
1:二個目 2:二個目
こうなっているはずです。
でも、実際は12行目で New で新しいオブジェクトを que に与えていますが、 que1 には値がちゃんと残ったままになっています。

つまり、代入は参照を渡すと言うよりも、参照の値渡しをしているということになります。
結局、変数の中身が値型であろと参照型であろうと、代入(と、たぶん引数で与えた場合も)は変数の中身を値渡ししているということになります(たぶんね・・・)
※結局、Javaと一緒なわけで変数のポインタを渡すことは出来ないということですね。このあたりでもいろいろ説明されてました。

[スレッド]どうやったら止まるのさ?!

前にStreamReader.ReadLineメソッドが、受信バッファに何も無いときに実行すると、ずっと処理が戻ってこないので、他のスレッドで受信待ちの状態にしておくと良いかもというアイディアをここに書いていたが、どうもそれもうまくいかないようです。
問題が起きたのはアプリケーションの終了時。この別スレッドで動いている受信待ちクラスが終了してくれずに、画面上は終わってるように見えてもプログラム自体はメモリに残ったままになってしまったのです。
Ctrl + Breakで止めてみると、やはりReadlineで止まってるぅー(T.T)
Threading.Abortメソッドを使っても、なぜか終了しないし。
(おそらく、Readlineの処理が終わって安全に終了できる状態になって止まるのでしょう)
むりやり、StreamReaderをCloseすればエラーで終了するのだが、それではあんまり。
(というか、受信を別スレッドでやること自体あんまりだったのか?!)
だが結局、やりたくなかったが、終了時のみエラーを無視して強制的に終了するようにして対処した(裏でエラーは起きているが画面に出ないだけ)。
他の方策としてはタイムアウト時間を短く指定すれば、タイムアウトで終了するので、その時のエラーをとばすとかすると、少しはエレガントかも。

スレッドを使わずに、順次必要な分だけReadしていっても良さそうなんだが、あちこちの文献に「IMAPクライアントはいつでもサーバーのデータを受信出来る必要がある」というのが、気になって変えるのもなんだかという感じ。
いろいろ調べてみても、その「いつでも受信できるようにする」必要があるコマンドがわからないんで、本当にそうすべきなのかちょっとわからないのだけど。

外国のサンプルとかを見てみるが、やはり必要回数分しかReadlineを実行しないサンプルしか見あたらなかった。別の手法では上にあげたタイムアウト時間を短くして対処する方法があった。この二つしか選択肢はないんだろうかねぇ。

2003年1月18日 12:30:09

[ファイル]shift-jisでファイルを書き出す

ファイルを作成しようと思ってヘルプを探すが、FileクラスはUTF-8での書き出ししかしないように出来ている。
MSはたぶんUTF-8を推奨しているのだろうが、自分が使ってるテキストエディタの秀丸くんは、何も指定せずにテキストを開くとShift-JISで開いてしまうようだ。
(XPについているメモ帳はちゃんとUTF-8で開くんだよな)
でも、Win98ユーザーとかは文字化けしたりするだろうから、やはりShift-JISで書きたい。
で、探したところ
http://www.atmarkit.co.jp/fdotnet/vb6tonet/vb6tonet08/vb6tonet08_02.html
にありました。さすが@IT。
StreamWriterクラスからもファイル作成できるんだね。調べが足りなかった。

2003年1月17日 0:26:15

[VB]プロパティーの素朴な疑問 2

うーん、.NETはまだちゃんと使えてないなー。
今日、思わず以下のようなコードを書いてどつぼにはまった
    Property PortNumber(ByVal port As Integer) As Integer
        Get
            Return Me.port
        End Get
        Set(ByVal Value As Integer)
            Me.port = Value
        End Set
    End Property
さらに値をセットしようと思い、
class.PortNumber = 123
なんて、書いたら、「c:\~\frmTest.vb(156): 'Public Property PortNumber(ByVal port As Integer) As Integer' のパラメータ 'port' に対して引数が指定されていません。」なんてエラーが出てコンパイルできない。
よく見てみると、「PortNumber() As Integer」と書くところを「PortNumber(ByVal port As Integer) As Integer」としている。
こんな構文あるのか?(笑)
プロパティー名の後に引数みたいなのがセットできるみたいだが・・・ということでMSDNを見ると
「これによりプロパティのシグネチャが識別されます」とある。
シグネチャ、つまりメソッドの名前と引数の組み合わせのことだね。
シグネチャが違うと言えば、オーバーロードを連想した。オーバーロードはメソッドの名前が同じでも別のメソッドとして扱われるという仕組みのこと。
確かに、ここの引数を変えると同じ名前でプロパティーが登録できた。うーん。
最初に間違って書いた方も
class.PortNumber(456) = 123
というように、引数を与えると普通に呼び出せるようだ。
どういうときに使うんだ、これ?(笑)

[VB]エラー処理の定石?

今まで、エラー処理はその時々考えついた方法でやっていて、定石みたいなのを見つけ切れていなかったので、なかなか迷って先に進めなかった。
たとえば、何かのメソッドの実行が成功したかどうかを Boolean の戻り値で返すのか、中でエラーをスローさせて受け取るようにした方がいいのか、というような感じだ。また、これが一つだけのクラスならいいが、クラス内で他のクラスを呼び出している場合に、うまく連鎖してエラー情報を渡せば良いのかがいつも迷う。

ヘルプを参考にして、エラー処理について考えてみる。
参考MSDN ms-help://MS.VSCC/MS.MSDNVS.1041/cpguide/html/cpconbestpracticesforhandlingexceptions.htm

イメージをわかせるために例を挙げながら考えてみる。
たとえば、サーバーへのログイン処理を行うメソッドならば、
Public Function Login() as Boolean
というように定義しておき、ユーザー名とパスワードが合っていてログインできたら戻り値にTrue、ユーザーかパスワードが間違っていてログインできなかったら False という感じでしょう。もしこの関数内で、サーバーへ接続しようとしたがサーバーまでの経路が見つからなかった場合や、メモリが足りなくて実行できなかった場合は、例外で処理すべきだろう(あたりまえかもしれませんが、まとめながら書いてるので・・・)
もし、そのメソッド内でエラー処理をしたくない(たとえば、ユーザーにメッセージを出したり)場合は、関数の呼び出し元にエラーをスローします。

たとえばこんな感じ。
Catch ex as Exception
  Throw new Exception("接続中にエラー")
End Try

エラー処理の Catch 節の中で、さらに新しい Exception (例外)を作っています。
こうするとこのメソッドの呼び出し元で Catch 節があれば、そこにジャンプします。なければ、さらにその呼び出し元をたどって行き、 Catch を探します。最終的に Catch が見つからなかった場合は、 .NET 標準のエラーダイアログが表示されてプログラムは終了します。

もし、下層で起きたエラーメッセージを知りたい場合は、
Catch ex as Exception
  Throw new Exception("接続中にエラー" & vbcrlf & ex.Message)
End Try
としておくと、詳細なエラー内容を持たせたまま、新たな情報も付け加えて、呼び出し元に例外を渡すことが出来る。
(※2004.6.20追記 こんな風に文字列連結しなくても第二引数にexをセットすれば、呼び出し元はInnerException見ればすみますね)

・・・でも、よくよく考えるとあまり意味がないのかも。
というのも、これもケースバイケースだとは思うけど、ユーザーに細かいエラー内容を出しても、意味がわからないだろうから、ユーザーには単純に「◎◎のため接続中にエラーが発生しました」というレベルのエラーで良いはず。
詳細なエラーが(たとえば、サポートなどで)必要な場合はログファイルに落とすなりすれば良いのかも(考えながら書いてます(笑))

また、
Catch ex as Exception
  Throw ex
End Try
とすると、発生した例外をそのまま呼び出し元に渡すことも出来る。

なぜ、自分がこのように例外をスローしまくっているかというと、エラー処理を何回も書きたくないからです。
それに、下層のクラスでいきなりエラーダイアログを出させるのもイヤですしね。
ちゅーことで、そろそろまとめると、下層のクラスは
Catch ex as Exception
  [ログファイルを書き出す処理]
  Throw new Exception("接続中にエラー")
End Try
というように、 .NET が出力する詳しいエラーはログに残し、例外はスローする。
最終的にユーザーに通知する必要がある場合は、簡単なエラーメッセージにとどめておき、 .NET が出しているエラーをそのまま表示することはしない。
ユーザーに表示させたい内容は、どのレベルで必要かを見極めて、そこで
Throw new Exception([ユーザーに見せたいエラー])
を書いて、実際に表示させるクラスに到達するまでは
Throw ex
で、そのままスローしつづけるのもいいかも(手抜き?)

まあ、他にも手間をかけたり、厳密にするということであればもっと細かく出来ると思うが、 .NET に関しては趣味でやってるレベルなので、これでいいかなーと思っている。
でも、他の人がエラー処理をどういう風にくんでいるかは見てみたいなぁ。


2003年1月15日 1:05:26

[メールチェッカー]base64 でエンコードされた文字コード ISO-2022-JP の表示

電子メールプロトコルの本や、MSDNと戦いやっと方法がわかりました。
EncodingクラスでgetDecoderを使うのはわかったけど、「Encoding.(どっと)」と打っても、ASCIIやUnicodeしか出ない(T_T)
getEncoding("ISO-2022")でもだめ。

インターネットをさまようこと一時間。ありました。
http://www.atmarkit.co.jp/fdotnet/vb6tonet/vb6tonet08/vb6tonet08_02.html
おしい!。正しくはISO-2022-JPだった。これで無事、メールのヘッダーをデコードして日本語表示できた。

サンプルを載せておく。
フォームにはテキストボックスを二つつけて、Text1にデコード前の文字列を貼り付けて、このメソッドを実行すると、Text2にデコードした内容が表示される。
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim base64chr() As Byte

        'base64をByte列に変換
        base64chr = System.Convert.FromBase64String(Me.TextBox1.Text)

        Dim dec As Decoder
        'EncodingクラスのGetDecoderでDecoderクラスのインスタンスが取得できる
        'Decoderは抽象クラスでサブクラスを使ってインスタンスをセットする
        dec = System.Text.Encoding.GetEncoding("ISO-2022-JP").GetDecoder

        Dim count As Integer
        count = dec.GetCharCount(base64chr, 0, base64chr.Length)
        Dim uniChr(count - 1) As Char

        dec.GetChars(base64chr, 0, base64chr.Length, uniChr, 0)

        Me.TextBox2.Text = System.Convert.ToString(uniChr)

    End Sub

[メールチェッカー]BASE64 エンコードとは?

.NET のクラスライブラリがやってくれるから、気にしなくてもいいけど、知っておくと上のソースが何しているかわかるだろうし、他のエンコーディングの時もイメージつかみやすいかも。

base64 エンコードとは、ぶっちゃけて言えば、バイナリデータを 7bit ASCII (文字列)で表せる形に変換する仕組みの一つのこと。
メールは基本的にテキストしか扱えないので、こういう仕組みを使って、バイナリデータを扱えるようにしている。
どういう変換をしているかというと、まずバイナリデータ6ビットずつで区切り、それ四つで一つの固まりとして扱う。
6ビットは10進数で言うと0から63の数字まで表せる。この0から63をそれぞれA-Z,a-z,+,/の文字列に割り当てて表現することで、 ASCII 文字だけでバイナリデータを表現することが出来る(ちなみにAは0、小文字iは34を表す)
当然元々8ビットのデータを6ビットで区切り直すと、あまりが出たりするので、そのぶんは=で帳尻を合わせる。
だいたい、こんな感じだと思う。
今回のメールチェッカーではこれの逆のデコード作業を行うわけです。

2003年1月13日 23:34:50

[メールチェッカー]日本語ヘッダのデコード

結局正月何もやらずに全く進んでないメールチェッカーだが、とりあえずソケットで接続して、IMAP サーバーとメッセージのやりとりをしながらだいたい方向がつかめてきた。
「もしかして二時間ぐらいで出来るんじゃ?」と思って、ガーっと今やってます。
で、一つの山にさしかかりました(笑)
そう、日本語ヘッダのデコードです。と思ってGoogleで調べてみたら、いいとこ見つかりました。
http://tokyo.cool.ne.jp/taquino/mailreader/dehead.shtml
まさに俺が求めていた情報。RFCをまず調べるのが王道なんだろうが、めんどいし(笑)
でも、やっぱBase64とか出てくるのか。名前しかしらんぞ(T.T)

2003年1月13日 10:38:33

[VB]プロパティの素朴な疑問

最近思ったのだが、なぜプロパティーの名前とSetの所両方に同じ型を書かなくてはいけないのか?
必ず一緒にする必要があるなら、片方だけでいいのでは?
継承とか、もしかすると関係するのかなー?

Property StartKojSEQ() As Integer
    Get
        Return mStartKojSEQ
    End Get
    Set(ByVal Value As Integer)
        mStartKojSEQ = Value
    End Set
End Property

2003年1月5日 16:37:41

[VB]StreamReader.Readlineの動き

ソケット通信でこのメソッドを実行してみたが、バッファ(?)に何も入ってきていない場合は、ずっと(?)通信待ち(だと思うが)の状態になり、プログラムはそこでストップしてしまう。
たとえば、何回かこのメソッドを実行しあちらから送信してきたデータをすべて読み込んだのに、さらにこのメソッドを実行したりした場合だ。
ドキュメントを見ると、「入力ストリームの末尾に到達した場合、戻り値は null 参照 (Visual Basic では Nothing) です。」とあるので、てっきりバッファ内のデータをすべて読み終えたらNothingが返ってくると思っていたのだが、どうやらそういう意味ではないようだ(ファイルの読み込みの時はファイルの最後まで来たらNothingが返ってくるのかも?!)

まあ、こういう動きだとわかればそのように作ればいいだけで、必要回数だけこのメソッドを呼び出すか、一度考えついたように、別スレッドでひたすら受信待ちさせておけば、ReadLineでプログラムがストップすることは無いだろう。