月刊ソフト作り!
Make The Software For VisualBasic


『常駐型ソフトを作る!』

2002.Jul.03
 Presented by kouta_y
感想等は掲示板、苦情はメールへ。


会社
最初に言います。
常駐型ソフト。俺はあまり好きではないです。
なんかさ。知らぬ内にメモリをバクバク食われてる気がして、、、



常駐するとは?

「常駐」を辞書で引くと、

 『いつでも駐在していること』

では「駐在」を辞書で引くと、

 『派遣された任地に一定期間留まっていること』

だそうです。(なんのこっちゃ)

つまりそういうソフトです。
この場合の「派遣された任地」というのは、貴方のパソコンの事です。
「一定期間」は普通は特にありません。あえて言うならそのソフトが起動している時間です。

常駐型アプリケーションは、「何か」を監視する場合によく用いられる手法です。
たろ'sソフトに「インターネット接続時間表示ソフト」というのがありますが、あれも常駐型ソフトです。

常駐型と普通のソフトは何が違うかと言えば、特に違いはありません。
ですが、常にウィンドウを表示させる必要がない常駐型は、作業の邪魔です。
そこで邪魔にならないように、システムトレイにアイコンを表示させ「見えないけど俺は動いてるゼー!」という事を教えてあげます。
この形が一般的な「常駐型ソフト」ですね。

アイコンの表示をしないで、ウィンドウを非表示にしてバックグラウンドで動かしても良いですが、なんとなくユーザーに優しい気がしないです。
ちなみに俺はそういうソフトは嫌いです。(俺の知らない所で動くな!)

今回作るもの

今回作るものは、「CPUの使用率を監視する常駐型ソフト」です。
CPUの使用率の部分は、興味ない人は気にしないでください。
知らない人にとっては興味津々だと思います(俺もそうだったし)、なのでサンプルに入れてみました。

ではまず使うAPIの宣言です。
標準モジュールを追加して、そこに宣言します。
定数が結構多いです。

' ウィンドウメッセージ
Public Const WM_MOUSEMOVE   As Long = &H200         ' マウスが移動した
Public Const WM_LBUTTONUP   As Long = &H202         ' マウス左ボタンが解放された
Public Const WM_RBUTTONUP   As Long = &H205         ' マウス右ボタンが解放された

' アイコンの設定
Private Const NIM_ADD       As Long = &H0           ' アイコン追加
Private Const NIM_MODIFY    As Long = &H1           ' アイコン修正
Private Const NIM_DELETE    As Long = &H2           ' アイコン削除

' フラグ
Public Const NIF_MESSAGE    As Long = &H1           ' メッセージを受け取る
Public Const NIF_ICON       As Long = &H2           ' アイコンを表示
Public Const NIF_TIP        As Long = &H4           ' ツールチップを表示

' ルートキー
Private Const HKEY_DYN_DATA  As Long = &H80000006   ' HKEY_DYN_DATAキー

' サブキー
Private Const STARTSTAT      As String = "PerfStats\StartStat"   ' 計測開始
Private Const STOPSTAT       As String = "PerfStats\StopStat"    ' 計測停止
Private Const STATDATA       As String = "PerfStats\StatData"    ' 現在値
Private Const CPUUSAGE       As String = "Kernel\CPUUsage"       ' 使用率キー

Private Const KEY_QUERY_VALUE As Long = &H1         ' 読み取り


' アイコンデータ構造体
Public Type NOTIFYICONDATA
    cbSize           As Long            ' 構造体のサイズ
    hwnd             As Long            ' ウィンドウハンドル
    uID              As Long            ' アイコンID(任意)
    uFlags           As Long            ' フラグ
    uCallbackMessage As Long            ' 通知するメッセージ
    hIcon            As Long            ' アイコンハンドル
    szTip            As String * 64     ' ツールチップ
End Type

' システムトレイのアイコン操作
Public Declare Function Shell_NotifyIcon Lib "shell32" Alias "Shell_NotifyIconA" ( _
    ByVal dwMessage As Long, _
    lpData As NOTIFYICONDATA _
) As Long

