« 「アイコンの変更」ダイアログを使ってみる。 | トップページ | 7-zip32.dllのUTF-8モードをC#から使用する(2) »

2010年12月 9日 (木)

7-zip32.dllのUTF-8モードをC#から使用する(1)

7-zip32.dll は、本来 7z 形式の書庫を扱うアンマネージドDLLですが、Zip 書庫自体も扱える様になっています。最新の 4.65.00.01 では、AESによる暗号化をサポートした事や、Zip 書庫でも UTF-8 のファイル名をサポートした事で、Zip 書庫操作用の DLL としても見逃せないものになっています。

さて、この 7-zip32.dll ですが、いざ Unicode ファイルを扱おうとすると、UTF-8 の文字コードを要求するので、C#では通常の呼び出し方が出来ません。

そこで、どうやればいいのかが、今回の研究テーマです。

まず、実際に API 呼び出しをする前に、API 呼び出しに関する基本的な事を考えてみましょう。

通常、API で char*(LPCSTR/LPSTR) の型を要求する場合、C#では、入力のみ(LPCSTR)であれば string、入出力(LPSTR)であれば StringBuilder クラスを指定します。しかしDLL側に正しく引数が渡されれば良い訳で、この様な指定方法にこだわる必要はありません。バイト型の配列で指定しても問題ない訳です。

通常の文字列型と、UTF-8エンコードされた byte 配列のやりとりは、以下の様に簡単です。

・string → UTF-8エンコード byte 配列
byte[] param = System.Text.Encoding.UTF8.GetBytes("エンコード文字列");

・UTF-8エンコード byte 配列 → string
string paramStr = System.Text.Encoding.UTF8.GetString(param);

但し、これを API として実際に使用するときは、文字列の最後にNULLターミネータが付加される必要がある事を考慮して、以下の変換メソッドを用意してみました。

・string → UTF-8エンコード byte 配列

private byte[] EncodeUTF8(string param)
{
    // 最後にNULLターミネータを付加する
    byte[] result = Encoding.UTF8.GetBytes(param + "\0");
    return result;
}

・UTF-8エンコード byte 配列 → string

private string DecodeUTF8(byte[] param)
{
    // UTF-8コードのバイト配列から通常の文字列へデコードする際、
    // NULLターミネーター以降はデコード対象外とする。
    int length = Array.IndexOf(param, (byte)0); 
    string result = Encoding.UTF8.GetString(param, 0, length);
    return result;
}

では、早速 7-zip32.dll を動作させる為のプログラムを用意しましょう。プログラムの流用元は、過去の記事「アンマネージドDLLの動的リンク(2)」で使用したソースを使用しました。とはいっても、この時のテストとは状況が違うので、結構手直ししています。また、動的リンクに使用したクラス UnmanagedLink も、SafeHandle から継承したものに変更しています。
今回は動的リンクがテーマではないので、ボタンクリックの度にDLLを動的リンク~開放を行なう必要もないのですが、クリックイベント処理だけで一つの処理が完結する方が、サンプルとして見易いとの判断から、あえてそのままにしてあります。

では、まずは Unicode メンバ付きの Zip 書庫ファイルが作成できる様に、書庫作成用の機能を用意してみましょう。

1.デリゲートの定義
まずは、動的リンクで使用するDLL関数用のデリゲート定義です。書庫作成には、コマンド実行用の関数 SevenZip のデリゲート定義が必要な他、Unicode 対応にする為、関数 SevenZipSetUnicodeMode のデリゲート定義も行ないます。

delegate bool SetUnicodeMode(bool bUnicode);
delegate int CommandU(IntPtr hWnd, byte[] szCmdLine, byte[] szOutput, int dwSize);

以前は、

delegate int CommandW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)]string szCmdLine,
    StringBuilder szOutput, int dwSize);

と記述していましたが、今回はUTF-8対応化の為、この様に byte[] に変更しています。

2.書庫作成の実行コード
流用元では書庫作成はなかったのですが、解凍か書庫作成かはコマンド文字列の内容の違いだけなので、流用元の書庫一括解凍のコードが流用出来ます。
ここでは、UTF-8指定でZIP書庫を作成するので、SevenZipSetUnicodeMode 呼び出しが増えます。また byte 配列への変更による対応と、7-zip32.dll 用のコマンドへの変更を行ないます。

