レジストリに保存してみる

レジストリ

HSPアプリケーションの設定データを保存する場合、設定ファイルを作成して、それに保存するというのが一般的な方法でしょう。初期化ファイル(.INIファイル)扱うモジュールなどもあり、それを利用しているかもしれませんね。

ファイルとして保存しておくのとは別に、しばしば利用されるものに、レジストリがあります。レジストリは、いわばWindowsが持っているデータベースであり、アプリケーションが保存している情報だけでなく、Windowsシステムに関する情報から、システムが内部で使用するために保存しているデータまで、様々なものが保存されているのです。

レジストリはシステムの根幹にもにかかわるものであるので、これが壊れてしまうと、OS自体が正常に動かなくなってしまいます。そのため、レジストリの操作はやや敬遠されがちな部分でもあります。しかし、ちゃんと決められた方法に従って操作すれば、そのような問題は起こらないですし、逆に、非常に便利なものとして活用できることでしょう。

レジストリを覗いてみる

Windowsには標準で「レジストリエディタ」があります。レジストリエディタは、その名が示すとおり、レジストリを編集するためのエディタです。まずは、これを使って、レジストリの中身を見てみることにしましょう。

レジストリエディタを起動するには、「スタート」メニューの「ファイル名を指定して実行」を選択し、『regedit』と指定してOKボタンを押します。すると、次のようなウィンドウが表示されます。

あらかじめ注意しておきますが、このレジストリの内容を不用意に変更すると、最悪の場合Windowsが起動しなくなる可能性すらあります。ここでのレジストリエディタの使用は、レジストリの中身を見てみることだけが目的ですので、くれぐれもデータを変更したりしないようにしてください。

ウィンドウの左側には、エクスプローラに似たツリービューが表示されていますね。実は、レジストリは、キー (key)値 (value) および実際のデータから構成されており、これらはファイルシステムのディレクトリ構造によく似たツリー型の階層構造をとっているのです。ファイルシステムと比較して、それぞれ以下のような対応関係があると考えると分かりやすいでしょう。(「」は「レジストリエントリ (registry entry)」と呼ばれることもあります。)

レジストリファイルシステム
キーディレクトリ
値(エントリ)ファイル
データ(ファイルの中に保存されている)データ

レジストリエディタのウィンドウの左側に表示されているのが、レジストリの「キー」のツリー型構造を表しているのです。また、ウィンドウの右側に表示されているのは、選択されているキーが持っている「値」の一覧です。値の名前と同時に、値のデータ型とデータの内容も一覧表示されていますね。

上の図では、ツリー構造のルート(マイ コンピュータ)に「HKEY_CLASSES_ROOT」や「HKEY_CURRENT_USER」などのいくつかのキーがあり、さらに、そのうち「HKEY_CURRENT_USER」の子キーがいくつか表示されています。あるキーが持つ子キーのことを一般に「サブキー」と呼びます。したがって、選択(反転表示)されているキーは、「HKEY_CURRENT_USER」キーの中の「Control Panel」サブキーの中の「Colors」サブキーである、ということです。

ファイルシステムではファイルパス(すなわちファイルが含まれているディレクトリの経路)を表すのに、ディレクトリをバックスラッシュ(日本語環境では円記号)「\」で区切って表していますが、レジストリの場合も同様にキーを「\」で区切って表します。例えば、上の図で選択されているキーは、

HKEY_CURRENT_USER\Control Panel\Colors

というように書き表されます。

一方、ウィンドウの右側に表示されているリストビューには、この「Colors」キーに保存されている値の名前とデータ型、そしてデータの内容の一覧が表示されています。「ActiveBorder」とか「ActiveTitle」などがキーの名前を表しています。一番上に書かれている「(既定)」は、「既定(デフォルト)の値」または「名前なしの値」と呼ばれているもので、それぞれのキーが1つ持つことのできる、名前を持たない値です。名前なしの値がアプリケーションの設定保存用に使用されることはあまり多くありません(使用できないわけではありませんが)。

