syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

INIファイルでUnicodeを扱う

今更ですが今日はINIファイルのお話です。初めに断っておきますが、ぶっちゃけWindowsアプリケーションでレガシーなINIファイルを使うのはもうやめましょうWindowsアプリケーション設定の管理には、今後はレジストリXMLファイルなどを使うべきです。

そもそもINIファイルとは?

INIファイルは昔からWindowsアプリケーションの設定管理に使われている簡易テキストフォーマットで、テキストエディターでも簡単に閲覧・編集することができるうえにXMLと比べて構造がシンプルなため、いまだによく使われていますが、その歴史はWin16時代にまでさかのぼります。自分はMS-DOSを少し使っていたことがあるものの、Win16を使ったことはありませんが、Windowsアプリケーションを開発していると、どうしてもINIファイルのようにWin16時代から引きずられてきた負の遺産に直面せざるを得ないことが多々あります。特にWin9x時代あるいはそれ以前に開発された昔のアプリケーションをメンテナンスする場合には、互換性維持のため泣く泣くINIファイルを使い続けないといけないこともあります。

INIファイルでは、具体的には下記のようにレコードを1行ずつ記録していきます。セクションやキーは複数含むことができますが、階層構造は扱えません。キーと値をつなぐ「=」の前後の空白は無視されます。

[SectionName]
KeyName=Value
; This is a comment.

コメントはセミコロン「;」で始め、改行で終わります。セミコロンではなく「#」をコメント開始記号に使っている人がときどきいますが、マイクロソフト仕様のINIファイルでは通用しないので注意しましょう。詳しくはWikipediaなどを参照してください。

Comments (any line that starts with a semicolon) are stripped out and not returned in the lpReturnedString buffer.

Win32 APIでINIファイルを扱う場合、下記のような関数を使います。

ほかにも、複数のレコードを一括して読み書きするGetPrivateProfileSection/WritePrivateProfileSectionや、バイナリデータを16進数表記文字列で扱うGetPrivateProfileStruct/WritePrivateProfileStructなどもありますが、たいていはXXXString関数で事足りるでしょう。これらのAPIは、一部の引数にNULLを指定すると挙動が変わるなどかなり高機能ですが、ややモノリシックな感は否めません。もし互換APIを自前で書こうとすると、存外にかなり大変だと思います。

なお、.NET FrameworkにはレガシーなINIファイルを扱うAPIは用意されていません。P/Invokeなどを利用して上記Win32 APIを使うか、自前でパーサーを書く必要があります。

INIファイルの欠点

INIファイルをWin32 APIで読み書きする場合、デフォルトではANSIマルチバイトエンコーディングが使われます。つまり、Windowsのシステムロケール設定(CP932/CP1252などのコードページ)に左右されることになります。例えば、日本語版WindowsではCP932 (Shift_JIS) が使われることになります。たとえUnicode対応のW系API関数(名前の末尾がWとなっている関数)を使って書き込んでも、ファイル出力する際にマルチバイト文字セット (MBCS) に強制変換されてしまいます。これは国際化対応が必要となるアプリケーションでは致命的な制約です。ANSIマルチバイトエンコーディングのテキストファイルでは、現在OSに設定されているシステムロケールに対応するANSIマルチバイト文字/文字列にマッピングできないようなUnicode文字/文字列を直接読み書きすることができないからです。セクション名・キー名はもちろん値の文字列もコメントもASCIIのみに限定する、あるいは読み書きの際に自前のエスケープやエンティティ参照・数値文字参照・パーセントエンコーディングを駆使する、という思い切った運用で回避するという方法もありますが……

しかし、実はUTF-16のBOMを持つテキストファイルをあらかじめ作成しておき、そのファイルに対して前述のWin32 APIでアクセスすると、UTF-16のINIファイルを直接扱うことができるようになります。このときW系関数を使えばUnicode文字/文字列を入出力できます。つまり、INIファイルでまともにUnicode文字/文字列を扱えるようになります。どうしてもINIファイルフォーマットを使い続けなければならない場合、UTF-16のINIは選択肢として検討してもよいでしょう。

そのほか、INIファイルでUTF-8エンコーディングを使う方法も一応あるのですが、これは制約の多い禁じ手です。説明と比較のため、UTF-16/UTF-8/ANSIのINIファイルをそれぞれ読み書きするC#サンプルプログラムを下記にアップロードしていますが、ANSI同様にUTF-8を使うのは避けたほうがよいでしょう。Win32 APIではなく自前でINIのパーサーを書くというのであれば、別にUTF-8を使ってもかまいませんが……

INIファイルの代替

INIファイルはすでに述べているようにレガシーな非推奨技術です。今後いきなりWin32 APIによるサポートが終了するといったことは考えられませんが、少なくとも積極的なサポートや技術革新はされないことくらいは肝に銘じておきましょう。
INIファイルの代替としては、レジストリXMLファイルがあります。

devblogs.microsoft.com

レジストリ

Windowsレジストリは大きく分けてシステムレジストリとユーザーレジストリの2つがあり、前者はオペレーティングシステム全体に影響を与えるため、書き換えには管理者権限(管理者特権)が必要となりますが、後者はWindowsログインユーザーごとにエントリが作成されるため、書き換えに特権は不要です。レジストリの実体は巨大なバイナリデータベースファイルの集合なので、バイナリデータも直接読み書きしやすいのが特徴です。文字列もUnicodeベースで標準的に管理されるため、INIファイルのような制限はありません。ただ、エンドユーザーがレジストリを編集するためには、起動に管理者特権が必要なレジストリエディター(regedit.exe)を使用しないといけないし、バックアップやレストアもファイルの単純コピーというわけにはいかないので、INIファイルほど気軽に扱えるものではないことは確かです。また、レジストリに依存してしまうと、Windows以外のプラットフォームにアプリケーションを移植する際にも問題となります。

XMLファイル

データ記述言語のデファクトスタンダードともいえるのがXMLです。XMLは主にWebで用いられることが多い技術ですが、MSXML/XmlLiteや.NET Framework/WinRTのようなマイクロソフト公式ライブラリによって標準サポートされているため、デスクトップアプリやUWPアプリでも扱いやすくなっています。Unicodeテキストの扱いも標準化されていますし、テキストフォーマットなので人間が直接編集するのにも困りません。バックアップやレストアもファイルの単純コピーで実施できるため、エンドユーザーにとっても扱いやすいです。欠点としてはXML自体がかなり巨大な仕様であるため、単純なアプリケーション設定を管理する方法としてはややオーバースペック気味であること、また特にDOMパーサーが重めであることが挙げられます。

汎用スクリプト言語/データ記述言語

そのほかの代替手段としては、Lua/Squirrel/Python/IronPythonといった汎用スクリプト言語や、JSONのようなデータ記述言語の処理系をアプリケーションに組み込んでしまうという方法も挙げられます。組み込みにはそれなりに手間がかかりますが、これらの言語は柔軟性が高く、スクリプト中で可変長配列データや改行を含むRaw文字列などを扱いやすいため、場合によってはINIやXMLよりも設定ファイルフォーマットとして適していることがあります。

※2021-08-15追記:
Windows 8以降のWinRTでは、Windows.Data.Jsonが利用できます。.NET FrameworkにはJSONのサポートがありませんが、.NET Coreでは3.0以降でSystem.Text.Jsonがサポートされています。

結論

繰り返しになりますが、INIファイルはもうやめましょう