المكتبات الثابتة مع مشكلة التعليمات البرمجية المُدارة

StackOverflow https://stackoverflow.com/questions/72769

  •  09-06-2019
  •  | 
  •  

سؤال

المشكلة (مبسطة لتوضيح الأمور):

    1.يوجد static.lib واحد مرتبط بشكل ثابت وله وظيفة تزيد:
    
        extern int CallCount = 0;
        int TheFunction()
        {
            void *p = &CallCount;
            printf("Function called");
            return CallCount++;
        }
    
    2.يتم ربط static.lib بملف C++/CLI Managed.dll مُدار والذي يلتف حول طريقة TheFunction:
    
        int Managed::CallLibFunc()
        {
            return TheFunction();
        }
    
    3.يحتوي تطبيق الاختبار على إشارة إلى Managed.dll ويقوم بإنشاء مجالات متعددة تستدعي غلاف C++/CLI:
    
        static void Main(string[] args)
        {
            Managed c1 = new Managed();
            int val1 = c1.CallLibFunc();
            // value is zero
    
            AppDomain ad = AppDomain.CreateDomain("NewDomain");
            Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
            int val2 = c.CallLibFunc();
            // value is one 
        }
    

سؤال:

استنادًا إلى ما قرأته في Essential .NET Vol1 The CLR من Don Box، أتوقع أن تكون قيمة val2 صفرًا حيث يتم تحميل نسخة جديدة تمامًا من Manage.dll/static.lib عند استدعاء CreateInstanceAndUnwrap.هل أسيء فهم ما يحدث؟لا يبدو أن المكتبة الثابتة تحترم حدود مجال التطبيق نظرًا لأنها رمز غير مُدار.هل هناك طريقة للتغلب على هذه المشكلة بخلاف إنشاء عملية جديدة تمامًا لإنشاء مثيل مُدار؟

شكرا جزيلا للجميع!

هل كانت مفيدة؟

المحلول

كان حدسي هو أنه، كما توقعت، يتم تحميل ملفات DLL غير المُدارة في سياق العملية وليس في سياق AppDomain، لذلك تتم مشاركة أي بيانات ثابتة في التعليمات البرمجية غير المُدارة بين AppDomains.

هذا الرابط يظهر شخصًا يعاني من نفس المشكلة التي تواجهها، ومع ذلك لم يتم التحقق بنسبة 100% من ذلك، ولكن ربما يكون هذا هو الحال.

هذا الرابط يتعلق الأمر بإنشاء رد اتصال من تعليمات برمجية غير مُدارة إلى AppDomain باستخدام خدعة thunking.لست متأكدًا من أن هذا يمكن أن يساعدك ولكن ربما ستجد هذا مفيدًا لإنشاء نوع من الحل البديل.

نصائح أخرى

بعد أن تتصل

Managed c1 = new Managed(); 

سيتم تحميل غلاف Manage.dll الخاص بك في مجال التطبيق الرئيسي لتطبيقك.حتى يتم مشاركة الأشياء غير المُدارة للمجال من static.lib مع المجالات الأخرى.بدلاً من إنشاء عملية منفصلة، ​​تحتاج فقط إلى التأكد (قبل كل مكالمة) من عدم تحميل Manage.dll في أي مجال تطبيق.

قارن مع ذلك

static void Main(string[] args)
{

    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  Value is zero

               AppDomain.Unload(ad)
    }
    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  I think value is zero

               AppDomain.Unload(ad)
    }


}
`

هام و:إذا قمت بإضافة سطر واحد فقط سوف يقوم برنامج التحويل البرمجي JIT بتحميل Managed.dll ويختفي السحر.

static void Main(string[] args)
{

    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  Value is zero 

               AppDomain.Unload(ad)
    }
    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  I think value is one

               AppDomain.Unload(ad)
    }
Managed c1 = new Managed(); 


}

إذا كنت لا تريد الاعتماد على مثل هذه الأسطر، فيمكنك إنشاء غلاف آخر ManagedIsolated.dll والذي سيشير إلى Managed.dll وسيقوم بإجراء كل مكالمة في مجال منفصل مع إلغاء تحميل المجال بعد المكالمة مباشرةً.سيعتمد التطبيق الرئيسي فقط على أنواع ManagedIsolated.dll ولن يتم تحميل Managed.dll في مجال التطبيق الرئيسي.

يبدو ذلك بمثابة خدعة ولكن قد يكون مفيدًا لشخص ما.`