/// <summary>
/// Unicode 文字列によるZIP書庫新規作成/メンバ追加
/// </summary>
/// <param name="addFolder">追加対象となるフォルダ</param>
/// <param name="archiveFile">ZIP書庫ファイルのフルパス</param>
private void UnicodeGen(string addFolder, string archiveFile)
{
    using (UnmanagedLink ul = new UnmanagedLink("7-zip32.dll"))
    {
        byte[] log = new Byte[10240];
        SetUnicodeMode setUnicodeMode = (SetUnicodeMode)ul.MethodGen(typeof(SetUnicodeMode),
            "SevenZipSetUnicodeMode");
        CommandU command = (CommandU)ul.MethodGen(typeof(CommandU), "SevenZip");
        if (!setUnicodeMode(true)) return;
        string zipCommand = String.Format("a -tzip {0} {1}\\ * -mcl=off -mcu=off",
            archiveFile, addFolder);
        command(this.Handle, EncodeUTF8(zipCommand), log, log.Length - 1);
        string logStr = DecodeUTF8(log);
        MessageBox.Show(logStr);
    }
}

最後の部分でログをデコードして、MessageBox に表示しているのは、Console.WriteLine では、出力ウィンドウに Unicode 文字が表示されず、Shift-JIS変換後の文字が表示されるからです。
当初、これに騙されてうまくデコードされていないと悩んでしまいました。同じ事で悩まないように、記しておきます。

Files

なお UTF-8 モードにすると、常にファイル名が UTF-8 にされて書庫ヘッダに記録されるイメージが沸きます。しかし 7-zip32.dll のドキュメントに書いてあるとおり、実際は上記コマンド中にある -mcl, -mcu オプションにより制御出来ます。その為、Unicode 固有の文字列がなければ、従来と同じShift-JISによる記録が出来る様です。ついでなので、このオプション指定の確認も含めてやってみたいと思います。

このテストも行なう為、左図の様なファイル構成のフォルダをZIP書庫にする事にします。-mcl と -mcu オプションの組み合わせ別による結果を下図に示します。これは、WindowsXPのエクスプローラから、ZIP書庫内の一覧を表示した時の、ハードコピーです(Unicode非対応のUNZIP32.DLLによる書庫一覧を出しても同様の結果となります)。

-mcl=off -mcu=off -mcl=off -mcu=on -mcl=on -mcu=off -mcl=on -mcu=on
-mcl=off -mcu=off で
作成した書庫
-mcl=off -mcu=on で
作成した書庫
-mcl=on -mcu=off で
作成した書庫
-mcl=on -mcu=on で
作成した書庫

-mcl=off -mcu=off で作成した書庫の場合
Unicode固有の文字を持つファイル名だけUTF-8で記録されるので、Shift-JISで表現可能な全角のファイル名と半角カタカナのファイル名は正しく表示されます。Unicode 固有文字のファイル名はUTF-8で記録されるので、文字化けして表示されています。

-mcl=off -mcu=on で作成した書庫の場合
非ASCII文字がUTF-8で記録されるので、Unicode 固有文字のファイル名はもちろん、全角文字ファイルと半角カナ文字ファイル名も文字化けして表示されます。

-mcl=on -mcu=off で作成した書庫の場合
常にローカルコードページを使用するので、全角文字ファイルと半角カナ文字ファイル名は、正常に表示されますが、Unicode 固有文字を持つファイル名は、Shift-JIS変換により、ウムラウト付きの Unicode ファイルは、ウムラウトが抜けた英字に変換されて記録されます。全角 Unicode ファイル名の方は、本当は"?"に変換される筈ですが、ファイル名に使用出来ない文字の為に、アンダースコア"_"に再変換されていると思われます(未確認)。

-mcl=on -mcu=onで作成した書庫の場合
-mcl=off -mcu=onと同じ結果になりました。これは、本来の意味からすれば矛盾する指定なので、使用しない方がいいでしょう。

以上の様に、mcl/mcu の指定を使い分ける事で、Unicode 固有文字さえなければ従来のShift-JISベース Zip 書庫と互換性のある書庫も作成可能なので、常時 UTF-8 モードで書庫を作成しても問題ないと思われます。では、上記テストで使用した各書庫は、UTF-8 モードによる書庫内メンバ一覧はどう表示されるでしょうか?

当然の疑問なのですが、ここは次回にする事にします。

では最後に、今回の記事に使用したサンプルソース一式をCAB形式で圧縮して、ここに置いておきます。
「Utf8Test1.cab」をダウンロード


« 「アイコンの変更」ダイアログを使ってみる。 | トップページ | 7-zip32.dllのUTF-8モードをC#から使用する(2) »

C#研究」カテゴリの記事

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: 7-zip32.dllのUTF-8モードをC#から使用する(1):

« 「アイコンの変更」ダイアログを使ってみる。 | トップページ | 7-zip32.dllのUTF-8モードをC#から使用する(2) »