Wadah Linux untuk aplikasi .NET Framework (ketika sulit untuk berangkat ke .Net Core)

Halo Habr.

Saya ingin berbagi dengan dunia tugas yang agak tidak lazim, setidaknya bagi saya, dan solusinya, yang menurut saya cukup dapat diterima. Diuraikan di bawah ini mungkin bukan jalan keluar yang ideal dari situasi, tetapi berhasil, dan berfungsi sebagaimana dimaksud.

Pengaturan dan latar belakang


Ada tugas untuk dikerjakan: Anda perlu membuat pratinjau 3D model BIM dari berbagai peralatan, bahan, objek di situs. Butuh sesuatu yang ringan, tidak rumit.

Di situs web, model objek-objek ini disimpan dan tersedia untuk diunduh dalam format eksklusif berbagai sistem CAD dan dalam bentuk format terbuka untuk model 3D. Di antara mereka adalah format IFC . Saya akan menggunakannya sebagai sumber untuk menyelesaikan tugas ini.

Salah satu opsi dan fiturnya


Secara formal, seseorang dapat membatasi diri untuk menulis semacam *. Jika konverter untuk sesuatu untuk ditampilkan pada halaman web. Di sinilah saya mulai.

Toolkit yang luar biasa, xBIM Toolkit, dipilih untuk konversi semacam itu .

Contoh-contoh menggunakan alat ini dengan sederhana dan jelas menggambarkan cara bekerja dengan IFC dan * .wexBIM khusus untuk format web.

Pertama, konversi * .ifc ke * .wexBIM:
using System.IO;
using Xbim.Ifc;
using Xbim.ModelGeometry.Scene;

namespace CreateWexBIM
{
    class Program
    {
        public static void Main()
        {
            const string fileName = "SampleHouse.ifc";
            using (var model = IfcStore.Open(fileName))
            {
                var context = new Xbim3DModelContext(model);
                context.CreateContext();

                var wexBimFilename = Path.ChangeExtension(fileName, "wexBIM");
                using (var wexBiMfile = File.Create(wexBimFilename))
                {
                    using (var wexBimBinaryWriter = new BinaryWriter(wexBiMfile))
                    {
                        model.SaveAsWexBim(wexBimBinaryWriter);
                        wexBimBinaryWriter.Close();
                    }
                    wexBiMfile.Close();
                }
            }
        }
    }
}


Selanjutnya, file yang dihasilkan digunakan di "player" xBIM WeXplorer .

Contoh penyematan * .wexBIM di halaman:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Hello building!</title>
    <script src="js/xbim-viewer.debug.bundle.js"></script>
</head>
<body>
    <div id="content">
        <canvas id="viewer" width="500" height="300"></canvas>
        <script type="text/javascript">
            var viewer = new xViewer('viewer');
            viewer.load('data/SampleHouse.wexbim');
            viewer.start();
        </script>
    </div>    
</body>
</html>


Baiklah, ayo pergi. Saya mengambil nuget dari xBIM. Saya menulis aplikasi konsol yang menerima banyak jalur ke file * .ifc sebagai input, dan di sebelahnya ia menambahkan banyak file * .wexBIM. Semuanya bisa diunggah ke situs.

Tapi entah kenapa ini cukup sederhana ... Saya ingin program ini menjadi semacam layanan, yang, setelah acara unggah * .ifc ke portal, segera membuat * .wexBIM yang diperlukan, dan segera muncul dalam wadah yang disiapkan.

Oke, saya membentuk persyaratan baru:

  1. Biarkan tugas konversi berasal dari RabbitMQ kami ;
  2. Saya ingin melihat tugas-tugas itu sendiri dalam bentuk pesan biner, yang sebenarnya akan siap untuk deserialisasi oleh kelas yang dijelaskan dalam file protobuf ;
  3. tugas akan berisi tautan untuk mengunduh file * .ifc sumber dari Minio kami ;
  4. tugas itu juga akan memberi tahu saya ember mana di Minio untuk menambahkan hasilnya;
  5. biarkan aplikasi itu sendiri dibangun di bawah .net core 3.1 dan bekerja di dalam wadah buruh pelabuhan Linux di "buruh pelabuhan" kami;

Kesulitan dan konvensi pertama


Saya tidak akan menjelaskan secara rinci 4 poin implementasi pertama. Mungkin nanti.

