人気ブログランキング | 話題のタグを見る
(.Net)設定値をXMLファイルの落とす(アプリケーション構成ファイル,user.configを使わない編)
以前に、(.Net)アプリケーションの設定ファイルを作ろうとしたけど。。。の最後で言及した自作XMLに設定を保存する方法です。クラスをシリアライズして落とす方法もあるんですが、あえてその方法を使わずXMLパーサを利用することに。(実はアプリから XML をパーサで操作するのは今回が初だったりするわけで。。)

.NetにはXMLパーサとして XmlReader,XmlWriter と XmlDocument があるようです。
いったいこの2つは何が違うんじゃということで軽く調査。
前者は SAX (Simple API for XML) と呼ばれるAPIに基づき、後者は DOM (Document Object Model) というAPIに基づくようです。
DOM はWEB系開発でちょこっと触ってるんで(JavaScriptから操作する時に使用)ある程度わかるんですが、SAX というのは初耳です。

詳しくは第10回 XMLプログラミングのためのAPIで両者の違いが説明されてました。
簡単にまとめると DOM はツリー上に情報もつのでランダムアクセス可能、メモリ消費大、すべて読み込んでから処理開始。
SAX はイベントを順次生成という感じなので順次アクセス、メモリ消費小、逐次処理ということだそうです。

結局設定ファイルの更新が発生すること(SAXは更新に弱いらしい(?))や今までの経験値を生かすためと友人の誘惑により DOM 操作にすることにしました。
.Netでは XmlDocument パーサ使うと本当に簡単に XML 操作ができます。

例えば、下記のような XML があって、savedir というタグの値を取りたいときはこう書けます。
<?xml version="1.0" encoding="utf-8" ?>
<settings>
<backup>
<savedir>aaa</savedir>
</backup>
</settings>

Dim  m_XmlDoc As New XmlDocument()
'設定値を読む
m_XmlDoc.Load("test.xml")
'ルートノードの取得
Dim rootElement As XmlElement = m_XmlDoc.DocumentElement
'savedir というタグのノードを検索
Dim nodeList As XmlNodeList = rootElement.GetElementsByTagName("savedir")
'タグの値を出力
For Each node As XmlNode In nodeList
Console.WriteLine(node.InnerText)
Next

しかし、GetElementsByTagName を使う方法だと下記の XML のように別要素の savedir まで取得してしまいます。

<?xml version="1.0" encoding="utf-8" ?>
<settings>
<backup>
<savedir>aaa</savedir>
</backup>
<history>
<savedir>bbbb</savedir>
</history>
</settings>

結果は aaa と bbbb になってしまう。

特定の位置のタグだけ取りたときは SelectNodes と使うとOKみたいですね。
例えば上記の XML で backup 内の savedir の値だけ取りたいときは下記のようにできます。

'ルートノードの取得
Dim rootElement As XmlElement = m_XmlDoc.DocumentElement
'backup の savedir というタグのノードを検索
Dim nodeList As XmlNodeList = rootElement.SelectNodes("//backup/savedir")
For Each node As XmlNode In nodeList
Console.WriteLine(node.InnerText)
Next

この SelectNodes の引数は XPath と呼ばれるもので、より複雑な条件で特定のノードを探すことができるようです。
ちなみに、 SelectSingleNode() を使うと1つだけノードが返るようなので(戻り値の型はXmlNode)、1つしかノードがないことが保証される場合はこっちの方が便利かもしれません。

保存は XmlNode.InnerText を書き換えて、 XmlDocument .Save メソッドで簡単にできます。上書きも勝手にするようです。

他にも XML の名前空間やスキーマ云々等があるようですが、まあ今回の要件ではそこまでいらないのでとりあえずこれで解決とします。

.Net からのDOM操作は連載 .NETで簡単XML 第4回 DOM(Document Object Model)を参考にしてます。

さて、上記でXMLに設定ファイルを書き込むアプリケーションを作って、Visual Studio セットアッププロジェクトでインストーラをつくり、Vista && UACがONの環境で試してみました。

インストール先はインストーラ標準の ProgramFiles 配下です。設定ファイルと exe と同じフォルダに配置します。

そして、アプリケーションを立ち上げ設定を行い保存処理をします。
すると案の定例外が発生。


System.UnauthorizedAccessException: パス 'C:\Program Files\test\xxx\xxx.xml' へのアクセスが拒否されました。
場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
場所 System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy)
場所 System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
場所 System.Xml.XmlDocument .Save(String filename)
.....

やはり、UAC のせいで書き込み時にアクセス拒否出ました。

exe をコンテキストメニューから「管理者として実行」から行うと問題なく書き込みできます。

今回作成中のアプリケーションは用途として管理者として実行させるものなので、exe を実行したら勝手に管理者権限に昇格させてやるようにしました。(UACのダイアログは毎回出ますが。。)

この昇格をさせるために マニフェストファイル という仕組みがあります。
マニフェストファイルはずっと前の某大手で構内作業者やってた時にVista対応で触ってたんで大体理解できてます。
(本当は、exeやインストーラにもデジタル署名させてあげたいんだけど、現場に証明書発行してもらう金がないみたいなで、未署名のまま。。。)
マニフェストで管理者権限昇格を定義し、exeを起動すると、管理者権限を持つユーザでWindowsにログオンしてばあい、下記のようなダイアログが毎回でます。
(.Net)設定値をXMLファイルの落とす(アプリケーション構成ファイル,user.configを使わない編)_e0091163_9434952.jpg

また、一般ユーザでログインしていると下記のようなダイアログになります。
(.Net)設定値をXMLファイルの落とす(アプリケーション構成ファイル,user.configを使わない編)_e0091163_9443589.jpg


マニフェストの設定は、ファイル名.exe.manifest という XML で下記のノードを追加してやればOKです。
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
これで管理者権限で実行しようとします。
level の値は AsInvoker:制限された権限で動作可能 ,highestAvailable:ユーザーが持つ制限されない権限が必要,requireAdministrator:管理者権限が必要 となっているようです。 (参考:Vista対応 その2 マニフェストファイルを使用する)

Visual Studio 2008(VB.Net)でUACがらみのマニフェスト設定するには、プロジェクトのプロパティ→UAC設定の表示 を押下するとマニフェストファイルのテンプレート(app.manifest)ができるので、そっから容易に変更できます。
(.Net)設定値をXMLファイルの落とす(アプリケーション構成ファイル,user.configを使わない編)_e0091163_9453047.jpg

こんな感じです。(管理者権限で実行を指定)
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">

<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC マニフェスト オプション
Windows のユーザー アカウント制御のレベルを変更するには、
requestedExecutionLevel ノードを以下のいずれかで置換します。
 
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
 
下位互換性のためにファイルおよびレジストリの仮想化を
利用する場合は、requestedExecutionLevel ノードを削除してください。
-->

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

</requestedPrivileges>
</security>
</trustInfo>
</asmv1:assembly>


Visual Studio のプロジェクトのプロパティから精製したマニフェストはアセンブリ埋め込みになるので、外に XML ファイルが作られないようですね。

マニフェストが埋め込まれた exe は下記のように盾のアイコンがつくみたいです。
(.Net)設定値をXMLファイルの落とす(アプリケーション構成ファイル,user.configを使わない編)_e0091163_9461216.jpg

by jehoshaphat | 2009-04-20 09:56 | .Net開発


<< (.Net,SQL)プログラム... Windows,Linuxの稼... >>