2024/06/03 電子書籍「電子書籍出版・技術文書作成を劇的に加速!秀丸エディタ + Markdown + Pandocの驚異」を出版

Windowsアプリケーションの設定情報の読み書き

C++Builder

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ファイル操作クラス
  • ファイルへの直接書き込みと読み込みが可能
  • 読み書きのたびにファイルを開閉するため、頻繁なアクセスには向かない
利点
  • シンプルでわかりやすい
  • 汎用性が高い
  • 他のプログラムとの互換性が高い
欠点
  • 頻繁なアクセスにはパフォーマンスが低下する
  • ファイルロックなどの競合が発生する可能性がある
System.IniFiles.TIniFile - RAD Studio API Documentation

TMemIniFileクラス

TMemIniFile は、すべての変更を INI ファイルにバッファリングします。オブジェクトが初めて作成されたときに、INI ファイルは一度読み込まれます。INI ファイルからのデータは、階層化された文字列リストに格納されます。INI ファイルにおける各セクションは、最上位の文字列リストでそれぞれ 1 つの要素となり、この中の各要素がまたそれぞれ文字列リストを保有します。保有されている文字列リストのそれぞれの中にある各要素は、セクション内のキーを表します。データが読み込まれると、データへの変更はいずれもメモリに格納されます。メモリからのデータを、関連付けられている INI ファイルに書き戻すには、UpdateFile メソッドを呼び出します。TMemIniFile は、TIniFile と同様の動作する関連オブジェクトですが、メモリ内のバッファに書き込むことでディスク アクセスが最小限に抑えられます。

概要
  • メモリ上にINIファイルの内容を保持するクラス
  • ファイル入出力を行わず、高速なアクセスが可能
  • 変更内容をファイルに書き込むには、UpdateFileメソッドを呼び出す必要がある
利点
  • 頻繁なアクセスでもパフォーマンスが低下しない
  • ファイルロックなどの競合が発生しない
  • 自動保存機能により、変更内容を確実に保存できる
欠点
  • ファイル入出力を行う必要があるため、TIniFileクラスより複雑
  • メモリ使用量が多くなる
System.IniFiles.TMemIniFile - RAD Studio API Documentation

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 XPC:\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/
GetHomePathで取得する各プラットフォームでのユーザーのホームパス
タイトルとURLをコピーしました