Help us understand the problem. What is going on with this article?

C#でLZ4を用いて他言語とやり取りするときは「IonKiwi.lz4.net」を使ってみよう

More than 1 year has passed since last update.

LZ4という解凍特化で高速に圧縮展開できる大変便利なフォーマットがあります。C#でこの圧縮形式を利用できるライブラリはいくつかあるのですが、圧縮したファイルを他言語で読ませるときにハマりました。Pythonとやり取りする場合を例に、問題点と解決方法を検討します。

環境:Python3.6(64bit)、lz4(0.21.6)、u-msgpack-python(2.4.1)
.NET Framework 4.6、lz4net(v1.0.15.93 - 2017/3/18)、MessagePack(v1.7.3.4 - 2018/1/30)
VisualStudio 2017 Community

LZ4 Frame Formatを使わない問題点

PythonのLZ4ライブラリである「python-lz4(パッケージ名はlz4)」を見ていたら興味深いことが書いてありました。

The bindings provided in this package cover the frame format and the block format specifications. The frame format bindings are the recommended ones to use, as this guarantees interoperability with other implementations and language bindings.

要約すると、「Frame FormatBlock Formatがあり、他言語とやりとりするなら相互運用性を保証できるFrame Formatを使いなさい」ということ。読んでみると、確かにFrame Formatのほうがヘッダーが厳密に決まっているため、相互運用時のトラブルが起きづらそうに見えます。

C#のLZ4ライブラリでダウンロード数が最多でおそらく一番有名な「lz4net」を見ると、Frame Formatを明示的に指定できないのですよね。リポジトリをFrameで検索してもそれっぽいものが出てきませんでした。似た名前のライブラリがありますが同じ結果になります。

例えば、lz4netを使って簡単なテキストを圧縮してPythonに渡してみます。

C#,lz4net
using System.Text;
using System.Threading.Tasks;
using System.IO;
using LZ4;

class Program
{
    static void Main(string[] args)
    {
        var str = "じゅげむ じゅげむ ごこうのすりきれ かいじゃりすいぎょの すいぎょうまつ うんらいまつ ふうらいまつ くうねるところにすむところ やぶらこうじのぶらこうじ ぱいぽ ぱいぽ ぱいぽのしゅーりんがん しゅーりんがんのぐーりんだい ぐーりんだいのぽんぽこぴーの ぽんぽこなーの ちょうきゅうめいのちょうすけ";

        using (var fs = new FileStream("jyugemu.lz4", FileMode.Create, FileAccess.Write))
        using (var lz4Stream = new LZ4Stream(fs, LZ4StreamMode.Compress))
        {
            var encode = new UTF8Encoding(false);//BOMなしUTF8
            var binary = encode.GetBytes(str);
            lz4Stream.Write(binary, 0, binary.Length);
        }
    }
}

このjyugemu.lz4をバイナリーエディタで見ると次のようになります。
lz4_1.png
Frame Formatの最初4バイトのマジックナンバー「0x184D2204」(詳しくはドキュメントを参照してください)が付与されていません。

Pythonのスクリプトのあるディレクトリにコピーし、Pythonで読んでみます。当然Frame Formatで解凍するとエラーになります。あらかじめlz4のモジュールをインストールしておきます。

Python
import lz4.frame

with lz4.frame.open("jyugemu.lz4", "r") as fp:
    decompress = fp.read()
print(decompress)
#    decompress = fp.read()
#  File "C:\Program Files\Python36\lib\site-packages\lz4\frame\__init__.py", line
# 509, in read
#    return self._buffer.read(size)
#  File "C:\Program Files\Python36\lib\_compression.py", line 103, in read
#    data = self._decompressor.decompress(rawblock, size)
#  File "C:\Program Files\Python36\lib\site-packages\lz4\frame\__init__.py", line
# 297, in decompress
#    return_bytearray=self._return_bytearray,
#RuntimeError: LZ4F_decompress failed with code: #ERROR_frameType_unknown

じゃあBlock Formatで解凍すればいいのかと言ったらそう上手くは行きませんでした。

Python
import lz4.block
import struct