' レジストリキー開く
Public Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" ( _
    ByVal hKey As Long, _
    ByVal lpSubKey As String, _
    ByVal ulOptions As Long, _
    ByVal samDesired As Long, _
    phkResult As Long _
) As Long

' レジストリ値取得
Public Declare Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" ( _
    ByVal hKey As Long, _
    ByVal lpValueName As String, _
    ByVal lpReserved As Long, _
    lpType As Long, _
    lpData As Any, _
    lpcbData As Long _
) As Long

' レジストリキー閉じる
Public Declare Function RegCloseKey Lib "advapi32.dll" ( _
    ByVal hKey As Long _
) As Long

レジストリ関係の説明は前にしたと思います。
とりあえずここではアイコン表示関連の関数を説明します。

Shell_NotifyIcon
タスクバーにアイコンを追加・修正・削除します。
今回のサンプルでは3つの機能をフルに使います。
dwMessage  追加・編集・削除かのフラグを指定します。NIM_xxxという定数です。
lpData    NOTIFYICONDATA構造体へのポインタを指定します。構造体の説明は後述します。


とりあえずこんだけですね。

さっさと作ろう

ではまずサブルーチンを作ります。
システムトレイへアイコンの追加・編集・削除の関数を作ります。


' アイコン追加
Public Function IconAdd(nid As NOTIFYICONDATA) As Long
    IconAdd = Shell_NotifyIcon(NIM_ADD, nid)
End Function


' アイコン編集
Public Function IconModify(nid As NOTIFYICONDATA) As Long
    IconModify = Shell_NotifyIcon(NIM_MODIFY, nid)
End Function


' アイコン削除
Public Function IconDelete(nid As NOTIFYICONDATA) As Long
    IconDelete = Shell_NotifyIcon(NIM_DELETE, nid)
End Function

関数にするまでもないですが、こういうものでも後々で関数の中身を変えたくなった場合など、メインルーチン部分の変更が少なくてすみます。
この辺は説明不要ですね。
では次にCPUの使用率を取得する関数です。


' 使用率計測開始(コンストラクタ)
Public Function StartCPUUsage() As Long
Dim hKey  As Long
Dim Usage As Long

' 計測開始
If RegOpenKeyEx(HKEY_DYN_DATA, STARTSTAT, 0, KEY_QUERY_VALUE, hKey) = 0 Then
    Call RegQueryValueEx(hKey, CPUUSAGE, 0, ByVal 0, Usage, Len(Usage))
    Call RegCloseKey(hKey)
End If

End Function

' CPUの使用率
Public Function GetCPUUsage() As Long
Dim hKey  As Long
Dim Usage As Long

' 現在の値を読む
If RegOpenKeyEx(HKEY_DYN_DATA, STATDATA, 0, KEY_QUERY_VALUE, hKey) = 0 Then
    Call RegQueryValueEx(hKey, CPUUSAGE, 0, ByVal 0, Usage, Len(Usage))
    GetCPUUsage = Usage
    Call RegCloseKey(hKey)
End If

End Function


' 使用率取得の終了(デストラクタ)
Public Function EndCPUUsage() As Long
Dim hKey  As Long
Dim Usage As Long

' 計測停止
If RegOpenKeyEx(HKEY_DYN_DATA, STOPSTAT, 0, KEY_QUERY_VALUE, hKey) = 0 Then
    Call RegQueryValueEx(hKey, CPUUSAGE, 0, ByVal 0, Usage, Len(Usage))
    Call RegCloseKey(hKey)
End If

End Function


CPU使用率取得に関して

えー、実はこれ。WinNT上では取得できません。
同じくWin2000, XPもダメです。たぶん
この方法はWin9x系のウィンドウズでのみ取得できます。
今回のサンプルでは「レジストリ」から直接CPUの使用率の値を読み取り、取得します。
しかしNT系のOSでは、このレジストリキーは存在せず、値を読み取ることが出来ません。
その代わり、NTには「NTQuerySystemInformation」という隠しAPINTDLL.dllというDLLに存在します。
ここではこの関数を用いたやり方はご紹介しませんが、下のサンプルプロジェクトには入れておきました。
VBでNT系のCPU使用率をサンプルソース付きで公開しているのは、おそらく筆者しかいません。(たぶん)
レアものですよ。(本当かい)
以下の解説はWin9x環境でのみの説明です。

