وبسایت شخصی حسن هاشمی

برنامه نویس. ایران. قم :))

AppDomain چیست؟

ApplicationDomain در واقعی مفهومی هست که همراه با دات نت فریمورک معرفی شد و منظور از طراحیش اینه که امکان ایزوله کردن برنامه های که تحت clr اجرا می شن رو فراهم کنه و در نتیجه از تأثیر اونها روی هم جلوگیری می کنه.

و البته وقتی که یه Process نابود میشه و یا خطا میده و terminate میشه، بقیه process ها به کارشون ادامه میدن همین خاصیت برای AppDomain ها هم صدق می کنه.

در واقع یه جورایی میشه گفت که AppDomain یه نمونه کوچکتر و سبک تر از Process های سیستم عامل هست و تفاوت آشکارش هم اینه که Process در سیستم عامل قرار داره ولی AppDoamin داخل یه Procecss دیگه هست که اون رو میزبان یا (Host) می نامیم قرار داره.


بذارید یکم ساده ترش کنیم.

در واقع هر کد تحت clr قبل از اجرا داخل یک AppDomain لود میشه و هر AppDomain برای خودش یه virtual address space جداگونه داره. 

برای کسایی که یادشون رفته virtual address space چی هست، یادآور میشم که خیلی ساده یه حافظه مجازیه که توسط سیستم عامل به حافظه اصلی که رم باشه map میشه و به هر Process یه دونه از اینا اختصاص داده میشه.

میدونم که ممکنه تا اینجا یه کوچولو گیج کننده باشه، اما طاقت بیارید تا بیشتر واردش بشیم و به جاهای جذابش برسیم. ;)


تا اینجای کار میدونیم که یه نمونه از clr به ازای هر برنامه نوشته شده تحت دات نت ساخته میشه و حافظه ای که در اختیار clr هست همون حافظه ی اختصاص شده به Process هست.

بعد از اینکه به قول معروف clr ما Initialize شد به صورت پیش فرض یه AppDomain می سازه و بعد موتور اجرایی clr از تابع Main برنامه شروع به اجرا می کنه.

اسم AppDomain پیش هم فرض هم اسم assembly هست که در حال اجراست.

به این عکس توجه کنید:


برای اینکه AppDomain پیش فرض رو چک کنیم کافیه یه برنامه کوچیک بنویسیم:

Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);

این یه خط کد اسم Assembly شما رو پرینت می کنه که گفته شد به عنوان نام AppDomain پیش فرض انتخاب شده.

خب، این همه از AppDomain گفتیم اما صحبت زیادی از کاربرد و فایدش نشد که از این به بعد می خواهیم به کاربردهاش توی برنامه های خودمون بپردازیم.


یکی از شعارهایی که از اهداف طراحی AppDomain بود، کمک در بازپس گیری حافظه اشغال شده هست.

به این صورت که قسمتی از برنامه که حافظه زیادی اشغال می کنه رو داخل یک AppDomain دیگه اجرا می کنیم (یعنی ما هاست میشیم) و بعد از Unload کردن AppDomain تمام حافظه اشغال شده با یه full garbage collection باز پس گیری میشه.

البته اگه یه نگاهی به سورس clr توی githup یه نگاهی بندازیم، میبینیم که برخلاف چیزی که توی مستندات نوشتن وقتی AppDomain آنلود شد تمامی اشیاء داخلش به عنوان unreachable علامت گذاری می شن در نتیجه لزوماً در لحظه آنلود AppDomain همگی dispose نمیشن و ممکنه یکم بعد اینکار انجام بشه.

یه تکه کد رو من از سورس clr از githup ّبرداشتم، اینجا می بینیم که finalizer منتظر آنلود شدن Appdomain نمی مونه و دقیقاً این قضیه بر عکسه.

// Finalizer thread can not wait until AD unload is done,
    // because AD unload is going to wait for Finalizer Thread.
    if (fSync && pThread == FinalizerThread::GetFinalizerThread() && 
        !pThread->HasThreadStateNC(Thread::TSNC_RaiseUnloadEvent))
        return COR_E_CANNOTUNLOADAPPDOMAIN;
 


کاربرد بعدی AppDomain امنیته.

