2004-02-10記。05-15修正。
参考: TClock Lightの書式処理、書式拡張の簡単なサンプル
ここでは、TClock Lightの書式を拡張して、使用可能メモリの量、CPU使用率、バッテリー残量を表示させるサンプルを作ってみます。
これはあくまで私の例ですので、システム情報を表示させたい人は、別のやり方でプログラムを組んでもかまいません。
tclocklight-sys-040512.zip (ソースのみ)
このページで解説してある改造版のソースです。改造するソースはdllの下だけですが、TClock Lightのソースまるごと入れてあります。「TClock Light開発中」のソースとは別のフォルダを作って展開するとよいでしょう。
追加したcpu.cの中に浮動小数点の演算がありますので、デフォルトライブラリのリンクが必要になります。実行ファイルのサイズを小さくするを参照してください。
できあがるtcdll.tclockのサイズは大きめになります。
書式MMとMKで使用可能メモリ量(MMはメガ単位、MKはキロ単位)を、CUでCPU使用率を、BLバッテリー残量を表示させることにします。
dll/format.cで配列 format_handers を初期化しているところに、{ 'M', NULL, MemoryHandler } 、 { 0, L"CU", CPUHandler } 、{ 0, L"BL", BatteryHandler } を追加します。書式に 'M' という文字が現れたら関数 MemoryHandler が、"CU" という文字列が現れたら関数 CPUHandler が、"BL" という文字列では関数 BatteryHandler が呼び出されます。
struct {
wchar_t ch;
wchar_t* prefix;
HANDLERFUNC func;
} format_handers[] =
{
{ '/', NULL, SDateHandler },
{ ':', NULL, STimeHandler },
(中略)
{ 'M', NULL, MemoryHandler },
{ 0, L"CU", CPUHandler },
{ 0, L"BL", BatteryHandler },
// add your functions
};
システム情報をまとめて扱うためのソースファイル sysinfo.c を新しく作ります。
情報を保存しておくためのスタティックな外部変数です。
static BOOL m_bCPU = FALSE, m_bMem = FALSE, m_bBattery = FALSE;
static wchar_t m_sMemoryK[12] = { 0 }, m_sMemoryM[12] = { 0 };
static wchar_t m_sCPU[12] = { 0 };
static wchar_t m_sBattery[12] = { 0 };
関数 EndSysInfo は、終了時と設定変更時に呼び出します。関数 CpuMoni_end は、CPU使用率の取得処理の後始末用で、cpu.cの中にあります。関数 FreeBatteryLife は、バッテリー残量取得の後始末用で、battery.cの中にあります。
void EndSysInfo(void)
{
m_bCPU = m_bMem = FALSE;
CpuMoni_end(); // cpu.c
FreeBatteryLife(); // battery.c
}
タイマーから定期的に呼ばれる関数 OnTimerSysInfo です。m_bMem がTRUEだったら使用可能メモリ量を取り出し、m_sMemoryK と m_sMemoryM に保存します。メモリ量はAPI GlobalMemoryStatus を呼び出すだけなので簡単です。
CPU使用率は、cpu.cの関数 CpuMoni_get で取り出します。
バッテリー残量は、battery.cの関数 GetBatteryLifePercent で取り出します。取得に成功したときは、0から100の数値が返ります。
整数を文字列に変換する処理は、面倒なので swprintf を使いました。どうせデフォルトライブラリをリンクすることだし。
その他のシステム情報の取得処理を加えるには、ここに追加するといいでしょう。
void OnTimerSysInfo(void)
{
if(m_bMem)
{
MEMORYSTATUS ms;
ms.dwLength = sizeof(ms);
GlobalMemoryStatus(&ms);
swprintf(m_sMemoryK, L"%d", ms.dwAvailPhys / 1024);
swprintf(m_sMemoryM, L"%d", ms.dwAvailPhys / 1024 / 1024);
}
if(m_bCPU)
{
int usage = CpuMoni_get(); // cpu.c
if(usage >= 0) swprintf(m_sCPU, L"%02d", usage);
else m_sCPU[0] = 0;
}
if(m_bBattery)
{
int per = GetBatteryLifePercent(); // battery.c
if(per <= 100)
swprintf(m_sBattery, L"%02d", per);
else m_sBattery[0] = 0;
}
}
format.cで呼び出され、書式MMとMKを処理する関数 MemoryHandler です。文字列 m_sMemoryK または m_sMemoryM の中身を出力先にコピーします。
旧TClockでは、起動時と設定変更時にシステム情報関連の書式が含まれているかどうかを判別し、必要な初期化処理を行っていました。その結果、ぐちゃぐちゃなソースになってしまいました。このsysinfo.cでは初めて書式処理用の関数が呼ばれたら初期化する、としています。初めて MemoryHandler が呼ばれたら、m_bMem を TRUE に変えます。そのままでは m_sMemoryK と m_sMemoryM に何も入っていないので、関数 OnTimerSysInfo を呼び出します。
また、システム情報表示には、しょっちゅう時計の表示が更新されなければなりません。グローバル変数 g_bDispSecond を TRUE にすれば、1秒おきに表示が更新されます。
void MemoryHandler(FORMATHANDLERSTRUCT* pstruc)
{
const wchar_t *p;
if(!m_bMem)
{
m_bMem = TRUE;
OnTimerSysInfo();
}
pstruc->sp++;
if(*pstruc->sp == 'K') p = m_sMemoryK;
else if(*pstruc->sp == 'M') p = m_sMemoryM;
else return;
while(*p && *pstruc->dp) *pstruc->dp++ = *p++;
pstruc->sp++;
g_bDispSecond = TRUE;
}
format.cで呼び出され、書式CUを処理する関数 CPUHandler です。文字列 m_sCPU の中身を出力先にコピーします。
初めて CPUHandler が呼ばれたときは、m_bCPU をオンにし、cpu.c の初期化用関数 CpuMoni_start を呼び出し、関数 OnTimerSysInfo でCPU使用率を一度取得させておきます。
MemoryHandler と同様にグローバル変数 g_bDispSecond をオンにします。
void CPUHandler(FORMATHANDLERSTRUCT* pstruc)
{
const wchar_t *p;
if(!m_bCPU)
{
m_bCPU = TRUE;
CpuMoni_start(); // cpu.c
OnTimerSysInfo();
}
p = m_sCPU;
while(*p && *pstruc->dp) *pstruc->dp++ = *p++;
pstruc->sp += 2;
g_bDispSecond = TRUE;
}
format.cで呼び出され、書式BLを処理する関数 BatteryHandler です。文字列 m_sBattery の中身を出力先にコピーします。
初めて BatteryHandler が呼ばれたときは、m_bBattery をオンにし、関数 OnTimerSysInfo でバッテリー残量を取得させておきます。
ここでもグローバル変数 g_bDispSecond をオンにします。
void BatteryHandler(FORMATHANDLERSTRUCT* pstruc)
{
const wchar_t *p;
if(!m_bBattery)
{
m_bBattery = TRUE;
OnTimerSysInfo();
}
p = m_sBattery;
while(*p && *pstruc->dp) *pstruc->dp++ = *p++;
pstruc->sp += 2;
g_bDispSecond = TRUE;
}
cpu.cは、TClock2chのcpu.cにちょっと手を加えたものです。関数 CpuMoni_start(初期化)、CpuMoni_get(取得)、CpuMoni_end(後始末)が記述されています。
cpu.cの中身については、ここでは解説しません(というか、cpu.cで何をやっているのか私にはよく分からない……)。
なお、Windows 95、98、Meでは、CPU使用率をうまく取得できないことがあります(旧TClock、TClock2chも同様)。
battery.c は、バッテリー残量の取得用に作ったものです。関数 GetBatteryLifePercent と FreeBatteryLife があります。
バッテリー残量は、API GetSystemPowerStatus で取れますが、Windows NT4がこのAPIに対応していないので、battery.c では kernel32.dll を動的にロードして関数へのポインタを取得するようにしています。
Windows NT4を無視するなら、OnTimerSysInfo で GetSystemPowerStatus をそのまま呼び出してもいいでしょう。
また、kernel32.dll のロードは newapi.c でやるほうがいいかもしれません。
詳しくは、battery.cの中身をご覧ください。
関数 OnTimerSysInfo が定期的に呼ばれるように、main2.cの関数 InitClock でタイマーをかけます。ここでは2秒おきのタイマーにしていますが、TClock2chのように、設定を読み込んでタイマーの秒数を変えるようにしてもいいでしょう。
void InitClock(HWND hwnd)
{
(略)
SetTimer(hwnd, IDTIMER_MAIN, 1000, NULL);
SetTimer(hwnd, IDTIMER_SYSINFO, 2000, NULL);
}
関数 EndClock では、sysinfo.cの関数 EndSysInfo を読んで後始末をさせます。また、システム情報取得用のタイマーを止めます。
void EndClock(HWND hwnd)
{
(略)
EndNewAPI(); // newapi.c
EndSysInfo(); // sysinfo.c
// Stop timers
KillTimer(hwnd, IDTIMER_MAIN);
KillTimer(hwnd, IDTIMER_SYSINFO);
(略)
dll/wndproc.cのサブクラス化用ウィンドウプロシージャ WndProc でメッセージ WM_TIMER を処理しているところに、関数 OnTimerSysInfo の呼び出しを加えます。これで、2秒おきにシステム情報が取得されるようになります。
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
(略)
case WM_TIMER:
switch (wParam)
{
case IDTIMER_MAIN:
OnTimerMain(hwnd); return 0;
case IDTIMER_SYSINFO:
OnTimerSysInfo(); return 0;
}
if(g_bNoClock) break;
return 0;
(略)
時計の設定が変更されたときに呼び出される関数 OnRefreshClock に関数 EndSysInfo の呼び出しを加えます。書式からMM、MK、CU、BLが削除されたときには、システム情報の取得は行われないようになります。
void OnRefreshClock(HWND hwnd)
{
InitUserStr(); // userstr.c
EndSysInfo(); // sysinfo.c
(略)
dll/tcdll.hには、必要な関数宣言を加えます。
/* ---------- sysinfo.c --------------- */ void EndSysInfo(void); void OnTimerSysInfo(void); void MemoryHandler(FORMATHANDLERSTRUCT* pstruc); void CPUHandler(FORMATHANDLERSTRUCT* pstruc); /* ---------- cpu.c --------------- */ void CpuMoni_start(void); int CpuMoni_get(void); void CpuMoni_end(void); /* ---------- battery.c --------------- */ int GetBatteryLifePercent(void); void FreeBatteryLife(void);
tcdll.makを修正し、sysinfo.obj、cpu.obj、battery.obj がリンクされるようにします。
OBJS=dllmain.obj dllmain2.obj dllwndproc.obj draw.obj\
format.obj formattime.obj tooltip.obj userstr.obj\
startbtn.obj startmenu.obj taskbar.obj taskswitch.obj traynotify.obj\
bmp.obj newapi.obj dllutl.obj sysinfo.obj cpu.obj battery.obj\
utl.obj reg.obj font.obj localeinfo.obj
sysinfo.c、cpu.c、battery.c がコンパイルされるようにします。
dllutl.obj: $(SRCDIR)\dllutl.c $(TCDLLH)
$(CC) $(COPT)$@ $(SRCDIR)\dllutl.c
newapi.obj: $(SRCDIR)\newapi.c $(TCDLLH)
$(CC) $(COPT)$@ $(SRCDIR)\newapi.c
sysinfo.obj: $(SRCDIR)\sysinfo.c $(TCDLLH)
$(CC) $(COPT)$@ $(SRCDIR)\sysinfo.c
cpu.obj: $(SRCDIR)\cpu.c $(TCDLLH)
$(CC) $(COPT)$@ $(SRCDIR)\cpu.c
battery.obj: $(SRCDIR)\battery.c $(TCDLLH)
$(CC) $(COPT)$@ $(SRCDIR)\battery.c
sysinfo.cとcpu.cには、デフォルトライブラリが必要なので、NODEFLIBOPT=〜 をコメントアウトします。
(略) !IFDEF _NMAKE_VER # NODEFLIBOPT=NODEFAULTLIB=1 (略) !ELSE # NODEFLIBOPT=-D NODEFAULTLIB=1 (略)
次の画面は、「自分で書式を入力する」を「mm/dd ddd hh:nn\n"Mem"MM "CPU"CU "BL"BL」としたものです。
