テクニカルノート


技術的なメモをここに書き留める事にします。特に断りのない限り Windows は、Windows 95 を意味します。

kentop pageへ戻る


CPU使用率のデータ取得

文字化けメールの見方

MIDI テスト

MSDOS.SYS と WINBOOT.INI によるブート設定

ダイナミック VxD のロードに関する色々

NEC PC9801 と PC/AT を判別する

ウイルスチェッカーの警告対策

Windows 95/98 はどうやって異常終了を検出するか

CTRL+ALT+DEL を無効にする方法

Win32 API をフックするには、

ユーザに強制終了されたくないプログラムはどうする

ショートファイル名からロングファイル名を取得する方法

Windows 95 終了時、「MS-DOSモードで再起動する」やり方

アプリケーションからブラウザを起動し、特定の URL を表示する。

Windows 95 のプログラムの検索順番


CPU使用率のデータ取得
CPU使用率を知る必要があったので調べていたのだけど何かこの処理がなぜ Registryを
経由しなくてはいけないのか釈然としないな。ま、とりあえずやり方は
GetCPUUagseStart() を1度コールする。
何か仕事中、何もする事がなければ、自分は、Sleep(xxxx) でひまをつぶす。
GetCPUUsgae() で CPU 使用率の % を取得する。
使用率計測完了を伝えるため、GetCPUUsageStop() をコールする

void GetCPUUsageStart()
{
HKEY hKey;
DWORD dwData=4;
DWORD type = REG_DWORD;
char dummy[4];
dwData=4;
RegOpenKeyEx(HKEY_DYN_DATA, "PerfStats\\StartStat",0,KEY_ALL_ACCESS,&hKey);
RegQueryValueEx(hKey,"KERNEL\\CPUUsage",0,&type,dummy,&dwData);
RegCloseKey(hKey);
}
void GetCPUUsageStop()
{
HKEY hKey;
DWORD dwData=4;
DWORD type = REG_DWORD;
char dummy[4];
dwData=4;
RegOpenKeyEx(HKEY_DYN_DATA, "PerfStats\\StopStat",0,KEY_ALL_ACCESS,&hKey);
RegQueryValueEx(hKey,"KERNEL\\CPUUsage",0,&type,dummy,&dwData);
RegCloseKey(hKey);
}
int GetCPUUsage()
{
HKEY hKey;
DWORD dwData=4;
DWORD type = REG_DWORD;
int usage;
dwData=4;
RegOpenKeyEx(HKEY_DYN_DATA, "PerfStats\\StatData",0,KEY_ALL_ACCESS,&hKey);
RegQueryValueEx(hKey,"KERNEL\\CPUUsage",0,&type,(char *)&usage,&dwData);
RegCloseKey(hKey);
return usage;

}


文字化けメールの見方

たまに、メールソフト等で文字化けしたメールを受け取る事がありますが、どうしても
その内容を確認したい場合は、MIMEエンコードの失敗で文字化けしているテキストを
いったんテキストファイルに保存します。例えば、それを ABC.TXT と言うファイルに
保存したら、インターネットエクスプローラのファイル/開く(O) でそのファイルを選択します。
(ファイルを IE へドラッグアンドドロップしても OKです。) これで、文字化けした内容が
正常に見えるはずです。もしだめなら、さらに 表示/オプションでファントの文字セット
あるいは、MIMEエンコードの指定を自動から、EUC/S-JIS/JIS に変更して試してみてください。
文字のエンコードの失敗が原因の文字化けは、これで何とか見えるはずです。


MIDI サウンドの再生

私のホームページには、MIDI サウンドをバックグランドで鳴らしているのだけど
環境によっては、この音が音楽としてまともに聞こえない人もいます。
MIDI に関してちょっと調べていたのだけど、色々複雑なのね。
MIDI サウンドテスト


MSDOS.SYS と WINBOOT.INI によるブート設定

Windows 95/98 のブートメニューの設定は、MSDOS.SYS によって定義されますが
もし、ブートディレクトリに WINBOOT.INI ファイルが存在する場合には、MSDOS.SYS
ではなく、WINBOOT.INI の設定が優先されます。このファイルは、元々 Windows 95
のUS版プレリリースの時には、存在していたんだけど、その後 MSDOS.SYS による
設定に変更されたんだけど、実はまだ機能しているんです。ま、これはバグではなく
故意にそうしているんでしょうけど。