باختصار، ربما.AppDomains هي مجرد مفهوم مُدار.عندما يتم إنشاء مثيل AppDomain، فإنه لا يتم تعيينه في نسخ جديدة من ملفات DLL الأساسية، ويمكنه إعادة استخدام التعليمات البرمجية الموجودة بالفعل في الذاكرة (على سبيل المثال، لا تتوقع منه تحميل نسخ جديدة من كافة تجميعات System.*، أليس كذلك؟ ؟)

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

يمكنك القيام بشيء معقد يفرض تحميل ملف Managed.dll فريدًا لكل مجال تطبيق، مما قد يؤدي إلى إحضار إصدار جديد من الملف الثابت أثناء الرحلة.على سبيل المثال، ربما يعمل استخدام Assembly.Load مع مصفوفة بايت، لكنني لا أعرف كيف سيحاول CLR التعامل مع التصادم في الأنواع إذا تم تحميل نفس التجميع مرتين.

لا أعتقد أننا وصلنا إلى المشكلة الفعلية هنا - راجع مقالة DDJ هذه.

القيمة الافتراضية لسمة تحسين أداة التحميل هي SingleDomain، والتي "تتسبب في قيام AppDomain بتحميل نسخة خاصة من كل رمز تجميع ضروري".حتى لو كانت إحدى قيم النطاقات المتعددة، "يحتفظ كل AppDomain دائمًا بنسخة مميزة من الحقول الثابتة".

"managed.dll" (كما يوحي اسمه) عبارة عن تجميع مُدار.تم تجميع التعليمات البرمجية الموجودة في static.lib بشكل ثابت (كرمز IL) في "managed.dll"، لذلك أتوقع نفس السلوك الذي يتوقعه Lenik....

...ما لم تكن static.lib عبارة عن مكتبة تصدير ثابتة لملف DLL غير مُدار.يقول لينيك إن الأمر ليس كذلك، لذلك ما زلت غير متأكد مما يحدث هنا.

هل حاولت التشغيل في عمليات منفصلة؟لا ينبغي للمكتبة الثابتة مشاركة مثيلات الذاكرة خارج نطاق عمليتها الخاصة.

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

يحرر:بعد قليل من البحث حولك، أعتقد أنه يمكنك فعل كل ما تحتاجه باستخدام System.Diagnostics.Process فصل.سيكون لديك الكثير من الخيارات في هذه المرحلة للتواصل ولكن .NET عن بعد أو ربما يكون WCF اختيارات جيدة وسهلة.

هذه هي أفضل مقالتين وجدتهما حول هذا الموضوع

الجزء المهم هو:

الحقول الثابتة المستندة إلى RVA هي عمليات عالمية.يقتصر ذلك على الكميات القياسية وأنواع القيم، لأننا لا نريد السماح للكائنات بالانتقال عبر حدود AppDomain.قد يتسبب ذلك في حدوث جميع أنواع المشكلات، خاصة أثناء عمليات إلغاء تحميل AppDomain.بعض اللغات مثل ILASM وMC++ تجعل من السهل تحديد الحقول الثابتة المستندة إلى RVA.معظم اللغات لا تفعل ذلك.

حسنًا، إذا كنت تتحكم في الكود الموجود في .lib، فسأحاول

class CallCountHolder {
   public:
     CallCountHolder(int i) : count(i) {}
     int count;
};

static CallCountHolder cc(0);
int TheFunction()
{
    printf("Function called");
    return cc.count++;
}

منذ أن قال أن الحقول الثابتة المستندة إلى RVA تقتصر على الكميات وأنواع القيمة.قد تعمل مجموعة int أيضًا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top