XmlSerializer

次のような簡単なアプリケーションは、.NET 1.1 SP1 環境で常に正常に動くとは限らないことを確認している。
こんなコードが動かない場合があるなんて…、というぐらい信じられないのだが、現実に発生しているのだから困ったもんだ。


問題のコードは次のようなとても単純なもの。

public class Test
{
  // new() なコンストラクタ、XmlSerializer を使う場合には必須
  public Test() {}

  // データメンバ(別になくても再現する)
  public int Data
  {
    get { return 0; }
    set { }
  }

  // 自身のシリアライザを保持するためのメンバ変数
  private static XmlSerializer ser;

  // 自身のシリアライザを提供するプロパティ
  public static XmlSerializer Serializer
  {
    get { return this.ser; }
  }

  static Test
  {
    // この new が例外を発生する場合がある
    this.ser = new XmlSerializer(typeof(Test));
  }

  static void Main()
  {
    // 実運用はクラスライブラリなのでエントリポイントは持たない
    // ここではテストアプリケーションとして動けるようにしただけ。
    System.Windows.Forms.MessageBox.Show("正常終了.");
  }
}

いくつかのノートPC、組み立てによるいわゆる自作PC、メーカー販売のPC、それらで OS インストール直後に Windows Update .NET Framework 1.1 と SP1、そして推奨されるすべてのアップデートを行った状態で、上記のコードをコンパイルしたアセンブリを実行すると、正常に動作しないことがある、というものだ。
CPU は Pentium III (single), Pentium III (dual), Pentium4(HT off), Pentium4(HT on)、OS としてはWindows 2000 Pro, Windows XP Pro (SP1), Windows XP Pro (SP2) あたりが確認済。


仕事で20台ちかくで再現していて困っていたところ、本日ついに手元の個人のPCでも再現してしまった。しかも再現させたのはテストアプリじゃなくて SharpReader…。
SharpReader.AppSettings クラスの ..ctor に

  AppSettings._settingsSerializer = new XmlSerializer(typeof(AppSettings));

というようなコードがあるのだが、やはりここで例外が飛んでしまった。


もちろん、平時は何も問題もなく XmlSerializer のインスタンスが生成され、Deserialize() によってアプリケーションの設定ファイルなどが XML ファイルから読み込まれる。
問題が発生した直後に、もう1つアプリケーションを動作させたら正常終了するぐらいだ。


google などで検索すると、XmlSerializer のデシリアライズの FAQ が引っかかるぐらいでまったく情報がない。そもそも正常に実行できるのだから、クラス/データ設計的に読めないわけではなく、実行環境に依存した問題である可能性が非常に高い。
この問題を解決できず、時間が取れるようになるまで放置としてから半月ほどして GDNJ へ投稿された XmlSerializeのハングアップ はかなり近いレベルの問題ではないかと思ったが、明確にエラー内容が掴めているかたちなので、ちょっと違う毛色かと思ったが、特定の PC の環境問題だったようなかんじですぐにクローズしてしまった。

      • -

とりあえず、現在わかっている限りでは次のような状態である。


XmlSerializer は Type を引数に与えると、その型に対してリフレクションを利用して public なフィールドを XML に読み書きする処理を記述した C#ソースコード環境変数 TEMP で示すディレクトリに作成する。*1
そして、作成した C#ソースコードcsc.exe を子プロセスとして起動してコンパイルし、クラスライブラリ形式のアセンブリを生成。そのアセンブリを実際に読み込んでシリアライズとデシリアライズの実装として使用する。*2
Visual Studio などのアセンブリ(DLL)の読み込みを出力する開発環境の上で実行していれば、毎回異なる名前のアセンブリが読み込まれていることに気が付くことができるし、ファイルシステムの監視をしたり、環境変数 TEMP のディレクトリに対してファイルの削除権限をなくすなどすれば、問題のソースコードなんかも手に入れることができる。


現在、遭遇している問題は上記のプロセスのうち、csc.exe によるコンパイル作業が終了しないというもの。
csc.exe が正常終了も異常終了もしないため、XmlSerializer のコンストラクタから処理が帰ってこなくなってしまう。*3
そのまま放置すると、やがてタイムアウトしたのか正常終了と勘違いしたのか、目的のアセンブリを読み込もうとして IOException (File not found) の例外を発生して終了する。


別に生成されたソースコードは悪いわけでもないので、このタイムアウトの待ち時間に別途コマンドプロンプトをたちあげ、csc.exe を使って dll を生成してしまうことが可能だったりするし、環境変数 TEMP で示されたディレクトリが読み書きできないわけでも決して無い。

*1:どうして CodeDOM や ICompiler を利用しないのか疑問

*2:コンパイルオプションも再現してくれるので、デバッグビルドしたアセンブリの型を渡すと、このアセンブリデバッグビルドされる

*3:前述の GDNJ の投稿では、cvtres.exe が問題を起していたようだが、この cvtres.exe は csc.exe や cl.exe などのコンパイラが、ソースコードとリソースをリンクして実行イメージを作成したりするのに利用するのだが、その呼び出しまで処理が進んでいなかったりする。