آشنایی با CLR: قسمت بیست و یکم
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: پنج دقیقه

آغاز فصل سوم:

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

مشکلی که در توزیع اسمبلی‌های عمومی وجود دارد این است که شما باید این اطمینان را کسب کنید که اسمبلی شما، همیشه همان اسمبلی خواهد بود و تغییری در آن رخ نخواهد داد. فرض کنید که شما از یک اسمبلی که توسط شرکت تلریک تهیه شده است استفاده کرده‌اید و برنامه‌ی شما به خوبی با آن کار می‌کند. ولی چه اتفاقی می‌افتد که اگر برنامه‌ی دیگری بعد از شما نصب شود و از همان اسمبلی، منتها از نسخه‌ی دیگر آن استفاده می‌کند؟ بله برنامه‌ی شما احتمال زیادی دارد که در این حالت به مشکل بر بخورد یا اینکه شخص دیگری یک اسمبلی دیگری همنام اسمبلی و هم نسخه‌ی اسمبلی شما تولید می‌کند. برای رفع این مشکلات مایکروسافت تمهیداتی را اندیشیده است که ما به آن می‌گوییم «اسمبلی با نام قوی Strong Name Assembly».

اسمبلی‌ها به دو دسته تقسیم می‌شوند: اسمبلی‌های با نام قوی و اسمبلی هایی با نام ضعیف ( این مورد در مستندات مایکروسافت نیست و توسط نویسنده‌ی کتاب، این اصطلاح ایجاد شده است).

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

حال یک اسمبلی نام قوی، دارای چهار خصوصیت است: نام اسمبلی بدون پسوند، نگارش، فرهنگ (Culture) و کلید عمومی.

از آنجا که خود کلید عمومی بسیار بزرگ می‌باشد، ما برای استفاده‌ی راحت‌تر، از توکن کلید عمومی استفاده می‌کنیم که طول کمتری دارد. توکن کلید عمومی، یک مقدار هش شده است که از کلید عمومی به دست می‌آید:

"MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
"MyTypes, Version=1.0.8123.0, Culture="en­US", PublicKeyToken=b77a5c561934e089" 
"MyTypes, Version=2.0.1234.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
"MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

استفاده از فضای نام

System.Reflection.AssemblyName

به شما اجازه‌ی ساخت و دریافت اطلاعاتی را از اسمبلی‌ها می‌دهد؛ هر نوع اطلاعاتی را که شامل 4 خصوصیت بالا می‌شود، به شما می‌دهد.

از آنجا که مطالب مربوطه به  امضاء کردن اسمبلی، در سایت جاری موجود می‌باشند، این مباحث را می‌توانید از طریق این مقاله "نام قوی" دنبال کنید تا در این باره گزافه گویی نکرده باشیم .

مکانیزم اعمال این امضاء بدین شکل است که بعد از ساخت فایل PE که شامل مانیفست است، کل محتویات آن ( به جز امضاهای شناسایی و هدر (Checksum) و اطلاعات مربوط به نام قوی) هش می‌شوند و سپس این مقدا هش شده توسط کلید خصوصی امضاء می‌شود و در همان PE، در بخش هش نشده‌ای به نام Reserved Section ذخیره می‌گردند و در نهایت CLR Header برای ارتباط با این بخش آپدیت می‌شود. 

تصویر زیر توضیح بند بالا را نشان می‌دهد:

 

مختصری در مورد GAC

اگر مقاله‌ی بالا را خوانده باشید، الان باید بدانید که GAC، وظیفه‌ی رجیستر و توزیع اسمبلی نام قوی را بر عهده دارد؛ ولی چرا باید اینکار را به GAC، واگذار کنیم؟ فرض کنید دو اسمبلی با نام‌های dotnettips.dll وجود دارند. اگر قرار باشد هر دو داخل یک دایرکتوری قرار بگیرند، مسلما اسمبلی دوم بر روی اسمبلی اولی overwrite خواهد شد. درنتیجه کاری که GAC انجام می‌دهد، جلوگیری از این اتفاق است و GAC مدیریت می‌کند که در داخل مسیر
%SystemRoot%\Microsoft.NET\Assembly
زیرشاخه هایی ایجاد شوند و اسمبلی‌ها در آن‌ها قرار بگیرند.

هر اسمبلی را که توزیع می‌کنید، حتما حداقل به یک اسمبلی نام قوی ارجاع خواهد داشت؛ دلیل این گفته هم وجود فضای نام system.object در اسمبلی mscorlib است. برای همین، موقعیکه شما با csc، کامپایل می‌کنید، از سوئیچ reference استفاده می‌شود تا ارجاعی را به نام اسمبلی مورد نظر داشته باشد. اگر نام اسمبلی به طور کامل به همراه مسیر ذکر شود که مستقیما از همانجا فراخوانی می‌شود؛ در غیر این صورت مسیرهای زیر مورد بررسی قرار می‌گیرند:
  1. مسیر پوشه‌ی کاری برنامه
  2. مسیر کامپایلر CSC
  3. هر مسیری که با سوئیچ lib به کامپایلر معرفی کرده باشید.
  4. هر مسیری که با متغیرهای Lib Environment مشخص شده باشند.
بنابراین اگر شما مثلا از یک اسمبلی مانند system.drawing استفاده کرده باشید، کمپایلر به طور خودکار دستور زیر را صادر می‌کند:
/reference:System.Drawing.dll
و همانطور که گفته شد، کامپایلر شروع به اسکن دایرکتوری‌ها می‌کند و در نهایت آن را در مسیر خود کامپایلر، یعنی مورد 2 پیدا می‌کند. برنامه کمپایل می‌شود، ولی این اسمبلی همان اسمبلی نیست که برنامه در زمان اجرا، مورد استفاده قرار می‌دهد؛ چون این اسمبلی یک اسمبلی نام قوی است و انتظار می‌رود تا در مسیر مورد تایید GAC یافت شود. به همین دلیل است که موقع نصب دات نت فریم ورک، از هر اسمبلی، دو نسخه بر روی سیستم کپی می‌شود. یکی در مسیر کامپایل و دیگری برای GAC.
در ضمن اسمبلی‌های موجود در مسیر کامپایلر به هیچ عنوان اهمیتی به نوع ماشین نمی‌دهند؛ چون متادیتاهای اسمبلی آن‌ها اهمیت دارند نه کد IL آن‌ها. کد IL فقط در زمان اجرا، برای ما اهمیت دارد و در زمان کامپایل، همان متادیتا کفایت می‌کند و در نهایت برنامه موقع اجرا، با توجه به نوع ماشین x86,x36,ARM، اسمبلی مورد نیاز خود را از طریق GAC فراهم می‌کند که هر یک از اسمبلی‌های مخصوص هر ماشین، توسط GAC، در داخل زیر شاخه‌های مخصوص خود قرار گرفته‌اند.