記事"MQL5でのZIPアーカイブの扱い"についてのディスカッション - ページ 8

 
GZIPアルゴリズムで圧縮された文字列を解凍する機能を追加する予定はありますか?
 
Ivan Titov CryptEncode(CRYPT_ARCH_ZIP)システム関数を使って解凍する必要がある。圧縮アルゴリズムは同じで、deflate(MQLではCRYPT_ARCH_ZIPという あまりよくない識別子がついている なので、サードパーティのライブラリを使わずに自分でやるのは難しくありません。MQL5ツールを使ってGZipアーカイブを扱う」という特別な記事がないのは残念だ。一般的に、これはシステム関数のタスクではなく、特別なMQLのbibilotekaのタスクであり、gzipフォーマットの周りにdeflateをラップします。
 
Jacobie Nycambren Barksdale #:
何か最新情報はありますか? エラーに遭遇しています。

'ZipLocalHeaderOpen' にはコンストラクタがあり、ユニオンメンバとして使用できません ZipHeader.mqh 52 23

Jacobie、Stanislav KorotkyはこのZipライブラリを含むパックを いくつかの修正とともにここで公開している

私はそれをテストしたが、今のところかなりうまく動いている。

MQL5 Program Packer
MQL5 Program Packer
  • www.mql5.com
This is MQL5 project packer: assemble all source and resource files from dependencies into a single ZIP.
 

CZipが解凍できないアーカイブに遭遇した。同時に7ZIPとWindowsアーカイバは問題なく解凍できた。
圧縮データのサイズをプリントアウトしたところ、アーカイブより数十メガバイト少なかった(しかも1ファイルしか入っていない)。

CompressedSize:76964920 
私はそれがどこで計算されるのか探し始め、FindZipFileSize()関数でそれを見つけた。

実験してみたところ...
end_sizeデータをすべてデータサイズとして返すと、アーカイブが正しく解凍されることがわかった。どうやら解凍時に、この関数からの応答に頼るのではなく、コード自身がデータの終わりを判断しているようだ。このままにしておいてもいいのだが、この関数が役に立たないことが判明した。そして、おそらく他のアーカイブも失敗するだろう...
もうひとつ実験したところ、以下の行をコメントアウトすると、このようになることがわかった。

    // if(pattern == cdheader) 
    // ブレーク;

アーカイブも解凍を始める。データ量は100%に近い。

CompressedSize:106638447 
アーカイブにはuint cdheader =0x504b0102;があり、これは圧縮データの一部であって、末尾のラベルではないことがわかった。

ラベルを間違えたのでしょうか?インターネット検索でそのようなラベルを見つけました。

このファイルで動いている関数: (ファイル ∕Include∕Zip∕Zip.mqh)

int CZip::FindZipFileSize(uchar &zip_array[],int offset)
{
   uint pattern =    0;
   int size =        0;
   uint header =     0x504b0304;
   uint cdheader =   0x504b0102;
   uint mask =       0xffff0000;
   int end_size = ArraySize(zip_array)-offset;
   //バイトの左シフトに基づくリング・バッファ: x = x << 8
   for(; size < end_size; size++)
   {
      pattern = pattern << 8;
      uint nbyte = zip_array[offset+size];
      pattern = pattern | nbyte;
      // 上位2バイトをチェック
      if((pattern & mask)!=(0x504b << 16))
         continue;
      //上位2バイトが0x504bに等しい場合、すべての署名をチェックする。
      if(pattern == header)
         break;
    // if(pattern == cdheader)
    // ブレーク;
   }
   //署名が見つかりません。不正なフォーマット。
   if(size == end_size-1)
      return 0;
   //リターンサイズ - 署名サイズ。
   return size-sizeof(ZIP_LOCAL_HEADER)+1;
}
もし興味があれば、プライベートメッセージでアーカイブファイルを送ることができる。
 

また別のファイルでエラー。今回は行をコメントすることで解決しました。

// if(pattern == header)
// ブレーク;

すなわち、uint header = 0x504b0304;というコードはアーカイブの内容にも発生し、7Zip、Windows、そしてこの修正されたバージョンのCZipで正常に解凍される。

ループの出口が両方とも無効になっているため、ループは不要になり、削除して戻すことができる:

return ArraySize(zip_array)-offset-sizeof(ZIP_LOCAL_HEADER)+1;

この関数には明らかに欠陥がある。結局のところ、条件

   //署名が見つかりません。不正なフォーマット。
   if(size == end_size-1)
      return 0;

というのも、データの終端に達したときにループを抜けると、 size == end_size となり、1小さくならないからです。

その結果、関数を1行に短縮した:

int CZip::FindZipFileSize(const uchar &zip_array[],int offset)
{
   /*uint pattern = 0;
 int size =0;
 uint header = 0x504b0304;
 uint cdheader =0x504b0102;
 uint mask = 0xffffff0000;
 int end_size = ArraySize(zip_array)-offset;
 //これはバイト左シフトに基づくリングバッファである:x = x << 8
 for(; size < end_size; size++)
 {
 pattern = pattern << 8;
 uint nbyte = zip_array[offset+size];
 pattern = pattern | nbyte;
 //上位2バイトをチェック
 if((pattern & mask)!=(0x504b << 16))
 continue;
 // 上位2バイトが0x504bに等しい場合、すべてのシグネチャをチェックする
// if(pattern == header)
//break;
// if(pattern == cdheader)
// // ヘッダをチェックする。break;
 }
 //シグネチャが見つかりません。
 if(size == end_size-1)
 return 0;
 //Return size - signature size.
 return size-sizeof(ZIP_LOCAL_HEADER)+1;
 */
   return ArraySize(zip_array)-offset-sizeof(ZIP_LOCAL_HEADER)+1;
}

このバージョンの関数を使ったプログラムでは、すでに合計12ギガバイトの110のアーカイブをダウンロードし、すべての解凍に成功している。

もし解凍に問題がある人がいたら、このバージョンの関数を試してみるといいだろう。

 
300近いファイルをダウンロードして解凍した。そして、その中のデータはどんどん大きくなり、サイズ制限に達してしまった。
ファイルには18億個の要素があるはずだが、解凍すると15億個になる。 いくつかのデータが失われている。こんなに短くカットされるのはおかしい。配列は最大2147483647 個の要素を持つことができる。
端末関数
はカットされたデータを生成する。
CryptDecode(CRYPT_ARCH_ZIP, m_file_puck, key, file_array);

どうしようもない...。

1024、1024*1024、1024*1024*10を指定したが、うまくいかなかった。

アーカイブを保存し、手動で解凍してから処理しなければならない。自動化なしでは不便だ。

Windowsのアーカイバを使う方法はありますか?WinExecを使ってファイルに解凍し、一行ずつ読み込む。そうすれば、自動化は可能だ。しかし、市場向けではない。
 
Forester #:
Windowsのアーカイバを使う方法はありますか?WinExecを使ってファイルに解凍し、それを一行ずつ読む。

明らかにできます。何が問題なんだ?私が誤解しているのでは?UnRAR.exeやUnZip.exeなどのコンソールアーカイバは昔からあります。

 
Forester #:

また別のファイルでエラー。今回は、以下の行をコメントすることで解決した。

すなわち、uint header = 0x504b0304;というコードはアーカイブの内容にも発生し、7Zip、Windows、そしてこの修正されたバージョンのCZipで正常に解凍される。

ループの終了が両方とも無効になっているので、ループは不要になり、削除して戻すことができる:

この関数には明らかに欠陥がある。結局のところ、条件

というのも、データの終端に達したときにループを抜けると、 size == end_size となり、1小さくならないからである。

その結果、関数を1行に短縮した:

このバージョンの関数を使ったプログラムでは、すでに合計12ギガバイトの110個のアーカイブをダウンロードし、すべての解凍に成功している。

もし解凍に問題がある人がいたら、このバージョンの関数を試してみるとよい。

ラベルを検索する必要はまだあると思いますが、アーカイブ本体ではなく、複数のファイルがある場合はファイル間で検索してください。おそらく、アーカイブの長さはどこかに記録されているはずです。
一般的に、私の解決策は、アーカイブに1つのファイルがある場合、私のタスクではプライベートなものです。

 
Forester #:

ラベルはまだ検索する必要があると推測できますが、アーカイブ本体ではなく、複数のファイルがある場合はファイル間で検索する必要があります。おそらく、アーカイブの長さはどこかに記録されているはずです。
一般的に、私の解決策は、アーカイブに1つのファイルがある場合、私のタスクではプライベートなものです。


ダウンロードしたファイルは0 0です。size=0であれば、前述のFindZipFileSize()が呼ばれる。

通常のアーカイバを使って1つ目のファイルをアーカイブにした。ヘッダのサイズ:
46389587 376516461

そして、最初のアーカイブに追加されたファイルを含む2つのファイルを含む別のアーカイブ:
46981880 314725045
46389587 376516461

どちらもヘッダにサイズが書かれており、FindZipFileSize() は呼び出されませんでした。

そして、私がダウンロードしたファイル(サイズが0 0)は、どうやらヘッダにサイズを書いていないソフトウェアによって作成されたようだ。

おそらく、FindZipFileSize() を短くする私の解決策は普遍的なものだろう。

 
Edgar Akhmadeev #:

もちろんできる。何が問題なんだ?私が誤解していたのでしょうか?コンソールアーカイバーUnRAR.exeやUnZip.exeなどは昔からありました。

私は7-zip経由で解凍しました(UnZip.exeはWin 7さえサポートについて2009年以来更新されていません書かれていません)。
私は速度を比較しました:
このCZIPライブラリ:

2025.06.14 20:59:06.758 アーカイブは正常に開かれました。
2025.06.14 20:59:07.345 解凍
587ms

7-zip
2025.06.14 21:00:07.312 7-Zip による解凍を開始します。
2025.06.14 21:00:09.274 7-Zipによる解凍。ファイルサイズ:428.22 MB
1962 ms

SSDディスクにリセットした状態で解凍した場合のみです。また、ディスクからファイルを一行ずつ読み込む必要があります。

解析開始から終了までの合計時間:
合計時間: 10s 709ms

合計時間: 12s 892ms
その差は2s 183ms。

一般的に、速さを求めるならこのCZIPライブラリを使うのが望ましく、ファイルが大きすぎる場合は他のアーカイバを使うのがよい。

私にとっては、~1000個のファイルをそれぞれ2秒で処理する場合、33分の節約になる。これは428mbの一番小さいファイルの例なので、実際にはそれ以上の節約になる。
それ以上(4GBまで)は7-zipで処理する。つまり、1~1.5時間の節約になる。

私の編集でCZIPをテストしたところ、100GBの600以上のファイルをエラーなしで解凍できた。