ダイナミック VxD のロードに関する色々

ダイナミック VxD をロードする場合には、CreateFile() API を使用してロードします。
例えば、sample.exe から sample.vxd をロードするのであれば、

CreateFile("\\\\.\\sample.vxd", 0, 0, NULL, 0, FILE_FLAG_DELETE_ON_CLOSE, NULL);
のようにするわけですが、この場合、sample.vxd は、WINDOWS\SYSTEM のディレクトリに
置いておく必要がありますが、私のように、アプリケーションと関連ファイルは、全て同一の
ディレクトリに配置しておき、できるだけファイルが分散しないようにするのが好きと言う人の
場合は、vxd の絶対パスを指定する必要があります。
この場合は、以下のようにします:

char devname[MAX_PATH];
char longname[MAX_PATH];
char vxdname[MAX_PATH];
HMODULE hm;
       hm=GetModuleHandle("SAMPLE.EXE");
       GetModuleFileName(hm,longname,sizeof(longname));
       GetShortPathName(longname,vxdname,sizeof(vxdname));
       strcpy(&vxdname[strlen(vxdname)-3],"VXD");
       strcpy(devname,"\\\\.\\");
       strcat(devname,vxdname);
       ghVxD = CreateFile(devname, 0, 0, NULL, 0, FILE_FLAG_DELETE_ON_CLOSE, NULL);

ここで、わざわざ、GetShortPathName() を使っているのは、*.VXD のロードに指定する
パス名には、ロングファイル名では正常にロードできないためです。少なくとも OSR2 までは
ロングファイル名で ダイナミック VxD をロードしようとすると失敗します。

ダイナミックVxD のロードの解説には、フラグとしてFILE_FLAG_DELETE_ON_CLOSE を指定する
ようにと書かれていますが、では、これを使わなかった場合は、どうなるのか、この場合、*.EXE が終了
した後も、VxD のみがメモリに常駐したままになります。この方が都合がいい場合もあるので覚えて
おくと便利です。


NEC PC9801 と PC/AT を判別する
GetKeyboardType() で判別すると言う方法が、MSDN などでは紹介されているのですが
この解説は、本当に NEC PC9800 シリーズと PC/AT とを判別可能かどうかあやしい。

私のやりかたは、MachineType をレジストリから取得してその先頭3文字が 'IBM .....' で
あるかどうかを判別していたのだけど、Windows 98 をインストールしてみたらこのレジストリ
が存在しなくなってしまっていたので現在は、暫定的に GetKeyboardType() で判定して
いるのだけど、明快な方法を Microsoft K.K. は示すべきではないかと思う。

あ、MachineType のレジストリは:
HKEY_LOCAL_MACHINE\Enum\Root\*PNP0C01\0000\MachineType です。


ウイルスチェッカーの警告対処

ソフトウエア作成者は、たまに、ユーザからオリジナルファイルがウイルスチェッカーが
警告を表示しているとクレームを受ける事があります。(私が作成したソフトウエアには
現在までウイルスに感染したものを配布した事はありませんが。)

ほとんどの作成者は、そのウイルスチェッカー製造元に感染の疑いのあるファイルを送って
調査してもらうと言う事しかできないのではないかと思いますが、ソフトウエア作成者側で
以下のような事を調査する事が可能です。

(1) もし本当のウイルスだった場合の事を考えて被害を受けてもかまわない動作環境を用意する。
(2) 感染の疑いのあるファイルを 16バイト毎に分割し、ファイル名に位置、拡張子は、元の拡張子
    にする。例えば、SAMPLE.EXE を 000000.EXE 000010.EXE 000020.EXE ....のように
    すると言う事です。なぜ、16バイト単位にファイルを分割するかと言うと、ウイルスチェッカー
    は、高速にパターンマッチングを行うために8086系の特徴のパラグラフ単位でパターンを
    調べているからです。
(3) 警告をレポートしたウイルスチェッカーに 000000.EXE ..... XXXXXX.EXE を検査させる
    この結果、再度警告が表示されたファイルにそのウイルスチェッカーがウイルスを検出した
    パターンが含まれています。
(4) そのパターンが、はたして正しいかそれともウイルスの断片かを調べます。
    このためには、アセンブラの知識がある程度必要です。

Windows 95/98 はどうやって異常終了を検出するか

