zipファイルを調べてみた
.NET Frameworkの圧縮クラスを使って作ったzipファイルが、別のシステムで「展開できない」ということが起きた(らしい)。どういうことかと調べてみた。
しかし、こうして作ったzipファイルが展開できないらしい。
2つのファイルを並べてみると、たしかにサイズが違う。exploer.zipが手で作ったZIPファイルだが、サイズが大きい(112Kと106K)。エクスプローラのZIPファイルを作成するコマンドは、.NET Framework 4 のクラスを使っていないのか。へえええ。
中身を見てみたいが、バイナリデータなので、メモ帳では見れない。こういうときはUNIXと云うことで、bashコマンドを起動して、odで生身を見てみた。
比較してみると2つのファイルの内容は違っていた。これは解析してみたいという欲望が湧いてきて(貴重な週末なのに)zipファイルを解析するソフトを作ってみた。
結果が下図。
中身の要素が無いzipファイルを作って、そこにCopyHereメソッドでフォルダを追加するという流れ。
ただしマイクロソフト的には推奨していないようす。COMはエラーが起きやすいからね。
CopyHere メソッドから Zip ファイルを処理することはできません
*
zipファイルを解析した方法は以下の通り。
ZIPファイルの構造については、あちこちに解説記事があるので、それらを参考に。
ここでは、ファイルの末尾にあるEOCDを読むクラスを載せてみる。
EOCDはzipファイルの末尾にあって「0x504b0506」というバイト列で始まる部分らしいので、以下の手順で読み込んでいる。
zipファイルを作る
.NET Framework 4からzipファイルを作るクラスが追加された。特にZipFileクラスのメソッドを使うと、フォルダを圧縮してzipファイルを作ることが1行でできる。超簡単。using System.IO; using System.IO.Compression; File.Delete(zipFilePath); ZipFile.CreateFromDirectory(dataFolderPath, zipFilePath, CompressionLevel.Optimal, true);
しかし、こうして作ったzipファイルが展開できないらしい。
エクスプローラで圧縮したzipファイル
ところが、エクスプローラでフォルダを選択して作ったzipファイルは別システムでも展開できると云う。なにか違うのだろうか。2つのファイルを並べてみると、たしかにサイズが違う。exploer.zipが手で作ったZIPファイルだが、サイズが大きい(112Kと106K)。エクスプローラのZIPファイルを作成するコマンドは、.NET Framework 4 のクラスを使っていないのか。へえええ。
21世紀にもなってod使うとは
比較してみると2つのファイルの内容は違っていた。これは解析してみたいという欲望が湧いてきて(貴重な週末なのに)zipファイルを解析するソフトを作ってみた。
結果が下図。
左がエクスプローラで作ったzipファイル
右がフレームワークで作ったzipファイル
ファイル構造の違い
- エクスプローラで作ったzipファイルにはフォルダ要素があるが、フレームワークで作ったzipファイルにはフォルダ要素が無い。つまりファイルだけ。
- フレームワークで作ったzipファイルの方が圧縮率が高いようだ。
フォルダ要素が無いのが、違いとしては大きい。
別システムがエラーの詳細を教えてくれないので、原因はさっぱりわからないのだが、このどちらかが原因と思われる。
回避策
安易に考えると、エクスプローラで作ったzipファイルはOKなのだから、それをC#で作れば問題は回避できる。
C#にはエクスプローラの動作を実行する「Microsoft Shell Controls And Automation」というCOMがあるので、これを使うと同じzipファイルを作れる。プロジェクトの参照設定で上記のCOMを追加します。
File.Delete(zipFilePath); byte[] a = { 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; File.WriteAllBytes(zipFilePath, a); var sh = new Shell32.Shell(); Shell32.Folder zip = sh.NameSpace(zipFilePath); Shell32.Folder d = sh.NameSpace(dataFolderPath); zip.CopyHere(d);
中身の要素が無いzipファイルを作って、そこにCopyHereメソッドでフォルダを追加するという流れ。
ただしマイクロソフト的には推奨していないようす。COMはエラーが起きやすいからね。
CopyHere メソッドから Zip ファイルを処理することはできません
*
zipファイルを解析した方法は以下の通り。
ZIPファイルの構造については、あちこちに解説記事があるので、それらを参考に。
ここでは、ファイルの末尾にあるEOCDを読むクラスを載せてみる。
public class zipEOCD { [StructLayout(LayoutKind.Sequential, Pack = 2)] public struct HEADER { public UInt32 sig; public Int16 diskCount; public UInt16 diskStart; public UInt16 diskRecordCount; public UInt16 count; public UInt32 size; public UInt32 pos; public UInt16 n; }; internal HEADER hd; internal zipEOCD(string path) { int getSigPos(byte[] bytes) { for (int i = 0; i < 100; i++) { if (bytes[i] == 0x50 && bytes[i + 1] == 0x4b && bytes[i + 2] == 0x05 && bytes[i + 3] == 0x06) { return i; } } return 0; } int pos = 0; using (FileStream st = File.OpenRead(path)) { byte[] a = new byte[100]; long n = st.Seek(-100, System.IO.SeekOrigin.End); st.Read(a, 0, 100); pos = getSigPos(a) - 100; } var size = Marshal.SizeOf(typeof(HEADER)); var ptr = Marshal.AllocHGlobal(size); using (FileStream st = File.OpenRead(path)) { st.Seek(pos, SeekOrigin.End); //byte[] a = new byte[100]; //st.Read(a, 0, 100); BinaryReader br = new BinaryReader(st); Marshal.Copy(br.ReadBytes(size), 0, ptr, size); HEADER hd = (HEADER)Marshal.PtrToStructure(ptr, typeof(HEADER)); this.hd = hd; }
Marshal.FreeHGlobal(ptr);} }
EOCDはzipファイルの末尾にあって「0x504b0506」というバイト列で始まる部分らしいので、以下の手順で読み込んでいる。
- zipファイルをオープンして、末尾から100バイトの位置にシークする
- 100バイトのデータを配列に読み込んで、「0x504b0506」を探す
- 見つかったら位置を記録する
- zipファイルを再度オープンして、見つかった位置へシークする
- バイナリリーダーを作って、EOCDの形式にあわせた構造体に読み込む。
調査のためのソフトなので、思い切り乱暴ですな。エラー処理もしていないし。C#でバイナリデータを読み込むのは、なかなか大変なのもよくわかった。Cだとreadに構造体のアドレスを渡せばOKだったよね(記憶があいまい)。
*
コメント
コメントを投稿