[C#]埋め込みDLLを敷居なく使う

Rubyやろうと思ってたのにRedMineよりTracの方が気に入ったからという理由でPythonに走ったりしてるけど、わたしは元気です。
長続きしてしまっているSE仕事の合間にC#でツール作ったりしていたのですが、諸事情によりDLL埋め込みで作る機会があったので、備忘を兼ねて。

#DLLを呼び出すだけならいろいろある
ちょこっと調べてみたらいろいろあった。

- 普通に参照設定してusingで読み込む
- DllImportで読み込む
- 埋め込んで実行中のリソースから引っこ抜いてアセンブリとしてロード
- 動的に読み込んでプラグインとして使う
- ビルド後にマージして1つにまとめる

今回必要だったのは一番面倒そうな「埋め込んで実行中のリソースから引っこ抜いてアセンブリとしてロード」。
なんでかと言えば、2つ理由がある。
1つ目は、モジュール自体を設置、設定するのがベテランさんばかりな現場でないので、DLLと分けてあると忘れたり、バージョン違いで動かないケースがあるので埋め込む必要があったということ。
2つ目は、そのツールを作ってもあんまり特殊なことをすると引継ぎが面倒だし、保守できなくなるということ。
そして追加の要件として「C#プログラマが多いわけではない職場なので、あんまりややこしいソースにすると保守できない」ということである。
現場に合わせた保守性大事！

ということで、比較的平易に読める状態を保持しつつ、DLLを埋め込んで敷居なしに使える状態にする。

#今回使う材料
今回は.NET2.0環境でZIP圧縮をしたいから、という理由だったので、Microsoft謹製のDotNetZipを使う。
ライセンスのことはあまり詳しくないのでよくわからないが、ざっくり調べてみたところ、「配布するなら元の著作権とかを表記しとくこと」「ビルドしたらそれも同じライセンス」「あとは好きに使え」っぽい。
というわけで、配布しないから好きに使う方向。

#基本的な使い方
まずDotNetZipの基本的な使い方から。
参照で読み込んで、普通にファイル追加して、普通に圧縮ファイルを保存するなら以下のような感じ。

```ZipArchive.cs
using Ionic.Zip;

namespace ZipWrapper
{
    class ZipArchive
    {
        public void ArchiveFiles()
        {
            ZipFile zip = new ZipFile(Encoding.GetEncoding(932));

            zip.AddFile(@"c:\test\text1.txt", "");
            zip.AddFile(@"c:\test\text2.txt", "");
            zip.AddFile(@"c:\test\img\img.png", "img");

            zip.Save(@"c:\test.zip");
        }
    }
}
```

このレベルならC#プログラマじゃないとしても普通のプログラマは読めるので、このレベルで書きたい。

#普通の埋め込み呼び出し
だが、埋め込みリソースからの読み込みになると以下のような。

```ZipArchive2.cs
using System.IO;
using System.Reflection;

namespace ZipWrapper
{
    class ZipArchive
    {
        public void ArchiveFiles()
        {
            Assembly exasm = Assembly.GetExecutingAssembly();
            Stream dllstrm = exasm.GetManifestResourceStream("ZipWrapper.Lib.Ionic.Zip.dll");

            int dlllen = (int)dllstrm.Length;
            BinaryReader dllbr = new BinaryReader(dllstrm);

            Assembly zipasm = Assembly.Load(dllbr.ReadBytes(dlllen));
            Type ZipFile = zipasm.GetType("Ionic.Zip.ZipFile");

            // これがインスタンス生成
            object _zip = Activator.CreateInstance(ZipFile, new object[] { Encoding.GetEncoding(932) });

            MethodInfo AddFile = ZipFile.GetMethod("AddFile", new Type[] { typeof(string), typeof(string) });
            // この辺がファイル追加してるところ
            AddFile.Invoke(_zip, new object[] { @"c:\test\text1.txt", "" });
            AddFile.Invoke(_zip, new object[] { @"c:\test\text2.txt", "" });
            AddFile.Invoke(_zip, new object[] { @"c:\test\img\img.png", "img" });

            MethodInfo Save = ZipFile.GetMethod("Save", new Type[] { typeof(string) });
            // この辺で保存
            Save.Invoke(_zip, new object[] { @"c:\test.zip" });
        }
    }
}
```

めんどくさいわー！
ちゃぶ台投げるわー！
日頃手抜きに慣れたC#プログラマ（私のことだ）にはいちいちこんな書き方すると読むのも辛いので、ラップして使う時を楽にする。

#最終形（ラップされた読み込み）
ということでラップ用のクラスを用意する。

```Lib.ZipFile.cs
using System.IO;
using System.Reflection;

namespace ZipWrapper.Lib
{
    class ZipFile
    {
        object _zip; //インスタンス用
        Type _zipfile;

        public ZipFile(Encoding encoding)
        {
            Assembly exasm = Assembly.GetExecutingAssembly();
            Stream dllstrm = exasm.GetManifestResourceStream("ZipWrapper.Lib.Ionic.Zip.dll");

            int dlllen = (int)dllstrm.Length;
            BinaryReader dllbr = new BinaryReader(dllstrm);

            Assembly zipasm = Assembly.Load(dllbr.ReadBytes(dlllen));
            _zipfile = zipasm.GetType("Ionic.Zip.ZipFile");

            _zip = Activator.CreateInstance(_zipfile, new object[] { encoding });
        }

        /// <summary>
        /// アーカイブにファイルを追加
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="directoryPathInArchive"></param>
        public void AddFile(string filename, string directoryPathInArchive)
        {
            MethodInfo addfile = this._zipfile.GetMethod("AddFile", new Type[] { typeof(string), typeof(string) });
            addfile.Invoke(this._zip, new object[] { filename, directoryPathInArchive });
        }

        /// <summary>
        /// アーカイブを保存
        /// </summary>
        /// <param name="filename"></param>
        public void Save(string filename)
        {
            MethodInfo save = this._zipfile.GetMethod("Save", new Type[] { typeof(string) });
            save.Invoke(this._zip, new object[] { filename });
        }
    }
}
```

これがあれば最初のソースに1行修正するだけで行けちゃう！かんたん！

```ZipArchive.cs
using ZipWrapper.Lib;

namespace ZipWrapper
{
    class ZipArchive
    {
        public void ArchiveFiles()
        {
            ZipFile zip = new ZipFile(Encoding.GetEncoding(932));

            zip.AddFile(@"c:\test\text1.txt", "");
            zip.AddFile(@"c:\test\text2.txt", "");
            zip.AddFile(@"c:\test\img\img.png", "img");

            zip.Save(@"c:\test.zip");
        }
    }
}
```

他のZIP関係のメソッドを使うようになるならラッパーに追加する必要があるけど、普通に圧縮するだけならこれで全部使えてしまいます。
ラッパー自体もパターンなので、追加もそんなに大変じゃない！
これなら読むのもらくちん！

* * *
可読性の高いソースは他人のためにも、数ヶ月後の自分のためにも、すごく大事なものだよね！