Menyebabkan aplikasi untuk mendengarkan antrian pekerjaan dan mengirim pesan dengan hasil ke antrian dari CorrelationId dari pesan pekerjaan. Kacau kelas permintaan / respons yang dihasilkan dari protobuf. Diajarkan untuk mengunduh / mengunggah file dalam bentuk minio .

Semua ini saya lakukan dalam proyek aplikasi konsol. Dalam pengaturan proyek:

<TargetFramework>netcoreapp3.1</TargetFramework>

Dan di komputer saya dengan Windows 10 semuanya benar-benar rusak dan berfungsi. Tetapi ketika saya mencoba menjalankan aplikasi di WSL, saya menangkap kesalahan System.IO.FileLoadException :

Informasi kesalahan penuh:
{
  "Type": "System.IO.FileLoadException",
  "Message": "Failed to load Xbim.Geometry.Engine64.dll",
  "TargetSite": "Void .ctor(Microsoft.Extensions.Logging.ILogger`1[Xbim.Geometry.Engine.Interop.XbimGeometryEngine])",
  "StackTrace": " at Xbim.Geometry.Engine.Interop.XbimGeometryEngine..ctor(ILogger`1 logger)\r\n at Xbim.Geometry.Engine.Interop.XbimGeometryEngine..ctor()\r\n at Xbim.ModelGeometry.Scene.Xbim3DModelContext.get_Engine()\r\n at Xbim.ModelGeometry.Scene.Xbim3DModelContext.CreateContext(ReportProgressDelegate progDelegate, Boolean adjustWcs)\r\n at My.Converter.ConvertIfc.CreateWebIfc(String ifcFileFullPath, String wexBIMFolder)",
  "Data": {},
  "InnerException": {
    "Type": "System.IO.FileNotFoundException",
    "Message": "Could not load file or assembly 'Xbim.Geometry.Engine.dll, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.",
    "FileName": "Xbim.Geometry.Engine.dll, Culture=neutral, PublicKeyToken=null",
    "FusionLog": "",
    "TargetSite": "System.Reflection.RuntimeAssembly nLoad(System.Reflection.AssemblyName, System.String, System.Reflection.RuntimeAssembly, System.Threading.StackCrawlMark ByRef, Boolean, System.Runtime.Loader.AssemblyLoadContext)",
    "StackTrace": " at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, RuntimeAssembly assemblyContext, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, AssemblyLoadContext assemblyLoadContext)\r\n at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, StackCrawlMark& stackMark, AssemblyLoadContext assemblyLoadContext)\r\n at System.Reflection.Assembly.Load(String assemblyString)\r\n at Xbim.Geometry.Engine.Interop.XbimGeometryEngine..ctor(ILogger`1 logger)",
    "Data": {},
    "Source": "System.Private.CoreLib",
    "HResult": -2147024894
  },
  "Source": "Xbim.Geometry.Engine.Interop",
  "HResult": -2146232799
}

Sesi googling aktif dan bacaan yang bijaksana menunjukkan kepada saya bahwa saya sangat lalai:
Recently at work, we were evaluating a few options to render building models in the browser. Building Information Modeling (BIM) in interoperability scenarios is done via Industry Foundation Classes, mostly in the STEP Physical File format. The schema is quite huge and complex with all the things you have to consider, so we were glad to find the xBim open source project on GitHub. They've got both projects to visualize building models in the browser with WebGL as well as conversion tools to create the binary-formatted geometry mesh. To achieve that, native C++ libraries are dynamically loaded (so no .Net Core compatibility) which must be present in the bin folder. The C++ libraries are expected either in the same folder as the application binaries or in a x64 (or x86, respectively) sub folder (See here for more details). In regular projects, the xBim.Geometry NuGet package adds a build task to copy the dlls into the build output folder, but this doesn't work with the new tooling. You can, however, get it to work in Visual Studio 2015 by taking care of supplying the interop dlls yourself.

Dan kesulitan yang sama bukan untuk saya sendiri. Banyak orang menginginkan xBIM di bawah .Net Core .
Tidak kritis, tetapi banyak berubah ... Semuanya tergantung pada ketidakmampuan memuat Xbim.Geometry.Engine64.dll secara normal . Anda perlu memiliki vc_redist.x64.exe di mesin . Apa saja pilihan saya?
Hal pertama yang saya pikirkan: โ€œDapatkah wadah Windows dengan .Net Framework penuh digunakan?
Memberikan Microsoft Visual C ++ Redistributable untuk Visual Studio 2015, 2017 dan 2019 ke wadah ini, dan semuanya akan beres? โ€ Saya mencoba ini:

