UTF8 ini

Vistaになってiniファイルに対して勝手なことされるので、.netのcpp/cliでUTF8でiniを扱うクラスを作りました。Windows98系でも動くはずです。
iniファイルとの整合性は全然取れてませんけど。


// WProfile.h
#pragma onceusing namespace System;
using namespace System::Collections;
namespace WProfiler {public ref class WProfile
 {
 private:
  WProfile(){}
  ~WProfile(){}

static Hashtable^ readall(String^ inipath);
  static bool writeall(Hashtable^ al, String^ inipath);
 public:
  static System::Boolean WProfile::WGetPrivateProfileInt(String^ app, String^ key, int def, int% ret, String^ inipath);
  static System::Boolean WProfile::WWritePrivateProfileInt(String^ app, String^ key, int val, String^ inipath);
  static System::Boolean WProfile::WGetPrivateProfileString(String^ app, String^ key, String^ def, String^% ret, String^ inipath);
  static System::Boolean WProfile::WWritePrivateProfileString(String^ app, String^ key, String^ val, String^ inipath);
 };
}


// これは メイン DLL ファイルです。
#include "stdafx.h"#include
#include "WProfile.h"
using namespace System;
using namespace System::Collections;

namespace WProfiler {

Hashtable^ WProfile::readall(String^ inipath)
 {
  Hashtable^ al = gcnew Hashtable;
  try {
  System::IO::StreamReader sr(inipath, System::Text::Encoding::UTF8);

String^ line = nullptr;
  Hashtable^ cursec = nullptr;

while ( (line=sr.ReadLine()) != nullptr )
  {
  line = line->TrimStart();
  if ( line->Length==0 || line[0] == L'#' )
  continue;

if ( line[0] == L'[' )
  {
  String^ secname = line->Trim( gcnew array{'[', ']'} );
  cursec = (Hashtable^)al[secname];
  if ( cursec == nullptr )
  {
  cursec = gcnew Hashtable;
  al[secname] = cursec;
  }
  continue;
  }
  else
  {
  if ( cursec == nullptr )
  continue;

array^ vals = line->Split( gcnew array{L'='}, 2 );
  if ( vals->Length < 2 )
  cursec[vals[0]] = L"";
  else
  cursec[vals[0]] = vals[1];
  }
  }
  }
  catch ( System::Exception^ )
  {
  return nullptr;
  }
  return al;
 }

bool WProfile::writeall(Hashtable^ al, String^ inipath)
 {
  if(!al)
  return false;

try {
  System::IO::StreamWriter sw(inipath, false, System::Text::Encoding::UTF8);

for each(String^ secname in al->Keys)
  {
  sw.Write(L"[");
  sw.Write(secname);
  sw.Write(L"]");
  sw.WriteLine();

Hashtable^ sec = (Hashtable^)al[secname];
  if ( !sec )
  continue;

for each( String^ keyname in sec->Keys )
  {
  sw.Write(keyname);
  sw.Write(L"=");
  sw.Write(sec[keyname]);
  sw.WriteLine();
  }
  sw.WriteLine();
  }
  }
  catch(System::Exception^ )
  {
  return false;
  }
  return true;
 }

System::Boolean WProfile::WGetPrivateProfileInt(String^ app, String^ key, int def, int% ret, String^ inipath)
 {
  ret = def;
  Hashtable^ al = readall(inipath);
  if ( !al )
  return false;

Hashtable^ sec = (Hashtable^)al[app];
  if ( !sec )
  return false;

String^ val = (String^)sec[key];
  if ( !val )
  return false;

if ( !System::Int32::TryParse(val, ret) )
  return false;

return true;
 }

System::Boolean WProfile::WWritePrivateProfileInt(String^ app, String^ key, int val, String^ inipath)
 {
  Hashtable^ al = readall(inipath);
  if ( !al )
  al = gcnew Hashtable;

Hashtable^ sec = (Hashtable^)al[app];
  if ( !sec )
  {
  sec = gcnew Hashtable;
  al[app] = sec;
  }

sec[key] = val;

return writeall(al, inipath);
 }

System::Boolean WProfile::WGetPrivateProfileString(String^ app, String^ key, String^ def, String^% ret, String^ inipath)
 {
  ret = def;
  Hashtable^ al = readall(inipath);
  if ( !al )
  return false;

Hashtable^ sec = (Hashtable^)al[app];
  if ( !sec )
  return false;

String^ val = (String^)sec[key];
  if ( !val )
  return false;

ret = val;
  return true;
 }

System::Boolean WProfile::WWritePrivateProfileString(String^ app, String^ key, String^ val, String^ inipath)
 {
  Hashtable^ al = readall(inipath);
  if ( !al )
  al = gcnew Hashtable;

Hashtable^ sec = (Hashtable^)al[app];
  if ( !sec )
  {
  sec = gcnew Hashtable;
  al[app] = sec;
  }

sec[key] = val;

return writeall(al, inipath);
 }
}