with open("jyugemu.lz4", "rb") as fp:
    binary = fp.read()
decompress = lz4.block.decompress(binary)
print(decompress)
#    decompress = lz4.block.decompress(binary)
#ValueError: Decompressor wrote 18446744073709551614 bytes, but 2315502849 bytes
#expected from header

バイト数が明らかにおかしい。やはりドキュメントにある通り、他言語と相互運用するならFrame Formatを使うべきでしょう。

MessagePack for C#のLZ4シリアライザーも注意

MessagePack for C#という大変便利なライブラリがあります。このライブラリのオプションで、MessagePackにLZ4圧縮をかける、つまり高速なシリアライザーにさらに高速の圧縮をかけるという、シリアライザーの頂点のようなことができます。
これを他言語との相互運用に使うというケースはそうないと思いますが、このLZ4シリアライザーもv1.7.3.4の2018年1月末現在、Frame Formatに対応していないようです。(LZ4を使わずにMessagePackだけ使う場合は問題ありません

例えば

C#
using System.IO;
using MessagePack;

class Program
{
    static void Main(string[] args)
    {
        var str = "じゅげむ じゅげむ ごこうのすりきれ かいじゃりすいぎょの すいぎょうまつ うんらいまつ ふうらいまつ くうねるところにすむところ やぶらこうじのぶらこうじ ぱいぽ ぱいぽ ぱいぽのしゅーりんがん しゅーりんがんのぐーりんだい ぐーりんだいのぽんぽこぴーの ぽんぽこなーの ちょうきゅうめいのちょうすけ";

        //MessagePackのLZ4
        using (var fs = new FileStream("jyugemu.msg.lz4", FileMode.Create, FileAccess.Write))
        {
            var binary = LZ4MessagePackSerializer.Serialize<string>(str);
            fs.Write(binary, 0, binary.Length);
        }
    }
}

とMessagePackを挟むとヘッダーは次のようになります。
lz4_2.png
同様にPythonで読めません。C#の間でやり取りするならいいんですけどね。

解決法:LZ4圧縮に「IonKiwi.lz4.net」を使う

C#→Python

Frame Formatが使えるLZ4ライブラリがありました。DL数もlz4netの1/100ぐらいであまり有名ではないのですが、「IonKiwi.lz4.net」です。ライブラリの説明から

This package is a .NET wrapper arround the C lz4 library (v1.8.0), compliant with the LZ4 Framing Format.

CのLZ4のラッパーとのこと。Framing Formatが使えます。早速試してみます。せっかくなのでMessagePackを挟んでやってみます(もちろんわざわざMessagePackを挟まなくても使えます)。

C#
using System.IO;
using lz4;
using MessagePack;

class Program
{
    static void Main(string[] args)
    {
        var str = "じゅげむ じゅげむ ごこうのすりきれ かいじゃりすいぎょの すいぎょうまつ うんらいまつ ふうらいまつ くうねるところにすむところ やぶらこうじのぶらこうじ ぱいぽ ぱいぽ ぱいぽのしゅーりんがん しゅーりんがんのぐーりんだい ぐーりんだいのぽんぽこぴーの ぽんぽこなーの ちょうきゅうめいのちょうすけ";
        var strSplit = str.Split(' ');
        var data = Enumerable.Range(0, strSplit.Length)
            .Select(x => new { id = x + 1, str = strSplit[x] }).ToArray();

        //MessagePackでPack(匿名型もいける)
        var msgpackBinary = MessagePackSerializer.Serialize<dynamic>(data);

        //IonKiwi.lz4.netでFrame Fomat圧縮
        using (var fs = new FileStream("frameformat.msg.lz4", FileMode.Create, FileAccess.Write))
        using (var lz4Stream = LZ4Stream.CreateCompressor(fs, LZ4StreamMode.Write))
        {
            lz4Stream.Write(msgpackBinary, 0, msgpackBinary.Length);
        }
    }
}

frameformat.msg.lz4をPythonのスクリプトのあるのディレクトリにコピーします。MessagePackのモジュール(u-msgpack-python)もインストールしておきます。

Python
import lz4.frame
import umsgpack

with lz4.frame.open("frameformat.msg.lz4", mode="rb") as fp:
    unpack = umsgpack.unpack(fp)
print(unpack)
#[{'id': 1, 'str': 'じゅげむ'}, {'id': 2, 'str': 'じゅげむ'}, {'id': 3, 'str': '
#ごこうのすりきれ'}, {'id': 4, 'str': 'かいじゃりすいぎょの'}, {'id': 5, 'str': '
#すいぎょうまつ'}, {'id': 6, 'str': 'うんらいまつ'}, {'id': 7, 'str': 'ふうらいま
#つ'}, {'id': 8, 'str': 'くうねるところにすむところ'}, {'id': 9, 'str': 'やぶらこ
#うじのぶらこうじ'}, {'id': 10, 'str': 'ぱいぽ'}, {'id': 11, 'str': 'ぱいぽ'}, {'
#id': 12, 'str': 'ぱいぽのしゅーりんがん'}, {'id': 13, 'str': 'しゅーりんがんのぐ
#ーりんだい'}, {'id': 14, 'str': 'ぐーりんだいのぽんぽこぴーの'}, {'id': 15, 'str
#': 'ぽんぽこなーの'}, {'id': 16, 'str': 'ちょうきゅうめいのちょうすけ'}]

無事読むことができました。

Python→C# 

せっかくなので逆もやってみましょう。

Python
import lz4.frame
import umsgpack

#オブジェクトの作成
str = "じゅげむ じゅげむ ごこうのすりきれ かいじゃりすいぎょの すいぎょうまつ うんらいまつ ふうらいまつ くうねるところにすむところ やぶらこうじのぶらこうじ ぱいぽ ぱいぽ ぱいぽのしゅーりんがん しゅーりんがんのぐーりんだい ぐーりんだいのぽんぽこぴーの ぽんぽこなーの ちょうきゅうめいのちょうすけ"
str_split = str.split(" ")
dic = [{"id":(x+1), "str":str_split[x]} for x in range(len(str_split))]

#MessagePack
msgpack = umsgpack.packb(dic)

#LZ4に保存
with lz4.frame.open("to csharp.msg.lz4", mode = "wb") as fp:
    fp.write(msgpack)

できたファイルをC#の実行ディレクトリにコピーします。

C#
using System.IO;
using lz4;
using MessagePack;

class Program
{
    static void Main(string[] args)
    {
        //IonKiwi.lz4.netでFrame Fomat解凍
        byte[] decompBinary;
        using (var fs = new FileStream("to csharp.msg.lz4", FileMode.Open, FileAccess.Read))
        using (var lz4Stream = LZ4Stream.CreateDecompressor(fs, LZ4StreamMode.Read))
        using (var ms = new MemoryStream())
        {
            lz4Stream.CopyTo(ms);
            decompBinary = ms.ToArray();
        }

        //MessagePackのUnpack
        var obj = MessagePackSerializer.Deserialize<dynamic>(decompBinary);
        foreach(var o in obj)
        {
            foreach(var pair in o)
            {
                Console.Write(pair.Key + ":" + pair.Value + " ");
            }
            Console.WriteLine();
        }
    }
}
//id:1 str:じゅげむ
//id:2 str:じゅげむ
//id:3 str:ごこうのすりきれ
//id:4 str:かいじゃりすいぎょの
//id:5 str:すいぎょうまつ
//id:6 str:うんらいまつ
//id:7 str:ふうらいまつ
//id:8 str:くうねるところにすむところ
//id:9 str:やぶらこうじのぶらこうじ
//id:10 str:ぱいぽ
//id:11 str:ぱいぽ
//id:12 str:ぱいぽのしゅーりんがん
//id:13 str:しゅーりんがんのぐーりんだい
//id:14 str:ぐーりんだいのぽんぽこぴーの
//id:15 str:ぽんぽこなーの
//id:16 str:ちょうきゅうめいのちょうすけ

Unpackした後の扱い方については他に方法あると思いますが、LZ4の受け渡しはFrame Formatにしてしまえば簡単ですね。バイナリのまま動かすと管理が楽そうです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした