C#でGetPrivateProfile*を使わずにINIファイルを扱う

ほんとにWindowsの世界では、定義をどこに浮かすのか紆余曲折が激しくて良く分からなくなる :? 考えられる候補は以下のような感じなのだが、INIファイルはそのお手軽さから結構生き残っていたりする。

  1. XMLファイル(動的プロパティも含む)
  2. レジストリ
  3. INIファイル

INIファイルを扱うためには、GetPrivateProfileStringとかのWin APIが用意されているわけだが…これらは、MSDNで以下のように書かれており、推奨されないAPIになっている。

注意  この関数は、16 ビット Windows ベースのアプリケーションとの互換性を保つ目的でのみ提供されています。Win32 ベースのアプリケーションでは、初期化情報をレジストリに格納してください。

しかし、このAPIは時期的に微妙だったのか、理由はわからないのだがWindows CEではサポートされてなかったりする :shock:

なので、C#でINIファイルを扱う方法を検索するとヒットするページで書かれているGetPrivateProfile*系のAPIを呼び出す方法はWindows CEでは使えない :(

と言うことで、INIファイルを扱うクラスを適当にでっちあげてみる :) INIファイルには、RFCもないし方言もあるので以下のような形式を前提としてみた。

; comment
[section] ; comment
KEY = VALUE  ; comment

まぁ、基本的なINIファイル形式ですが…以下のような制約があるので注意してください。普通に使用する分には問題ないと思います ;)

  • セクションは行の先頭から記述されている必要がある
  • セクション名の空白文字は有効になる
  • キー名の空白文字は無効になる
  • キー名にセミコロンは使用できる
  • セミコロン(;)以降はコメントとして扱うためVALUE(値)にセミコロンを含めることはできない
  • VALUE(値)の後ろの空白文字(スペース, タブ)はTrimする

他にもありそうな気もしますが…。

まず、呼び出し側と言うか使い方は以下のような感じ。

IniFile ini = new IniFile();         // INIファイルインスタンス生成
ini.Filename = "\\TEST.INI";    // INIファイル名設定(フルパス)
if (ini.Read() == false)           // INIファイルの読み込み
{
        // 読み込み失敗
        Console.Error.WriteLine("INI file error.");
        return;
}
// [SECTION1]
// KEY1=HOGE FUGA
// KEY2=8080
string value1 = ini.GetString("SECTION1", "KEY1"); // value1 = "HOGE FUGA"
int value2 = ini.GetStringInt("SECTION1", "KEY2", 80); // value2 = 8080

で、INIファイルを扱うクラス側のコード。バグなど含んでる可能性があるので、自己責任で :arrow:

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Text.RegularExpressions;
 
namespace Slashcolon.Common
{
    /// <summary>
    /// INIファイルを扱うクラス
    /// </summary>
    public class IniFile
    {
        /// <summary>
        /// INIファイル名
        /// </summary>
        private string filename;
 
        /// <summary>
        /// セクションとキー名をキーにした値のハッシュテーブル
        /// </summary>
        private Hashtable hash;
 
        /// <summary>
        /// 定義ファイル名
        /// </summary>
        public string Filename
        {
            get { return filename; }
            set { filename = value; }
        }
 
        /// <summary>
        /// INIファイルを扱うクラスのコンストラクタ
        /// </summary>
        public IniFile()
        {
            filename = null;
            hash = new Hashtable();
        }
 
        /// <summary>
        /// 定義ファイルから整数値を取得する
        /// </summary>
        /// <param name="section">取得するセクション名</param>
        /// <param name="key">取得するキー名</param>
        /// <param name="value">取得できない場合に返すデフォルト値</param>
        /// <returns>取得した値</returns>
        public int GetInt(string section, string key, int value)
        {
            int result = value;
            try
            {
                // 文字列で取得して整数値へ変換
                result = int.Parse(GetString(section, key));
            }
            catch
            {
                // 変換失敗
            }
            return result;
        }
 
        /// <summary>
        /// 定義ファイルから文字列を取得する
        /// </summary>
        /// <param name="section">取得するセクション名</param>
        /// <param name="key">取得するキー名</param>
        /// <returns>取得した文字列。取得失敗の場合はnullを返す</returns>
        public string GetString(string section, string key)
        {
            return (string)hash[section + "#" + key];
        }
 
        /// <summary>
        /// 定義ファイルをメモリに読み込む
        /// </summary>
        /// <returns>読み込み結果 true 成功 false 失敗</returns>
        public bool Read()
        {
            int codePage = 932; // MS932(Shift_JIS)コードページ
            string section = "";
            string buffer;
            string[] result;
 
            try
            {
                using (StreamReader sr = new StreamReader(filename, Encoding.GetEncoding(codePage)))
                {
                    while ((buffer = sr.ReadLine()) != null)
                    {
                        if (buffer == "")
                        {
                            continue;
                        }
                        result = IniParse(buffer);
                        if (result.Length == 0)
                        {
                           continue;
                        }
                        if (result.Length == 1)
                        {
                            // セクション名設定
                            section = result[0];
                        }
                        if (result.Length == 2)
                        {
                            // ハッシュテーブル設定
                            hash.Add(section + "#" + result[0], result[1]);
                        }
                    }
                }
            }
            catch
            {
                return false;
            }
            return true;
        }
 
        private string[] IniParse(string buffer)
        {
            ArrayList list = new ArrayList();
 
            if (Regex.IsMatch(buffer, "^\\[(.+)\\](\\s*);(.*)$|^\\[(.+)\\]$"))
            {
                // セクション
                string section = Regex.Replace(buffer, "\\](.*)$", "");
                section = Regex.Replace(section, "^\\[", "");
                list.Add(section);
            }
            if (Regex.IsMatch(buffer, "^(.+)=(.*)$"))
            {
                // キー
                string key = Regex.Replace(buffer, "(\\s*)=(.*)", "");
                key = Regex.Replace(key, "(\\s+)", "");
                list.Add(key);
                string value = Regex.Replace(buffer, "(.*)=(\\s*)", "");
                value = Regex.Replace(value, "(\\s*);(.*)$", "");
                list.Add(value);
            }
            return (string[])list.ToArray(typeof(string));
        }
    }
}

━超適当な作りだなぁ~ :oops:

もっと、効率的でスマートな方法があると思うので教えていただければうれしいです。

追記 2008-08-28 01:17:11
usingキーワードを使う形に修正して、finallyブロックを削除してみました。using便利ですね ;)

Be Sociable, Share!

TrackBack URL :

Comments

  1. 2月 27th, 2009 | 12:17 AM

    CでGetPrivateProfile*を使わずにINIファイルを扱う

    以前、『Slashcolon /: » C#でGetPrivateProfile*を使わずにINIファイルを扱う』というエントリで超適当なコードを晒したわけですが…今回は、そのC言語版です
    要するに客先指定で、C#ではなくC/…

Leave a reply