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


『スクリーンセーバーを作る(上級編その1)』
2002.May.14

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

会社
スクリーンセーバー上級編。
上級編はちょっと難しいので2回程度に分けます。
多少長くなりますが辛抱してください。


前回の続き


前回までで大体の基本動作は作りました。
今回はさらに機能を付け加えます。
付け加える機能は、

・ パスワード機能の付加
・ 画面のプロパティにプレビューを表示

この2つです。
この2つが意外と厄介で、前回はやりませんでした。

今回は1段目の「パスワード機能の付加」をやります。


パスワード機能とは?

今更言うまでもありません。
スクリーンセーバーを解除する為に、予め設定しておいた「合言葉」を入力しないとスクリーンセーバーを解除出来なくしてしまう機能の事です。


何故必要なの?

筆者はパスワード嫌いです。
むしろ「俺のパソコンのクセに、何故俺にパスワード要求するんじゃい!」と逆ギレします。
まあ特に守るべくデータがないだけなのですが、、、(そうでもないけど)
「人に見られたらまずいんよ」という人の為です。特にサーバー管理などしているパソコンには必須でしょう。


使うAPIの宣言

まずパスワード機能を使うにあたって言っておきますが、
ソフト側ではパスワード分かりません。

どういう意味かというと、おそらく「自分でパスワードダイアログ作って、レジストリに保存かな〜?」とか思ってる人がいると思います。
違います。
違うんです。
実はパスワード入力も、確認ダイアログもAPIを使って呼び出します。
んでAPIは入力されたパスワードをレジストリに勝手に保存します。
他にも方法はあると思いますが、今回のやり方はAPIを使う方法です。
意外と簡単なので拍子抜けしないように、、、

宣言です。
'パスワード設定ダイアログ
Declare Function PwdChangePassword Lib "mpr.dll" Alias "PwdChangePasswordA" ( _
  ByVal lpClassName As String, _
  ByVal
hWnd As Long, _
  ByVal
lpA As Long, _
  ByVal
lpB As Long _
) As Long

'パスワード入力ダイアログ
Declare Function VerifyScreenSavePwd Lib "password.cpl" ( _
  ByVal hWnd As Long _
) As Long

'レジストリキーオープン
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

'レジストリ値取得
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

'レジストリキークローズ
Declare Function RegCloseKey Lib "advapi32.dll" ( _
  ByVal hKey As Long _
) As Long


「あれ?やっぱレジストリ使うんだ」
はい。これは「パスワードが設定されてるか?」のフラグを取得する為に必要です。


レジストリとはなんぞい?

レジストリとは、「データの格納庫」だと思っていただければ結構です。
しかしその「格納庫」は、とーっても重要な格納庫だということです。
「レジストリエディタ」なるもので、中身を容易に変えれますが、ヘタな場所をいじるとWindowsが立ち上がらなくなるかもしれないので止めときましょう。
アプリケーションの設定などはINIファイル(設定ファイル又は初期化ファイル)に保存をする方法もありますが、
実はマイクロソフトはこのレジストリに保存する事を推奨しています。

しかし実際は「レジストリはあまり触りたくない」という人が多い為、使用している人としていない人とは半々くらいでしょう。
とーっても重要な故、「ちょっと敬遠・・・」というのが実情です。
ちなみに筆者もあまり使いません。


それぞれのAPIの説明

まず「PwdChangePassword」関数ですが、これは「パスワード設定のダイアログ」を表示させます。
第1引数 → クラス名を指定。今回はスクリーンセーバー用なので”SCRSAVE”というクラス名で固定。
第2引数 → ウィンドウハンドルを指定。「/A」引数と一緒に渡されるハンドルを指定します。
第3引数 → リザーブ。0で固定。
第4引数 → リザーブ。0で固定。
戻り値はパスワードが設定されたら0、キャンセルされたらそれ以外です。
使うタイミングは、起動時の引数が「/A」だった場合に呼び出します。