値の一覧で「REG_SZ」と書かれているのは、その値が文字列型のデータを持っていることを示しています。レジストリには、文字列だけでなく数値やバイナリデータなどといったいろいろなデータ型を格納することができます。例えば、32ビット数値なら「REG_DWORD」と、バイナリデータなら「REG_BINARY」と表示されています。

アプリケーションの設定を保存するためのキー

レジストリには、さまざな情報が、決められたキーの中の決められた値に格納されています。しかし、どのキーにどのようなデータが格納されているか、というのは、格納されている情報の数が非常に多く、さらに、Windowsのバージョンによっても異なるので、ここでは詳しく述べていくことはできません。

今必要なのは、アプリケーション用の設定データをレジストリに格納するということです。このようなデータをどのキーに保存するべきかというのは、ちゃんと決められています。Windowsでは1つのマシンを複数のユーザーが使用する場合もあるので、ユーザーごとに異なる個別の情報と、どのユーザーにも適用される共通の情報とで、それぞれ分けて保存することができます。

  1. ユーザーごとに個別の情報

    それぞれのユーザーごとに異なるタイプの情報は、

    HKEY_CURRENT_USER\Software

    の下にアプリケーション独自のサブキーを作成して、そこに格納します。この Software キーの下に、会社名(作者名)、製品名(ソフトウェア名)、バージョン番号のサブキーを作成し、そこに格納するようにすることが推奨されています。例えば、

    HKEY_CURRENT_USER\Software\chokuto\HSP_App\1.0

    といった感じでキーを作成し、そこに値を作成してデータを格納します。バージョン番号のキーは作成されないことも多いです。異なるバージョンを共存させる場合や、以前のバージョンの設定が残っていることで新しいバージョンの動作がおかしくなる可能性がある場合も考えると、バージョンごとに別のキーに保存するのが安全かもしれませんが、必要ないと判断するなら、バージョンのサブキーは作成する必要はないでしょう。

  2. すべてのユーザーに共通の情報

    アプリケーションのインストールに関する情報や、マシン固有の情報など、どのユーザーにも共通して使用されるタイプの情報は、

    HKEY_LOCAL_MACHINE\Software

    の下にアプリケーション独自のサブキーを作成して、そこに格納します。この場合も上と同様で、 Software キーの下に、会社名(作者名)、製品名(ソフトウェア名)、バージョン番号のサブキーを作成し、そこに格納するようにすることが推奨されています。例えば、

    HKEY_LOCAL_MACHINE\Software\chokuto\HSP_App\1.0

    といった感じでキーを作成し、そこに値を作成してデータを格納します。ここでも、必要なければバージョン番号のキーは作成しなくてよいでしょう。

    このキーは、通常、アプリケーションのインストール時に作成および設定されます。また、NT系Windowsで管理者権限を持たないユーザーの場合には、このキーを読み取ることはできますが、変更することはできないようになっていることが多いですので、注意しましょう。

レジストリに書き込む

新しいレジストリキーの作成

では、実際にレジストリにデータを保存してみましょう。まず、レジストリキーを作成します。今回のスクリプトでは、

HKEY_CURRENT_USER\Software\chokuto\HSP_Test

というキーを作成することにしましょう。

レジストリキーを新しく作成するには、 RegCreateKeyEx 関数を呼び出します。

LONG RegCreateKeyExA(
    HKEY    hKey,             // キーのハンドル
    PCTSTR  pszSubKey,        // サブキーの名前
    DWORD   Reserved,         // 予約(0を指定)
    PTSTR   pszClass,         // クラス名
    DWORD   dwOptions,        // オプション
    REGSAM  samDesired,       // セキュリティアクセスマスク
    PSECURITY_ATTRIBUTES psa, // セキュリティ属性
    PHKEY   phkResult,        // ハンドルを格納する変数
    PDWORD  pdwDisposition
);