Windows 95(OSR2)/98 を正常終了させずに電源を切ると、次回システムを
起動した時に自動的に SCANDISK が走りますが、ではどうやって Windows は
前回正常終了しなかった事を検出しているのか。

おそらく、レジストリかファイルにフラグ情報を記録しておいて正常終了時にその
フラグをクリアしているのだろうと思っていたのだけど、大間違い。

Windows は、ブートすると、FAT の 4 バイト目を FF から 7F に変更します。
(このバイトの Bit7 が ON か OFF かによって SCANDISK をするかどうかのフラグ
になっています。) 正常にシャットダウンすると、このバイトは、7F から FF になります。

ただし、Windows の DOS BOX 中で DEBUG/SYMDEB などで L 100 2 1 1 などで
FAT をリードしても 4 バイト目は、7F ではなく FF が返されてしまいます。(INT 13h
を使った場合も同じです。)
DOS BOX 中では、INT 21h AX=7305h,SI=0、CX=ffffh,DL=3、DS:BX=1,1,buff で 1
セクタリードすれば本当の FAT イメージをリード可能です。

正常シャットダウンの FAT イメージ
F8 FF FF FF FF FF ....
異常シャットダウンの FAT イメージ
F8 FF FF 7F FF FF ....


CTRL+ALT+DEL を無効にする方法

アプリケーションからこのキーのコンビネーションを効かなくする方法がないわけ
ではありません。たぶん、目的としては、システムの強制リブートを禁止するのが
目的だと思いますが、今思い付く方法としては、キーボードドライバをフックする方法
です。Win95 のキーボードドライバの VxD には、他の VxD がキーコードをモニタ
したりフィルターをかけるためのサービス,VKD_Filter_Keyboard_Input が存在します。
VxD を作成して、このサービスをフックして CTRL+ALT+DEL を無効化する事が可能です。
具体的には、

        mov eax,@@VKD_Filter_Keyboard_Input
        mov esi,OFFSET32 MyKEYB_MONITOR ; with our filter
        VMMCall Hook_Device_Service ; DoIt

で自分の MyKEYB_MONITOR のルーチンでキー入力を監視して CTRL+ALT+DEL が
成立する場合には、最後のキーを無効化してしまうと言う方法を使用します。

BeginProc MyKEYB_MONITOR, HIGH_FREQ, HOOK_PROC, OldKEYB_MONITOR
        ;cl=キーコード
         cl のキーコードを検査し、CTRL,ALT,DEL の場合にはそれぞれを
         フラグとして記憶しておき、CTRL+ALT+DEL が成立する場合には
         そのキーを無効化するために stc でリターンする、条件が成立し
         ないのであれば、clc でリターンする
key_ok:   clc   ;cl を入力キーとして処理する
          ret
Ignore:   stc   ;今回入力されたキーは無視する
          ret
EndProc MyKEYB_MONITOR

この方法以外には、REBOOT VxD に対して何か処理を行えば REBOOT 動作を無効化
できるのではないかと思うのだけど、REBOOT VxD に関する記述は DDK には存在しな
かったように思うのでその場合は、REBOOT.VxD を解析する必要があります。

P.S. ここに解説したキーボードをフックする方法は、上記の目的以外にもキーのレジェンド変更
(レジェンド=刻印)にも利用できます。よくあるのは、CAPS と CTRL を入れ替えたい時とか
Windowsキーをエミュレートしたりする場合なんかにも利用できます。

実際の各キーコードの確認などは、Wintip Ver 1.90 のキーボードモニターを利用すると簡単に
確認できます。Wintip のキーボードモニター機能も上記のキーボードフック機能を用いて
実現しています。


Win32 API をフックするには、
Windows 95 内部解析や、Windows 95 システムプログラムなどに記載されている
方法は、いまいちなんですね。と言っても、私もまだこの命題に対する完全回答を
持っているわけでもありません。Wintip の Program execution paramater monitor
は、CreateProcess() を実験的にフックして、そのパラメータをモニタしていますが
Ring 0 の VxD Code で Ring 3 のコードを書き換え、Ring 3 から Ring 0 のコードを
Ring 3 の コードとしてコールすると言う荒業です。あくまで実験的レベルの話であ
って、色々と問題があるので現在別の方法を模索中です。