VerifyScreenSavePwd」関数。
パスワード入力ダイアログを表示します。
引数はウィンドウハンドルを指定します。これはスクリーンセーバーを実行しているウィンドウとなります。
戻り値は、パスワードが正しければ0、キャンセルされたらそれ以外です。誤入力は再入力を促します。
使うタイミングは、メインフォームが閉じる時です。Unload イベントもしくはQueryUnloadイベント内で使います。


RegOpenKeyEx」関数。
レジストリキーを開きます。そのキーのハンドルを取得すると考えても結構です。
第1引数 → 現在開いているキーのハンドルを指定。今回は定義済みのハンドル「HKEY_CURRENT_USER」を指定します。
第2引数 → 開きたいサブキーの名前を文字列で指定。ここでは「Control Panel\desktop」という文字列を指定します。
第3引数 → 予約。0で固定
第4引数 → アクセス方法をフラグを組み合わせて指定。今回は値を取得するだけなので「KEY_QUERY_VALUE」フラグのみ指定します。
第5引数 → 新しく開いたキーのハンドルを格納する変数へのポインタ。Long型の変数を指定します。
戻り値は、キーが正常に開けたら「ERROR_SUCCESS」が返ります。失敗したら(サブキーが存在しなかったら)それ以外が返ります。
使うタイミングは、スタートアップモジュールの最初の方で使います。


RegQueryValueEx」関数。
レジストリキーの「値」と「値の種類」を取得します。
第1引数 → 現在開いているキーのハンドルを指定。RegOpenKeyExで開いたハンドルを指定します。
第2引数 → 値の名前を文字列で指定します。パスワード有無のフラグは「ScreenSaveUsePassword」となります。
第3引数 → 予約。0で固定。
第4引数 → Long型の変数へのポインタを指定。ここに「値の種類」が格納されます。特に必要ないのでNULL(ByVal 0&)を指定します。
第5引数 → 値を受け取るバッファ(変数)へのポインタを指定。ここに「値」が格納されます。Long型変数を指定します。
第6引数 → バッファのサイズが入った変数へのポインタを指定。ここに実際に格納されたサイズが入ります。バッファのサイズが小さい時は必要なバイト数が入ります。
戻り値は、値が正常に取得出来たら「ERROR_SUCCESS」が返ります。失敗したらそれ以外が返ります。
使うタイミングはRegOpenKeyExと同じです。


RegCloseKey」関数。
レジストリキーのハンドルを閉じます。
引数には現在開いているキーのハンドルを指定します。
戻り値は、正常に閉じれたら「ERROR_SUCCESS」が返ります。失敗したらそれ以外です。
レジストリキーを開いたら、必ずこの関数を呼び出しハンドルを閉じてください。



ん〜いっぱい使うな〜

手順1つ1つでAPIを使わないといけないだけなので、流れとしては結構簡単です。
どういうタイミングで使うかというと、、、

パスワードフラグの取得

          ↓

設定されてたら確認ダイアログ
なかったら、そのまま終了

これだけです。


パスワード設定ダイアログ

これは引数に「/A」が渡されたら呼び出します。
では前回のプロジェクトをそのまま使い、引数をSelect Caseしている所に以下のコードを追加します。

Case "/A", "-A": 'パスワード
'    ウィンドウハンドルを取得

    hWnd = Right$(Trim$(Cmd), Len(Cmd) - 2)
'    パスワード設定ダイアログの表示
    Call PwdChangePassword(SCRSAVE, hWnd, 0&, 0&)

「-A」「/A」の両方としました。
まずコマンドラインは「/A 1234」という形で渡されてきますので、「1234」のみ取り出します。
その後、それをウィンドウハンドルとし「PwdChangePassword」関数を呼び出します。

環境によってデザインが違うかもしれませんが、筆者の環境では下のようなダイアログが表示されます。


特に戻り値を受け取る必要はないので、Callで呼び出しています。
これでパスワード設定はOK。


パスワードが設定されているか