CPUの使用率を取得するには、単に「このレジストリキーから読めば良い」という訳ではなく、アクセスする順序があります。
以下の順序で、該当のキーにアクセスします。

HKEY_DYN_DATA\PerfStats\StartStat\Kernel\CPUUsage(計測開始)
    ↓
HKEY_DYN_DATA\PerfStats\StatData\Kernel\CPUUsage(現在の値)
    ↓
HKEY_DYN_DATA\PerfStats\StopStat\Kernel\CPUUsage(計測停止)

「Kernel\CPUUsage」は値の名前です。キー名ではありません。間違えないでください。(Kernelというキーは存在しません)
まず「StartStat」にアクセスして、使用率の測定開始をデバイスに通知します。
次に「StatData」にアクセスして、CPU使用率を取得します。
最後に「StopStat」にアクセスして、測定を停止
こんな順序でCPUの使用率は取得できます。


「StartStat」「StopStat」にアクセスしなくても、使用率は取得できてる感じもしますが、正しい値を得たい時はやっておいた方が良いでしょう。
ちなみにちゃんとした文献で調べた訳ではありませんので・・・。


フォームのデザイン

では次にメインフォームをデザインします。
以下のようなプロパティにします。
あっちなみにプロジェクト名は「MyTaskIcon」としています。

Form1
(オブジェクト名) Form1
Caption 監視中!
BorderStyle 1 - 固定(実線)
MaxButton False
MinButton False

さらに監視間隔用のタイマーコントロールと、表示用のラベルも追加します。
Timer1
(オブジェクト名) Timer1
Interval 1000
Label1
(オブジェクト名) Label1
Caption なし

全体の外観はこんな感じです。
ラベルのフォントも変えています。



では次にフォームモジュール内のコードです。
順序良くイベントごとに説明していきましょう。

まずフォームが起動したら、タスクトレイへ追加するアイコン構造体の初期設定を行います。

Private Sub Form_Load()

' アイコンの設定
With nid
    .cbSize = Len(nid)
    .hwnd = Me.hwnd
    .hIcon = Me.Icon                    ' アイコンハンドルはこれでOK
    .uID = 1
    .uCallbackMessage = WM_MOUSEMOVE    ' MouseMoveに通知する
'   .szTip = ""
    .uFlags = NIF_ICON Or _
              NIF_MESSAGE Or _
              NIF_TIP                   ' アイコン表示&メッセージ通知&ツールチップ表示あり
End With

Me.Visible = 0      ' フォームは隠す
Call IconAdd(nid)   ' タスクトレイへ!
Call StartCPUUsage  ' 計測開始
End Sub

NOTIFYICONDATA構造体のメンバの説明は、上のソース中のコメントに書いてある通りです。
hIconには、そのままウィンドウのIconプロパティを渡します。
Iconプロパティは、さらにそのメンバとしてHandleプロパティというのを持っており、これがアイコンハンドルとなります。
Iconを渡せば、デフォルトでHandleプロパティが渡される仕組みになっています。
uIDにはアイコンの識別子である任意のIDを渡します。
複数のアイコンを表示させないならば、適当な数字で構いません。
uCallbackMessageには、通知するメッセージの定数を指定します。
ここではWM_MOUSEMOVEを渡して、MouseMoveイベントに通知するようにしています。

通知とは?
通知とは、ウィンドウズが「あなた」に何かを知らせたい事がある場合、その事を教えることです。
この場合、例えばタスクトレイアイコンがクリックされた時や、その上をマウスが通った事を教える為に、どういうメッセージを送るか。という事です。
C言語でれば、自作メッセージを作って「それをオイラに送ってちょ」とやるのですが、VBでは「自作メッセージ」というものが作れない為、既存のメッセージに通知するよう設定します。

