winformsのresxを他のGUIフレームワークで使いまわす

Windows Formsのコードを他のGUIフレームワークに移植して使い回したい時、障害の一つになるのがresx形式のリソースだ。この時、特にmonoやモバイル環境などで使うことを考えて、System.Windows.Forms.dllやSystem.Drawing.dllへの参照を要求しないようにしたい。これは可能だろうか?

WPFで\*.resxから自動生成された Resources.Designer.cs のようなファイルを使いまわすには、XAMLのStaticResource要素を使えば簡単に出来るようだ。しかし、その中ではあくまでSystem.Drawing.Bitmapなどが参照されている。これでは上記の前提を満たしていないことになる。

というわけで、今回は（2015年にもなって）\*.resxを他のGUIフレームワークで使いまわせるようなやり方を考えたので、それについてまとめておきたい。

本題に入る前に念を押しておくべきことは、\*.resxを使っているのはWindows Formsくらいのものである、ということだ。筆者は今ほとんどWindowsとVisual Studioを使っていないので詳しくはわからないが、Visual Studioでは*.resxファイルを編集するUIがあって、そこには文字列や画像ファイルを登録する仕組みがある、はずだ。

## \*.resourcesによるリソース解決

resxファイルは、Visual Studioあるいはresgen.exeによって、\*.resourcesファイルに変換されることもあるし、C#などのソースコード（Resources.Designer.csなど）が生成されることもある。

\*.resourcesファイルは、\*.resxファイルで指定された各種リソースをバイナリシリアライズしておき、アセンブリにEmbeddedResourceとして埋め込んだ上で、実行時にmanifest resource streamからバイナリデシリアライズする。

実際にはバイナリシリアライズである必要は無く、&lt;resheader name="writer"&gt; という要素の内容で指定されたCLI型のSystem.Resources.IResourceWriterの実装（通常はSystem.Windows.Forms.dllに含まれるSystem.Resources.ResXResourceWriter）でシリアライズし、&lt;resheader name="reader"&gt; という要素の内容で指定されたCLI型のSystem.Resources.IResourceReaderの実装（通常はSystem.Windows.Forms.dllに含まれるSystem.Resources.ResXResourceReader）でデシリアライズ出来れば良い。はずである。resourcesについては、筆者は動作確認していないので、実際の動作と異なる部分があるかもしれない。

実際に\*.resourcesをリソースの解決に使用することはあるかどうかは、筆者には実のところ分からないが、ひとつ言えることとしては、\*.resourcesを読み込むためには、ResXResourceReaderが必要になり、これはSystem.Windows.Forms.dllに含まれているため、望ましくない参照が発生してしまうことになる。そうすると、冒頭で挙げた前提を満たさなくなってしまうのである。

## Contentとしてアプリケーション ディレクトリにコピーされたリソースを解決する - dynamicアプローチ

一方、実は、\*.resxで指定されたリソースファイルをContent形式でアプリケーションのフォルダにコピーし、実行時に\*.resxファイルの内容から直接リソースをデシリアライズする仕組みであれば、ResXResourceReaderおよびResXResourceWriterに依存することなく、リソースが取り出せるのである。

このとき、\*.resxはEmbeddedResourceとして（\*.resourcesの代わりに）アセンブリに埋め込まれることになるし、各リソースファイル自体は、Contentとしてアプリケーションのフォルダにコピーするようにしておく。

この時点で筆者が考えたのは、Properties.Resources型のstaticメンバーとしてstrongly typedな（C#などのコードからアクセスできる）リソースを表すメンバーを生成する代わりに、C# 4.0のdynamic型を使用して、リソースを動的に解決できないか、ということだった。これは、以下のようなクラスで実現できる（やっつけではある）。

<pre>
public static class Properties
{
    internal static dynamic Resources = new DynamicResource ();
}
public class DynamicResource : DynamicObject
{
    static readonly string[] resource_names = new DirectoryInfo (".").GetFiles ("resources/*").Select (d => d.ToString ()).ToArray ();

    public override bool TryGetMember (GetMemberBinder binder, out object result)
    {
        result = GetResource (binder.Name);
        return result != null;
    }

    public object GetResource (string name)
    {
        var res = resource_names.FirstOrDefault (n => Path.GetFileNameWithoutExtension (n) == name);
        if (res != null) {
            switch (Path.GetExtension (res)) {
            case ".png":
                return Xwt.Drawing.BitmapImage.FromFile (res);
            case ".ico":
                return null;
            case ".txt":
                return File.ReadAllText (res);
            }
        }
        return null;
    }
}
</pre>

画像はXwt.Drawing.BitmapImageとして返される。これで、以下のようなコードが書けるようになる:

<pre>
Xwt.Drawing.Image image1 = Properties.Resources.image1;
</pre>

これでもう十分ではないか。とも思ったが、筆者のコードでは、この後さらにXwt.Drawing.BitmapImageに対するextension methodの呼び出しが多用されていて、dynamicとの相性が非常に悪かった（拡張メソッドはコンパイル時に静的に解決されるものなので、dynamicオブジェクトからは解決できない）。

## Contentとしてアプリケーション ディレクトリにコピーされたリソースを解決する - ResXFileRef

というわけで、もう少しオリジナルのProperties.Resourcesクラスに近いコードが利用できるような解決策を考えなければならなくなった。とりあえず、もう少し\*.resxの仕組みを掘り下げてみよう。

\*.resxファイル内では、各リソースは、次のようなdata要素として指定されている:

<pre>
&lt;data name="alarm_clock" type="System.Resources.ResXFileRef, System.Windows.Forms">
  &lt;value>..\resources\alarm-clock.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