最初の hKey パラメータには、すでにオープンされているレジストリキーのハンドルを指定します。ただし、レジストリの最も上のキー「HKEY_CURRENT_USER」や「HKEY_LOCAL_MACHINE」などはあらかじめオープンされているということになっているので、これらを直接指定することができます。HKEY_CURRENT_USER は 0x80000001、HKEY_LOCAL_MACHINE は 0x80000002 と定義されています。

pszSubKey はサブキーの名前を表す文字列へのポインタを指定します。この名前は、 hKey で指定されたキーから見た名前を指定します。今回の例では、「Software\chokuto\HSP_Test」という文字列を指定します(ただし、スクリプト中では「\」を「\\」と書かなければならないことに注意)。

Reserved には常に 0 を、 pszClass には 0 (NULL) を指定しなければいけません。

dwOptions には、オプションを表す数値を指定します。通常は 0x00000000(REG_OPTION_NON_VOLATILE) を指定することになります。

samDesired には、レジストリへどのようなアクセスをしようとしているのかを指定します。通常は 0x20006 (KEY_WRITE) か 0x20019 (KEY_READ) か 0xF003F (KEY_ALL_ACCESS) を指定することになるでしょうが、もっと細かい指定をすることもできます。 KEY_ALL_ACCESS でフルアクセスを得ることは可能ですが、読み取りを行なうためのだけに常にフルアクセスを指定することはせず、必要なアクセス指定(例えば KEY_READ など)だけを指定するべきです。

psa はセキュリティ指定子を指定するパラメータですが、通常は 0 (NULL) を指定することになると思います。

phkResult には、作成したキーのハンドルを格納する変数のアドレスを指定します。レジストリを操作する関数では、この変数に格納されたハンドルによってキーを識別することになります。

pdwDisposition には、キーが新しく作成されたかどうかを表す値を格納するための変数のアドレスを指定します。関数呼び出しによって、新しいキーが作成された場合には、この変数に 1 (KEY_QUERY_VALUE) が格納されます。また、指定されたキーがすでに存在しており、そのキーがオープンされた場合には、この変数に 2 (REG_OPENED_EXISTING_KEY) が格納されます。ただし、この情報が必要ないのであれば、このパラメータに 0 (NULL) を指定することができます。

この関数の呼び出しが成功すると、戻り値は 0 (ERROR_SUCCESS) になります。失敗するとそれ以外の値になります。

この関数も含めて、レジストリ関連の関数は advapi32.dll により提供されています。LLMODモジュールではデフォルトでロードされていませんので、あらかじめ ll_libload 命令でDLLをロードしておく必要があります。WA_MACROでは advapi32.as として提供されています。

値の書き込み

次に、作成されたキーに値を格納します。ここでは、文字列データを「DataString」という名前の値として、また、数値データを「DataInteger」という名前の値として保存してみることにします。

レジストリに値を書き込むには、 RegSetValueEx 関数を使います。

LONG RegSetValueExA(
    HKEY    hKey,        // キーのハンドル
    PCTSTR  pValueName,  // 値の名前
    DWORD   Reserved,    // 予約(0を指定)
    DWORD   dwType,      // データのタイプ
    CONST BYTE *pData,   // データバッファのアドレス
    DWORD   cbData       // データサイズ
);

hKey には、最初に作成(またはオープン)したレジストリキーのハンドルを指定します。このハンドルは、少なくとも KEY_SET_VALUE アクセスを指定して取得されたものでなければいけません。 KEY_WRITE や KEY_ALL_ACCESS アクセスを持っているのなら問題ないです。

pValueName には、値の名前を指定します。ここでは「DataString」と「DataInteger」という名前の値を作成するので、これらの文字列へのポインタを指定します。また、今回は使用しませんが、名前なしの値(デフォルトの値)を設定する場合にはこのパラメータに 0 (NULL) を指定するか、または空文字列("")へのポインタを指定します。

Reserved は常に 0 を指定しなければいけません。