در واقع امنیتی که اینجا ازش صحبت میشه سطح دسترسی کدی هست که قراره توی AppDomain جدید که ما هاستش هستیم اجرا بشه هست. 

فرض کنید قسمتی از برنامه شما توسط شرکت دیگه ای پیاده سازی شده و یا حتی شما برنامه رو به یه سازمان فروختید و حالا برنامه نویسای اون سازمان موظفند برای اون برنامه پلاگین تولید کنند و برنامه شما به صورت داینامیک پلاگین ها رو اجرا می کنه.

داستان وقتی شروع میشه که شما نمیدونید اونا چی می نویسن، از کجا معلوم که پاشونو از محدودشون درازتر نمی کنند. در ساده ترین حالت اونا می تونن یه Exception ساده throw کنند و در صورت Handle نشدنش کل Process شما توسط clr متوقف و بعدش هم توسط ویندوز kill بشه.

و یا اگر از سطح دسترسی برنامه شما استفاده کنه و رجیستری سرور رو دستکاری کنه.

در این صورت شما نه می تونید اجازه بدید این اتفاقات بیفته و نه میتونید قابلیت اجرای پلاگین رو از برنامه حذف کنید. اگر هم بخواید یه Process دیگه ای اجرا کنید و بعد از امکانات ارتباطی بین Process استفاده کنید هم که کلی دردسر و ضمن اینکه Process منابع سخت افزاری زیادی رو مصرف می کنه.

پس بهترین راه ساخت یه AppDomain دیگه و اجرای پلاگین در اون هست.

در نتیجه حالا می تونیم برای هر AppDomain یه Permission Set جدا گونه تعیین کنیم. Trusted و یا untrusted...

 برای جلوگیری از، از بین رفتن Process در صورت بروز خطا هم کافیه کد داخل AppDomain جدید رو، روی یه thread دیگه اجرا کنیم تا در صورت خطا process کلی ما یه thread براش بمونه و از بین نره.


  1. public class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. ExecutePluginOnNewAppDomain();
  6. }
  7.  
  8. private static void ExecutePluginOnNewAppDomain()
  9. {
  10. // ساخت appdomain
  11. AppDomain domain = AppDomain.CreateDomain("PluginDomain");
  12.  
  13. // توی دامین ساخته شده یه نمونه از کلاس
  14. // plugincontainer
  15. // می سازیم
  16. PluginContainer wrapper =
  17. (PluginContainer)domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName,
  18. typeof(PluginContainer).FullName);
  19.   // روی یه
  20. // Thread
  21. // دیگه پلاگین رو اجرا می کنیم
  22. Task.Run(() =>
  23. {
  24. wrapper.ExecutePlugin();
  25. });
  26. Thread.Sleep(300);
  27. AppDomain.Unload(domain);
  28. }
  29. }
  30.  
  31. public class Plugin
  32. {
  33. public void DoSomeThing()
  34. {
  35. Console.WriteLine("Did some thing");
  36. // یه کاری اینجا می کنیم
  37. // این خطا باعث نمیشه کل برنامه از بین بره
  38. throw new Exception();
  39. }
  40. }
  41.  
  42. // از
  43. // MarshalByRefObject
  44. // ارث می بریم تا اجازه بده نمونه های این کلاس
  45. // بین دو تا دامین استفاده بشن
  46. public class PluginContainer : MarshalByRefObject
  47. {
  48.  
  49. List<Plugin> _plugins;
  50. public PluginContainer()
  51. {
  52. _plugins = LoadFromSomeWhere();
  53. }
  54. private List<Plugin> LoadFromSomeWhere()
  55. {
  56. // از یه جایی می گیریم پلاگین ها رو
  57. return new List<Plugin>() { new Plugin() };
  58. }
  59. internal void ExecutePlugin()
  60. {
  61. foreach (var item in _plugins)
  62. {
  63. item.DoSomeThing();
  64. }
  65. }
  66. }
  67.  

چیز جالبتر این که کلاس دامین یه سری رویداد جالب داره که خیلی به درد لاگ کردن و اینجور کارها میخوره که ور رفتن با اونا رو می سپرم به دست خودتون.


در پایان بگم این کدی که اینجا نوشتم فقط جهت نمونه بود و کاربرد عملی نداره. :)

Loading