&lt;/data>
</pre>

この内容が今回のキモのひとつだ。typeという属性は、そのリソースをどのCLI型でdata要素からインスタンス化するかを示している。この例では、System.Windows.Forms.dll上に存在する System.Resources.ResXFileRef という型が使用されている。この型は何かというと、ファイル名と変換先のCLI型を渡すことで、その型に対する変換をサポートする、というものだ。

具体的には、System.Resources.ResXFileRefには、TypeConverterAttributeが指定されていて、その中ではSystem.Resources.ResXFileRef.Converterの型名が指定されている。このConverterはTypeConverterの実装クラスで、ResXFileRefからSystem.Drawing.Bitmapへの変換（つまりResXFileRef.FileNameをBitmap.FromFile()に渡すだけの処理）などをまとめている、と考えられる（monoの実装はそのようになっている）。

strongly-typedなリソースへのアクセスを実現している Resources.Designer.cs では、各リソースに対応するメンバーは、次のように定義されている:

<pre>
internal static System.Drawing.Bitmap image1
{
    get
    {
        object obj = ResourceManager.GetObject("image1", resourceCulture);
        return ((System.Drawing.Bitmap)(obj));
    }
}
</pre>

実のところ、このResourceManagerというのも、（型ではなく）このResourcesクラスに定義されたプロパティだ:

<pre>
internal static global::System.Resources.ResourceManager ResourceManager { get; }
</pre>

この System.Resources.ResourceManager型は、mscorlib.dllで定義されていて、特にWindows.Formsに固有のものではない。これに対して、ResXFileRef型などは、System.Windows.Forms.dllで定義されている、Windows.Forms固有の変換をサポートしたものだ。ResourceManager.GetObject()では、指定されたリソースについて、\*.resxファイル（EmbeddedResourceとして取得できる）の内容を読み込んで、data要素でname属性がマッチするものを取得し、type属性で指定された型のインスタンスを、value属性で指定されたコンストラクタ引数をもとに生成する。その上で、要求された型のオブジェクトへの変換が行われる。

今回は、System.Windows.Forms.dllに依存しないリソースの定義を志しているので、このResXFileRef型を使うわけにはいかない。ではどうすればいいのか? 簡単である。同じようなResXFileRefクラスを、ただし自分たちが期待するような型変換（たとえば、System.Drawing.dllのBitmapではなく、Xwt.dllのBitmapへの変換）をサポートするかたちで、定義して、\*.resxで指定してやればいいのである。

ResXFileRefで定義すべき内容は簡単なので、ここではmonoのソースコードにリンクするにとどめておきたい。
https://github.com/mono/mono/blob/master/mcs/class/System.Windows.Forms/System.Resources/ResXFileRef.cs

## 未解決の問題 - デザイナー クラスの自動生成

ここまでで、とりあえずプラットフォーム中立なかたちでresxファイルを定義することは、何となく可能そうであるということが伝わったかもしれない。ここで最後に問題になるのが、デザイナー クラスのコードを自動生成できるかどうか、である。

実のところ、System.Resources.StronglyTypedResourceBuilderというクラスを使うと、このデザイナー クラスを自動生成することができる。このクラスはSystem.Design.dllに含まれていて、プラットフォーム中立と言えなくもない。実際にはこのアセンブリはSystem.Windows.Forms.dllなどを参照するのだけど、われわれは実行時にコード生成を行うわけではなく、生成されたコードは不必要な参照を要求しない。

Windows SDKに含まれるresgen.exeには、/strというオプションがあって、このデザイナー クラスを生成することは出来るようだ。monoのresgen.exeは、そこまでの要求がなかったこともあってか、未だに実装されていないようだ（Windows.Forms自体がobsoleteな感じになっているので、着手する価値があるとは考えにくい）。

ただ、いずれにしろ、問題は、\*.resxに変更を加えて保存した時に、自動的にデザイナー クラスを生成する仕組みは、IDE側で個別に実装するしか無い、ということだ。このデザイナー クラスをどう生成するかを指定する方法は、筆者には分からない。Visual Studioでは、旧バージョンではCOMを登録する必要があったようだ。VS2013では、たぶんVS SDKとアドインAPIでいけることだろう。
(参考: http://www.codeproject.com/Articles/13830/Extended-Strongly-Typed-Resource-Generator)

MSBuildでビルドするcsprojなどの中で、&lt;BeforeBuild&gt;としてresgen.exeあるいは自分のツールの呼び出しを行う、という方法も考えられる（ただしIDEの保存から自動的に呼び出すことは出来ないだろう）。resgen.exeには、独自のアセンブリ参照（たとえばXwt.dllなど）を追加することが出来ないようなので、StronglyTypedResourceBuilderを呼び出す自前のツールを書かなければならないことも、あるかもしれない。

monodevelopの場合、コードジェネレーターはこの辺にある。
https://github.com/mono/monodevelop/tree/master/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CustomTools

筆者は、とりあえず手元のプロジェクトではリソースが追加されることがないので、手書きでResources.Designer.csを書き換えることで対応した。

## 総括

非Windows.Forms環境でも、\*.resxリソースを使用することは可能である。ただし、内部的にはちまちまとWindows.Forms依存の実装が含まれているので（とは言っても、そうならないようにMicrosoftがAPIを慎重に規定している様子が、関係ある型の存在するアセンブリの分散ぶりからも、窺い知ることが出来る）、使いまわせる部分と、そうでない部分を意識したほうが良いだろう。ともあれ、もし\*.resxを使いまわす方が楽な状況であったら、このアプローチを試してみるのもいいだろう。