ユーザに強制終了されたくないプログラムはどうする
CTRL+ALT+DEL でプログラムの強制終了リストが画面に表示されるとそこにリストされた
プロセスはユーザが故意に強制終了させる事ができます。プログラムによってはどうしても
強制終了されると都合がわるい場合があります。そのような場合の解決策は、以下の
ようにそのプロセスをサービスとして登録します。これによりそのプロセスは、CTRL+ALT+DEL
のダイアログボックスにリストされなくなります。この API は、インポートライブラリには含まれて
いないので、GetProcAddress() でエントリを動的に取得してそれをコールする必要があります。

{
FARPROC RegisterServiceProcess;

RegisterServiceProcess=GetProcAddress( GetModuleHandle("KERNEL32.DLL"),
                                        "RegisterServiceProcess"); //関数のエントリアドレスを取得する
   RegisterServiceProcess(NULL, //プロセスハンドル (NULLはカレントの意味)
                          1     // 1=サービスプロセスの指定、0=サービスプロセス解除
                          );
}

ショートファイル名からロングファイル名を取得する方法
Windows 95 には、この目的の Windows API は、存在しません。
逆にロングファイル名からショートファイル名を取得するのであれば GetShortPathName() API
があります。また、フルパス名を取得するのであれば GetFullPathName() API がありますが
あってもよさそうな GetLongPathName() API は、なぜか存在しないんです。
で、どうしてもショートファイル名をロングファイル名に変換する必要がある場合は、

     AX = 7160h
     CX= 0002h
     DS:SI= ショートファイル名の ASCIIZ
     ES:DI= ロングファイル名を受け取るバッファアドレス (最大 260バイト)
     INT 21h

をコールして変換します。
ただ、32bit アプリケーションから直接 INT 21h をコールする事はできないので方法としては
Thunk 経由で 16bit の DLL からコールするか、VXD を用意してDeviceIoControl を経由して
Exec_Vxd_Int で V86 をコールするかのどちらかを使用します。

もし、WIN32 API のみでこれを実現する場合には、FindFirstFile() API を使って各ディレクトリ名
ファイル名を検索してロングファイル名に変換するという方法もあります。


Windows 95 終了時、「MS-DOSモードで再起動する」やり方
C:\Windows\MS-DOSモード.PIF ファイルを実行します。
この PIF を実行すると、最終的には、C:\WINDOWS\SYSTEM\5344414D.BAT が実行されます。
この BAT ファイルは、「MS-DOSモードで再起動」した時の環境を整えるためのものです。
5344414D.BAT の内容は、

    @ECHO OFF
    ADDDRV DOSIME.SYS
    %1 %2 %3 %4 .....
    DELDRV DOSIME.SYS


です。%1 %2 %3 の部分は、COMMAND.COM が実行されるため、EXIT を行うと DELDRV が実行され
WIN.COM へ制御が戻り、Windows が再び起動されます。

Microsoftは、何で 5344414D.BAT なんて変な名前を付けたのだろうか?
この数値の意味は何なんだろうか。


アプリケーションからブラウザを起動し、特定の URL を表示する。
WINTIP Ver 1.90 で行っている方法は、ファイルの関連付けを利用するやり方です。直接表示したい
htm 拡張子の URL をファイルとして指定します。

ShellExecute(NULL,
             "open",
             "http://www.pse.co.jp/KANDO/kentop.htm",
             NULL,
             NULL,
             SW_SHOWNORMAL);

単にブラウザだけを起動したい場合には、FindExecutable() API を使用して"open" 処理に関連する
アプリケーション名を取得してを直接起動するようにします。 "open" 以外のアプリケーション名は、レジストリから
取得する必要があります。


Windows 95 のプログラムの検索順番
拡張子を指定せずにプログラム名のみを指定して実行を試みた場合、Windows 95 では以下の
プログラムを検索する。例えば、「ファイル名を指定して実行」で ABC を実行しようとすると
システムは、

    (1) ABC.PIF
    (2) ABC.COM
    (3) ABC.EXE
    (4) ABC.BAT
    (5) ABC.LNK

の順に、デスクトップ、Windows\System、パスの通っているディレクトリの順に検索し発見されれば
そのプログラムを起動。
Windows 98 Pre-release版:

    (6) ABC.CMD (OS/2 や Windows NT の BATCH ファイル)

ただし現在は、検索は行っているが実行はされていない。将来、実行されるのかも。


kentop pageへ戻る