将Brotli编译为一个DLL供.NET引用

11

所以我想利用Brotli技术,但我不熟悉Python和C++..

我知道有人已将其编译为Windows的.exe文件。但我该如何将其封装到一个DLL或其他.NET应用程序可以引用的内容中呢?我知道有IronPython,难道我只需要在IronPython项目中导入所有源文件,并编写一个调用Brotli API并公开它们的.NET适配器吗?但实际上,我甚至不确定Brotli API是Python还是C++。

tools/bro.cc 看来,“entry”方法在 encode.cdecode.c 中定义,表现为 BrotliCompress(), BrotliDecompressBuffer(), BrotliDecompressStream() 方法。因此,我认为可以从C++类编译出DLL。


1
不确定您是否想要更多关于Brotli的资源。它可以在GitHub上找到。 - gt6707a
从您的答案中删除“这对我不再是一个行动”的句子。SO问题并不仅仅是为了您而存在。希望您不介意。 - jgauffin
1
@jgauffin,您是否可以通过Python来完成这个任务(仍然从.NET\C#代码中调用)? - Evk
解码器是用C语言编写的,编码器则是用C++编写的。不需要在其中包含Python,你可以使用C++/CLI或者甚至是P/Invoke来实现,只需使用VS将库编译为DLL即可。 - Lucas Trzesniewski
微软已经在CorefxLab中添加了对Brotli的支持(https://github.com/dotnet/corefxlab/tree/master/src/System.IO.Compression.Brotli)。您可以在我的博客上找到一个示例:https://www.meziantou.net/2017/07/17/use-brotli-compression-with-asp-net-core - meziantou
3个回答

13
为避免需要使用Python,我在这里分叉了原始的brotli源代码(https://github.com/smourier/brotli)并创建了一个Windows DLL版本,您可以将其与.NET一起使用。 我添加了一个包含“WinBrotli”Visual Studio 2015解决方案的目录,其中包括两个项目:
  • WinBrotli:Windows DLL(x86和x64),包含未更改的原始C / C ++ brotli代码。
  • Brotli:Windows控制台应用程序(任何CPU),使用C#编写,包含WinBrotli的P / Invoke互操作代码。

要重用Winbrotli DLL,只需将WinBrotli.x64.dll和WinBrotli.x86.dll(您可以在WinBrotli/二进制文件文件夹中找到已经构建的发布版本)复制到您的.NET应用程序旁边,并将BrotliCompression.cs文件合并到您的C#项目中(如果C#不是您最喜欢的语言,则将其移植到VB或另一种语言)。Interop代码将自动选择与当前进程位数相对应的正确DLL(X86或X64)。

完成后,使用它非常简单(输入和输出可以是文件路径或标准.NET流):

        // compress
        BrotliCompression.Compress(input, output);

        // decompress
        BrotliCompression.Decompress(input, output);

为了创建WinBrotli,我完成了以下步骤(供其他人使用其他版本的Visual Studio)

  • 创建了一个标准的DLL项目,删除了预编译头文件
  • 包含所有编码器和解码器原始的brotli C/C++文件(从未更改过其中的任何内容,因此我们可以在需要时更新原始文件)
  • 配置项目以删除对MSVCRT的依赖关系(这样我们就不需要部署其他DLL)
  • 禁用了4146警告(否则我们将无法编译)
  • 添加了一个非常标准的dllmain.cpp文件,没有做任何特殊处理
  • 添加了一个WinBrotli.cpp文件,将brotli压缩和解压缩代码暴露给外部Windows世界(通过一个非常薄的适配层,使其更容易在.NET中进行交互)
  • 添加了一个WinBrotli.def文件,导出4个函数

5
我将展示一种通过从.NET代码调用Python本机库来实现的方法。你需要:
  1. 你需要安装Python 2.7(希望这是显而易见的)
  2. 你需要从源代码编译brotli。希望这很容易。首先安装Microsoft Visual C++ Compiler for Python 2.7。然后通过git clone https://github.com/google/brotli.git克隆brotli存储库并使用python setup.py build_ext进行编译。完成后,在build\lib.win32-2.7目录中,您将找到brotli.pyd文件。这是Python C++模块 - 我们稍后会用到它。

  3. 你需要下载pythonnet二进制文件或从源代码编译它。我们在这里使用pythonnet的原因是因为Iron Python不支持本地(C\C++)Python模块,而这正是我们在这里所需要的。因此,要从源代码编译,请通过git clone https://github.com/pythonnet/pythonnet.git进行克隆,然后通过python setup.py build进行编译。结果你将得到Python.Runtime.dll(在build\lib.win32-2.7目录中),这是我们所需要的。

当你准备好所有这些后,创建控制台项目,引用Python.Runtime.dll然后:

public static void Main()
{            
    PythonEngine.Initialize();            
    var gs = PythonEngine.AcquireLock();
    try {                
        // import brotli module
        dynamic brotli = PythonEngine.ImportModule(@"brotli");
        // this is a string we will compress
        string original = "XXXXXXXXXXYYYYYYYYYY";
        // compress and interpret as byte array. This array you can save to file for example
        var compressed = (byte[]) brotli.compress(original);                
        // little trick to pass byte array as python string
        dynamic base64Encoded = new PyString(Convert.ToBase64String(compressed));
        // decompress and interpret as string
        var decompressed = (string) brotli.decompress(base64Encoded.decode("base64"));
        // works
        Debug.Assert(decompressed == original);
    }
    finally {
        PythonEngine.ReleaseLock(gs);
        PythonEngine.Shutdown();
    }            
    Console.ReadKey();
}

然后构建它并将你上面获得的brotli.pyc文件放在与你的.exe文件相同的目录中。完成所有这些操作后,你就可以像上面所看到的那样从.NET代码压缩和解压缩。


使用 using (Py.GIL()) {} 可以省去 7 行代码!但是在结束时仍需要调用 PythonEngine.Shutdown() - denfromufa
不知道我们在这里进行代码行经济竞赛 :) - Evk
@Evk看起来你需要再多努力一点,克服一下你的懒惰 :) - Lucas Trzesniewski
@Evk,这不仅仅是减少代码行数,还包括增加API和软件包的使用简单性。这难道不是为什么不是每个人都编写C/C++代码的原因吗? - denfromufa

4
您可以使用提供全流支持的Brotli.NET。 压缩流以生成brotli数据:
   public Byte[] Encode(Byte[] input)
   {
       Byte[] output = null;
       using (System.IO.MemoryStream msInput = new System.IO.MemoryStream(input))
       using (System.IO.MemoryStream msOutput = new System.IO.MemoryStream())
       using (BrotliStream bs = new BrotliStream(msOutput, System.IO.Compression.CompressionMode.Compress))
       {
           bs.SetQuality(11);
           bs.SetWindow(22);
           msInput.CopyTo(bs);
           bs.Close();
           output = msOutput.ToArray();
           return output;
       }
   }

要解压缩brotli流:

   public Byte[] Decode(Byte[] input)
   {
       using (System.IO.MemoryStream msInput = new System.IO.MemoryStream(input))
       using (BrotliStream bs = new BrotliStream(msInput, System.IO.Compression.CompressionMode.Decompress))
       using (System.IO.MemoryStream msOutput = new System.IO.MemoryStream())
       {
           bs.CopyTo(msOutput);
           msOutput.Seek(0, System.IO.SeekOrigin.Begin);
           output = msOutput.ToArray();
           return output;
       }

   }

为了支持Web应用程序中的动态压缩,请在Global.asax.cs中添加以下代码:
    protected void Application_PostAcquireRequestState(object sender, EventArgs e)
    {
                       var app = Context.ApplicationInstance;
            String acceptEncodings = app.Request.Headers.Get("Accept-Encoding");

            if (!String.IsNullOrEmpty(acceptEncodings))
            {
                System.IO.Stream baseStream = app.Response.Filter;
                acceptEncodings = acceptEncodings.ToLower();

                if (acceptEncodings.Contains("br") || acceptEncodings.Contains("brotli"))
                {
                    app.Response.Filter = new Brotli.BrotliStream(baseStream, System.IO.Compression.CompressionMode.Compress);
                    app.Response.AppendHeader("Content-Encoding", "br");
                }
                else
                if (acceptEncodings.Contains("deflate"))
                {
                    app.Response.Filter = new System.IO.Compression.DeflateStream(baseStream, System.IO.Compression.CompressionMode.Compress);
                    app.Response.AppendHeader("Content-Encoding", "deflate");
                }
                else if (acceptEncodings.Contains("gzip"))
                {
                    app.Response.Filter = new System.IO.Compression.GZipStream(baseStream, System.IO.Compression.CompressionMode.Compress);
                    app.Response.AppendHeader("Content-Encoding", "gzip");
                }

            }
       }        

按照.NET的精神,我认为这是目前最好的选择。也就是说,另一个答案中提到的WinBrotli也可以满足要求。 - gt6707a
WinBrotli在输入/输出较小的情况下表现良好。然而,它需要在内存中压缩/解压数据,这意味着它需要更多的内存并且会有更高的延迟。 - Jinjun Xie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接