これはレジストリを直接読み取ります。

まず、「RegOpenKeyEx」でパスワード設定のフラグがあるサブキーを開きます。
その後、そのサブキーから「RegQueryValueEx」でデータを読み取り、グローバル変数へ格納します。
最後に、「RegCloseKey」でキーのハンドルをクローズして終了します。

この処理は「GetINI」自作関数内に記述します。

Public Function GetINI()
'設定の取得
Dim hKey As Long ' レジストリキーハンドル
Dim Data As Long ' データ格納用
Dim Size As Long ' バッファのサイズ

msg = Space$(250) '領域の確保 250byte(適当です)
Call GetPrivateProfileString("MyScr", "Message", "たろの部屋", msg, Len(msg), path & "\MyScr.ini")
intval = GetPrivateProfileInt("MyScr", "Interval", 500, path & "\MyScr.ini")
msg = Trim$(msg) 'スペース削除

Size = Len(Data)

'パスワード使用かのフラグを取得
If RegOpenKeyEx(HKEY_CURRENT_USER, SUBKEY, 0, KEY_QUERY_VALUE, hKey) = ERROR_SUCCESS Then
  If RegQueryValueEx(hKey, SSUP, 0&, ByVal 0&, Data, Size) = ERROR_SUCCESS Then
    PassWord = Data
  End If
'  開いたら閉じる
  Call RegCloseKey(hKey)
End If

End Function

「PassWord」というのがBoolean型のグローバル変数です。
各関数に渡している引数のほとんは定数で宣言しています。
定数の宣言は、

Private Const HKEY_CURRENT_USER As Long = &H80000001 ' HKEY_CURRENT_USER
Private Const KEY_QUERY_VALUE  As Long = &H1     ' サブキーデータの問い合わせ許可
Private Const ERROR_SUCCESS   As Long = 0      ' ノンエラー

Private Const SCRSAVE As String = "SCRSAVE"         ' スクリーンセーバー
Private Const SUBKEY As String = "Control Panel\desktop"  '
Private Const SSUP  As String = "ScreenSaveUsePassword"  ' パスワードの有無


パスワードの設定フラグは以下の階層下に設定されています。
レジストリエディタを使って、一度見てみると良いかもしれません。

HKEY_CURRENT_USER
       |
       |
       |
       +--- Control Panel
                  |
                  |
                  |
                  +--- Desktop

                         |
                         |
                         |
                         +--- ScreenSaveUsePassword  =  値(DWORD値)

パスワード入力を促す

これはメインフォームのアンロード時に呼び出します。
呼び出し側では0か1かの判定だけなので簡単です。

一旦ウィンドウ最前面の解除とマウスを表示させます。
関数を呼び出し、結果が真なら終了、偽なら初期化を行い終了処理をキャンセルします。


ではアンロードイベントプロージャのコードです。

Private Sub Form_Unload(Cancel As Integer)

Timer.Interval = 0
Call MouseShow(1)
Call WinTop(Me.hWnd, 0)

'パスワードあり?
If PassWord Then
'  確認ダイアログ表示
  If VerifyScreenSavePwd(Me.hWnd) = 0 Then
'    キャンセルされたら初期化 -> 再実行
    Cancel = 1
    XX = 0
    YY = 0
    Timer.Interval = intval
    Call MouseShow(0)
    Call WinTop(Me.hWnd, 1)
    Exit Sub
  End If
End If

End Sub

パスワードの入力ダイアログです。


前回サンプルとの相違点

まずパスワード機能の付加は言うまでもありません。
2重実行禁止コードをフォームロードプロージャではなく、スタートアッププロージャにしました。
あとグローバル変数「PassWord」の追加をしました。


終わり

これでセキュリティも完璧なスクリーンセーバーになりました。(本当か?!)
今回のサンプルプロジェクトは下からダウンロードできます。
では次回はスクリーンセーバー編最終回、プレビュー機能の実現です。


サンプルプロジェクト(12.2KB)