布団が俺を呼んでいる

丘山大一のぶろぐ

Delphi でXMLファイルを作成しようとしてハマる

ね、ハマるでしょ?


環境

いつも通り、サポート切れのXE5。


基本構文

xmlファイルを新規作成します。

var
  XML : TXMLDocument;
  RootNode, ChildNode : IXMLNode;
begin
  XML := TXMLDocument.Create(nil);
  XML.Active := True;
  RootNode := XML.AddChild('Root');
  ChildNode := RootNode.AddChild('Child');
  ChildNode.Text := 'ガキ';
  XML.SaveToFile('D:\Doc.xml');
  XML.Active := False;
  FreeAndNil(FResultXML);
end;


ここでハマったよその1 別スレッドで上記を操作

こんなエラーが出ました。
「DOMException Microsoft MSXML がインストールされていません」
Microsoft MSXMLはTXMLDocumentでデフォルトで使用するものです。
当初、このエラーを素直に読み取ったので、
「あれ、コンポーネント配置してない(IDEのポトペタで作ってない)から、デフォルト値がうまく読み込めてないのかな?」
などと思い、
  XML.DOMVendor := MSXML_DOM;
を追記しました。
が、それでもエラーが解消されません。
「ということは、ポトペタ配置と他にもプロパティが違う場所があるのか、おまじないが必要なのか」
と探すも、それらしいものが見つからず。


結論:仕様です

下記の「エラーハンドリングおかしくね?」というページを発見。
さらにこんなページたちも。


原因と解決策

・エラーメッセージが微妙に意図しているところと違う。
・GUI経由、つまりはVCLフォームを表示しているメインスレッドならCOMは普通に呼び出せるが、
 別スレッドの場合はCoInitialize 、CoUninitialize を呼び出してやらなくてはならない。



ここでハマったよその2 TXMLDocumentインスタンスが勝手に破棄される

実際にプログラミングしたのは、上記基本構文よりもちょっとだけ複雑で、
Nodeを作るところ・そのためのデータをとってくるところや、SaveToFileするクラスは別に作っています。
そんなこんなで、SaveToFileしようとすると、「Activeでないよ」というエラーが発生するように。
あれ?
もしかして勝手にクローズしちゃうのかな?
と思って直前でActiveを活性化させても同じ。
なんでやねん、と思って追ってみるとTXMLDocumentインスタンスが死んでいる……
ウソみたいだろ、Freeしてないのに解放されているんだぜ。


結論:仕様です

「作成時に Owner が設定されていない TXMLDocument は、インターフェイス オブジェクトと同様の動作をします。 つまり、そのインターフェイスに対する参照がすべて解放されると、TXMLDocument インスタンスは自動的に解放されます。」
なんでそんな動作なんだよ(怒)
「インターフェイス オブジェクトと同様の動作」なんてさせるくらいなら最初からインターフェースで返せよ!分からんだろうが!


解決策

というわけで、Owner を設定してやれば解決します。
適当なTComponentインスタンスを生成してCreateの引数に渡してやりましょう。


総括:今回ハマったところを全部のせるとこんな感じ。


var
  XML : TXMLDocument;
  RootNode, ChildNode : IXMLNode;
  OwnerComponent : TComponent;
begin
  CoInitialize(nil);
  XML := TXMLDocument.Create(OwnerComponent);
  XML.Active := True;
  RootNode := XML.AddChild('Root');
  ChildNode := RootNode.AddChild('Child');
  ChildNode.Text := 'ガキ';
  XML.SaveToFile('D:\Doc.xml');
  XML.Active := False;
  FreeAndNil(FResultXML);
  FreeAndNil(OwnerComponent);
  CoUninitialize;
end;



コメントを書く