Uji gambar Windows untuk buruh pelabuhan:
.Net Core :

<TargetFramework>net47</TargetFramework>

Dockerfile:

FROM microsoft/dotnet-framework:4.7
WORKDIR /bimlibconverter
COPY lib/VC_redist.x64.exe /VC_redist.x64.exe
RUN C:\VC_redist.x64.exe /quiet /install
COPY bin/Release .
ENTRYPOINT ["MyConverter.exe"]

Yah, itu berhasil ... Ini hidup! Tapi. Tapi bagaimana dengan mesin host Linux kami dengan buruh pelabuhan? Ini tidak akan berfungsi untuk mengarahkan wadah dengan gambar di Windows Server Core ke atasnya . Perlu keluar ...

Kompromi dan kesudahan


Pencarian lain di Web membawaku ke sebuah artikel . Di dalamnya, penulis membutuhkan implementasi yang serupa:
Untuk memperburuk keadaan:
Semua binari berukuran 32-bit (x86).
Beberapa memerlukan komponen runtime visual C ++ yang dapat didistribusikan.
Beberapa membutuhkan .NET runtime.
Beberapa membutuhkan sistem windowing, meskipun kami hanya menggunakan antarmuka baris perintah (CLI).
Posting ini menjelaskan potensi untuk menjalankan aplikasi Windows dalam anggur dalam wadah Linux. Karena penasaran, saya memutuskan.

Setelah beberapa pengujian, bug, dan tambahan, Dockerfile diterima:

Gambar buruh pelabuhan berbasis Ubuntu dengan Wine, .Net Framework dan vcredist di papan:

FROM ubuntu:latest
#  x86
RUN dpkg --add-architecture i386 \
    && apt-get update \
    #   
    && apt-get install -qfy --install-recommends \
        software-properties-common \
        gnupg2 \
        wget \
        xvfb \
        cabextract \
    #  Wine
    && wget -nv https://dl.winehq.org/wine-builds/winehq.key \
    && apt-key add winehq.key \
    && apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ bionic main' \
    #     Wine
    && add-apt-repository ppa:cybermax-dexter/sdl2-backport \
    #  Wine
    && apt-get install -qfy --install-recommends \
        winehq-staging \
        winbind \
    # 
    && apt-get -y clean \
    && rm -rf \
      /var/lib/apt/lists/* \
      /usr/share/doc \
      /usr/share/doc-base \
      /usr/share/man \
      /usr/share/locale \
      /usr/share/zoneinfo
#    Wine
ENV WINEDEBUG=fixme-all
ENV WINEPREFIX=/root/.net
ENV WINEARCH=win64
#  Wine
RUN winecfg \
    # winetricks,   .Net Framework  
    && wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks \
    -O /usr/local/bin/winetricks \
    && chmod +x /usr/local/bin/winetricks \
# 
    && apt-get -y clean \
    && rm -rf \
      /var/lib/apt/lists/* \
      /usr/share/doc \
      /usr/share/doc-base \
      /usr/share/man \
      /usr/share/locale \
      /usr/share/zoneinfo \
    # Wine   
    && wineboot -u && winetricks -q dotnet472 && xvfb-run winetricks -q vcrun2015

WORKDIR /root/.net/drive_c/myconverter/

#  
COPY /bin/Release/ /root/.net/drive_c/myconverter/

ENTRYPOINT ["wine", "MyConverter.exe"]

UPD: . rueler

Build tidak cepat, tetapi berakhir dengan sukses. Saya coba, periksa. Bekerja!

Hasil, kesimpulan, pemikiran


Itu berhasil. Outputnya adalah gambar Linux untuk wadah buruh pelabuhan. Ini "bengkak" (~ 5.2GB), tetapi mulai cukup cepat dan di dalamnya menjalankan aplikasi konsol Windows di .Net Framework 4.7, yang mendengarkan RabbitMQ, menulis log ke Graylog , mengunduh dan mengunggah file ke / dalam Minio. Saya akan memperbarui aplikasi dengan API buruh pelabuhan jarak jauh.

Solusi untuk tugas khusus utilitarian diimplementasikan. Mungkin, dan kemungkinan besar, tidak universal. Tetapi pada prinsipnya saya puas. Mungkin seseorang akan berguna juga.

Terima kasih sudah membaca. Saya menulis di Habr untuk pertama kalinya. Sampai jumpa di komentar.

All Articles