ダウンロード

アプリケーションの設定をどうやって保存するか? ini vs レジストリ vs xml

1, その保存場所について

いままでは設定の保存はGetPrivatePrifile* APIを使ってiniファイルに保存していればよかったんですが、Vistaの登場でそういうわけにはいかなくなってしまいました。Vistaでは”Program files”下に置かれたアプリがexeのあるフォルダにファイルを作ることも書き込むことも許してくれないみたいです。

iniファイルの利点は、フォルダを削除すればアプリケーションのすべてのファイルを一括削除できることにあると思います。痕跡は残りません。もちろんOSがいろいろ残すでしょうから痕跡が全く残らないわけではありませんが。また設定ファイルのせいでアプリの挙動がおかしくなったときでも、iniファイルを削除してしまえば初期値に戻るので復旧が簡単です。

復旧といえばインストーラでインストールもアンインストールもできなくなって困った経験がある人も多いんじゃないでしょうか?

さて、レジストリに書き込むとユーザーごとに別々の場所に書くことになるので、アンインストーラーが本当に全員分の設定を削除してくれているのかはわかりません。もちろんこれはユーザーごとの”Documents and Settings”配下にデータを置いた場合も同じです。つまりアプリを色々使えば使うほどゴミファイルがどんどんたまっていってしまうのです。またアプリの設定場所が固定されているとウイルスも作りやすくなってしまいます。

よってアプリの設定情報は、そのフォルダの配下に置いた方がいいんじゃないでしょうか。ユーザーごとに分けたい場合は”user”フォルダを作成し、その配下におけばいいですし。

2, 保存のフォーマット

話は変わってxmlですが、なんか呼び方がserialilzeとかdeserializeとかになってますね。でもこれって退化じゃないでしょうか?いままではそういうことを意識しなくてもよかったのに連続化という下位概念を持ち出してくるわけですから。

そしてこれは言葉の問題だけじゃなくて本当に意識しなければいけないのではないでしょうか?iniファイルやレジストリの場合には、保存の際、それが壊れてしまうということを意識しなくてもよかったと思いますが、xmlで一気に書くとなると、もし書き始めたときにPCが固まってしまったら全部壊れるんじゃないかとか、また、ユーザーがエディタでそれをいじって読み込み不能になって、終了時デフォルト値のまま全部保存しちゃって結局全部消えちゃうとか、あるいはクラスの構成が変わったときの互換性は大丈夫かとか、考えなくてはいけないことが一気に多くなるような気がします。もちろんちゃんと調べたわけではないので嘘いってるかもしれませんが。

逆にxmlで有利な点はUTF-8で書けることだとおもいます。そしてそれはWindows98系でも同じなんでしょう(多分)。iniファイルだとそれが難しいです。W系のAPIを使ってしまうと98系で動かなくなりますし、A系だと中国語や韓国語が保存できません。あえてやるとすればGetPrivateProfileStructでバイナリ保存でしょうが、これだとユーザーが編集できなくなってしまいます。

PHP デバッグ環境の構築

Fedora5(サーバー),Windows2000(クライアント)構成でPHPのWEBデバッグ環境を構築する。

サーバーにはすでにapacheとphpがインストールされているものとする。
サーバーのIPは192.168.0.41とする。phpのバージョンは5.1.6。

/etc/php.iniの中から extension_dir= の行を探しそのディレクトリを確認する。

http://dd.cron.ru/dbg/downloads.php からlinux用のDBG 2.15.5 dbg modulesをサーバににダウンロード。
解凍してx86の中のファイル dbg.so-5.1.x を dbg.soとして、先ほど確認したextension_dirにコピー。

/etc/php.iniの最後に以下を追加。

apacheを再起動(service httpd restart)
phpinfoをブラウザで表示させ”DBG”で検索して以下のような記述があるか確認。

これでサーバー側は終了。

http://dd.cron.ru/dbg/downloads.php からwin32のDBG 2.15.5 dbglistnerをダウンロードして解凍
register.batを実行。

ISHONO16 http://uprising.s16.xrea.com/ishino16/index.html からphpsimpledbg.zip
をダウンロードして解凍
register.batを実行。

サーバーにサンプル用のsample.phpを置く。

DbgListener.exeを実行(タスクトレイにレーダーのアイコンが出現)

コマンドプロンプトを開き ipconfig を実行して自分のアドレスを確認。(192.168.0.154)
ブラウザのアドレス欄に http://192.168.0.41/sample.php?DBGSESSID=1@192.168.0.154:7869 を入力
phpsimpledbgが立ち上がりデバッグ可能になる。

一度上記のアドレスと開くと次からは?以降は不要のようです。
デバッグを止めるにはhttp://192.168.0.41/sample.php?DBGSESSID=0とする。
phpsimpledbgはステップするとき固まるときがあるが、マウスを動かしたりすると元に戻る。