dwType には値のデータの型を示す数値を指定します。例えば、文字列型なら 1 (REG_SZ) を、32ビット数値なら 4 (REG_DWORD) を、バイナリデータなら 3 (REG_BINARY) を指定します。

pData にはデータを格納したバッファへのポインタを、 cbData には、そのデータのサイズをバイト単位で指定します。データサイズとしては、32ビット数値データなら 4 を、文字列データなら終端ヌル文字の1バイト分も含めた値(strlen 命令で得られるサイズ+1)を指定しなければいけません。

値がレジストリへ正常に書き込まれると、戻り値は 0 (ERROR_SUCCESS) になります。失敗するとそれ以外の値になります。

ハンドルのクローズ

レジストリへの操作が終了したら、 RegCloseKey 関数を呼び出して、ハンドルをクローズしなければいけません。

LONG RegCloseKey(
    HKEY hKey     // キーのハンドル
);

サンプルスクリプト(書き込み)

実際のサンプルスクリプトです。上で書いたとおり、

HKEY_CURRENT_USER\Software\chokuto\HSP_Test

というキーの中に文字列データを「DataString」という名前の値として、また、数値データを「DataInteger」という名前の値として書き込みます。

ここでは、関数の呼び出しにWA_MACROの advapi32.dll を使用します。以下のスクリプトを見てみますと、なんか異様に横に長いような気がしますが、これは、関数の引数の数が多いのに対して、HSPでは1つのコマンド(ここでは関数呼び出しを行なうマクロ)を複数行にまたがって記述できないためです。あまり気にしないように。(実は古いタイプの RegCreateKey 関数の方を使うと、もっと短くなるんですけど、これは互換性のためだけに残されているので、使うべきではないでしょう。)

注意:念のため、レジストリをバックアップしておくようにしましょう。

; レジストリにデータを書き込む
#include "wam/advapi32.as"

#define HKEY_CURRENT_USER       $80000001
#define REG_OPTION_NON_VOLATILE $00000000
#define KEY_WRITE               $00020006
#define REG_SZ                  $00000001
#define REG_DWORD               $00000004

; レジストリキーを作成
name = "Software\\chokuto\\API_Test"
RegCreateKeyEx HKEY_CURRENT_USER,PTR(name),0,NULL,REG_OPTION_NON_VOLATILE,KEY_WRITE,NULL,PTR(hkey),NULL
if dllret { dialog "キーを作成(オープン)できません",1 : end }

; 文字列データを値 "DataString" として書き込む
data = "サンプルデータ"
strlen l, data
RegSetValueEx hkey, SPTR("DataString"), 0, REG_SZ, PTR(data), l+1

; 数値データを値 "DataString" として書き込む
data = 123
RegSetValueEx hkey, SPTR("DataInteger"), 0, REG_DWORD, PTR(data), 4

; レジストリキーのハンドルをクローズ
RegCloseKey hkey
end

これで、データがレジストリに書き込まれました。実際に、レジストリエディタを実行させて調べてみると、ちゃんと書き込まれていることが確認できますね。(変更が反映されていない場合は、[F5]キーを押すか、「表示」メニューから「最新の情報に更新」を選択してください。)

レジストリから読み取る

では、レジストリに書き込んだ値を取得してみることにしましょう。

既存のレジストリキーのオープン

まず、値が格納されているレジストリキーをオープンして、そのハンドルを取得しなければいけません。これには、先ほどと同じように RegCreateKeyEx 関数を使用することができます。

一方、すでに対象となるキーが存在している場合には、 RegCreateKeyEx 関数の代わりに RegOpenKeyEx 関数を使うこともできます。指定されたキーがすでに存在していたときには、どちらの関数も、そのキーをオープンしてハンドルを返します。一方、指定されたキーが存在しなかったときには、 RegCreateKeyEx 関数では指定されたキーが新しく作成されるのに対して、 RegOpenKeyEx 関数では新しく作成されずにエラーが返されます。

