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だったよね(記憶があいまい)。
*
コメント
コメントを投稿