حاوية Linux لتطبيق .NET Framework (عندما يكون من الصعب المغادرة إلى .Net Core)

مرحبا يا هابر.

أريد أن أشارك مع العالم مهمة غير اعتيادية ، على الأقل بالنسبة لي ، وحلها ، الذي يبدو لي مقبولًا تمامًا. قد لا يكون الوصف الموضح أدناه هو الطريقة المثالية للخروج من الموقف ، ولكنه يعمل ، ويعمل على النحو المنشود.

الإعداد والخلفية


كانت هناك مهمة للعمل: تحتاج إلى عمل معاينات ثلاثية الأبعاد لنماذج BIM لمختلف المعدات والمواد والأشياء على الموقع. تحتاج إلى شيء خفيف الوزن وغير معقد.

على موقع الويب ، يتم تخزين نماذج هذه الكائنات ومتاحة للتنزيل في تنسيقات خاصة بأنظمة CAD مختلفة وفي شكل تنسيقات مفتوحة للنماذج ثلاثية الأبعاد. من بينها تنسيق IFC . سأستخدمه كمصدر لحل هذه المهمة.

أحد الخيارات وميزاته


بشكل رسمي ، يمكن للمرء أن يقيد نفسه لكتابة نوع من محول * .ifc لشيء ما ليتم عرضه على صفحة ويب. من هنا بدأت.

تم اختيار مجموعة أدوات رائعة ، مجموعة أدوات xBIM ، لمثل هذا التحويل . تصف

أمثلة استخدام هذه الأداة ببساطة ووضوح كيفية العمل مع IFC و * .wexBIM المتخصصة في تنسيق الويب.

أولاً ، قم بتحويل * .ifc إلى * .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();
                }
            }
        }
    }
}


بعد ذلك ، يتم استخدام الملف الناتج في "player" xBIM WeXplorer .

مثال على تضمين * .wexBIM في الصفحة:
<!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>


حسنا ، دعنا نذهب. آخذ شذرات من xBIM. أنا أكتب تطبيق وحدة تحكم يتلقى مجموعة من المسارات إلى ملفات * .ifc كإدخال ، وبجانبها يضيف مجموعة من ملفات * .wexBIM. يمكن تحميل كل شيء على الموقع.

ولكن بطريقة أو بأخرى بسيطة للغاية ... أريد أن يصبح هذا البرنامج نوعًا من الخدمة ، والتي ، عند حدث تحميل * .ifc إلى البوابة ، تنشئ على الفور * .wexBIM الضروري ، وتظهر على الفور في الحاوية المعدة.

حسنًا ، أقوم بتشكيل متطلبات جديدة:

  1. دع مهام التحويل تأتي من RabbitMQ الخاص بنا ؛
  2. أريد أن أرى المهام نفسها في شكل رسالة ثنائية ، والتي ستكون في الواقع جاهزة لإلغاء التسلسل من قبل الفئة الموصوفة في ملف protobuf ؛
  3. سيحتوي مهمة رابط لتحميل المصدر * .ifc ملف من وجهة نظرنا Minio .
  4. ستخبرني المهمة أيضًا أي مجموعة في Minio لإضافة النتيجة ؛
  5. السماح للتطبيق نفسه أن يتم بناؤه تحت .net core 3.1 والعمل داخل حاوية docker Linux في "مزرعة docker" الخاصة بنا ؛

الصعوبات والاتفاقيات الأولى


لن أصف بالتفصيل نقاط التنفيذ الأربعة الأولى. ربما في وقت لاحق.

تسبب التطبيق في الاستماع إلى قائمة انتظار المهام وإرسال رسالة مع النتيجة إلى قائمة الانتظار من CorrelationId لرسالة الوظيفة. ثمل فئات الطلب / الاستجابة التي تم إنشاؤها من protobuf. علمت تنزيل / تحميل الملفات في minio .

كل هذا أفعله في مشروع تطبيق وحدة التحكم. في إعدادات المشروع:

<TargetFramework>netcoreapp3.1</TargetFramework>

وعلى جهازي مع نظام التشغيل Windows 10 ، تم تصحيح كل شيء ويعمل بشكل كامل. ولكن عندما أحاول تشغيل التطبيق في WSL ، ألاحظ خطأ System.IO.FileLoadException :

معلومات الخطأ الكاملة:
{
  "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
}

أظهرت لي جلسة من غوغل النشطة والقراءة المدروسة أنني كنت غير مهتم للغاية:
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.

وصعوبات مماثلة ليست لي وحدي. كثير من الناس يريدون xBIM تحت .Net Core .
ليس بالغ الأهمية ، ولكنه يتغير كثيرًا ... كل هذا يرجع إلى عدم القدرة على تحميل Xbim.Geometry.Engine64.dll بشكل طبيعي . يجب أن يكون لديك vc_redist.x64.exe على الجهاز . ما هي خياراتي؟
أول شيء فكرت فيه: "هل يمكن استخدام حاوية Windows مع إطار عمل صافي كامل؟
تسليم Microsoft Visual C ++ القابل لإعادة التوزيع لبرنامج Visual Studio 2015 و 2017 و 2019 إلى هذه الحاوية ، وسيكون كل شيء على ما يرام؟ " جربت هذا:

اختبار صورة Windows للرسو:
تم تغيير .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"]

حسنًا ، لقد نجحت ... إنها على قيد الحياة! لكن. ولكن ماذا عن جهازنا المضيف Linux مع عامل الميناء؟ لن تعمل على تشغيل حاوية تحتوي على صورة على Windows Server Core عليها . بحاجة الى الخروج ...

حل وسط وخاتمة


قادني بحث آخر على الويب إلى مقال . في ذلك ، يحتاج المؤلف إلى تنفيذ مماثل:
لجعل الأمور أسوأ:
جميع الثنائيات هي 32 بت (x86).
تتطلب بعض مكونات وقت التشغيل المرئي C ++ القابلة لإعادة التوزيع.
يتطلب البعض وقت تشغيل .NET.
يحتاج البعض إلى نظام إطارات ، على الرغم من أننا لا نستخدم سوى واجهة سطر الأوامر (CLI).
يصف المنشور إمكانية تشغيل تطبيقات Windows في النبيذ في حاوية Linux. فضولي ، قررت.

بعد بعض الاختبارات والأخطاء والإضافات ، تم استلام ملف Dockerfile:

صورة لرسو السفن القائم على Ubuntu مع Wine و .Net Framework و vcredist على متن الطائرة:

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: تعديل الملف قليلا لبناء صورة أكثر إحكاما. بفضل التعليقأكثر ضراوة

البناء ليس سريعًا ، ولكنه ينتهي بنجاح. أحاول ، تحقق. يعمل!

النتائج والاستنتاجات والأفكار


انها عملت. الإخراج هو صورة Linux لحاوية عامل الميناء. إنه "منتفخ" (~ 5.2GB) ، ولكنه يبدأ بسرعة كبيرة ويدير تطبيق وحدة تحكم Windows على .Net Framework 4.7 ، الذي يستمع إلى RabbitMQ ، يكتب السجلات إلى Graylog ، وينزل ويحمل الملفات إلى / في Minio. سوف أقوم بتحديث التطبيق عن طريق API docker عن بعد.

يتم تنفيذ الحل للمهمة النفعية المخصصة. ربما ، وعلى الأرجح ، ليس عالميًا. ولكن من حيث المبدأ كنت راضيا. ربما شخص ما سيكون في متناول اليد أيضا.

شكرا للقراءة. أكتب على هبر لأول مرة. أراك في التعليقات.

All Articles