LONG RegOpenKeyExA(
    HKEY   hKey,       // キーのハンドル
    PCTSTR pSubKey,    // オープンするサブキーの名前
    DWORD  ulOptions,  // 予約(0を指定)
    REGSAM samDesired, // セキュリティアクセスマスク
    PHKEY  phkResult   // ハンドルを格納する変数のアドレス
);

パラメータ数が少ないことを除けば、それぞれのパラメータについては、 RegCreateKeyEx 関数で指定しなければいけないものとほぼ同じです。今回は値の読み取りを行なうのですから、 samDesired パラメータには少なくとも KEY_QUERY_VALUE アクセスが必要です。 KEY_READ や KEY_ALL_ACCESS でもいいでしょう。

値の取得

レジストリのキーから値のデータを取得するには、 RegQueryValueEx 関数を使います。

LONG RegQueryValueExA(
    HKEY   hKey,        // キーのハンドル
    PCTSTR pValueName,  // 値の名前
    PDWORD pReserved,   // 予約(NULLを指定)
    PDWORD pType,       // データタイプを格納する変数
    PBYTE  pData,       // データを格納するバッファ
    PDWORD pcbData      // バッファサイズを入れた変数
);

hKey にはキーのハンドルを指定します。このハンドルは、少なくとも KEY_QUERY_VALUE アクセスを持っている必要があります。 KEY_READ や KEY_ALL_ACCESS はこれを含んでいるので、これらのいずれかのアクセスならOKです。

pValueName には値の名前を示す文字列へのポインタを指定します。

pReserved には常に 0 (NULL) を指定します。

pType には、値のデータの型を表す数値を格納するための変数のアドレスを指定します。例えば、指定された値が文字列データを持っている場合には、この変数に 1 (REG_SZ) が格納されます。すでにデータ型が分かっているなどの理由で、データ型を取得する必要がない場合には、このパラメータに 0 (NULL) を指定することができます。

pData には、取得されたデータを格納するバッファへのポインタを指定します。

pcbData には、 pData バッファのサイズをバイト単位で格納した変数のアドレスを指定します。この変数にはあらかじめバッファサイズを格納しておきますが、値が取得されると、その値のデータサイズで上書きされます。取得された値のデータが文字列の場合、このサイズには終端ヌル文字の1バイト分が含まれています。

あらかじめデータサイズが分かっている(少なくとも、データの最大サイズが分かっている)場合にはよいのですが、データサイズを知らない場合には、データを格納するバッファを確保することができませんね。この場合には、 pData に 0 (NULL) を指定し、 pcbData に変数のアドレスを指定すれば、必要なバッファサイズがその変数に格納されます。そして、そのサイズのバッファを確保した上で、もう一度関数を呼び出せばよいのです。

サンプルスクリプト(読み取り)

実際のサンプルスクリプトです。上でレジストリに保存したデータを読み取ってみましょう。

ここでは、値「DataString」が文字列型、値「DataInteger」が32ビット整数型であることがあらかじめ分かっているものとします。ただし、「DataString」の文字列データのサイズは分からず、最大サイズも決まっていないものとします。そのため、まず文字列データのサイズを取得してからバッファを確保し、その後でデータを取得する、という手順を踏まなければいけません。

#include "wam/advapi32.as"

#define HKEY_CURRENT_USER       $80000001
#define KEY_READ                $00020019

; レジストリキーをオープン
name = "Software\\chokuto\\API_Test"
RegOpenKeyEx HKEY_CURRENT_USER, PTR(name), 0, KEY_READ, PTR(hkey)
if dllret { dialog "キーをオープンできません",1 : end }

; 値 "DataString" の文字列データのサイズを取得
l=0
RegQueryValueEx hkey, SPTR("DataString"), 0, NULL, NULL, PTR(l)
if dllret {
    mes "DataString  : (データを取得できません)"
} else {
    ; バッファ確保後に値 "DataString" の文字列データを取得
    sdim data, l
    RegQueryValueEx hkey, SPTR("DataString"), 0, NULL, PTR(data), PTR(l)
    mes "DataString  : "+data
}

