ちなみに秒単位までの表示/保存でよければこのままでいいのだが、0.1秒単位も表示/記録したいとなると、この方式ではうまくいかない。というのは、そもそもtime型のsystem serviceが「秒数」でしか記録を行っていないためだ。一応方法はいくつかあり、一つはtimeGetTime()を使うもので、こちらだと1ms単位で時間を返してくれる。ただ問題はtimeGetTime()の時間はシステム時間(そのハードウェア上でWindowsが立ち上がってから現在までの経過時間)なので、TimeGetTimeの秒数をそのまま使う訳にはいかないことだ。どうするか、というと(どの程度の精度が必要か、という議論はあるのだが)TIMER_ID2をパラメータにWM_TIMERイベントが呼ばれた際に、かならずその際の「時刻」と「システム時間」を記録しておき、前回呼び出された際の「時刻」と今回呼び出された際の「時刻が」異なれば、小数以下は0。両方の「時刻」が一致していれば、前回の「システム時刻」と今回の「システム時刻」の差を計算し、それを小数以下に当てはめるという方法がある。0.1秒単位の精度ならこの程度で十分であろう(それより細かい精度が必要だと、そもそもtime型を使うのが無理という話なので、システム時間ですべて記録して最後に何かしらで時刻に変換するといった方法になるだろう)。

さて、時刻が確定したら、次は温度取得である。といっても取得そのものはTIMER_ID1の方で取得しているので、こちらはバッファに格納された値を文字列に変換して、表示/ファイル出力するだけである。文字列変換は_gcvt_s()で行っているが、その後に妙なコードがあるのは、_gcvt_s()の動作がちょっと面倒なためである。_gcvt_s()は例えば12.34567という数字を有効桁数4桁で変換すると"12.34"としてくれるのだが、12.00000を同じように変換すると"12."になってしまう。今回の場合、画面表示やファイル表示には"12.0"と表示したいため、変換後の結果を見て小数部の数字が入っていなければ、'0'を追加するという処理をしているわけだ。

実はこのあたりが激しく手抜きな訳で、本来ならまず変換後の文字列から'.'(小数点)の位置を探し、その次がNULLならば'0'を追加するという風にする必要がある。今のコードはあくまで測定温度範囲が10.0℃~99.9℃の場合にのみ正しく動くという、あんまり褒められた代物ではないからだ。このあたりは注意してほしい(*1)。

さて文字列化が済んだらまず表示である。表示にはUnicode化が必要なので、MultiByteToWideChar()を呼び出して変換を行っている。変換後の表示自体はWM_PAINTのMessageの処理内で行うので、ここでは単にバッファに入れ、InvalidateRect()を呼び出すだけだ。これを行うことで、今のTIMER_ID2のMessage処理が終わると、次にWM_PAINTのMessageの処理に入ることになる。

さて、InvalidateRect()を呼び出す前に、もしSaveFlagが立っていればファイル書き込み処理を行う。SaveFlagは"ファイル"→"Log File"→"Save"を指定したらTRUE、"Close"を指定したらFALSEにするという内部変数で、初期値はFALSEにしている。で、"Save"を呼んだ時点でファイルを開いているので、ここでは書き込みを行うだけである。

まずは書き込むべき行の内容を、改めてsprintf_sを使って生成する。ここでの文字コードはANSIで行うことにした。これが日本語とかを入れるとなるとUNICODEでないとまずいのだが、日付/時間/温度だけなら、ANSIのままで足りるからだ。で、書き込みそのものはWriteFile()で行うのだが、その前にSetFilePointer()を呼び、WriteFile()によって新たに書き込む場所をファイルの最後にしている。これは、既にログファイルがあって、そこに追記の形で書き込む様な動作の方が使い勝手が良いだろう、と筆者が判断したためである。例えば実際に測定する際に、連続して測定しっぱなしのケースもあるだろうが、何度か止めて再開を繰り返すようなケースもあると思われ、そのたびに新しいログファイルを作るのは、ファイル指定も後での集計も面倒だろうと思われるからだ。ということで、無条件でWriteFile()の直前でSetFilePointerを呼び出し、これでファイルの最後尾から書き込む形にしている。

(*1) 「じゃ、直せよ」と言われそうだが。いや直してもいいんですが、まぁコードはあくまでサンプルなので、軽いプラクティスという事でトライして見てください。

(続く)