C++Builderを使用してWindowsアプリケーションを開発する際、ユーザーの設定情報を保存することは、アプリケーションの使い勝手を向上させるために重要です。この記事では、C++Builderで、設定情報の読み書きする方法について説明します。
設定情報を保存する方法
設定情報を保存する方法として、以下の方法が一般的に使用されます。
INIファイルを使用する方法
INIファイルは、Windows 3.xの時代から使用されてきた設定ファイル形式で、構造がシンプルで読みやすいことから広く利用されました。Windows 95や98でも標準的な方法として使われていました。
INIファイルは、テキストベースのファイル形式で、C++Builderでは、TIniFile
クラス、またはTMemIniFile
クラスを使用してINIファイルにアクセスします。
レジストリを使用する方法
Windows 95から導入されたレジストリは、システム全体およびアプリケーションの設定情報を集中管理するためのデータベースです。INIファイルの欠点(分散管理やパフォーマンスの問題)を克服するために設計されました。レジストリはキーと値のペアで構成され、階層構造を持っています。
C++Builderでは、TRegistry
クラスを使用してレジストリにアクセスします。レジストリに情報を保存する場合、アプリケーションの設定情報は通常、HKEY_CURRENT_USER
またはHKEY_LOCAL_MACHINE
のサブキーに保存されます。
XMLファイルを使用する方法
XML(Extensible Markup Language)は、1990年代後半からデータ交換や保存の標準形式として広く採用されるようになりました。階層構造を持つデータの保存に適しており、設定ファイルとしても利用されます。XMLは、タグと属性を使用してデータを表現します。
C++Builderでは、TXMLDocument
クラスを使用してXMLファイルにアクセスします。
JSONファイルを使用する方法
JSON(JavaScript Object Notation)は、2000年代に入り、軽量で人間に読みやすく、機械で解析しやすいデータフォーマットとして普及しました。特にWebアプリケーションで広く使用されており、設定ファイルとしても利用されます。JSONは、キーと値のペアでデータを表現します。オブジェクトや配列を含むことができます。
C++Builderでは、TJSONObject
クラスを使用してJSONファイルにアクセスします。
各方法について
これらの方法は、それぞれの時代や用途に応じて適切に選択されてきました。INIファイルはシンプルで使いやすく、レジストリはシステム全体の設定管理に適しています。XMLは階層構造のデータに適しており、JSONは軽量でモダンなデータ交換形式として広く利用されています。アプリケーションの要件やデータの複雑さに応じて、適切な方法を選択することが重要です。
INIファイルを使用した設定情報の読み書き
INIファイルを使用した設定情報の読み書きについて説明します。INIファイルは、構造がシンプルなテキストファイルなので、エディタを使用して簡単に設定の変更ができるので、アプリケーションの開発時にパラメータを変更して動作確認をするときなど非常に便利です。筆者はINIファイルを良く使用しています。
使用するクラス
C++Builderでは、TIniFile
クラス、またはTMemIniFile
クラスを使用してINIファイルにアクセスします。先ず、これらのクラスについて説明します。
筆者は、TMemIniFile
クラスを使用しています。UpdateFileメソッドを呼び出すのを忘れると、INIファイルに書込が行われませんのでご注意ください。
TIniFileクラス
TIniFile は、INI ファイルにあるアプリケーション固有の情報や設定を格納したり取得します。 TIniFile を使用すると、標準的な INI ファイルにあるアプリケーション固有の情報や設定の格納および取得を行うことができます。 INI ファイルには、”セクション” という論理的なグループに分かれて情報が格納されています。 各セクション内では、名前の付いたキーの中に実際のデータ値が格納されます。 キーの形式は次のとおりです。
<キー名> = <値>
概要
- ファイル入出力を行う標準的なINIファイル操作クラス
- ファイルへの直接書き込みと読み込みが可能
- 読み書きのたびにファイルを開閉するため、頻繁なアクセスには向かない
利点
- シンプルでわかりやすい
- 汎用性が高い
- 他のプログラムとの互換性が高い
欠点
- 頻繁なアクセスにはパフォーマンスが低下する
- ファイルロックなどの競合が発生する可能性がある
TMemIniFileクラス
TMemIniFile は、すべての変更を INI ファイルにバッファリングします。オブジェクトが初めて作成されたときに、INI ファイルは一度読み込まれます。INI ファイルからのデータは、階層化された文字列リストに格納されます。INI ファイルにおける各セクションは、最上位の文字列リストでそれぞれ 1 つの要素となり、この中の各要素がまたそれぞれ文字列リストを保有します。保有されている文字列リストのそれぞれの中にある各要素は、セクション内のキーを表します。データが読み込まれると、データへの変更はいずれもメモリに格納されます。メモリからのデータを、関連付けられている INI ファイルに書き戻すには、UpdateFile メソッドを呼び出します。TMemIniFile は、TIniFile と同様の動作する関連オブジェクトですが、メモリ内のバッファに書き込むことでディスク アクセスが最小限に抑えられます。
概要
- メモリ上にINIファイルの内容を保持するクラス
- ファイル入出力を行わず、高速なアクセスが可能
- 変更内容をファイルに書き込むには、UpdateFileメソッドを呼び出す必要がある
利点
- 頻繁なアクセスでもパフォーマンスが低下しない
- ファイルロックなどの競合が発生しない
- 自動保存機能により、変更内容を確実に保存できる
欠点
- ファイル入出力を行う必要があるため、TIniFileクラスより複雑
- メモリ使用量が多くなる
TMemIniFileクラスの使用方法
INIファイルからの読み出し/書込のサンプルコードです。
INIファイルからの読み出し
void __fastcall TChgDataURIAppForm::ReadIniFile(void)
{
TMemIniFile *ini = new TMemIniFile(inifname);
String Param = "Markdown";
// ===========================================================
RGDataType->ItemIndex = ini->ReadInteger(Param, "Type", 0);
CB_AddTitle->Checked = ini->ReadBool( Param, "fAddTitle", 0); // ・タイトルを付ける
CB_AddPath->Checked = ini->ReadBool( Param, "fAddPath", 0); // ・パスの付加
Ed_AddPath->Text = ini->ReadString( Param, "Path", "./image/");
delete ini;
}
new を使わずに、std::unique_ptrを使用した場合
void __fastcall TChgDataURIAppForm::ReadIniFile(void)
{
std::unique_ptr<TMemIniFile> ini(new TMemIniFile(inifname));
String Param = "Markdown";
// ===========================================================
RGDataType->ItemIndex = ini->ReadInteger(Param, "Type", 0);
CB_AddTitle->Checked = ini->ReadBool( Param, "fAddTitle", 0); // ・タイトルを付ける
CB_AddPath->Checked = ini->ReadBool( Param, "fAddPath", 0); // ・パスの付加
Ed_AddPath->Text = ini->ReadString( Param, "Path", "./image/");
}
INIファイルへの書込
void __fastcall TChgDataURIAppForm::WriteInifile(void)
{
TMemIniFile *ini = new TMemIniFile(inifname);
String Param = "Markdown";
// ===========================================================
ini->WriteInteger(Param, "Type", RGDataType->ItemIndex);
ini->WriteBool( Param, "fAddTitle", CB_AddTitle->Checked); // ・タイトルを付ける
ini->WriteBool( Param, "fAddPath", CB_AddPath->Checked); // ・パスの付加
ini->WriteString( Param, "Path", Ed_AddPath->Text);
ini->UpdateFile();
delete ini;
}
new を使わずに、std::unique_ptrを使用した場合
void __fastcall TChgDataURIAppForm::WriteInifile(void)
{
std::unique_ptr<TMemIniFile> ini(new TMemIniFile(inifname));
String Param = "Markdown";
// ===========================================================
ini->WriteInteger(Param, "Type", RGDataType->ItemIndex);
ini->WriteBool( Param, "fAddTitle", CB_AddTitle->Checked); // ・タイトルを付ける
ini->WriteBool( Param, "fAddPath", CB_AddPath->Checked); // ・パスの付加
ini->WriteString( Param, "Path", Ed_AddPath->Text);
ini->UpdateFile();
}
設定ファイルを作成するフォルダ
INIファイルを格納するフォルダですが、通常、Program FilesまたはProgram Files (x86)フォルダにインストールされることが多いと思います。しかし、これらのフォルダにはユーザーアカウント制御(UAC)によるセキュリティがかかっており、一般ユーザー権限では書き込みが制限されています。そのため、実行ファイルと同じフォルダに設定ファイルを作成すると、一般ユーザー権限では書き込むことができません。このような場合には、以下に説明するユーザーのホームパスに設定ファイルを作成することをお勧めします。
実行ファイルと同じフォルダに作成
C++Builderで、実行ファイルのフルパスを取得は、Application->ExeName で可能です。コマンドライン引数用の変数ParamStrのParamStr(0)を使用しても取得できますが、Application->ExeName の方を使用した方が良いようです。
拡張子を”.ini”に変換するには、ChangeFileExt関数か、TPath::ChangeExtension関数を使用します。
以下、サンプルコードです。
ChangeFileExt関数を使用
// インクルードファイル
#include <System.SysUtils.hpp>
// 使用例
String inifname = ChangeFileExt(Application->ExeName, ".ini");
TPath::ChangeExtension関数の使用
// インクルードファイル
#include <System.IOUtils.hpp>
// 使用例
String inifname = TPath::ChangeExtension(Application->ExeName, ".ini");
ユーザーのホームパスに作成
ユーザーのホームパスにINIファイルを作成するには、GetHomePath関数を使用してユーザーのホームパスを取得します。
C:\Users\%USERNAME%\AppData\Roaming\<CompanyName>\<ProductName>\
に、INIファイルを作成するサンプルコードです。
MakeIniFileName関数を以下のように使用します。
String inifname = MakeIniFileName("KeiYou");
MakeHomePath関数
C:\Users\%USERNAME%\AppData\Roaming\<CompanyName>\<ProductName>\ というファイル名を作ります。MakeIniFileName関数から呼び出される関数です。
static String MakeHomePath(String CompanyName, String ProductName)
{
String FilePath;
String filename;
filename = ChangeFileExt(ExtractFileName(ProductName), "");
FilePath = GetHomePath(); // DispMessage(FilePath);
FilePath += (String)"\\" + CompanyName + "\\" + filename;
if( DirectoryExists(FilePath) )
{
//
} else {
if(ForceDirectories(FilePath) == false)
{
FilePath = GetHomePath(); // for Fail Safe
}
}
return FilePath;
}
MakeIniFileName関数
CompanyNameを引数として渡して、ユーザーのホームパス対応のファイル名を作成する関数です。
static String MakeIniFileName(String CompanyName)
{
String inifname;
String FilePath;
String fname;
FilePath = MakeHomePath(CompanyName, Application->ExeName);
fname = ChangeFileExt(ExtractFileName(Application->ExeName), ".ini");
inifname = FilePath + "\\"+ fname;
return inifname;
}
GetHomePath関数
GetHomePathで取得する各プラットフォームでのユーザーのホームパスです。
プラットフォーム | ホームパス |
---|---|
Windows XP | C:\Documents and Settings\\Application Data |
Windows Vista 以降 | C:\Users\\AppData\Roaming |
OS X | /Users/ |
iOS デバイス | /private/var/mobile/Containers/Data/Application/ |
iOS シミュレータ | /Users//Library/Developer/CoreSimulator/Devices//data/Containers/Data/Application/ |
Android | /data/data//files |
Linux | /home/ |