で、次にuFlagsに、全てのフラグを設定して、これでアイコンの設定は終わりです。

最後に起動時のフォームは非表示。
タスクトレイへ追加。
CPU使用率を監視するため、計測開始関数を呼びます。

実行するとこんな感じ


メッセージ通知が来たぞ!

さて、ユーザーによりアイコンがクリックされたとします。
すると、さっき説明した通りフォームが表示されていないのにも関わらず、MouseMoveイベントが発生します。
このイベント内で、マウスがクリックされたか、それは右か左クリックかの分岐処理を行います。

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
Dim msg As Long

' 通知メッセージが来た!


msg = X \ Screen.TwipsPerPixelX  ' メッセージに翻訳する(ピクセル単位にする)

Select Case msg
    Case WM_LBUTTONUP: ' 左クリックされた
        Me.Visible = 1          ' フォームを表示する
        Me.WindowState = 0

    Case WM_RBUTTONUP: ' 右クリックされた
        '
End Select

End Sub

通知されたら、今度はメッセージに翻訳します。
実は筆者、昔この部分のコードの意味が理解できず「やってられっか!」なんて投げ出した記憶があります。
そんな経験上、ここは初心者の方にわかり易く説明しましょう。

まず「メッセージ」とはイベントの事だと思ってください。
そのイベントは内部で1つ1つに「定数」が付いています。200とか300とか
んで、上で説明した「通知メッセージ」とは、あくまで通知用のメッセージであり、実際にMouseMoveイベントが発生した訳ではありません。
言い方を変えれば「通知イベント」という事です。
んで、アイコンがクリックされたかという「真のメッセージ(イベント)」は、引数として渡されます。
判りましたか?
つまり「通知イベント」が発生したら「アイコンはユーザーに何をされたか」のイベントが「引数」に隠れているわけです。
これを翻訳するという事です。オーケー?

では話を戻して。
メッセージに翻訳するには、X引数をピクセル単位に直します。
すると、本来のメッセージ定数となります。
もしフォームのScaleModeプロパティをピクセルにしているのであればこの翻訳処理はいりません。
X引数にはダイレクトにメッセージの種類が格納されています。
次にSelect Caseでメッセージを分岐させ、処理を書きます。
ここでは右クリックされたら、フォームを表示させる処理を書きます。


タイマーの処理
あぶなく忘れる所でした。
今回タイマーのインターバルは1秒間隔としていますので、1秒毎にCPUの使用率をラベルとアイコンツールチップ文字列に反映させます。

Private Sub Timer1_Timer()
Dim tip As String

tip = "使用率は " & GetCPUUsage & " %" & vbNullChar
Label1.Caption = tip
nid.szTip = "CPU監視中!" & vbCrLf & tip    ' 2段にする

Call IconModify(nid)    ' アイコンの編集
End Sub

トレイアイコンの編集を行うには、構造体に変えたいアイコンのIDを入れ、他のメンバを編集します。
nid変数はグローバル変数としているので、そのままツールチップの文字列のみを変更すればOKです。
ローカル変数tipの最後にvbNullCharを指定していますが、これはNULL文字を入れています。
APIはこのNULL文字を見て「ここが文字列の終端だ」と区別します。

とりあえず主要イベント内のコード説明はこれだけです。
他のイベント内処理は大体予想つくと思うので、ご自分で作ってみてください。
Unloadイベント内に「アイコンの削除」と「CPU測定停止」の両関数を忘れずに入れてください。


終わり

常駐ソフトでした。
これはテクニックとしては、簡単な部類に入ると思います。
通知メッセージ1つだけですしね。
構造体のメンバも少ないですし。
慣れれば結構楽に覚えられると思います。

CPU使用率取得部分は、「本当にこの数値が出ている」事の確認ができない為、意外と怪しいかもしれません。
誤差もかなりあると思います。
NT系のやり方はサンプルプロジェクトにコメント付きで入れていますので、そちらを参照ください。
結構難しいです。

今回のサンプルプロジェクト
おまけ:
NT系でのCPU使用率取得方法
プップアップメニュー
vb13.lzh(9.51KB)