; 値 "DataInteger" の数値データのサイズを取得
data = 0 : l=4
RegQueryValueEx hkey, SPTR("DataInteger"), 0, NULL, PTR(data), PTR(l)
if dllret {
    mes "DataInteger : (データを取得できません)"
} else {
    mes "DataInteger : "+data
}

; レジストリキーのハンドルをクローズ
RegCloseKey hkey
stop

レジストリの値の削除・キーの削除

レジストリのキーや値は、明示的に削除しない限りは残り続けます。アプリケーションでは通常、アンインストールするときに、自分が使用していたレジストリキーを削除するようになっていますよね(アプリケーションによってはアンインストールしてもレジストリ情報は残したままにするものもありますけど)。こういった作業は普通アンインストーラーがやるものですが、アプリケーション自身がレジストリの値やキーを削除したいときもあるでしょう。

値の削除

値を削除するには、 RegDeleteValue 関数を使います。

LONG RegDeleteValueA(
    HKEY    hKey,        // キーのハンドル
    PCTSTR  pszValuName  // 削除する値の名前
);

hKey には、削除対象となる値を持っているキーのハンドルを指定します。このハンドルは、少なくとも KEY_SET_VALUE アクセスを持っている必要があります。 KEY_WRITE や KEY_ALL_ACCESS ならばこのアクセスを含んでいます。

pszValuName には、削除する値の名前を表す文字列へのポインタを指定します。


実際に値を削除するスクリプトを書いてみましょう。先ほど作成したレジストリの値のうち、「DataString」の方を削除してみましょう。

; レジストリの値を削除する
#include "wam/advapi32.as"

#define HKEY_CURRENT_USER       $80000001
#define KEY_WRITE               $00020006

; レジストリキーをオープン
name = "Software\\chokuto\\API_Test"
RegOpenKeyEx HKEY_CURRENT_USER, PTR(name), 0, KEY_WRITE, PTR(hkey)
if dllret { dialog "キーをオープンできません",1 : end }

; 値 "DataString" を削除する
RegDeleteValue hkey, SPTR("DataString")

; レジストリキーのハンドルをクローズ
RegCloseKey hkey
end

レジストリエディタで確認してみると、実際に値「DataString」が削除されて、「DataInteger」の方しか残っていないことが確認できます。

キーの削除

キーを削除するには、 RegDeleteKey 関数を使います。

LONG RegDeleteKeyA(
    HKEY    hKey,       // キーのハンドル
    PCTSTR  pszSubKey   // 削除するキーの名前
);

hKey には、削除対象となるキーを含むキーのハンドルを指定します。このハンドルは、これまでの KEY_*** という定数名で表されるアクセス権とは異なった、 DELETE アクセス(0x10000 と定義されています)を少なくとも持っている必要があります。このアクセスは KEY_ALL_ACCESS には含まれていますが、 KEY_READ や KEY_WRITE には含まれていないので注意しましょう。ただし、通常は HKEY_CURRENT_USER などの定義済みキーを指定するでしょうから、あまり心配する必要はないかもしれません。

pszSubKey には削除するキーの名前を指定します。より下の階層にあるキーを削除するには「\」で区切って指定することができます。例えば「Software\chokuto\HSP_Test」のように指定します。


実際のスクリプトは以下のようになります。キーのハンドルを指定するパラメータには定義済みキー(HKEY_CURRENT_USER)を指定するので、レジストリキーのオープンとクローズの操作が不要になっています。

; レジストリのキーを削除する
#include "wam/advapi32.as"

#define HKEY_CURRENT_USER       $80000001

; キー "Software\chokuto\API_Test" を削除する
name = "Software\\chokuto\\API_Test"
RegDeleteKey HKEY_CURRENT_USER, PTR(name)
if dllret { dialog "キーを削除できません",1 : end }
end

レジストリエディタで、キーが正しく削除できていることが確認できます。